diff --git a/assets/index.less b/assets/index.less
index aa10b3d3a..e2cb4e411 100644
--- a/assets/index.less
+++ b/assets/index.less
@@ -73,6 +73,10 @@
&-dragging&-dragging&-dragging {
border-color: tint(@primary-color, 20%);
box-shadow: 0 0 0 5px tint(@primary-color, 50%);
+
+ &-delete {
+ opacity: 0;
+ }
}
&:focus {
@@ -186,11 +190,11 @@
left: 5px;
width: 4px;
}
-
+
&-track-draggable {
- border-top:0;
- border-bottom: 0;
+ border-top: 0;
border-right: 5px solid rgba(0, 0, 0, 0);
+ border-bottom: 0;
border-left: 5px solid rgba(0, 0, 0, 0);
transform: translateX(-5px);
}
diff --git a/docs/demo/editable.md b/docs/demo/editable.md
new file mode 100644
index 000000000..3c31ad9b4
--- /dev/null
+++ b/docs/demo/editable.md
@@ -0,0 +1,8 @@
+---
+title: Multiple
+nav:
+ title: Demo
+ path: /demo
+---
+
+
diff --git a/docs/examples/editable.tsx b/docs/examples/editable.tsx
new file mode 100644
index 000000000..051ef4e8f
--- /dev/null
+++ b/docs/examples/editable.tsx
@@ -0,0 +1,43 @@
+/* eslint react/no-multi-comp: 0, no-console: 0 */
+import Slider from 'rc-slider';
+import React from 'react';
+import '../../assets/index.less';
+
+const style: React.CSSProperties = {
+ width: 400,
+ margin: 50,
+};
+
+export default () => {
+ const [value, setValue] = React.useState([0, 50, 80]);
+
+ return (
+
+
+ {
+ console.error('Change:', nextValue);
+ setValue(nextValue as any);
+ }}
+ onChangeComplete={(nextValue) => {
+ console.log('Complete', nextValue);
+ }}
+ styles={{
+ rail: {
+ background: `linear-gradient(to right, blue, red)`,
+ },
+ }}
+ />
+
+
+ );
+};
diff --git a/docs/examples/multiple.tsx b/docs/examples/multiple.tsx
index 55bc91d25..5f5e96301 100644
--- a/docs/examples/multiple.tsx
+++ b/docs/examples/multiple.tsx
@@ -17,7 +17,7 @@ const NodeWrapper = ({ children }: { children: React.ReactElement }) => {
};
export default () => {
- const [value, setValue] = React.useState([0, 5, 8]);
+ const [value, setValue] = React.useState([0, 50, 80]);
return (
@@ -27,7 +27,7 @@ export default () => {
// defaultValue={[0, 10, 30]}
// onChange={log}
min={0}
- max={10}
+ max={100}
value={value}
onChange={(nextValue) => {
// console.log('>>>', nextValue);
diff --git a/package.json b/package.json
index 08efda1f8..a33a27dcc 100644
--- a/package.json
+++ b/package.json
@@ -51,6 +51,7 @@
"@testing-library/react": "^12.1.3",
"@types/classnames": "^2.2.9",
"@types/jest": "^29.5.1",
+ "@types/node": "^20.14.10",
"@types/react": "^18.2.42",
"@types/react-dom": "^18.0.11",
"@umijs/fabric": "^4.0.1",
diff --git a/src/Handles/Handle.tsx b/src/Handles/Handle.tsx
index bf802145e..1a54f6ccd 100644
--- a/src/Handles/Handle.tsx
+++ b/src/Handles/Handle.tsx
@@ -10,6 +10,7 @@ interface RenderProps {
prefixCls: string;
value: number;
dragging: boolean;
+ draggingDelete: boolean;
}
export interface HandleProps
@@ -19,7 +20,9 @@ export interface HandleProps
value: number;
valueIndex: number;
dragging: boolean;
+ draggingDelete: boolean;
onStartMove: OnStartMove;
+ onDelete: (index: number) => void;
onOffsetChange: (value: number | 'min' | 'max', valueIndex: number) => void;
onFocus: (e: React.FocusEvent, index: number) => void;
onMouseEnter: (e: React.MouseEvent, index: number) => void;
@@ -37,9 +40,11 @@ const Handle = React.forwardRef((props, ref) => {
value,
valueIndex,
onStartMove,
+ onDelete,
style,
render,
dragging,
+ draggingDelete,
onOffsetChange,
onChangeComplete,
onFocus,
@@ -118,6 +123,11 @@ const Handle = React.forwardRef((props, ref) => {
case KeyCode.PAGE_DOWN:
offset = -2;
break;
+
+ case KeyCode.BACKSPACE:
+ case KeyCode.DELETE:
+ onDelete(valueIndex);
+ break;
}
if (offset !== null) {
@@ -177,6 +187,7 @@ const Handle = React.forwardRef((props, ref) => {
{
[`${handlePrefixCls}-${valueIndex + 1}`]: valueIndex !== null && range,
[`${handlePrefixCls}-dragging`]: dragging,
+ [`${handlePrefixCls}-dragging-delete`]: draggingDelete,
},
classNames.handle,
)}
@@ -197,6 +208,7 @@ const Handle = React.forwardRef((props, ref) => {
prefixCls,
value,
dragging,
+ draggingDelete,
});
}
diff --git a/src/Handles/index.tsx b/src/Handles/index.tsx
index c412c9c96..c7a4f97bd 100644
--- a/src/Handles/index.tsx
+++ b/src/Handles/index.tsx
@@ -12,6 +12,7 @@ export interface HandlesProps {
onOffsetChange: (value: number | 'min' | 'max', valueIndex: number) => void;
onFocus?: (e: React.FocusEvent) => void;
onBlur?: (e: React.FocusEvent) => void;
+ onDelete: (index: number) => void;
handleRender?: HandleProps['render'];
/**
* When config `activeHandleRender`,
@@ -20,6 +21,7 @@ export interface HandlesProps {
*/
activeHandleRender?: HandleProps['render'];
draggingIndex: number;
+ draggingDelete: boolean;
onChangeComplete?: () => void;
}
@@ -37,6 +39,7 @@ const Handles = React.forwardRef((props, ref) => {
handleRender,
activeHandleRender,
draggingIndex,
+ draggingDelete,
onFocus,
...restProps
} = props;
@@ -74,24 +77,30 @@ const Handles = React.forwardRef((props, ref) => {
return (
<>
- {values.map((value, index) => (
- {
- if (!node) {
- delete handlesRef.current[index];
- } else {
- handlesRef.current[index] = node;
- }
- }}
- dragging={draggingIndex === index}
- style={getIndex(style, index)}
- key={index}
- value={value}
- valueIndex={index}
- {...handleProps}
- />
- ))}
+ {values.map((value, index) => {
+ const dragging = draggingIndex === index;
+
+ return (
+ {
+ if (!node) {
+ delete handlesRef.current[index];
+ } else {
+ handlesRef.current[index] = node;
+ }
+ }}
+ dragging={dragging}
+ draggingDelete={dragging && draggingDelete}
+ style={getIndex(style, index)}
+ key={index}
+ value={value}
+ valueIndex={index}
+ {...handleProps}
+ />
+ );
+ })}
+ {/* Used for render tooltip, this is not a real handle */}
{activeHandleRender && (
((props, ref) => {
value={values[activeIndex]}
valueIndex={null}
dragging={draggingIndex !== -1}
+ draggingDelete={draggingDelete}
render={activeHandleRender}
style={{ pointerEvents: 'none' }}
tabIndex={null}
diff --git a/src/Slider.tsx b/src/Slider.tsx
index 1168e4b38..78af41525 100644
--- a/src/Slider.tsx
+++ b/src/Slider.tsx
@@ -1,4 +1,5 @@
import cls from 'classnames';
+import { useEvent } from 'rc-util';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import isEqual from 'rc-util/lib/isEqual';
import warning from 'rc-util/lib/warning';
@@ -13,6 +14,7 @@ import type { SliderContextProps } from './context';
import SliderContext from './context';
import useDrag from './hooks/useDrag';
import useOffset from './hooks/useOffset';
+import useRange from './hooks/useRange';
import type {
AriaValueFormat,
Direction,
@@ -35,6 +37,11 @@ import type {
* - keyboard support pushable
*/
+export type RangeConfig = {
+ editable?: boolean;
+ draggableTrack?: boolean;
+};
+
export interface SliderProps {
prefixCls?: string;
className?: string;
@@ -51,7 +58,7 @@ export interface SliderProps {
onBlur?: (e: React.FocusEvent) => void;
// Value
- range?: boolean;
+ range?: boolean | RangeConfig;
count?: number;
min?: number;
max?: number;
@@ -68,8 +75,6 @@ export interface SliderProps {
// Cross
allowCross?: boolean;
pushable?: boolean | number;
- /** range only */
- draggableTrack?: boolean;
// Direction
reverse?: boolean;
@@ -94,6 +99,7 @@ export interface SliderProps {
// Components
handleRender?: HandlesProps['handleRender'];
activeHandleRender?: HandlesProps['handleRender'];
+ track?: boolean;
// Accessibility
tabIndex?: number | number[];
@@ -138,7 +144,6 @@ const Slider = React.forwardRef>((prop
// Cross
allowCross = true,
pushable = false,
- draggableTrack,
// Direction
reverse,
@@ -160,6 +165,7 @@ const Slider = React.forwardRef>((prop
// Components
handleRender,
activeHandleRender,
+ track,
// Accessibility
tabIndex = 0,
@@ -179,6 +185,8 @@ const Slider = React.forwardRef>((prop
}, [reverse, vertical]);
// ============================ Range =============================
+ const [rangeEnabled, rangeEditable, rangeDraggableTrack] = useRange(range);
+
const mergedMin = React.useMemo(() => (isFinite(min) ? min : 0), [min]);
const mergedMax = React.useMemo(() => (isFinite(max) ? max : 100), [max]);
@@ -247,7 +255,7 @@ const Slider = React.forwardRef>((prop
let returnValues = mergedValue === null ? [] : [val0];
// Format as range
- if (range) {
+ if (rangeEnabled) {
returnValues = [...valueList];
// When count provided or value is `undefined`, we fill values
@@ -269,38 +277,49 @@ const Slider = React.forwardRef>((prop
});
return returnValues;
- }, [mergedValue, range, mergedMin, count, formatValue]);
+ }, [mergedValue, rangeEnabled, mergedMin, count, formatValue]);
// =========================== onChange ===========================
- const rawValuesRef = React.useRef(rawValues);
- rawValuesRef.current = rawValues;
+ const getTriggerValue = (triggerValues: number[]) =>
+ rangeEnabled ? triggerValues : triggerValues[0];
- const getTriggerValue = (triggerValues: number[]) => (range ? triggerValues : triggerValues[0]);
-
- const triggerChange = (nextValues: number[]) => {
+ const triggerChange = useEvent((nextValues: number[]) => {
// Order first
const cloneNextValues = [...nextValues].sort((a, b) => a - b);
// Trigger event if needed
- if (onChange && !isEqual(cloneNextValues, rawValuesRef.current, true)) {
+ if (onChange && !isEqual(cloneNextValues, rawValues, true)) {
onChange(getTriggerValue(cloneNextValues));
}
// We set this later since it will re-render component immediately
setValue(cloneNextValues);
- };
+ });
- const finishChange = () => {
- const finishValue = getTriggerValue(rawValuesRef.current);
+ const finishChange = useEvent(() => {
+ const finishValue = getTriggerValue(rawValues);
onAfterChange?.(finishValue);
warning(
!onAfterChange,
'[rc-slider] `onAfterChange` is deprecated. Please use `onChangeComplete` instead.',
);
onChangeComplete?.(finishValue);
+ });
+
+ const onDelete = (index: number) => {
+ if (!disabled && rangeEditable) {
+ const cloneNextValues = [...rawValues];
+ cloneNextValues.splice(index, 1);
+
+ onBeforeChange?.(getTriggerValue(cloneNextValues));
+ triggerChange(cloneNextValues);
+
+ const nextFocusIndex = Math.max(0, index - 1);
+ handlesRef.current.focus(nextFocusIndex);
+ }
};
- const [draggingIndex, draggingValue, cacheValues, onStartDrag] = useDrag(
+ const [draggingIndex, draggingValue, draggingDelete, cacheValues, onStartDrag] = useDrag(
containerRef,
direction,
rawValues,
@@ -310,11 +329,20 @@ const Slider = React.forwardRef>((prop
triggerChange,
finishChange,
offsetValues,
+ rangeEditable,
);
+ /**
+ * When `rangeEditable` will insert a new value in the values array.
+ * Else it will replace the value in the values array.
+ */
const changeToCloseValue = (newValue: number, e?: React.MouseEvent) => {
if (!disabled) {
+ // Create new values
+ const cloneNextValues = [...rawValues];
+
let valueIndex = 0;
+ let valueBeforeIndex = 0; // Record the index which value < newValue
let valueDist = mergedMax - mergedMin;
rawValues.forEach((val, index) => {
@@ -323,15 +351,23 @@ const Slider = React.forwardRef>((prop
valueDist = dist;
valueIndex = index;
}
+
+ if (val < newValue) {
+ valueBeforeIndex = index;
+ }
});
- // Create new values
- const cloneNextValues = [...rawValues];
+ let focusIndex = valueIndex;
- cloneNextValues[valueIndex] = newValue;
+ if (rangeEditable && valueDist !== 0) {
+ cloneNextValues.splice(valueBeforeIndex + 1, 0, newValue);
+ focusIndex = valueBeforeIndex + 1;
+ } else {
+ cloneNextValues[valueIndex] = newValue;
+ }
- // Fill value to match default 2
- if (range && !rawValues.length && count === undefined) {
+ // Fill value to match default 2 (only when `rawValues` is empty)
+ if (rangeEnabled && !rawValues.length && count === undefined) {
cloneNextValues.push(newValue);
}
@@ -339,8 +375,8 @@ const Slider = React.forwardRef>((prop
triggerChange(cloneNextValues);
if (e) {
(document.activeElement as HTMLElement)?.blur?.();
- handlesRef.current.focus(valueIndex);
- onStartDrag(e, valueIndex, cloneNextValues);
+ handlesRef.current.focus(focusIndex);
+ onStartDrag(e, focusIndex, cloneNextValues);
}
}
};
@@ -402,20 +438,20 @@ const Slider = React.forwardRef>((prop
// ============================= Drag =============================
const mergedDraggableTrack = React.useMemo(() => {
- if (draggableTrack && mergedStep === null) {
+ if (rangeDraggableTrack && mergedStep === null) {
if (process.env.NODE_ENV !== 'production') {
warning(false, '`draggableTrack` is not supported when `step` is `null`.');
}
return false;
}
- return draggableTrack;
- }, [draggableTrack, mergedStep]);
+ return rangeDraggableTrack;
+ }, [rangeDraggableTrack, mergedStep]);
- const onStartMove: OnStartMove = (e, valueIndex) => {
+ const onStartMove: OnStartMove = useEvent((e, valueIndex) => {
onStartDrag(e, valueIndex);
- onBeforeChange?.(getTriggerValue(rawValuesRef.current));
- };
+ onBeforeChange?.(getTriggerValue(rawValues));
+ });
// Auto focus for updated handle
const dragging = draggingIndex !== -1;
@@ -435,12 +471,12 @@ const Slider = React.forwardRef>((prop
// Provide a range values with included [min, max]
// Used for Track, Mark & Dot
const [includedStart, includedEnd] = React.useMemo(() => {
- if (!range) {
+ if (!rangeEnabled) {
return [mergedMin, sortedCacheValues[0]];
}
return [sortedCacheValues[0], sortedCacheValues[sortedCacheValues.length - 1]];
- }, [sortedCacheValues, range, mergedMin]);
+ }, [sortedCacheValues, rangeEnabled, mergedMin]);
// ============================= Refs =============================
React.useImperativeHandle(ref, () => ({
@@ -474,7 +510,7 @@ const Slider = React.forwardRef>((prop
included,
includedStart,
includedEnd,
- range,
+ range: rangeEnabled,
tabIndex,
ariaLabelForHandle,
ariaLabelledByForHandle,
@@ -492,7 +528,7 @@ const Slider = React.forwardRef>((prop
included,
includedStart,
includedEnd,
- range,
+ rangeEnabled,
tabIndex,
ariaLabelForHandle,
ariaLabelledByForHandle,
@@ -521,13 +557,15 @@ const Slider = React.forwardRef>((prop
style={{ ...railStyle, ...styles?.rail }}
/>
-
+ {track !== false && (
+
+ )}
>((prop
style={handleStyle}
values={cacheValues}
draggingIndex={draggingIndex}
+ draggingDelete={draggingDelete}
onStartMove={onStartMove}
onOffsetChange={onHandleOffsetChange}
onFocus={onFocus}
@@ -550,6 +589,7 @@ const Slider = React.forwardRef>((prop
handleRender={handleRender}
activeHandleRender={activeHandleRender}
onChangeComplete={finishChange}
+ onDelete={rangeEditable ? onDelete : undefined}
/>
diff --git a/src/hooks/useDrag.ts b/src/hooks/useDrag.ts
index 49fb8fc80..37de73a42 100644
--- a/src/hooks/useDrag.ts
+++ b/src/hooks/useDrag.ts
@@ -3,6 +3,9 @@ import * as React from 'react';
import type { Direction, OnStartMove } from '../interface';
import type { OffsetValues } from './useOffset';
+/** Drag to delete offset. It's a user experience number for dragging out */
+const REMOVE_DIST = 130;
+
function getPosition(e: React.MouseEvent | React.TouchEvent | MouseEvent | TouchEvent) {
const obj = 'touches' in e ? e.touches[0] : e;
@@ -19,14 +22,17 @@ function useDrag(
triggerChange: (values: number[]) => void,
finishChange: () => void,
offsetValues: OffsetValues,
+ editable: boolean,
): [
draggingIndex: number,
draggingValue: number,
+ draggingDelete: boolean,
returnValues: number[],
onStartMove: OnStartMove,
] {
const [draggingValue, setDraggingValue] = React.useState(null);
const [draggingIndex, setDraggingIndex] = React.useState(-1);
+ const [draggingDelete, setDraggingDelete] = React.useState(false);
const [cacheValues, setCacheValues] = React.useState(rawValues);
const [originValues, setOriginValues] = React.useState(rawValues);
@@ -50,50 +56,55 @@ function useDrag(
[],
);
- const flushValues = (nextValues: number[], nextValue?: number) => {
+ const flushValues = (nextValues: number[], nextValue?: number, deleteMark?: boolean) => {
// Perf: Only update state when value changed
- if (cacheValues.some((val, i) => val !== nextValues[i])) {
+ if (cacheValues.some((val, i) => val !== nextValues[i]) || deleteMark) {
if (nextValue !== undefined) {
setDraggingValue(nextValue);
}
setCacheValues(nextValues);
- triggerChange(nextValues);
+
+ let changeValues = nextValues;
+ if (deleteMark) {
+ changeValues = nextValues.filter((_, i) => i !== draggingIndex);
+ }
+ triggerChange(changeValues);
}
};
- const updateCacheValue = useEvent((valueIndex: number, offsetPercent: number) => {
- // Basic point offset
-
- if (valueIndex === -1) {
- // >>>> Dragging on the track
- const startValue = originValues[0];
- const endValue = originValues[originValues.length - 1];
- const maxStartOffset = min - startValue;
- const maxEndOffset = max - endValue;
-
- // Get valid offset
- let offset = offsetPercent * (max - min);
- offset = Math.max(offset, maxStartOffset);
- offset = Math.min(offset, maxEndOffset);
-
- // Use first value to revert back of valid offset (like steps marks)
- const formatStartValue = formatValue(startValue + offset);
- offset = formatStartValue - startValue;
- const cloneCacheValues = originValues.map((val) => val + offset);
- flushValues(cloneCacheValues);
- } else {
- // >>>> Dragging on the handle
- const offsetDist = (max - min) * offsetPercent;
-
- // Always start with the valueIndex origin value
- const cloneValues = [...cacheValues];
- cloneValues[valueIndex] = originValues[valueIndex];
-
- const next = offsetValues(cloneValues, offsetDist, valueIndex, 'dist');
-
- flushValues(next.values, next.value);
- }
- });
+ const updateCacheValue = useEvent(
+ (valueIndex: number, offsetPercent: number, deleteMark: boolean) => {
+ if (valueIndex === -1) {
+ // >>>> Dragging on the track
+ const startValue = originValues[0];
+ const endValue = originValues[originValues.length - 1];
+ const maxStartOffset = min - startValue;
+ const maxEndOffset = max - endValue;
+
+ // Get valid offset
+ let offset = offsetPercent * (max - min);
+ offset = Math.max(offset, maxStartOffset);
+ offset = Math.min(offset, maxEndOffset);
+
+ // Use first value to revert back of valid offset (like steps marks)
+ const formatStartValue = formatValue(startValue + offset);
+ offset = formatStartValue - startValue;
+ const cloneCacheValues = originValues.map((val) => val + offset);
+ flushValues(cloneCacheValues);
+ } else {
+ // >>>> Dragging on the handle
+ const offsetDist = (max - min) * offsetPercent;
+
+ // Always start with the valueIndex origin value
+ const cloneValues = [...cacheValues];
+ cloneValues[valueIndex] = originValues[valueIndex];
+
+ const next = offsetValues(cloneValues, offsetDist, valueIndex, 'dist');
+
+ flushValues(next.values, next.value, deleteMark);
+ }
+ },
+ );
const onStartMove: OnStartMove = (e, valueIndex, startValues?: number[]) => {
e.stopPropagation();
@@ -105,6 +116,8 @@ function useDrag(
setDraggingIndex(valueIndex);
setDraggingValue(originValue);
setOriginValues(initialValues);
+ setCacheValues(initialValues);
+ setDraggingDelete(false);
const { pageX: startX, pageY: startY } = getPosition(e);
@@ -119,23 +132,34 @@ function useDrag(
const { width, height } = containerRef.current.getBoundingClientRect();
let offSetPercent: number;
+ let removeDist: number;
+
switch (direction) {
case 'btt':
offSetPercent = -offsetY / height;
+ removeDist = offsetX;
break;
case 'ttb':
offSetPercent = offsetY / height;
+ removeDist = offsetX;
break;
case 'rtl':
offSetPercent = -offsetX / width;
+ removeDist = offsetY;
break;
default:
offSetPercent = offsetX / width;
+ removeDist = offsetY;
}
- updateCacheValue(valueIndex, offSetPercent);
+
+ // Check if need mark remove
+ const deleteMark = editable ? Math.abs(removeDist) > REMOVE_DIST : false;
+ setDraggingDelete(deleteMark);
+
+ updateCacheValue(valueIndex, offSetPercent, deleteMark);
};
// End
@@ -166,12 +190,21 @@ function useDrag(
const sourceValues = [...rawValues].sort((a, b) => a - b);
const targetValues = [...cacheValues].sort((a, b) => a - b);
- return sourceValues.every((val, index) => val === targetValues[index])
- ? cacheValues
- : rawValues;
- }, [rawValues, cacheValues]);
+ const counts: Record = {};
+ targetValues.forEach((val) => {
+ counts[val] = (counts[val] || 0) + 1;
+ });
+ sourceValues.forEach((val) => {
+ counts[val] = (counts[val] || 0) - 1;
+ });
+
+ const maxDiffCount = editable ? 1 : 0;
+ const diffCount: number = Object.values(counts).reduce((prev, next) => prev + next, 0);
+
+ return diffCount <= maxDiffCount ? cacheValues : rawValues;
+ }, [rawValues, cacheValues, editable]);
- return [draggingIndex, draggingValue, returnValues, onStartMove];
+ return [draggingIndex, draggingValue, draggingDelete, returnValues, onStartMove];
}
export default useDrag;
diff --git a/src/hooks/useRange.ts b/src/hooks/useRange.ts
new file mode 100644
index 000000000..ff109f239
--- /dev/null
+++ b/src/hooks/useRange.ts
@@ -0,0 +1,21 @@
+import { warning } from 'rc-util/lib/warning';
+import { useMemo } from 'react';
+import type { SliderProps } from '../Slider';
+
+export default function useRange(
+ range?: SliderProps['range'],
+): [range: boolean, rangeEditable: boolean, rangeDraggableTrack: boolean] {
+ return useMemo(() => {
+ if (range === true || !range) {
+ return [!!range, false, false];
+ }
+
+ const { editable, draggableTrack } = range;
+
+ if (process.env.NODE_ENV !== 'production') {
+ warning(!editable || !draggableTrack, '`editable` can not work with `draggableTrack`.');
+ }
+
+ return [true, editable, !editable && draggableTrack];
+ }, [range]);
+}
diff --git a/tests/Range.test.js b/tests/Range.test.tsx
similarity index 81%
rename from tests/Range.test.js
rename to tests/Range.test.tsx
index 638e22d93..63010ee4d 100644
--- a/tests/Range.test.js
+++ b/tests/Range.test.tsx
@@ -5,54 +5,70 @@ import keyCode from 'rc-util/lib/KeyCode';
import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
import { resetWarned } from 'rc-util/lib/warning';
import React from 'react';
-import Slider from '../src/';
+import Slider from '../src';
describe('Range', () => {
- let container;
-
beforeAll(() => {
spyElementPrototypes(HTMLElement, {
getBoundingClientRect: () => ({
width: 100,
height: 100,
+ left: 0,
+ top: 0,
+ bottom: 100,
+ right: 100,
}),
});
});
beforeEach(() => {
- container = document.createElement('div');
- document.body.appendChild(container);
- });
-
- afterEach(() => {
- document.body.removeChild(container);
+ resetWarned();
});
- function doMouseMove(container, start, end, element = 'rc-slider-handle') {
+ function doMouseDown(container: HTMLElement, start: number, element = 'rc-slider-handle') {
const mouseDown = createEvent.mouseDown(container.getElementsByClassName(element)[0]);
- mouseDown.pageX = start;
- mouseDown.pageY = start;
+ (mouseDown as any).pageX = start;
+ (mouseDown as any).pageY = start;
+ Object.defineProperties(mouseDown, {
+ clientX: { get: () => start },
+ clientY: { get: () => start },
+ });
+
fireEvent(container.getElementsByClassName(element)[0], mouseDown);
+ }
+
+ function doMouseMove(
+ container: HTMLElement,
+ start: number,
+ end: number,
+ element = 'rc-slider-handle',
+ ) {
+ doMouseDown(container, start, element);
// Drag
const mouseMove = createEvent.mouseMove(document);
- mouseMove.pageX = end;
- mouseMove.pageY = end;
+ (mouseMove as any).pageX = end;
+ (mouseMove as any).pageY = end;
fireEvent(document, mouseMove);
}
- function doTouchMove(container, start, end, element = 'rc-slider-handle') {
+ function doTouchMove(
+ container: HTMLElement,
+ start: number,
+ end: number,
+ element = 'rc-slider-handle',
+ ) {
const touchStart = createEvent.touchStart(container.getElementsByClassName(element)[0], {
touches: [{}],
});
- touchStart.touches[0].pageX = start;
+ (touchStart as any).touches[0].pageX = start;
fireEvent(container.getElementsByClassName(element)[0], touchStart);
// Drag
const touchMove = createEvent.touchMove(document, {
touches: [{}],
});
- touchMove.touches[0].pageX = end;
+ (touchMove as any).touches[0].pageX = end;
fireEvent(document, touchMove);
}
@@ -344,7 +360,7 @@ describe('Range', () => {
return (
{
+ onChange={(values: number[]) => {
setValue(values);
onChange(values);
}}
@@ -377,7 +393,7 @@ describe('Range', () => {
const onChange = jest.fn();
const { container, unmount } = render(
- ,
+ ,
);
// Do move
@@ -507,15 +523,15 @@ describe('Range', () => {
it('focus()', () => {
const handleFocus = jest.fn();
const { container } = render();
- container.getElementsByClassName('rc-slider-handle')[0].focus();
+ container.querySelector('.rc-slider-handle').focus();
expect(handleFocus).toBeCalled();
});
it('blur()', () => {
const handleBlur = jest.fn();
const { container } = render();
- container.getElementsByClassName('rc-slider-handle')[0].focus();
- container.getElementsByClassName('rc-slider-handle')[0].blur();
+ container.querySelector('.rc-slider-handle').focus();
+ container.querySelector('.rc-slider-handle').blur();
expect(handleBlur).toHaveBeenCalled();
});
});
@@ -523,8 +539,7 @@ describe('Range', () => {
it('warning for `draggableTrack` and `mergedStep=null`', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
- resetWarned();
- render();
+ render();
expect(errorSpy).toHaveBeenCalledWith(
'Warning: `draggableTrack` is not supported when `step` is `null`.',
@@ -534,16 +549,15 @@ describe('Range', () => {
it('Track should have the correct thickness', () => {
const { container } = render(
- ,
+ ,
);
const { container: containerVertical } = render(
,
@@ -599,4 +613,108 @@ describe('Range', () => {
expect(container.querySelector('.rc-slider-handle')).toHaveClass('my-handle');
expect(container.querySelector('.rc-slider-rail')).toHaveClass('my-rail');
});
+
+ describe('editable', () => {
+ it('click to create', () => {
+ const onChange = jest.fn();
+ const { container } = render(
+ ,
+ );
+
+ doMouseDown(container, 50, 'rc-slider');
+
+ expect(onChange).toHaveBeenCalledWith([0, 50, 100]);
+ });
+
+ it('can not editable with draggableTrack at same time', () => {
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ render();
+
+ expect(errorSpy).toHaveBeenCalledWith(
+ 'Warning: `editable` can not work with `draggableTrack`.',
+ );
+ errorSpy.mockRestore();
+ });
+
+ describe('drag out to remove', () => {
+ it('uncontrolled', () => {
+ const onChange = jest.fn();
+ const onChangeComplete = jest.fn();
+ const { container } = render(
+ ,
+ );
+
+ doMouseMove(container, 0, 1000);
+ expect(onChange).toHaveBeenCalledWith([50, 100]);
+
+ // Fire mouse up
+ fireEvent.mouseUp(container.querySelector('.rc-slider-handle'));
+ expect(onChangeComplete).toHaveBeenCalledWith([50, 100]);
+ });
+
+ it('controlled', () => {
+ const onChange = jest.fn();
+ const onChangeComplete = jest.fn();
+
+ const Demo = () => {
+ const [value, setValue] = React.useState([0, 50, 100]);
+ return (
+ {
+ onChange(nextValue);
+ setValue(nextValue);
+ }}
+ onChangeComplete={onChangeComplete}
+ min={0}
+ max={100}
+ value={value}
+ range={{ editable: true }}
+ />
+ );
+ };
+
+ const { container } = render();
+
+ doMouseMove(container, 0, 1000);
+ expect(onChange).toHaveBeenCalledWith([50, 100]);
+
+ // Fire mouse up
+ fireEvent.mouseUp(container.querySelector('.rc-slider-handle'));
+ expect(onChangeComplete).toHaveBeenCalledWith([50, 100]);
+ });
+ });
+
+ it('key to delete', () => {
+ const onChange = jest.fn();
+
+ const { container } = render(
+ ,
+ );
+
+ fireEvent.keyDown(container.querySelectorAll('.rc-slider-handle')[1], {
+ keyCode: keyCode.DELETE,
+ });
+
+ expect(onChange).toHaveBeenCalledWith([0, 100]);
+ });
+ });
});
diff --git a/tests/__snapshots__/Range.test.js.snap b/tests/__snapshots__/Range.test.tsx.snap
similarity index 100%
rename from tests/__snapshots__/Range.test.js.snap
rename to tests/__snapshots__/Range.test.tsx.snap