Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
33 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
364e2f4
Merge branch 'master' into feat/disabled-handle-array
zombieJ May 12, 2026
e73fabc
fix disabled handle boundary handling
zombieJ May 13, 2026
ecb3d5a
docs clarify disabled handle behavior
zombieJ May 13, 2026
06502b2
chore: label useDisabled return tuple
zombieJ May 13, 2026
82ac6b4
docs explain disabled handle helpers
zombieJ May 13, 2026
5c8e0bc
refactor disabled handle hook
zombieJ May 13, 2026
c02365d
refactor disabled state hook
zombieJ May 13, 2026
3228a79
Revert "refactor disabled state hook"
zombieJ May 13, 2026
8dcf6a1
refactor closest handle selection
zombieJ May 13, 2026
a89ec37
test: simplify disabled range cases
zombieJ May 13, 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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,13 @@ 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. Can also be an array to disable specific handles in range mode, e.g. `[true, false, true]` disables first and third handles. |
| 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. |
| onChange | Function | NOOP | `onChange` will be triggered while the value of Slider changing. |
| onChangeComplete | Function | NOOP | `onChangeComplete` will be triggered when `ontouchend` or `onmouseup` is triggered. |
| onDisabledChange | (disabled: boolean[]) => void | - | Callback when disabled array needs to be updated (e.g., when handles are added/removed in editable mode). Use with `disabled` as array to keep disabled states in sync. |
| minimumTrackStyle | Object | | please use `trackStyle` instead. (`only used for slider, just for compatibility , will be deprecate at rc-slider@9.x `) |
| maximumTrackStyle | Object | | please use `railStyle` instead (`only used for slider, just for compatibility , will be deprecate at rc-slider@9.x`) |
| handleStyle | Array[Object] \| Object | `[{}]` | The style used for handle. (`both for slider(`Object`) and range(`Array of Object`), the array will be used for multi handle following element order`) |
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>
136 changes: 136 additions & 0 deletions docs/examples/disabled-handle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/* 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,
};

// Basic editable with disabled handles
const EditableWithDisabled = () => {
const [value, setValue] = useState<number[]>([0, 30, 100]);
const [disabled, setDisabled] = useState<boolean[]>([true, false, true]);

return (
<div>
<Slider
range={{
editable: true,
minCount: 2,
maxCount: 5,
}}
value={value}
onChange={(v) => setValue(v as number[])}
disabled={disabled}
onDisabledChange={setDisabled}
/>
Slider disabled {JSON.stringify(disabled)}
<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: Click track to add handle • Drag handle to edge to delete • Toggle checkboxes to disable handles
</p>
</div>
);
};

const BasicDisabledHandle = () => {
const [value, setValue] = useState<number[]>([0, 30, 60, 100]);
const [disabled, setDisabled] = useState([true]);

return (
<div>
<Slider range={{ draggableTrack: true }} value={value} onChange={(v) => setValue(v as number[])} disabled={disabled} />
Slider disabled {JSON.stringify(disabled)}
<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>
</div>
);
};

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

return (
<div>
<Slider range value={value} onChange={(v) => setValue(v as number[])} 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 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>
);
}

export default () => (
<div>
<div>
single handle disabled
<SingleSlider />
</div>
<div>

</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>
<div style={style}>
<h3>Editable + Disabled Array</h3>
<p>Toggle checkboxes to enable/disable handles in editable mode</p>
<EditableWithDisabled />
</div>
</div>
</div>
);
19 changes: 13 additions & 6 deletions src/Handles/Handle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const Handle = React.forwardRef<HTMLDivElement, HandleProps>((props, ref) => {
min,
max,
direction,
disabled,
disabled: globalDisabled,
keyboard,
range,
tabIndex,
Expand All @@ -65,15 +65,21 @@ const Handle = React.forwardRef<HTMLDivElement, HandleProps>((props, ref) => {
ariaValueTextFormatterForHandle,
styles,
classNames,
isHandleDisabled,
} = React.useContext(SliderContext);

const handleDisabled =
globalDisabled || (isHandleDisabled ? isHandleDisabled(valueIndex) : false);

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

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

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

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

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

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