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()}>
+
,
);
@@ -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 });