Skip to content

Commit 01ed49e

Browse files
authored
Merge 1ffd578 into bbbc103
2 parents bbbc103 + 1ffd578 commit 01ed49e

3 files changed

Lines changed: 57 additions & 0 deletions

File tree

‎packages/media-editor/CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Bug Fixes
6+
7+
- Stop a pan drag started just after a wheel zoom from recording one undo entry per frame. The drag now ends the pending wheel gesture instead of letting its debounce timer close the drag's gesture mid-drag ([#83558](https://github.com/WordPress/gutenberg/pull/83558)).
8+
59
## 0.19.0 (2026-09-23)
610

711
### Bug Fixes

‎packages/media-editor/src/image-editor/core/interaction-controller.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,14 @@ export class InteractionController {
305305
// Capture pointer so drag works across iframe boundaries.
306306
el.setPointerCapture( e.pointerId );
307307

308+
// End a pending wheel gesture now, so its debounce timer cannot
309+
// fire mid-drag and end the drag's gesture along with it.
310+
if ( this.wheelGestureActive ) {
311+
clearTimeout( this.wheelGestureTimer );
312+
this.wheelGestureActive = false;
313+
this.options.onGestureEnd?.();
314+
}
315+
308316
this.setStatus( { isDragging: true } );
309317
this.options.onGestureStart?.();
310318

‎packages/media-editor/src/image-editor/core/test/interaction-controller.ts‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,51 @@ describe( 'InteractionController', () => {
562562

563563
vi.useRealTimers();
564564
} );
565+
566+
it( 'does not end the gesture mid-drag when the drag starts within the wheel debounce', () => {
567+
vi.useFakeTimers( {
568+
toFake: [ 'setTimeout', 'clearTimeout' ],
569+
} );
570+
const state = makeState( { zoom: 2 } );
571+
const calls: string[] = [];
572+
const { controller } = createController( state, {
573+
onGestureStart: () => calls.push( 'start' ),
574+
onGestureEnd: () => calls.push( 'end' ),
575+
} );
576+
const el = createMockElement();
577+
578+
// Zoom, then start dragging before the 300ms debounce elapses.
579+
controller.handleWheel(
580+
createWheelEvent( { deltaY: -50, currentTarget: null } )
581+
);
582+
vi.advanceTimersByTime( 100 );
583+
controller.handlePointerDown(
584+
createPointerEvent( { clientX: 100, clientY: 100 } ),
585+
el
586+
);
587+
const dragStartIndex = calls.lastIndexOf( 'start' );
588+
589+
// Keep dragging past the point where the debounce fires.
590+
el._fire(
591+
'pointermove',
592+
createPointerEvent( { clientX: 110, clientY: 100 } )
593+
);
594+
vi.advanceTimersByTime( 350 );
595+
el._fire(
596+
'pointermove',
597+
createPointerEvent( { clientX: 120, clientY: 100 } )
598+
);
599+
const callsDuringDrag = calls.slice( dragStartIndex + 1 );
600+
601+
el._fire( 'pointerup', createPointerEvent() );
602+
vi.useRealTimers();
603+
604+
expect( actionMocks.setPan ).toHaveBeenCalledTimes( 2 );
605+
// An end here closes the undo batch, so every later pan frame
606+
// records its own undo entry.
607+
expect( callsDuringDrag ).not.toContain( 'end' );
608+
expect( calls[ calls.length - 1 ] ).toBe( 'end' );
609+
} );
565610
} );
566611

567612
describe( 'keyboard', () => {

0 commit comments

Comments
 (0)