From a526a9ba0e293da6e7591dcaae090b32e142e643 Mon Sep 17 00:00:00 2001 From: Ferit Date: Wed, 21 Oct 2026 09:20:49 +0300 Subject: [PATCH 1/2] fix: reset touch state on cleanup and handle touchcancel --- src/hooks/useMobileTouchMove.ts | 6 ++- tests/touch.test.js | 69 +++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/src/hooks/useMobileTouchMove.ts b/src/hooks/useMobileTouchMove.ts index 35ec3d8..4ca00a2 100644 --- a/src/hooks/useMobileTouchMove.ts +++ b/src/hooks/useMobileTouchMove.ts @@ -64,8 +64,6 @@ export default function useMobileTouchMove( }; const onTouchEnd = () => { - touchedRef.current = false; - cleanUpEvents(); }; @@ -80,13 +78,17 @@ export default function useMobileTouchMove( elementRef.current = e.target as HTMLElement; elementRef.current.addEventListener('touchmove', onTouchMove, { passive: false }); elementRef.current.addEventListener('touchend', onTouchEnd, { passive: true }); + elementRef.current.addEventListener('touchcancel', onTouchEnd, { passive: true }); } }; cleanUpEvents = () => { + touchedRef.current = false; + if (elementRef.current) { elementRef.current.removeEventListener('touchmove', onTouchMove); elementRef.current.removeEventListener('touchend', onTouchEnd); + elementRef.current.removeEventListener('touchcancel', onTouchEnd); } }; diff --git a/tests/touch.test.js b/tests/touch.test.js index ae53fb6..36d65a8 100644 --- a/tests/touch.test.js +++ b/tests/touch.test.js @@ -90,6 +90,75 @@ describe('List.Touch', () => { unmount(); }); + it('recover touch scroll after multi-touch', () => { + const { container, unmount } = genList({ itemHeight: 20, height: 100, data: genData(100) }); + const element = container.querySelector('.rc-virtual-list-holder'); + + // 1. Initial touch start with 1 finger + const touchStart1 = new Event('touchstart'); + touchStart1.touches = [{ pageY: 100 }]; + element.dispatchEvent(touchStart1); + + // 2. Second finger touches down (multi-touch gesture) + const multiTouchStart = new Event('touchstart'); + multiTouchStart.touches = [{ pageY: 100 }, { pageY: 120 }]; + element.dispatchEvent(multiTouchStart); + + // 3. User releases fingers and performs normal single-finger touch + act(() => { + const touchStart2 = new Event('touchstart'); + touchStart2.touches = [{ pageY: 100 }]; + element.dispatchEvent(touchStart2); + + const moveEvent = new Event('touchmove'); + moveEvent.touches = [{ pageY: 90 }]; + element.dispatchEvent(moveEvent); + + const endEvent = new Event('touchend'); + element.dispatchEvent(endEvent); + + jest.runAllTimers(); + }); + + expect(container.querySelector('ul').scrollTop > 10).toBeTruthy(); + + unmount(); + }); + + it('recover touch scroll after touchcancel', () => { + const { container, unmount } = genList({ itemHeight: 20, height: 100, data: genData(100) }); + const element = container.querySelector('.rc-virtual-list-holder'); + + // 1. Touch start with 1 finger + const touchStart1 = new Event('touchstart'); + touchStart1.touches = [{ pageY: 100 }]; + element.dispatchEvent(touchStart1); + + // 2. Browser fires touchcancel (e.g. system gesture) + const cancelEvent = new Event('touchcancel'); + element.dispatchEvent(cancelEvent); + + // 3. User touches again + act(() => { + const touchStart2 = new Event('touchstart'); + touchStart2.touches = [{ pageY: 100 }]; + element.dispatchEvent(touchStart2); + + const moveEvent = new Event('touchmove'); + moveEvent.touches = [{ pageY: 90 }]; + element.dispatchEvent(moveEvent); + + const endEvent = new Event('touchend'); + element.dispatchEvent(endEvent); + + jest.runAllTimers(); + }); + + expect(container.querySelector('ul').scrollTop > 10).toBeTruthy(); + + unmount(); + }); + it('not call when not scroll-able', () => { const { container } = genList({ itemHeight: 20, height: 100, data: genData(100) }); From ebcf945c007dd478fdbe1e2e94218f7a5fc344a3 Mon Sep 17 00:00:00 2001 From: Ferit Date: Wed, 4 Nov 2026 08:54:25 +0200 Subject: [PATCH 2/2] Test cancelled touch moves before the next gesture --- tests/touch.test.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/touch.test.js b/tests/touch.test.js index 36d65a8..74416ce 100644 --- a/tests/touch.test.js +++ b/tests/touch.test.js @@ -138,6 +138,14 @@ describe('List.Touch', () => { const cancelEvent = new Event('touchcancel'); element.dispatchEvent(cancelEvent); + act(() => { + const cancelledMove = new Event('touchmove'); + cancelledMove.touches = [{ pageY: 90 }]; + element.dispatchEvent(cancelledMove); + jest.runAllTimers(); + }); + expect(container.querySelector('ul').scrollTop).toBe(0); + // 3. User touches again act(() => { const touchStart2 = new Event('touchstart');