-
-
Notifications
You must be signed in to change notification settings - Fork 776
feat: Range 组件支持 disabled 数组形式 #1068
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
bedc645
a7db356
4f28cc4
218f061
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| /* 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} | ||
| /> | ||
| <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} /> | ||
| <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> | ||
| ); | ||
| }; | ||
|
|
||
| export default () => ( | ||
| <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> | ||
| ); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -57,7 +57,7 @@ export interface SliderProps<ValueType = number | number[]> { | |
| id?: string; | ||
|
|
||
| // Status | ||
| disabled?: boolean; | ||
| disabled?: boolean | boolean[]; | ||
| keyboard?: boolean; | ||
| autoFocus?: boolean; | ||
| onFocus?: (e: React.FocusEvent<HTMLDivElement>) => void; | ||
|
|
@@ -131,8 +131,7 @@ const Slider = React.forwardRef<SliderRef, SliderProps<number | number[]>>((prop | |
|
|
||
| id, | ||
|
|
||
| // Status | ||
| disabled = false, | ||
| disabled: rawDisabled = false, | ||
| keyboard = true, | ||
| autoFocus, | ||
| onFocus, | ||
|
|
@@ -188,6 +187,25 @@ const Slider = React.forwardRef<SliderRef, SliderProps<number | number[]>>((prop | |
| const handlesRef = React.useRef<HandlesRef>(null); | ||
| const containerRef = React.useRef<HTMLDivElement>(null); | ||
|
|
||
| // ============================ Disabled ============================ | ||
| const disabled = React.useMemo(() => { | ||
| if (typeof rawDisabled === 'boolean') { | ||
| return rawDisabled; | ||
| }; | ||
|
|
||
| return Array.isArray(value) && value.length === rawDisabled.length && rawDisabled.every(Boolean); | ||
| }, [rawDisabled, value]); | ||
|
Comment on lines
+191
to
+197
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 聚合 Line 196 现在只拿 🔧 建议修复 const disabled = React.useMemo(() => {
if (typeof rawDisabled === 'boolean') {
return rawDisabled;
- };
+ }
- return Array.isArray(value) && value.length === rawDisabled.length && rawDisabled.every(Boolean);
- }, [rawDisabled, value]);
+ const sourceValues = Array.isArray(value)
+ ? value
+ : Array.isArray(defaultValue)
+ ? defaultValue
+ : undefined;
+
+ const expectedLength =
+ sourceValues?.length ?? (range ? (count >= 0 ? count + 1 : 2) : 1);
+
+ return (
+ expectedLength > 0 &&
+ rawDisabled.length === expectedLength &&
+ Array.from({ length: expectedLength }, (_, index) => rawDisabled[index] === true).every(Boolean)
+ );
+ }, [rawDisabled, value, defaultValue, range, count]);🤖 Prompt for AI Agents |
||
|
|
||
| const isHandleDisabled = React.useCallback( | ||
| (index: number) => { | ||
| if (typeof rawDisabled === 'boolean') { | ||
| return rawDisabled; | ||
| } | ||
| return rawDisabled[index] || false; | ||
| }, | ||
| [rawDisabled], | ||
| ); | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const direction = React.useMemo<Direction>(() => { | ||
| if (vertical) { | ||
| return reverse ? 'ttb' : 'btt'; | ||
|
|
@@ -247,6 +265,7 @@ const Slider = React.forwardRef<SliderRef, SliderProps<number | number[]>>((prop | |
| markList, | ||
| allowCross, | ||
| mergedPush, | ||
| isHandleDisabled, | ||
| ); | ||
|
|
||
| // ============================ Values ============================ | ||
|
|
@@ -257,8 +276,8 @@ const Slider = React.forwardRef<SliderRef, SliderProps<number | number[]>>((prop | |
| mergedValue === null || mergedValue === undefined | ||
| ? [] | ||
| : Array.isArray(mergedValue) | ||
| ? mergedValue | ||
| : [mergedValue]; | ||
| ? mergedValue | ||
| : [mergedValue]; | ||
|
|
||
| const [val0 = mergedMin] = valueList; | ||
| let returnValues = mergedValue === null ? [] : [val0]; | ||
|
|
@@ -321,7 +340,7 @@ const Slider = React.forwardRef<SliderRef, SliderProps<number | number[]>>((prop | |
| }); | ||
|
|
||
| const onDelete = (index: number) => { | ||
| if (disabled || !rangeEditable || rawValues.length <= minCount) { | ||
| if (disabled || !rangeEditable || rawValues.length <= minCount || isHandleDisabled(index)) { | ||
| return; | ||
| } | ||
|
|
||
|
|
@@ -348,6 +367,7 @@ const Slider = React.forwardRef<SliderRef, SliderProps<number | number[]>>((prop | |
| offsetValues, | ||
| rangeEditable, | ||
| minCount, | ||
| isHandleDisabled, | ||
| ); | ||
|
|
||
| /** | ||
|
|
@@ -378,10 +398,39 @@ const Slider = React.forwardRef<SliderRef, SliderProps<number | number[]>>((prop | |
| let focusIndex = valueIndex; | ||
|
|
||
| if (rangeEditable && valueDist !== 0 && (!maxCount || rawValues.length < maxCount)) { | ||
| const leftDisabled = isHandleDisabled(valueBeforeIndex); | ||
| const rightDisabled = isHandleDisabled(valueBeforeIndex + 1); | ||
|
|
||
| if (leftDisabled && rightDisabled) { | ||
| return; | ||
| } | ||
|
|
||
| cloneNextValues.splice(valueBeforeIndex + 1, 0, newValue); | ||
| focusIndex = valueBeforeIndex + 1; | ||
| } else { | ||
| if (isHandleDisabled(valueIndex)) { | ||
| let nearestIndex = -1; | ||
| let nearestDist = mergedMax - mergedMin; | ||
|
|
||
| rawValues.forEach((val, index) => { | ||
| if (!isHandleDisabled(index)) { | ||
| const dist = Math.abs(newValue - val); | ||
| if (dist < nearestDist) { | ||
| nearestDist = dist; | ||
| nearestIndex = index; | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| // If all handles are disabled, do nothing | ||
| if (nearestIndex === -1) { | ||
| return; | ||
| } | ||
|
|
||
| valueIndex = nearestIndex; | ||
| } | ||
| cloneNextValues[valueIndex] = newValue; | ||
| focusIndex = valueIndex; | ||
EmilyyyLiu marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+411
to
+433
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 轨道 / Mark 点击的回退选择会让 handle 穿过禁用边界。 Line 415-423 在最近的 handle 是禁用时,会从整个 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| // Fill value to match default 2 (only when `rawValues` is empty) | ||
|
|
@@ -443,7 +492,7 @@ const Slider = React.forwardRef<SliderRef, SliderProps<number | number[]>>((prop | |
| const [keyboardValue, setKeyboardValue] = React.useState<number>(null); | ||
|
|
||
| const onHandleOffsetChange = (offset: number | 'min' | 'max', valueIndex: number) => { | ||
| if (!disabled) { | ||
| if (!disabled && !isHandleDisabled(valueIndex)) { | ||
| const next = offsetValues(rawValues, offset, valueIndex); | ||
|
|
||
| onBeforeChange?.(getTriggerValue(rawValues)); | ||
|
|
@@ -546,6 +595,7 @@ const Slider = React.forwardRef<SliderRef, SliderProps<number | number[]>>((prop | |
| ariaValueTextFormatterForHandle, | ||
| styles: styles || {}, | ||
| classNames: classNames || {}, | ||
| isHandleDisabled, | ||
| }), | ||
| [ | ||
| mergedMin, | ||
|
|
@@ -565,6 +615,7 @@ const Slider = React.forwardRef<SliderRef, SliderProps<number | number[]>>((prop | |
| ariaValueTextFormatterForHandle, | ||
| styles, | ||
| classNames, | ||
| isHandleDisabled, | ||
| ], | ||
| ); | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.