diff --git a/.github/workflows/deploy-site-preview.yml b/.github/workflows/deploy-site-preview.yml index bd04b4a790..06dd899740 100644 --- a/.github/workflows/deploy-site-preview.yml +++ b/.github/workflows/deploy-site-preview.yml @@ -1,8 +1,7 @@ name: deploy-site-preview on: - workflow_run: - workflows: ['pr_check'] - types: completed + pull_request: + types: [opened, synchronize, reopened] push: tags: @@ -65,7 +64,7 @@ jobs: - name: get PreviewID run: | - if ${{ github.event_name == 'pull_request' }}; then + if ${{ github.event_name == 'push' }}; then echo "PreviewID=${{ github.event.number }}" >> $GITHUB_ENV else echo "PreviewID=main" >> $GITHUB_ENV diff --git a/components/Slider/__demo__/input.md b/components/Slider/__demo__/input.md index 2919a701a0..1a2638e755 100644 --- a/components/Slider/__demo__/input.md +++ b/components/Slider/__demo__/input.md @@ -7,7 +7,7 @@ title: ## zh-CN -当 `showInput` 为 true 时,将显示输入框。当设置 `onlyMarkValue` 为 `true` 时,输入框始终不会显示。 +当 `showInput` 为 true 时,将显示输入框。当设置 `onlyMarkValue` 为 `true` 或者范围内多点选择时,输入框始终不会显示。 当 `showInput` 传入 `InputNumberProps` 时,`min`、`max`、`step` 属性会以 `SliderProps` 为先。 @@ -15,7 +15,7 @@ title: ## en-US -When `showInput` is set to true, the input box will be displayed. But when setting `onlyMarkValue` to `true`, the input box will never be displayed. +When `showInput` is set to true, the input box will be displayed. But when setting `onlyMarkValue` to `true` or selecting multiple points within the range, the input box will never be displayed. When `showInput` is passed `InputNumberProps`, the `min`, `max`, `step` properties will be preceded by `SliderProps`. diff --git a/components/Slider/__demo__/marks.md b/components/Slider/__demo__/marks.md index c554c68a47..672f761653 100644 --- a/components/Slider/__demo__/marks.md +++ b/components/Slider/__demo__/marks.md @@ -1,5 +1,5 @@ --- -order: 4 +order: 5 title: zh-CN: 添加标签文本 en-US: Marks diff --git a/components/Slider/__demo__/range-multi.md b/components/Slider/__demo__/range-multi.md new file mode 100644 index 0000000000..365356df9e --- /dev/null +++ b/components/Slider/__demo__/range-multi.md @@ -0,0 +1,33 @@ +--- +order: 4 +title: + zh-CN: 多点选择 + en-US: Multiple Dots +--- + +## zh-CN + +范围内多个点选择。(`2.61.0` 支持) + +## en-US + +Select multiple points within the range. (in `2.61.0`) + + +```js +import { useState } from 'react'; +import { Slider, Typography } from '@arco-design/web-react'; + +function App() { + const [value, setValue] = useState([0, 20, 50]); + return ( +
+ +
+ value: {JSON.stringify(value)} +
+ ); +} + +export default App; +``` diff --git a/components/Slider/__test__/__snapshots__/demo.test.ts.snap b/components/Slider/__test__/__snapshots__/demo.test.ts.snap index 20291989ab..b525b31229 100644 --- a/components/Slider/__test__/__snapshots__/demo.test.ts.snap +++ b/components/Slider/__test__/__snapshots__/demo.test.ts.snap @@ -1524,6 +1524,70 @@ exports[`renders Slider/demo/range.md correctly 1`] = ` `; +exports[`renders Slider/demo/range-multi.md correctly 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+
+ + + value: [0,20,50] + + +
+`; + exports[`renders Slider/demo/reversed.md correctly 1`] = ` Array [
)} diff --git a/components/Slider/hooks/useLegalValue.ts b/components/Slider/hooks/useLegalValue.ts index 4b4eea63f0..be4cb20a70 100644 --- a/components/Slider/hooks/useLegalValue.ts +++ b/components/Slider/hooks/useLegalValue.ts @@ -78,10 +78,12 @@ export default function useLegalValue(props: { if (isRange) { if (isArray(val)) { beginVal = getLegalValue(val[0]); - endVal = getLegalValue(val[1]); - } else { - console.error('value must be an array when range is true'); + // endVal = getLegalValue(val[1]); + return val.map((v) => { + return getLegalValue(v); + }); } + console.error('value must be an array when range is true'); } else if (isNumber(val)) { endVal = getLegalValue(val); } else { diff --git a/components/Slider/index.tsx b/components/Slider/index.tsx index 76136cf3ee..47a652d445 100644 --- a/components/Slider/index.tsx +++ b/components/Slider/index.tsx @@ -7,7 +7,13 @@ import Dots from './dots'; import Input from './input'; import Ticks from './ticks'; import { isFunction, isObject, isArray } from '../_util/is'; -import { formatPercent, getIntervalOffset } from './utils'; +import { + findNearestIndex, + formatPercent, + getIntervalOffset, + needSort, + sortNumberArray, +} from './utils'; import cs from '../_util/classNames'; import { ConfigContext } from '../ConfigProvider'; import { TooltipPosition, SliderProps } from './interface'; @@ -79,10 +85,10 @@ function Slider(baseProps: SliderProps, ref) { }); // 计算合法值 - const curVal = getLegalRangeValue(value); + let curVal = getLegalRangeValue(value); const lastVal = useRef(curVal); - let [beginVal, endVal] = curVal; - const reverseOrder = useRef(beginVal > endVal); + // let [beginVal, endVal] = curVal; + const reverseOrder = useRef(needSort(curVal)); // value变化后 更新lastVal useUpdate(() => { @@ -90,14 +96,14 @@ function Slider(baseProps: SliderProps, ref) { }, [value, getLegalRangeValue]); if (reverseOrder.current) { - [beginVal, endVal] = [endVal, beginVal]; + curVal = sortNumberArray(curVal); } - // 偏移比例 - const beginOffset = getIntervalOffset(beginVal, intervalConfigs); - const endOffset = getIntervalOffset(endVal, intervalConfigs); - // 是否显示输入框 - const isShowInput = showInput && !onlyMarkValue; + const maxVal = curVal[curVal.length - 1]; + const minVal = curVal[0]; + + // 是否显示输入框。多点选择不显示 input + const isShowInput = showInput && !onlyMarkValue && (!range || curVal.length < 3); const extraInputProps = useMemo(() => { if (isShowInput && (isArray(showInput) || isObject(showInput))) { return isArray(showInput) ? [...showInput] : [{ ...showInput }, { ...showInput }]; @@ -118,29 +124,26 @@ function Slider(baseProps: SliderProps, ref) { const isDragging = useRef(false); const barStartDragVal = useRef(0); - function getEmitParams([beginVal, endVal]: number[]): number | number[] { - if (beginVal > endVal) { - [beginVal, endVal] = [endVal, beginVal]; - } - return range ? [beginVal, endVal] : endVal; + function getEmitParams(value: number[]): number | number[] { + const sortedValue = sortNumberArray(value); + return range ? sortedValue : sortedValue[sortedValue.length - 1]; } function updateValue(val) { - let [newBeginVal, newEndVal] = val; - newBeginVal = getLegalValue(newBeginVal); - newEndVal = getLegalValue(newEndVal); - lastVal.current = [newBeginVal, newEndVal]; - return [newBeginVal, newEndVal]; + const copyVal = val.map((x) => getLegalValue(x)); + lastVal.current = copyVal; + return copyVal; } function onChange(val, reason?: 'mousemove' | 'jumpToClick' | 'inputValueChange') { - const [newBeginVal, newEndVal] = updateValue(val); - const emitParams = getEmitParams([newBeginVal, newEndVal]); + const newValue = updateValue(val); + const emitParams = getEmitParams(newValue); + setValue(emitParams); // 在手动修改的情况下才可能出现反序问题。 if (reason === 'inputValueChange') { - reverseOrder.current = newBeginVal > newEndVal; + reverseOrder.current = newValue.some((x, i) => x > newValue[i]); } else { // 在mousemove 跟 jumpToClick 顺序会保持 [begin,end] reverseOrder.current = false; @@ -158,7 +161,7 @@ function Slider(baseProps: SliderProps, ref) { } function inRange(val: number) { - let [range1, range2] = [beginVal, endVal]; + let [range1, range2] = [curVal[0], curVal[curVal.length - 1]]; if (range1 > range2) { [range1, range2] = [range2, range1]; } @@ -261,10 +264,18 @@ function Slider(baseProps: SliderProps, ref) { if (disabled) return; const value = getLegalValue(val); - if (range && endVal - value > value - beginVal) { - onChange([value, endVal], 'jumpToClick'); + // 找到 value 临近的两个值。 + const [beforeIndex, nextIndex] = findNearestIndex(value, curVal); + const nearBeginVal = curVal[beforeIndex]; + const nearEndValue = curVal[nextIndex]; + const copyVal = curVal.slice(0); + + if (range && nearEndValue - value > value - nearBeginVal) { + copyVal[beforeIndex] = value; + onChange(copyVal, 'jumpToClick'); } else { - onChange([beginVal, value], 'jumpToClick'); + copyVal[nextIndex] = value; + onChange(copyVal, 'jumpToClick'); } onMouseUp(); } @@ -275,15 +286,13 @@ function Slider(baseProps: SliderProps, ref) { } // 拖动开始节点 - function handleBeginMove(x: number, y: number) { + function handleMove(x: number, y: number, index) { isDragging.current = true; - onChange([getValueByCoords(x, y), endVal], 'mousemove'); - } + const copyVal = curVal.slice(0); - // 拖动结束节点 - function handleEndMove(x: number, y: number) { - isDragging.current = true; - onChange([beginVal, getValueByCoords(x, y)], 'mousemove'); + copyVal[index] = getValueByCoords(x, y); + + onChange(copyVal, 'mousemove'); } function handleMoveEnd() { @@ -292,27 +301,23 @@ function Slider(baseProps: SliderProps, ref) { } // 结束节点的 arrow event - function handleEndArrowEvent(type: 'addition' | 'subtraction') { + function handleArrowEvent(type: 'addition' | 'subtraction', index: number) { if (disabled) return; + const copyVal = curVal.slice(0); - onChange([beginVal, getNextMarkValue(endVal, type)]); - } + copyVal[index] = getNextMarkValue(curVal[index], type); - // 起始节点的 arrow event - function handleBeginArrowEvent(type: 'addition' | 'subtraction') { - if (disabled) return; - onChange([getNextMarkValue(beginVal, type), endVal]); + onChange(copyVal); } // bar 移动中 function onBarMouseMove(e) { const newVal = getLegalValue(getValueByCoords(e.clientX, e.clientY)); const offsetVal = newVal - barStartDragVal.current; - const newBeginVal = beginVal + offsetVal; - const newEndVal = endVal + offsetVal; + const copyVal = curVal.map((x) => x + offsetVal); - if (isLegalValue(newBeginVal) && isLegalValue(newEndVal)) { - onChange([newBeginVal, newEndVal], 'mousemove'); + if (copyVal.every((v) => isLegalValue(v))) { + onChange(copyVal, 'mousemove'); } } @@ -355,13 +360,19 @@ function Slider(baseProps: SliderProps, ref) { })} onMouseDown={onRoadMouseDown} > -
+
{showTicks && ( - {range && ( - - )} - + {curVal.map((val, index) => { + if (!range && index !== curVal.length - 1) { + return null; + } + return ( + handleMove(x, y, index)} + onMoveEnd={handleMoveEnd} + onArrowEvent={(type) => handleArrowEvent(type, index)} + /> + ); + })}
{isShowInput && ( = new Map(); @@ -26,7 +26,7 @@ export default memo(function Ticks(props: TicksProps) { const stepVal = plus(i * step, begin); if (stepVal <= min || stepVal >= max) continue; const offset = formatPercent(getIntervalOffset(stepVal, intervalConfigs)); - stepsMap.set(offset, { offset, isActive: valueInRange(stepVal, value) }); + stepsMap.set(offset, { offset, isActive: valueInRange(stepVal, valueRange) }); } }; diff --git a/components/Slider/utils.ts b/components/Slider/utils.ts index 87ae278ea4..e12665ca76 100644 --- a/components/Slider/utils.ts +++ b/components/Slider/utils.ts @@ -60,3 +60,28 @@ export function getIntervalOffset(val, intervalConfig: IntervalConfig[]) { return plus(beginOffset, offset); } } + +// 从小到大排序 +export function sortNumberArray(arr: number[]) { + const copyArr = arr.slice(0); + copyArr.sort((a, b) => a - b); + return copyArr; +} + +// 是否需要排序 +export function needSort(arr: number[]) { + return arr.some((x, i) => x > arr[i + 1]); +} + +// 找到 value 在 array 中的索引。value: 5 ,arrry: [1, 3, 8], return: 2. +export function findNearestIndex(value: number, array: number[]): [number, number] { + let valueIndex = array.indexOf(value); + if (valueIndex === -1) { + const arr = sortNumberArray(array.concat(value)); + + valueIndex = arr.indexOf(value); + + return [Math.max(valueIndex - 1, 0), Math.min(valueIndex, array.length - 1)]; + } + return [valueIndex, valueIndex + 1]; +}