Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0667d6f
feat: add support for disabling specific slider handles and update st…
Apr 15, 2026
52c7208
feat: add onDisabledChange callback to sync disabled handles in edita…
Apr 15, 2026
5303584
docs: update README and example for disabled array feature
Apr 15, 2026
15e473f
docs: update README and examples to clarify disabled handle functiona…
Apr 15, 2026
204809d
feat: implement boundary constraints for disabled slider handles
Apr 15, 2026
a4c1c18
test: add test coverage
Apr 15, 2026
ed2438b
Update src/Slider.tsx
EmilyyyLiu Apr 16, 2026
4e23930
Update src/Slider.tsx
EmilyyyLiu Apr 16, 2026
eee70b9
test: add coverage tests for pushable with disabled handles
zombieJ Apr 17, 2026
ea5b72e
refactor: simplify disabled handle implementation
Apr 17, 2026
9584ac3
fix: enforce required isHandleDisabled parameter in useDrag and useOf…
Apr 17, 2026
cda5aeb
refactor: make isHandleDisabled required in context
Apr 17, 2026
7fae517
fix: add missing isHandleDisabled default value in context
Apr 17, 2026
cf5819c
refactor: optimize disabled handle implementation based on review fee…
Apr 20, 2026
fbb2822
refactor: extract useDisabled hook to unify disabled handling
Apr 20, 2026
bc3a910
refactor: optimize disabled handle boundary calculation
Apr 22, 2026
cb346c4
Update docs/examples/disabled-handle.tsx
EmilyyyLiu Apr 22, 2026
672a8ab
refactor: use Array.some instead of for loop in hasDisabledHandle
Apr 22, 2026
f94b6b1
Update src/Slider.tsx
EmilyyyLiu Apr 22, 2026
866486b
fix: add boundary validation for click-to-move when handles are locked
Apr 22, 2026
98901c8
refactor: simplify useDisabled hook by removing isHandleDisabled
Apr 22, 2026
93f1734
refactor: streamline useDisabled hook by removing mergedValue parameter
Apr 22, 2026
9f9f44d
fix: ensure disabled state is correctly evaluated when rawValue is empty
Apr 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ The following APIs are shared by Slider and Range.
| handle | (props) => React.ReactNode | | A handle generator which could be used to customized handle. |
| included | boolean | `true` | If the value is `true`, it means a continuous value interval, otherwise, it is a independent value. |
| reverse | boolean | `false` | If the value is `true`, it means the component is rendered reverse. |
| disabled | boolean | `false` | If `true`, handles can't be moved. |
| disabled | boolean \| boolean[] | `false` | If `true`, handles can't be moved. This prop can also be an array to disable specific handles in range mode, e.g. `[true, false, true]` disables first and third handles. When disabled is an array with any `true` value, `editable` mode will be disabled. |
| keyboard | boolean | `true` | Support using keyboard to move handlers. |
| dots | boolean | `false` | When the `step` value is greater than 1, you can set the `dots` to `true` if you want to render the slider with dots. |
| onBeforeChange | Function | NOOP | `onBeforeChange` will be triggered when `ontouchstart` or `onmousedown` is triggered. |
Expand Down
14 changes: 14 additions & 0 deletions assets/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,20 @@
cursor: -webkit-grabbing;
cursor: grabbing;
}

&-disabled {
background-color: #fff;
border-color: @disabledColor;
box-shadow: none;
cursor: not-allowed;

&:hover,
&:active {
border-color: @disabledColor;
box-shadow: none;
cursor: not-allowed;
}
}
}

