88 clamp ,
99 cornerAt ,
1010 cornerCoords ,
11+ directionCorner ,
1112 offScreenEdge ,
13+ quadrantCorner ,
1214 stepAxis ,
1315} from './trigger'
1416import type { TanStackDevtoolsConfig } from '../context/devtools-context'
@@ -133,6 +135,43 @@ describe('hot corners', () => {
133135 } )
134136} )
135137
138+ describe ( 'magnetic direction' , ( ) => {
139+ it ( 'reads a corner from the direction of a nudge alone' , ( ) => {
140+ expect ( directionCorner ( - 40 , - 40 , null ) ) . toBe ( 'top-left' )
141+ expect ( directionCorner ( 40 , - 40 , null ) ) . toBe ( 'top-right' )
142+ expect ( directionCorner ( - 40 , 40 , null ) ) . toBe ( 'bottom-left' )
143+ expect ( directionCorner ( 40 , 40 , null ) ) . toBe ( 'bottom-right' )
144+ } )
145+
146+ it ( 'keeps the fallback when neither axis leaves the deadzone' , ( ) => {
147+ expect ( directionCorner ( 4 , - 4 , 'bottom-left' ) ) . toBe ( 'bottom-left' )
148+ expect ( directionCorner ( 0 , 0 , null ) ) . toBeNull ( )
149+ } )
150+
151+ it ( 'ignores sideways wander on a long drag along one axis' , ( ) => {
152+ expect ( directionCorner ( 12 , - 200 , 'bottom-left' ) ) . toBe ( 'top-left' )
153+ expect ( directionCorner ( - 12 , - 200 , 'bottom-right' ) ) . toBe ( 'top-right' )
154+ expect ( directionCorner ( 200 , 12 , 'top-right' ) ) . toBe ( 'top-right' )
155+ expect ( directionCorner ( 200 , - 12 , 'bottom-left' ) ) . toBe ( 'bottom-right' )
156+ } )
157+
158+ it ( 'still reads a diagonal once the minor axis is deliberate' , ( ) => {
159+ expect ( directionCorner ( 120 , - 200 , 'bottom-left' ) ) . toBe ( 'top-right' )
160+ expect ( directionCorner ( - 120 , - 200 , 'bottom-right' ) ) . toBe ( 'top-left' )
161+ } )
162+
163+ it ( 'falls back to the quadrant the trigger sits in' , ( ) => {
164+ const size = { width : 56 , height : 56 }
165+ const viewport = { width : 1024 , height : 768 }
166+ expect ( quadrantCorner ( { x : 8 , y : 8 } , size , viewport ) ) . toBe ( 'top-left' )
167+ expect ( quadrantCorner ( { x : 960 , y : 8 } , size , viewport ) ) . toBe ( 'top-right' )
168+ expect ( quadrantCorner ( { x : 8 , y : 704 } , size , viewport ) ) . toBe ( 'bottom-left' )
169+ expect ( quadrantCorner ( { x : 960 , y : 704 } , size , viewport ) ) . toBe (
170+ 'bottom-right' ,
171+ )
172+ } )
173+ } )
174+
136175describe ( 'off screen edge' , ( ) => {
137176 const size = { width : 56 , height : 56 }
138177 const viewport = { width : 1024 , height : 768 }
@@ -217,6 +256,80 @@ describe('dragging a floating trigger into a corner', () => {
217256 } )
218257} )
219258
259+ describe ( 'the drag tooltip' , ( ) => {
260+ beforeEach ( ( ) => {
261+ localStorage . clear ( )
262+ Element . prototype . setPointerCapture = vi . fn ( )
263+ Element . prototype . releasePointerCapture = vi . fn ( )
264+ Element . prototype . hasPointerCapture = vi . fn ( ( ) => false )
265+ } )
266+
267+ const drag = ( el : Element , type : string , x : number , y : number ) =>
268+ el . dispatchEvent (
269+ new PointerEvent ( type , {
270+ bubbles : true ,
271+ button : 0 ,
272+ pointerId : 1 ,
273+ clientX : x ,
274+ clientY : y ,
275+ } ) ,
276+ )
277+
278+ it ( 'stays up when the pointer outruns the button mid-drag' , ( ) => {
279+ const { getByLabelText, queryByText } = renderTrigger ( {
280+ triggerMode : 'floating' ,
281+ } )
282+ const button = getByLabelText ( 'Open TanStack Devtools' )
283+
284+ drag ( button , 'pointerdown' , 400 , 400 )
285+ expect ( queryByText ( / D r a g t o m o v e / ) ) . not . toBeNull ( )
286+
287+ fireEvent . mouseLeave ( button )
288+ drag ( button , 'pointermove' , 700 , 300 )
289+ expect ( queryByText ( / D r a g t o m o v e / ) ) . not . toBeNull ( )
290+
291+ drag ( button , 'pointerup' , 700 , 300 )
292+ expect ( queryByText ( / D r a g t o m o v e / ) ) . toBeNull ( )
293+ } )
294+
295+ it ( 'gets out of the way while the drag is still going' , ( ) => {
296+ vi . useFakeTimers ( )
297+ try {
298+ const { getByLabelText, queryByText } = renderTrigger ( {
299+ triggerMode : 'floating' ,
300+ } )
301+ const button = getByLabelText ( 'Open TanStack Devtools' )
302+
303+ drag ( button , 'pointerdown' , 400 , 400 )
304+ expect ( queryByText ( / D r a g t o m o v e / ) ) . not . toBeNull ( )
305+
306+ vi . advanceTimersByTime ( 1500 )
307+ expect ( queryByText ( / D r a g t o m o v e / ) ) . toBeNull ( )
308+
309+ drag ( button , 'pointermove' , 500 , 400 )
310+ expect ( queryByText ( / D r a g t o m o v e / ) ) . toBeNull ( )
311+ } finally {
312+ vi . useRealTimers ( )
313+ }
314+ } )
315+
316+ it ( 'still hides on hover-out when no drag is in flight' , ( ) => {
317+ const { getByLabelText, queryByText } = renderTrigger ( {
318+ triggerMode : 'floating' ,
319+ } )
320+ const button = getByLabelText ( 'Open TanStack Devtools' )
321+
322+ drag ( button , 'pointerdown' , 400 , 400 )
323+ drag ( button , 'pointerup' , 400 , 400 )
324+ drag ( button , 'pointerdown' , 400 , 400 )
325+ expect ( queryByText ( / D r a g t o m o v e / ) ) . not . toBeNull ( )
326+
327+ drag ( button , 'pointercancel' , 400 , 400 )
328+ fireEvent . mouseLeave ( button )
329+ expect ( queryByText ( / D r a g t o m o v e / ) ) . toBeNull ( )
330+ } )
331+ } )
332+
220333describe ( 'escape during a trigger drag' , ( ) => {
221334 beforeEach ( ( ) => {
222335 localStorage . clear ( )
@@ -269,8 +382,8 @@ describe('escape during a trigger drag', () => {
269382 const button = getByLabelText ( 'Open TanStack Devtools' )
270383
271384 drag ( button , 'pointerdown' , 0 , 0 )
272- drag ( button , 'pointermove' , 2000 , 2000 )
273- drag ( button , 'pointerup' , 2000 , 2000 )
385+ drag ( button , 'pointermove' , 1016 , 760 )
386+ drag ( button , 'pointerup' , 1016 , 760 )
274387 expect ( storedSettings ( ) . triggerCorner ) . toBe ( 'bottom-right' )
275388
276389 drag ( button , 'pointerdown' , 1016 , 760 )
@@ -285,8 +398,8 @@ describe('escape during a trigger drag', () => {
285398 const button = getByLabelText ( 'Open TanStack Devtools' )
286399
287400 drag ( button , 'pointerdown' , 0 , 0 )
288- drag ( button , 'pointermove' , 2000 , 2000 )
289- drag ( button , 'pointerup' , 2000 , 2000 )
401+ drag ( button , 'pointermove' , 1016 , 760 )
402+ drag ( button , 'pointerup' , 1016 , 760 )
290403
291404 escape ( )
292405 expect ( storedSettings ( ) . triggerCoords ) . toEqual ( { x : 1016 , y : 760 } )
@@ -466,6 +579,26 @@ describe('dragging the trigger off screen', () => {
466579 ) . not . toBeInTheDocument ( )
467580 } )
468581
582+ it ( 'docks to the edge even when the release is also corner-hot' , ( ) => {
583+ const { container, getByLabelText } = renderTrigger ( {
584+ triggerMode : 'floating' ,
585+ } )
586+ const button = getByLabelText ( 'Open TanStack Devtools' )
587+
588+ drag ( button , 'pointerdown' , 0 , 0 )
589+ drag ( button , 'pointermove' , 5000 , 0 )
590+
591+ expect ( container . querySelector ( '[data-tsd-hot-corner]' ) ) . toBeNull ( )
592+ expect (
593+ getByLabelText ( 'Show TanStack Devtools trigger' ) ,
594+ ) . toBeInTheDocument ( )
595+
596+ drag ( button , 'pointerup' , 5000 , 0 )
597+
598+ expect ( storedSettings ( ) . triggerEdge ) . toBe ( 'right' )
599+ expect ( storedSettings ( ) . triggerCorner ) . toBeUndefined ( )
600+ } )
601+
469602 it ( 'keeps the trigger when the release never crosses the edge' , ( ) => {
470603 const { getByLabelText, queryByLabelText } = renderTrigger ( {
471604 triggerMode : 'floating' ,
0 commit comments