Slider.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
  2. import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
  3. import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
  4. import _typeof from "@babel/runtime/helpers/esm/typeof";
  5. import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
  6. import cls from 'classnames';
  7. import useEvent from "rc-util/es/hooks/useEvent";
  8. import useMergedState from "rc-util/es/hooks/useMergedState";
  9. import isEqual from "rc-util/es/isEqual";
  10. import warning from "rc-util/es/warning";
  11. import * as React from 'react';
  12. import Handles from "./Handles";
  13. import Marks from "./Marks";
  14. import Steps from "./Steps";
  15. import Tracks from "./Tracks";
  16. import SliderContext from "./context";
  17. import useDrag from "./hooks/useDrag";
  18. import useOffset from "./hooks/useOffset";
  19. import useRange from "./hooks/useRange";
  20. /**
  21. * New:
  22. * - click mark to update range value
  23. * - handleRender
  24. * - Fix handle with count not correct
  25. * - Fix pushable not work in some case
  26. * - No more FindDOMNode
  27. * - Move all position related style into inline style
  28. * - Key: up is plus, down is minus
  29. * - fix Key with step = null not align with marks
  30. * - Change range should not trigger onChange
  31. * - keyboard support pushable
  32. */
  33. var Slider = /*#__PURE__*/React.forwardRef(function (props, ref) {
  34. var _props$prefixCls = props.prefixCls,
  35. prefixCls = _props$prefixCls === void 0 ? 'rc-slider' : _props$prefixCls,
  36. className = props.className,
  37. style = props.style,
  38. classNames = props.classNames,
  39. styles = props.styles,
  40. id = props.id,
  41. _props$disabled = props.disabled,
  42. disabled = _props$disabled === void 0 ? false : _props$disabled,
  43. _props$keyboard = props.keyboard,
  44. keyboard = _props$keyboard === void 0 ? true : _props$keyboard,
  45. autoFocus = props.autoFocus,
  46. onFocus = props.onFocus,
  47. onBlur = props.onBlur,
  48. _props$min = props.min,
  49. min = _props$min === void 0 ? 0 : _props$min,
  50. _props$max = props.max,
  51. max = _props$max === void 0 ? 100 : _props$max,
  52. _props$step = props.step,
  53. step = _props$step === void 0 ? 1 : _props$step,
  54. value = props.value,
  55. defaultValue = props.defaultValue,
  56. range = props.range,
  57. count = props.count,
  58. onChange = props.onChange,
  59. onBeforeChange = props.onBeforeChange,
  60. onAfterChange = props.onAfterChange,
  61. onChangeComplete = props.onChangeComplete,
  62. _props$allowCross = props.allowCross,
  63. allowCross = _props$allowCross === void 0 ? true : _props$allowCross,
  64. _props$pushable = props.pushable,
  65. pushable = _props$pushable === void 0 ? false : _props$pushable,
  66. reverse = props.reverse,
  67. vertical = props.vertical,
  68. _props$included = props.included,
  69. included = _props$included === void 0 ? true : _props$included,
  70. startPoint = props.startPoint,
  71. trackStyle = props.trackStyle,
  72. handleStyle = props.handleStyle,
  73. railStyle = props.railStyle,
  74. dotStyle = props.dotStyle,
  75. activeDotStyle = props.activeDotStyle,
  76. marks = props.marks,
  77. dots = props.dots,
  78. handleRender = props.handleRender,
  79. activeHandleRender = props.activeHandleRender,
  80. track = props.track,
  81. _props$tabIndex = props.tabIndex,
  82. tabIndex = _props$tabIndex === void 0 ? 0 : _props$tabIndex,
  83. ariaLabelForHandle = props.ariaLabelForHandle,
  84. ariaLabelledByForHandle = props.ariaLabelledByForHandle,
  85. ariaRequired = props.ariaRequired,
  86. ariaValueTextFormatterForHandle = props.ariaValueTextFormatterForHandle;
  87. var handlesRef = React.useRef(null);
  88. var containerRef = React.useRef(null);
  89. var direction = React.useMemo(function () {
  90. if (vertical) {
  91. return reverse ? 'ttb' : 'btt';
  92. }
  93. return reverse ? 'rtl' : 'ltr';
  94. }, [reverse, vertical]);
  95. // ============================ Range =============================
  96. var _useRange = useRange(range),
  97. _useRange2 = _slicedToArray(_useRange, 5),
  98. rangeEnabled = _useRange2[0],
  99. rangeEditable = _useRange2[1],
  100. rangeDraggableTrack = _useRange2[2],
  101. minCount = _useRange2[3],
  102. maxCount = _useRange2[4];
  103. var mergedMin = React.useMemo(function () {
  104. return isFinite(min) ? min : 0;
  105. }, [min]);
  106. var mergedMax = React.useMemo(function () {
  107. return isFinite(max) ? max : 100;
  108. }, [max]);
  109. // ============================= Step =============================
  110. var mergedStep = React.useMemo(function () {
  111. return step !== null && step <= 0 ? 1 : step;
  112. }, [step]);
  113. // ============================= Push =============================
  114. var mergedPush = React.useMemo(function () {
  115. if (typeof pushable === 'boolean') {
  116. return pushable ? mergedStep : false;
  117. }
  118. return pushable >= 0 ? pushable : false;
  119. }, [pushable, mergedStep]);
  120. // ============================ Marks =============================
  121. var markList = React.useMemo(function () {
  122. return Object.keys(marks || {}).map(function (key) {
  123. var mark = marks[key];
  124. var markObj = {
  125. value: Number(key)
  126. };
  127. if (mark && _typeof(mark) === 'object' && ! /*#__PURE__*/React.isValidElement(mark) && ('label' in mark || 'style' in mark)) {
  128. markObj.style = mark.style;
  129. markObj.label = mark.label;
  130. } else {
  131. markObj.label = mark;
  132. }
  133. return markObj;
  134. }).filter(function (_ref) {
  135. var label = _ref.label;
  136. return label || typeof label === 'number';
  137. }).sort(function (a, b) {
  138. return a.value - b.value;
  139. });
  140. }, [marks]);
  141. // ============================ Format ============================
  142. var _useOffset = useOffset(mergedMin, mergedMax, mergedStep, markList, allowCross, mergedPush),
  143. _useOffset2 = _slicedToArray(_useOffset, 2),
  144. formatValue = _useOffset2[0],
  145. offsetValues = _useOffset2[1];
  146. // ============================ Values ============================
  147. var _useMergedState = useMergedState(defaultValue, {
  148. value: value
  149. }),
  150. _useMergedState2 = _slicedToArray(_useMergedState, 2),
  151. mergedValue = _useMergedState2[0],
  152. setValue = _useMergedState2[1];
  153. var rawValues = React.useMemo(function () {
  154. var valueList = mergedValue === null || mergedValue === undefined ? [] : Array.isArray(mergedValue) ? mergedValue : [mergedValue];
  155. var _valueList = _slicedToArray(valueList, 1),
  156. _valueList$ = _valueList[0],
  157. val0 = _valueList$ === void 0 ? mergedMin : _valueList$;
  158. var returnValues = mergedValue === null ? [] : [val0];
  159. // Format as range
  160. if (rangeEnabled) {
  161. returnValues = _toConsumableArray(valueList);
  162. // When count provided or value is `undefined`, we fill values
  163. if (count || mergedValue === undefined) {
  164. var pointCount = count >= 0 ? count + 1 : 2;
  165. returnValues = returnValues.slice(0, pointCount);
  166. // Fill with count
  167. while (returnValues.length < pointCount) {
  168. var _returnValues;
  169. returnValues.push((_returnValues = returnValues[returnValues.length - 1]) !== null && _returnValues !== void 0 ? _returnValues : mergedMin);
  170. }
  171. }
  172. returnValues.sort(function (a, b) {
  173. return a - b;
  174. });
  175. }
  176. // Align in range
  177. returnValues.forEach(function (val, index) {
  178. returnValues[index] = formatValue(val);
  179. });
  180. return returnValues;
  181. }, [mergedValue, rangeEnabled, mergedMin, count, formatValue]);
  182. // =========================== onChange ===========================
  183. var getTriggerValue = function getTriggerValue(triggerValues) {
  184. return rangeEnabled ? triggerValues : triggerValues[0];
  185. };
  186. var triggerChange = useEvent(function (nextValues) {
  187. // Order first
  188. var cloneNextValues = _toConsumableArray(nextValues).sort(function (a, b) {
  189. return a - b;
  190. });
  191. // Trigger event if needed
  192. if (onChange && !isEqual(cloneNextValues, rawValues, true)) {
  193. onChange(getTriggerValue(cloneNextValues));
  194. }
  195. // We set this later since it will re-render component immediately
  196. setValue(cloneNextValues);
  197. });
  198. var finishChange = useEvent(function (draggingDelete) {
  199. // Trigger from `useDrag` will tell if it's a delete action
  200. if (draggingDelete) {
  201. handlesRef.current.hideHelp();
  202. }
  203. var finishValue = getTriggerValue(rawValues);
  204. onAfterChange === null || onAfterChange === void 0 || onAfterChange(finishValue);
  205. warning(!onAfterChange, '[rc-slider] `onAfterChange` is deprecated. Please use `onChangeComplete` instead.');
  206. onChangeComplete === null || onChangeComplete === void 0 || onChangeComplete(finishValue);
  207. });
  208. var onDelete = function onDelete(index) {
  209. if (disabled || !rangeEditable || rawValues.length <= minCount) {
  210. return;
  211. }
  212. var cloneNextValues = _toConsumableArray(rawValues);
  213. cloneNextValues.splice(index, 1);
  214. onBeforeChange === null || onBeforeChange === void 0 || onBeforeChange(getTriggerValue(cloneNextValues));
  215. triggerChange(cloneNextValues);
  216. var nextFocusIndex = Math.max(0, index - 1);
  217. handlesRef.current.hideHelp();
  218. handlesRef.current.focus(nextFocusIndex);
  219. };
  220. var _useDrag = useDrag(containerRef, direction, rawValues, mergedMin, mergedMax, formatValue, triggerChange, finishChange, offsetValues, rangeEditable, minCount),
  221. _useDrag2 = _slicedToArray(_useDrag, 5),
  222. draggingIndex = _useDrag2[0],
  223. draggingValue = _useDrag2[1],
  224. draggingDelete = _useDrag2[2],
  225. cacheValues = _useDrag2[3],
  226. onStartDrag = _useDrag2[4];
  227. /**
  228. * When `rangeEditable` will insert a new value in the values array.
  229. * Else it will replace the value in the values array.
  230. */
  231. var changeToCloseValue = function changeToCloseValue(newValue, e) {
  232. if (!disabled) {
  233. // Create new values
  234. var cloneNextValues = _toConsumableArray(rawValues);
  235. var valueIndex = 0;
  236. var valueBeforeIndex = 0; // Record the index which value < newValue
  237. var valueDist = mergedMax - mergedMin;
  238. rawValues.forEach(function (val, index) {
  239. var dist = Math.abs(newValue - val);
  240. if (dist <= valueDist) {
  241. valueDist = dist;
  242. valueIndex = index;
  243. }
  244. if (val < newValue) {
  245. valueBeforeIndex = index;
  246. }
  247. });
  248. var focusIndex = valueIndex;
  249. if (rangeEditable && valueDist !== 0 && (!maxCount || rawValues.length < maxCount)) {
  250. cloneNextValues.splice(valueBeforeIndex + 1, 0, newValue);
  251. focusIndex = valueBeforeIndex + 1;
  252. } else {
  253. cloneNextValues[valueIndex] = newValue;
  254. }
  255. // Fill value to match default 2 (only when `rawValues` is empty)
  256. if (rangeEnabled && !rawValues.length && count === undefined) {
  257. cloneNextValues.push(newValue);
  258. }
  259. var nextValue = getTriggerValue(cloneNextValues);
  260. onBeforeChange === null || onBeforeChange === void 0 || onBeforeChange(nextValue);
  261. triggerChange(cloneNextValues);
  262. if (e) {
  263. var _document$activeEleme, _document$activeEleme2;
  264. (_document$activeEleme = document.activeElement) === null || _document$activeEleme === void 0 || (_document$activeEleme2 = _document$activeEleme.blur) === null || _document$activeEleme2 === void 0 || _document$activeEleme2.call(_document$activeEleme);
  265. handlesRef.current.focus(focusIndex);
  266. onStartDrag(e, focusIndex, cloneNextValues);
  267. } else {
  268. // https://github.com/ant-design/ant-design/issues/49997
  269. onAfterChange === null || onAfterChange === void 0 || onAfterChange(nextValue);
  270. warning(!onAfterChange, '[rc-slider] `onAfterChange` is deprecated. Please use `onChangeComplete` instead.');
  271. onChangeComplete === null || onChangeComplete === void 0 || onChangeComplete(nextValue);
  272. }
  273. }
  274. };
  275. // ============================ Click =============================
  276. var onSliderMouseDown = function onSliderMouseDown(e) {
  277. e.preventDefault();
  278. var _containerRef$current = containerRef.current.getBoundingClientRect(),
  279. width = _containerRef$current.width,
  280. height = _containerRef$current.height,
  281. left = _containerRef$current.left,
  282. top = _containerRef$current.top,
  283. bottom = _containerRef$current.bottom,
  284. right = _containerRef$current.right;
  285. var clientX = e.clientX,
  286. clientY = e.clientY;
  287. var percent;
  288. switch (direction) {
  289. case 'btt':
  290. percent = (bottom - clientY) / height;
  291. break;
  292. case 'ttb':
  293. percent = (clientY - top) / height;
  294. break;
  295. case 'rtl':
  296. percent = (right - clientX) / width;
  297. break;
  298. default:
  299. percent = (clientX - left) / width;
  300. }
  301. var nextValue = mergedMin + percent * (mergedMax - mergedMin);
  302. changeToCloseValue(formatValue(nextValue), e);
  303. };
  304. // =========================== Keyboard ===========================
  305. var _React$useState = React.useState(null),
  306. _React$useState2 = _slicedToArray(_React$useState, 2),
  307. keyboardValue = _React$useState2[0],
  308. setKeyboardValue = _React$useState2[1];
  309. var onHandleOffsetChange = function onHandleOffsetChange(offset, valueIndex) {
  310. if (!disabled) {
  311. var next = offsetValues(rawValues, offset, valueIndex);
  312. onBeforeChange === null || onBeforeChange === void 0 || onBeforeChange(getTriggerValue(rawValues));
  313. triggerChange(next.values);
  314. setKeyboardValue(next.value);
  315. }
  316. };
  317. React.useEffect(function () {
  318. if (keyboardValue !== null) {
  319. var valueIndex = rawValues.indexOf(keyboardValue);
  320. if (valueIndex >= 0) {
  321. handlesRef.current.focus(valueIndex);
  322. }
  323. }
  324. setKeyboardValue(null);
  325. }, [keyboardValue]);
  326. // ============================= Drag =============================
  327. var mergedDraggableTrack = React.useMemo(function () {
  328. if (rangeDraggableTrack && mergedStep === null) {
  329. if (process.env.NODE_ENV !== 'production') {
  330. warning(false, '`draggableTrack` is not supported when `step` is `null`.');
  331. }
  332. return false;
  333. }
  334. return rangeDraggableTrack;
  335. }, [rangeDraggableTrack, mergedStep]);
  336. var onStartMove = useEvent(function (e, valueIndex) {
  337. onStartDrag(e, valueIndex);
  338. onBeforeChange === null || onBeforeChange === void 0 || onBeforeChange(getTriggerValue(rawValues));
  339. });
  340. // Auto focus for updated handle
  341. var dragging = draggingIndex !== -1;
  342. React.useEffect(function () {
  343. if (!dragging) {
  344. var valueIndex = rawValues.lastIndexOf(draggingValue);
  345. handlesRef.current.focus(valueIndex);
  346. }
  347. }, [dragging]);
  348. // =========================== Included ===========================
  349. var sortedCacheValues = React.useMemo(function () {
  350. return _toConsumableArray(cacheValues).sort(function (a, b) {
  351. return a - b;
  352. });
  353. }, [cacheValues]);
  354. // Provide a range values with included [min, max]
  355. // Used for Track, Mark & Dot
  356. var _React$useMemo = React.useMemo(function () {
  357. if (!rangeEnabled) {
  358. return [mergedMin, sortedCacheValues[0]];
  359. }
  360. return [sortedCacheValues[0], sortedCacheValues[sortedCacheValues.length - 1]];
  361. }, [sortedCacheValues, rangeEnabled, mergedMin]),
  362. _React$useMemo2 = _slicedToArray(_React$useMemo, 2),
  363. includedStart = _React$useMemo2[0],
  364. includedEnd = _React$useMemo2[1];
  365. // ============================= Refs =============================
  366. React.useImperativeHandle(ref, function () {
  367. return {
  368. focus: function focus() {
  369. handlesRef.current.focus(0);
  370. },
  371. blur: function blur() {
  372. var _containerRef$current2;
  373. var _document = document,
  374. activeElement = _document.activeElement;
  375. if ((_containerRef$current2 = containerRef.current) !== null && _containerRef$current2 !== void 0 && _containerRef$current2.contains(activeElement)) {
  376. activeElement === null || activeElement === void 0 || activeElement.blur();
  377. }
  378. }
  379. };
  380. });
  381. // ========================== Auto Focus ==========================
  382. React.useEffect(function () {
  383. if (autoFocus) {
  384. handlesRef.current.focus(0);
  385. }
  386. }, []);
  387. // =========================== Context ============================
  388. var context = React.useMemo(function () {
  389. return {
  390. min: mergedMin,
  391. max: mergedMax,
  392. direction: direction,
  393. disabled: disabled,
  394. keyboard: keyboard,
  395. step: mergedStep,
  396. included: included,
  397. includedStart: includedStart,
  398. includedEnd: includedEnd,
  399. range: rangeEnabled,
  400. tabIndex: tabIndex,
  401. ariaLabelForHandle: ariaLabelForHandle,
  402. ariaLabelledByForHandle: ariaLabelledByForHandle,
  403. ariaRequired: ariaRequired,
  404. ariaValueTextFormatterForHandle: ariaValueTextFormatterForHandle,
  405. styles: styles || {},
  406. classNames: classNames || {}
  407. };
  408. }, [mergedMin, mergedMax, direction, disabled, keyboard, mergedStep, included, includedStart, includedEnd, rangeEnabled, tabIndex, ariaLabelForHandle, ariaLabelledByForHandle, ariaRequired, ariaValueTextFormatterForHandle, styles, classNames]);
  409. // ============================ Render ============================
  410. return /*#__PURE__*/React.createElement(SliderContext.Provider, {
  411. value: context
  412. }, /*#__PURE__*/React.createElement("div", {
  413. ref: containerRef,
  414. className: cls(prefixCls, className, _defineProperty(_defineProperty(_defineProperty(_defineProperty({}, "".concat(prefixCls, "-disabled"), disabled), "".concat(prefixCls, "-vertical"), vertical), "".concat(prefixCls, "-horizontal"), !vertical), "".concat(prefixCls, "-with-marks"), markList.length)),
  415. style: style,
  416. onMouseDown: onSliderMouseDown,
  417. id: id
  418. }, /*#__PURE__*/React.createElement("div", {
  419. className: cls("".concat(prefixCls, "-rail"), classNames === null || classNames === void 0 ? void 0 : classNames.rail),
  420. style: _objectSpread(_objectSpread({}, railStyle), styles === null || styles === void 0 ? void 0 : styles.rail)
  421. }), track !== false && /*#__PURE__*/React.createElement(Tracks, {
  422. prefixCls: prefixCls,
  423. style: trackStyle,
  424. values: rawValues,
  425. startPoint: startPoint,
  426. onStartMove: mergedDraggableTrack ? onStartMove : undefined
  427. }), /*#__PURE__*/React.createElement(Steps, {
  428. prefixCls: prefixCls,
  429. marks: markList,
  430. dots: dots,
  431. style: dotStyle,
  432. activeStyle: activeDotStyle
  433. }), /*#__PURE__*/React.createElement(Handles, {
  434. ref: handlesRef,
  435. prefixCls: prefixCls,
  436. style: handleStyle,
  437. values: cacheValues,
  438. draggingIndex: draggingIndex,
  439. draggingDelete: draggingDelete,
  440. onStartMove: onStartMove,
  441. onOffsetChange: onHandleOffsetChange,
  442. onFocus: onFocus,
  443. onBlur: onBlur,
  444. handleRender: handleRender,
  445. activeHandleRender: activeHandleRender,
  446. onChangeComplete: finishChange,
  447. onDelete: rangeEditable ? onDelete : undefined
  448. }), /*#__PURE__*/React.createElement(Marks, {
  449. prefixCls: prefixCls,
  450. marks: markList,
  451. onClick: changeToCloseValue
  452. })));
  453. });
  454. if (process.env.NODE_ENV !== 'production') {
  455. Slider.displayName = 'Slider';
  456. }
  457. export default Slider;