From 5ceeade4f130b99c926ae246b1cb300a8bbd21bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=B3=E5=BB=B7=E5=AE=89?= <73953029+nrps9909@users.noreply.github.com> Date: Thu, 27 Aug 2026 01:57:59 +0800 Subject: [PATCH 1/3] fix: finish drag when mouseup propagation stops --- src/hooks/useDrag.ts | 6 +++--- tests/Slider.test.js | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/hooks/useDrag.ts b/src/hooks/useDrag.ts index a497e3972..0f3db9732 100644 --- a/src/hooks/useDrag.ts +++ b/src/hooks/useDrag.ts @@ -58,7 +58,7 @@ function useDrag( document.removeEventListener('mousemove', mouseMoveEventRef.current); } if (mouseUpEventRef.current) { - document.removeEventListener('mouseup', mouseUpEventRef.current); + document.removeEventListener('mouseup', mouseUpEventRef.current, true); } if (touchEventTargetRef.current) { if (mouseMoveEventRef.current) { @@ -213,7 +213,7 @@ function useDrag( const onMouseUp: EventListener = (event) => { event.preventDefault(); - document.removeEventListener('mouseup', onMouseUp); + document.removeEventListener('mouseup', onMouseUp, true); document.removeEventListener('mousemove', onMouseMove); if (touchEventTargetRef.current) { if (mouseMoveEventRef.current) { @@ -233,7 +233,7 @@ function useDrag( setDraggingDelete(false); }; - document.addEventListener('mouseup', onMouseUp); + document.addEventListener('mouseup', onMouseUp, true); document.addEventListener('mousemove', onMouseMove); e.currentTarget.addEventListener('touchend', onMouseUp); e.currentTarget.addEventListener('touchmove', onMouseMove); diff --git a/tests/Slider.test.js b/tests/Slider.test.js index 2300fe046..69c058ff3 100644 --- a/tests/Slider.test.js +++ b/tests/Slider.test.js @@ -651,6 +651,33 @@ describe('Slider', () => { expect(onAfterChange).toHaveBeenCalledWith(20); }); + it('should finish dragging when a parent stops mouseup propagation', () => { + const onChange = jest.fn(); + const onChangeComplete = jest.fn(); + const { container, getByTestId } = render( +
event.stopPropagation()}> + +
, + ); + const handle = container.querySelector('.rc-slider-handle'); + + fireEvent.mouseDown(handle, { pageX: 50 }); + const firstMove = createEvent.mouseMove(document); + firstMove.pageX = 60; + fireEvent(document, firstMove); + expect(onChange).toHaveBeenCalled(); + const lastDraggedValue = onChange.mock.calls[onChange.mock.calls.length - 1][0]; + + onChange.mockClear(); + fireEvent.mouseUp(getByTestId('parent')); + expect(onChangeComplete).toHaveBeenCalledWith(lastDraggedValue); + + const moveAfterRelease = createEvent.mouseMove(document); + moveAfterRelease.pageX = 70; + fireEvent(document, moveAfterRelease); + expect(onChange).not.toHaveBeenCalled(); + }); + // https://github.com/react-component/slider/pull/948 it('could drag handler after click tracker', () => { const onChange = jest.fn(); From 88b475231dbd073f5736f098efcf33670a10e686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=B3=E5=BB=B7=E5=AE=89?= <73953029+nrps9909@users.noreply.github.com> Date: Thu, 27 Aug 2026 12:05:18 +0800 Subject: [PATCH 2/3] fix: preserve mouseup default behavior --- src/hooks/useDrag.ts | 4 +++- tests/Slider.test.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/hooks/useDrag.ts b/src/hooks/useDrag.ts index 0f3db9732..0257c6f08 100644 --- a/src/hooks/useDrag.ts +++ b/src/hooks/useDrag.ts @@ -211,7 +211,9 @@ function useDrag( // End const onMouseUp: EventListener = (event) => { - event.preventDefault(); + if (event.type === 'touchend') { + event.preventDefault(); + } document.removeEventListener('mouseup', onMouseUp, true); document.removeEventListener('mousemove', onMouseMove); diff --git a/tests/Slider.test.js b/tests/Slider.test.js index be56cf169..1adebb330 100644 --- a/tests/Slider.test.js +++ b/tests/Slider.test.js @@ -662,8 +662,9 @@ describe('Slider', () => { it('should finish dragging when a parent stops mouseup propagation', () => { const onChange = jest.fn(); const onChangeComplete = jest.fn(); + const onParentMouseUp = jest.fn((event) => event.stopPropagation()); const { container, getByTestId } = render( -
event.stopPropagation()}> +
, ); @@ -678,6 +679,7 @@ describe('Slider', () => { onChange.mockClear(); fireEvent.mouseUp(getByTestId('parent')); + expect(onParentMouseUp.mock.calls[0][0].defaultPrevented).toBe(false); expect(onChangeComplete).toHaveBeenCalledWith(lastDraggedValue); const moveAfterRelease = createEvent.mouseMove(document); From a536a739b6892ddce9b5b7c8201b6059c45f318a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=B3=E5=BB=B7=E5=AE=89?= <73953029+nrps9909@users.noreply.github.com> Date: Thu, 27 Aug 2026 12:16:31 +0800 Subject: [PATCH 3/3] test: cover touch drag completion --- tests/Range.test.tsx | 92 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 79 insertions(+), 13 deletions(-) diff --git a/tests/Range.test.tsx b/tests/Range.test.tsx index dd6ad0044..32323733b 100644 --- a/tests/Range.test.tsx +++ b/tests/Range.test.tsx @@ -94,6 +94,28 @@ describe('Range', () => { fireEvent(container.getElementsByClassName(element)[0], touchMove); } + it('prevents the native default action when a touch drag ends', () => { + const onChangeComplete = jest.fn(); + const { container } = render( + , + ); + const handle = container.getElementsByClassName('rc-slider-handle')[0]; + const touchStart = createEvent.touchStart(handle, { + touches: [{}], + targetTouches: [{}], + }); + (touchStart as any).targetTouches[0].pageX = 20; + fireEvent(handle, touchStart); + + const touchEnd = createEvent.touchEnd(handle); + const preventDefault = jest.fn(); + Object.defineProperty(touchEnd, 'preventDefault', { value: preventDefault }); + fireEvent(handle, touchEnd); + + expect(preventDefault).toHaveBeenCalledTimes(1); + expect(onChangeComplete).toHaveBeenCalledWith([20, 40]); + }); + it('should render Range with correct DOM structure', () => { const { asFragment } = render(); expect(asFragment().firstChild).toMatchSnapshot(); @@ -140,9 +162,7 @@ describe('Range', () => { }); it('should render Range without tabIndex (equal null) correctly', () => { - const { container } = render( - , - ); + const { container } = render(); expect(container.getElementsByClassName('rc-slider-handle')[0]).not.toHaveAttribute('tabIndex'); expect(container.getElementsByClassName('rc-slider-handle')[1]).not.toHaveAttribute('tabIndex'); }); @@ -872,7 +892,12 @@ describe('Range', () => { it('respects handle disabled state and boolean disabled fallback', () => { const onChange = jest.fn(); const { container, rerender } = render( - , + , ); const disabledHandle = getHandle(container, 0); @@ -909,17 +934,23 @@ describe('Range', () => { expect(onChange).toHaveBeenCalledWith([0, 10, 100]); onChange.mockClear(); - rerender(); + rerender( + , + ); doMouseDown(container, 10, 'rc-slider', true); fireEvent.mouseUp(document); expect(onChange).not.toHaveBeenCalled(); - rerender(); + rerender( + , + ); doMouseDown(container, 90, 'rc-slider', true); fireEvent.mouseUp(document); expect(onChange).not.toHaveBeenCalled(); - rerender(); + rerender( + , + ); doMouseDown(container, 10, 'rc-slider', true); fireEvent.mouseUp(document); expect(onChange).not.toHaveBeenCalled(); @@ -963,7 +994,12 @@ describe('Range', () => { it('disables draggableTrack only when rendered handles are disabled', () => { const onChange = jest.fn(); const { container, unmount } = render( - , + , ); const track = container.getElementsByClassName('rc-slider-track')[0]; @@ -1002,7 +1038,12 @@ describe('Range', () => { it('keeps keyboard movement inside disabled handle boundaries', () => { const onChange = jest.fn(); const { container, unmount } = render( - , + , ); repeatKeyDown(getHandle(container, 0), keyCode.RIGHT, 50); @@ -1012,7 +1053,12 @@ describe('Range', () => { onChange.mockClear(); const { container: boundaryContainer } = render( - , + , ); const middleHandle = getHandle(boundaryContainer, 1); @@ -1029,7 +1075,13 @@ describe('Range', () => { it('respects pushable boundaries around disabled handles', () => { const onChange = jest.fn(); const { container } = render( - , + , ); repeatKeyDown(getHandle(container, 0), keyCode.UP, 30); @@ -1046,7 +1098,13 @@ describe('Range', () => { it('respects disabled boundaries with allowCross=false and step=null', () => { const onChange = jest.fn(); const { container, unmount } = render( - , + , ); repeatKeyDown(getHandle(container, 0), keyCode.RIGHT, 50); @@ -1056,7 +1114,15 @@ describe('Range', () => { onChange.mockClear(); const { container: stepContainer } = render( - , + , ); fireEvent.keyDown(getHandle(stepContainer, 0), { keyCode: keyCode.RIGHT });