&-mark {
Expand Down
9 changes: 9 additions & 0 deletions docs/demo/disabled-handle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title: Disabled Handle
title.zh-CN: 禁用特定滑块
nav:
title: Demo
path: /demo
---

<code src="../examples/disabled-handle.tsx"></code>
169 changes: 169 additions & 0 deletions docs/examples/disabled-handle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/* eslint react/no-multi-comp: 0, no-console: 0 */
import Slider from '@rc-component/slider';
import React, { useState } from 'react';
import '../../assets/index.less';

const style: React.CSSProperties = {
width: 400,
margin: 50,
};

const defaultValue = [0, 30, 60, 100];
const BasicDisabledHandle = () => {
const [disabled, setDisabled] = useState([true]);

return (
<div>
<Slider range={{ draggableTrack: true }} defaultValue={defaultValue} disabled={disabled} />
Slider disabled {JSON.stringify(disabled)}
<div style={{ marginTop: 16 }}>
{defaultValue.map((_, index) => (
<label key={index} style={{ marginRight: 16 }}>
<input
type="checkbox"
checked={!!disabled[index]}
onChange={() => {
const newDisabled = [...disabled];
newDisabled[index] = !newDisabled[index];
setDisabled(newDisabled);
}}
/>
Handle {index + 1} {disabled[index] ? 'Disabled' : 'Enabled'}
</label>
))}
</div>
</div>
);
};

const DisabledHandleAsBoundary = () => {
const [value, setValue] = useState<number[]>([10, 50, 90]);

return (
<div>
<Slider
range
step={10}
value={value}
onChange={(v) => Array.isArray(v) && setValue(v)}
disabled={[false, true, false]}
/>
<p style={{ marginTop: 8, color: '#999' }}>
Middle handle (50) is disabled and acts as a boundary. First handle cannot go beyond 50,
third handle cannot go below 50. Disabled handle has gray border and not-allowed cursor.
</p>
</div>
);
};

const PushableWithDisabledHandle = () => {
const [value, setValue] = useState<number[]>([20, 40, 60, 80]);

return (
<div>
<Slider
range
value={value}
onChange={(v) => setValue(v as number[])}
disabled={[false, true, false, false]}
pushable={10}
/>
<p style={{ marginTop: 8, color: '#999' }}>
Second handle (40) is disabled. Drag the first handle toward it or push the last two handles
together: enabled handles keep at least 10 apart without crossing the disabled handle.
</p>
</div>
);
};

const SingleSlider = () => {
const [value1, setValue1] = useState(30);
const [value2, setValue2] = useState(30);

return (
<div>
<Slider value={value1} onChange={(v) => setValue1(v as number)} disabled />
<br />
<Slider value={value2} onChange={(v) => setValue2(v as number)} disabled={false} />
</div>
);
};

// Editable mode with disabled handles - editable is disabled when any handle is disabled
const EditableWithDisabled = () => {
const [value, setValue] = useState<number[]>([0, 30, 100]);
const [disabled, setDisabled] = useState<boolean[]>([true, false, false]);

const hasDisabled = disabled.some((d) => d);

return (
<div>
<Slider
range={{
editable: true,
minCount: 2,
maxCount: 5,
}}
value={value}
onChange={(v) => setValue(v as number[])}
disabled={disabled}
/>
<p style={{ marginTop: 8, color: hasDisabled ? '#f5222d' : '#52c41a' }}>
{hasDisabled
? 'Editable mode is DISABLED because at least one handle is disabled. Clicking track will move nearest enabled handle.'
: 'Editable mode is ENABLED. Click track to add handles, drag to edge to delete.'}
</p>
<div style={{ marginTop: 16 }}>
{value.map((val, index) => (
<label key={index} style={{ marginRight: 16 }}>
<input
type="checkbox"
checked={!!disabled[index]}
onChange={() => {
const newDisabled = [...disabled];
newDisabled[index] = !newDisabled[index];
setDisabled(newDisabled);
}}
/>
Handle {index + 1} ({val}) {disabled[index] ? 'Disabled' : 'Enabled'}
</label>
))}
</div>
<p style={{ marginTop: 8, color: '#999', fontSize: 12 }}>
Try: Toggle checkboxes to enable/disable handles. When any handle is disabled, you cannot
add or remove handles. When all handles are enabled, editable mode works normally.
</p>
</div>
);
};

export default () => (
<div>
<div>
single handle disabled
<SingleSlider />
</div>
<div style={style}>
<h3>Disabled Handle + Draggable Track</h3>
<p>
Toggle checkboxes to disable/enable specific handles. Drag the track area to move the range.
</p>
<BasicDisabledHandle />
</div>

<div style={style}>
<h3>Disabled Handle as Boundary</h3>
<DisabledHandleAsBoundary />
</div>

<div style={style}>
<h3>Disabled Handle + Pushable</h3>
<PushableWithDisabledHandle />
Comment thread
EmilyyyLiu marked this conversation as resolved.
</div>

<div style={style}>
<h3>Editable + Disabled (Editable Disabled When Any Handle Disabled)</h3>
<EditableWithDisabled />
</div>
</div>
);
17 changes: 11 additions & 6 deletions src/Handles/Handle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ const Handle = React.forwardRef<HTMLDivElement, HandleProps>((props, ref) => {
min,
max,
direction,
disabled,
keyboard,
range,
tabIndex,
Expand All @@ -65,15 +64,20 @@ const Handle = React.forwardRef<HTMLDivElement, HandleProps>((props, ref) => {
ariaValueTextFormatterForHandle,
styles,
classNames,
isHandleDisabled,
} = React.useContext(SliderContext);

const mergedDisabled = isHandleDisabled(valueIndex);

const handlePrefixCls = `${prefixCls}-handle`;

// ============================ Events ============================
const onInternalStartMove = (e: React.MouseEvent | React.TouchEvent) => {
if (!disabled) {
onStartMove(e, valueIndex);
if (mergedDisabled) {
e.stopPropagation();
return;
}
onStartMove(e, valueIndex);
};

const onInternalFocus = (e: React.FocusEvent<HTMLDivElement>) => {
Expand All @@ -86,7 +90,7 @@ const Handle = React.forwardRef<HTMLDivElement, HandleProps>((props, ref) => {

// =========================== Keyboard ===========================
const onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (e) => {
if (!disabled && keyboard) {
if (!mergedDisabled && keyboard) {
let offset: number | 'min' | 'max' = null;

// Change the value
Expand Down Expand Up @@ -161,12 +165,12 @@ const Handle = React.forwardRef<HTMLDivElement, HandleProps>((props, ref) => {

if (valueIndex !== null) {
divProps = {
tabIndex: disabled ? null : getIndex(tabIndex, valueIndex),
tabIndex: mergedDisabled ? null : getIndex(tabIndex, valueIndex),
role: 'slider',
'aria-valuemin': min,
'aria-valuemax': max,
'aria-valuenow': value,
'aria-disabled': disabled,
'aria-disabled': mergedDisabled,
'aria-label': getIndex(ariaLabelForHandle, valueIndex),
'aria-labelledby': getIndex(ariaLabelledByForHandle, valueIndex),
'aria-required': getIndex(ariaRequired, valueIndex),
Expand All @@ -190,6 +194,7 @@ const Handle = React.forwardRef<HTMLDivElement, HandleProps>((props, ref) => {
[`${handlePrefixCls}-${valueIndex + 1}`]: valueIndex !== null && range,
[`${handlePrefixCls}-dragging`]: dragging,
[`${handlePrefixCls}-dragging-delete`]: draggingDelete,
[`${handlePrefixCls}-disabled`]: mergedDisabled,
},
classNames.handle,
)}
Expand Down
Loading
Loading