@@ -134,6 +134,16 @@ export interface PanGestureOptions {
134134 * @default { clientX: 0, clientY: 0 }
135135 */
136136 from ?: { clientX : number ; clientY : number } ;
137+ /**
138+ * Modifier keys held during the gesture. Propagated to every synthesized
139+ * mouse event so that `syncModifierKeys` (iframe support) sees them.
140+ */
141+ modifiers ?: {
142+ shiftKey ?: boolean ;
143+ ctrlKey ?: boolean ;
144+ metaKey ?: boolean ;
145+ altKey ?: boolean ;
146+ } ;
137147}
138148
139149/** Return type of {@link renderApp}. */
@@ -176,7 +186,16 @@ export interface RenderApp {
176186 */
177187 trackPadPan : ( options : PanGestureOptions ) => void ;
178188 /** Simulate wheel-driven zoom to a target scale value. */
179- zoom : ( options : { value : number ; center ?: [ number , number ] } ) => void ;
189+ zoom : ( options : {
190+ value : number ;
191+ center ?: [ number , number ] ;
192+ modifiers ?: {
193+ shiftKey ?: boolean ;
194+ ctrlKey ?: boolean ;
195+ metaKey ?: boolean ;
196+ altKey ?: boolean ;
197+ } ;
198+ } ) => void ;
180199 /** Simulate a two-finger pinch gesture to a target scale value. */
181200 pinch : ( options : {
182201 value : number ;
@@ -330,7 +349,7 @@ export const renderApp = ({
330349 const wrapper = screen . getByTestId ( "wrapper" ) ;
331350
332351 const zoom : RenderApp [ "zoom" ] = ( options ) => {
333- const { value, center } = options ;
352+ const { value, center, modifiers = { } } = options ;
334353 if ( ! ref . current ) throw new Error ( "ref.current is null" ) ;
335354
336355 userEvent . hover ( content ) ;
@@ -363,6 +382,7 @@ export const renderApp = ({
363382 deltaY : isZoomIn ? - newStep : newStep ,
364383 clientX : cx ,
365384 clientY : cy ,
385+ ...modifiers ,
366386 } ) ,
367387 ) ;
368388 } else {
@@ -395,43 +415,51 @@ export const renderApp = ({
395415 const from = isZoomIn ? 1 : 2 ;
396416 const step = 0.1 ;
397417
398- let pinchValue = 0 ;
418+ const scaleCloseEnough = ( a : number , b : number ) => Math . abs ( a - b ) < 1e-5 ;
419+
420+ // Pinch zoom is driven by touch distance vs pinch-start distance. Increasing
421+ // separation zooms in; narrowing zooms out — the previous helper only ever
422+ // increased separation, so zoom-out gestures ran away to max scale.
423+ let spread = isZoomIn ? step : from + 6 ;
399424
400425 fireEvent . touchStart ( content , {
401- touches : getPinchTouches ( content , center , step , from ) ,
426+ touches : getPinchTouches ( content , center , spread , from ) ,
402427 } ) ;
403428
404429 const startTime = Date . now ( ) ;
405430 while ( Date . now ( ) - startTime < 1000 ) {
431+ const currentScale = ref . current . instance . state . scale ;
432+ if ( scaleCloseEnough ( currentScale , value ) ) {
433+ break ;
434+ }
406435 if (
407- ( isZoomIn
408- ? ref . current . instance . state . scale < value
409- : ref . current . instance . state . scale > value ) &&
410- ref . current . instance . state . scale !== value
436+ isZoomIn
437+ ? currentScale < value - 1e-5
438+ : currentScale > value + 1e-5
411439 ) {
412- const scaleDifference = Math . abs (
413- ref . current . instance . state . scale - value ,
414- ) ;
440+ const scaleDifference = Math . abs ( currentScale - value ) ;
415441 const isNearScale = scaleDifference < 0.1 ;
416442 const newStep = isNearScale ? step / 6 : step ;
417443
418- // Pinch-out must narrow finger spacing (smaller dx); pinch-in widens it.
419- pinchValue += isZoomIn ? newStep : - newStep ;
444+ spread = isZoomIn ? spread + newStep : spread - newStep ;
445+ if ( ! isZoomIn ) {
446+ spread = Math . max ( spread , - from + step ) ;
447+ }
420448
421449 fireEvent . touchMove ( content , {
422- touches : getPinchTouches ( content , center , pinchValue , from ) ,
450+ touches : getPinchTouches ( content , center , spread , from ) ,
423451 } ) ;
424452 } else {
425453 break ;
426454 }
427455 }
428456
429457 fireEvent . touchMove ( content , {
430- touches : getPinchTouches ( content , targetCenter , pinchValue , from ) ,
458+ touches : getPinchTouches ( content , targetCenter , spread , from ) ,
431459 } ) ;
432460
433461 fireEvent . touchEnd ( content , {
434- touches : getPinchTouches ( content , center , pinchValue , from ) ,
462+ touches : getPinchTouches ( content , center , spread , from ) ,
435463 } ) ;
436464 } ;
437465
@@ -441,12 +469,14 @@ export const renderApp = ({
441469 moveEventCount = 1 ,
442470 msPerStep = DEFAULT_MS_PER_STEP ,
443471 from = { clientX : 0 , clientY : 0 } ,
472+ modifiers = { } ,
444473 } ) => {
445474 userEvent . hover ( content ) ;
446475 fireEvent . mouseDown ( content , {
447476 clientX : from . clientX ,
448477 clientY : from . clientY ,
449478 buttons : 1 ,
479+ ...modifiers ,
450480 } ) ;
451481
452482 for ( let i = 1 ; i <= moveEventCount ; i ++ ) {
@@ -456,6 +486,7 @@ export const renderApp = ({
456486 clientX : from . clientX + x * progress ,
457487 clientY : from . clientY + y * progress ,
458488 buttons : 1 ,
489+ ...modifiers ,
459490 } ) ;
460491 }
461492
0 commit comments