UNPKG

21.3 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4Object.defineProperty(exports, "__esModule", {
5 value: true
6});
7exports.Identity = void 0;
8exports.default = useSlider;
9exports.valueToPercent = valueToPercent;
10var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
11var React = _interopRequireWildcard(require("react"));
12var _utils = require("@mui/utils");
13var _utils2 = require("../utils");
14function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
15function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
16const INTENTIONAL_DRAG_COUNT_THRESHOLD = 2;
17function asc(a, b) {
18 return a - b;
19}
20function clamp(value, min, max) {
21 if (value == null) {
22 return min;
23 }
24 return Math.min(Math.max(min, value), max);
25}
26function findClosest(values, currentValue) {
27 var _values$reduce;
28 const {
29 index: closestIndex
30 } = (_values$reduce = values.reduce((acc, value, index) => {
31 const distance = Math.abs(currentValue - value);
32 if (acc === null || distance < acc.distance || distance === acc.distance) {
33 return {
34 distance,
35 index
36 };
37 }
38 return acc;
39 }, null)) != null ? _values$reduce : {};
40 return closestIndex;
41}
42function trackFinger(event, touchId) {
43 // The event is TouchEvent
44 if (touchId.current !== undefined && event.changedTouches) {
45 const touchEvent = event;
46 for (let i = 0; i < touchEvent.changedTouches.length; i += 1) {
47 const touch = touchEvent.changedTouches[i];
48 if (touch.identifier === touchId.current) {
49 return {
50 x: touch.clientX,
51 y: touch.clientY
52 };
53 }
54 }
55 return false;
56 }
57
58 // The event is MouseEvent
59 return {
60 x: event.clientX,
61 y: event.clientY
62 };
63}
64function valueToPercent(value, min, max) {
65 return (value - min) * 100 / (max - min);
66}
67function percentToValue(percent, min, max) {
68 return (max - min) * percent + min;
69}
70function getDecimalPrecision(num) {
71 // This handles the case when num is very small (0.00000001), js will turn this into 1e-8.
72 // When num is bigger than 1 or less than -1 it won't get converted to this notation so it's fine.
73 if (Math.abs(num) < 1) {
74 const parts = num.toExponential().split('e-');
75 const matissaDecimalPart = parts[0].split('.')[1];
76 return (matissaDecimalPart ? matissaDecimalPart.length : 0) + parseInt(parts[1], 10);
77 }
78 const decimalPart = num.toString().split('.')[1];
79 return decimalPart ? decimalPart.length : 0;
80}
81function roundValueToStep(value, step, min) {
82 const nearest = Math.round((value - min) / step) * step + min;
83 return Number(nearest.toFixed(getDecimalPrecision(step)));
84}
85function setValueIndex({
86 values,
87 newValue,
88 index
89}) {
90 const output = values.slice();
91 output[index] = newValue;
92 return output.sort(asc);
93}
94function focusThumb({
95 sliderRef,
96 activeIndex,
97 setActive
98}) {
99 var _sliderRef$current, _doc$activeElement;
100 const doc = (0, _utils.unstable_ownerDocument)(sliderRef.current);
101 if (!((_sliderRef$current = sliderRef.current) != null && _sliderRef$current.contains(doc.activeElement)) || Number(doc == null ? void 0 : (_doc$activeElement = doc.activeElement) == null ? void 0 : _doc$activeElement.getAttribute('data-index')) !== activeIndex) {
102 var _sliderRef$current2;
103 (_sliderRef$current2 = sliderRef.current) == null ? void 0 : _sliderRef$current2.querySelector(`[type="range"][data-index="${activeIndex}"]`).focus();
104 }
105 if (setActive) {
106 setActive(activeIndex);
107 }
108}
109function areValuesEqual(newValue, oldValue) {
110 if (typeof newValue === 'number' && typeof oldValue === 'number') {
111 return newValue === oldValue;
112 }
113 if (typeof newValue === 'object' && typeof oldValue === 'object') {
114 return (0, _utils2.areArraysEqual)(newValue, oldValue);
115 }
116 return false;
117}
118const axisProps = {
119 horizontal: {
120 offset: percent => ({
121 left: `${percent}%`
122 }),
123 leap: percent => ({
124 width: `${percent}%`
125 })
126 },
127 'horizontal-reverse': {
128 offset: percent => ({
129 right: `${percent}%`
130 }),
131 leap: percent => ({
132 width: `${percent}%`
133 })
134 },
135 vertical: {
136 offset: percent => ({
137 bottom: `${percent}%`
138 }),
139 leap: percent => ({
140 height: `${percent}%`
141 })
142 }
143};
144const Identity = x => x;
145
146// TODO: remove support for Safari < 13.
147// https://caniuse.com/#search=touch-action
148//
149// Safari, on iOS, supports touch action since v13.
150// Over 80% of the iOS phones are compatible
151// in August 2020.
152// Utilizing the CSS.supports method to check if touch-action is supported.
153// Since CSS.supports is supported on all but Edge@12 and IE and touch-action
154// is supported on both Edge@12 and IE if CSS.supports is not available that means that
155// touch-action will be supported
156exports.Identity = Identity;
157let cachedSupportsTouchActionNone;
158function doesSupportTouchActionNone() {
159 if (cachedSupportsTouchActionNone === undefined) {
160 if (typeof CSS !== 'undefined' && typeof CSS.supports === 'function') {
161 cachedSupportsTouchActionNone = CSS.supports('touch-action', 'none');
162 } else {
163 cachedSupportsTouchActionNone = true;
164 }
165 }
166 return cachedSupportsTouchActionNone;
167}
168/**
169 *
170 * Demos:
171 *
172 * - [Slider](https://mui.com/base/react-slider/#hook)
173 *
174 * API:
175 *
176 * - [useSlider API](https://mui.com/base/react-slider/hooks-api/#use-slider)
177 */
178function useSlider(parameters) {
179 const {
180 'aria-labelledby': ariaLabelledby,
181 defaultValue,
182 disabled = false,
183 disableSwap = false,
184 isRtl = false,
185 marks: marksProp = false,
186 max = 100,
187 min = 0,
188 name,
189 onChange,
190 onChangeCommitted,
191 orientation = 'horizontal',
192 rootRef: ref,
193 scale = Identity,
194 step = 1,
195 tabIndex,
196 value: valueProp
197 } = parameters;
198 const touchId = React.useRef();
199 // We can't use the :active browser pseudo-classes.
200 // - The active state isn't triggered when clicking on the rail.
201 // - The active state isn't transferred when inversing a range slider.
202 const [active, setActive] = React.useState(-1);
203 const [open, setOpen] = React.useState(-1);
204 const [dragging, setDragging] = React.useState(false);
205 const moveCount = React.useRef(0);
206 const [valueDerived, setValueState] = (0, _utils.unstable_useControlled)({
207 controlled: valueProp,
208 default: defaultValue != null ? defaultValue : min,
209 name: 'Slider'
210 });
211 const handleChange = onChange && ((event, value, thumbIndex) => {
212 // Redefine target to allow name and value to be read.
213 // This allows seamless integration with the most popular form libraries.
214 // https://github.com/mui/material-ui/issues/13485#issuecomment-676048492
215 // Clone the event to not override `target` of the original event.
216 const nativeEvent = event.nativeEvent || event;
217 // @ts-ignore The nativeEvent is function, not object
218 const clonedEvent = new nativeEvent.constructor(nativeEvent.type, nativeEvent);
219 Object.defineProperty(clonedEvent, 'target', {
220 writable: true,
221 value: {
222 value,
223 name
224 }
225 });
226 onChange(clonedEvent, value, thumbIndex);
227 });
228 const range = Array.isArray(valueDerived);
229 let values = range ? valueDerived.slice().sort(asc) : [valueDerived];
230 values = values.map(value => clamp(value, min, max));
231 const marks = marksProp === true && step !== null ? [...Array(Math.floor((max - min) / step) + 1)].map((_, index) => ({
232 value: min + step * index
233 })) : marksProp || [];
234 const marksValues = marks.map(mark => mark.value);
235 const {
236 isFocusVisibleRef,
237 onBlur: handleBlurVisible,
238 onFocus: handleFocusVisible,
239 ref: focusVisibleRef
240 } = (0, _utils.unstable_useIsFocusVisible)();
241 const [focusedThumbIndex, setFocusedThumbIndex] = React.useState(-1);
242 const sliderRef = React.useRef();
243 const handleFocusRef = (0, _utils.unstable_useForkRef)(focusVisibleRef, sliderRef);
244 const handleRef = (0, _utils.unstable_useForkRef)(ref, handleFocusRef);
245 const createHandleHiddenInputFocus = otherHandlers => event => {
246 var _otherHandlers$onFocu;
247 const index = Number(event.currentTarget.getAttribute('data-index'));
248 handleFocusVisible(event);
249 if (isFocusVisibleRef.current === true) {
250 setFocusedThumbIndex(index);
251 }
252 setOpen(index);
253 otherHandlers == null ? void 0 : (_otherHandlers$onFocu = otherHandlers.onFocus) == null ? void 0 : _otherHandlers$onFocu.call(otherHandlers, event);
254 };
255 const createHandleHiddenInputBlur = otherHandlers => event => {
256 var _otherHandlers$onBlur;
257 handleBlurVisible(event);
258 if (isFocusVisibleRef.current === false) {
259 setFocusedThumbIndex(-1);
260 }
261 setOpen(-1);
262 otherHandlers == null ? void 0 : (_otherHandlers$onBlur = otherHandlers.onBlur) == null ? void 0 : _otherHandlers$onBlur.call(otherHandlers, event);
263 };
264 (0, _utils.unstable_useEnhancedEffect)(() => {
265 if (disabled && sliderRef.current.contains(document.activeElement)) {
266 var _document$activeEleme;
267 // This is necessary because Firefox and Safari will keep focus
268 // on a disabled element:
269 // https://codesandbox.io/s/mui-pr-22247-forked-h151h?file=/src/App.js
270 // @ts-ignore
271 (_document$activeEleme = document.activeElement) == null ? void 0 : _document$activeEleme.blur();
272 }
273 }, [disabled]);
274 if (disabled && active !== -1) {
275 setActive(-1);
276 }
277 if (disabled && focusedThumbIndex !== -1) {
278 setFocusedThumbIndex(-1);
279 }
280 const createHandleHiddenInputChange = otherHandlers => event => {
281 var _otherHandlers$onChan;
282 (_otherHandlers$onChan = otherHandlers.onChange) == null ? void 0 : _otherHandlers$onChan.call(otherHandlers, event);
283 const index = Number(event.currentTarget.getAttribute('data-index'));
284 const value = values[index];
285 const marksIndex = marksValues.indexOf(value);
286
287 // @ts-ignore
288 let newValue = event.target.valueAsNumber;
289 if (marks && step == null) {
290 newValue = newValue < value ? marksValues[marksIndex - 1] : marksValues[marksIndex + 1];
291 }
292 newValue = clamp(newValue, min, max);
293 if (marks && step == null) {
294 const currentMarkIndex = marksValues.indexOf(values[index]);
295 newValue = newValue < values[index] ? marksValues[currentMarkIndex - 1] : marksValues[currentMarkIndex + 1];
296 }
297 if (range) {
298 // Bound the new value to the thumb's neighbours.
299 if (disableSwap) {
300 newValue = clamp(newValue, values[index - 1] || -Infinity, values[index + 1] || Infinity);
301 }
302 const previousValue = newValue;
303 newValue = setValueIndex({
304 values,
305 newValue,
306 index
307 });
308 let activeIndex = index;
309
310 // Potentially swap the index if needed.
311 if (!disableSwap) {
312 activeIndex = newValue.indexOf(previousValue);
313 }
314 focusThumb({
315 sliderRef,
316 activeIndex
317 });
318 }
319 setValueState(newValue);
320 setFocusedThumbIndex(index);
321 if (handleChange && !areValuesEqual(newValue, valueDerived)) {
322 handleChange(event, newValue, index);
323 }
324 if (onChangeCommitted) {
325 onChangeCommitted(event, newValue);
326 }
327 };
328 const previousIndex = React.useRef();
329 let axis = orientation;
330 if (isRtl && orientation === 'horizontal') {
331 axis += '-reverse';
332 }
333 const getFingerNewValue = ({
334 finger,
335 move = false
336 }) => {
337 const {
338 current: slider
339 } = sliderRef;
340 const {
341 width,
342 height,
343 bottom,
344 left
345 } = slider.getBoundingClientRect();
346 let percent;
347 if (axis.indexOf('vertical') === 0) {
348 percent = (bottom - finger.y) / height;
349 } else {
350 percent = (finger.x - left) / width;
351 }
352 if (axis.indexOf('-reverse') !== -1) {
353 percent = 1 - percent;
354 }
355 let newValue;
356 newValue = percentToValue(percent, min, max);
357 if (step) {
358 newValue = roundValueToStep(newValue, step, min);
359 } else {
360 const closestIndex = findClosest(marksValues, newValue);
361 newValue = marksValues[closestIndex];
362 }
363 newValue = clamp(newValue, min, max);
364 let activeIndex = 0;
365 if (range) {
366 if (!move) {
367 activeIndex = findClosest(values, newValue);
368 } else {
369 activeIndex = previousIndex.current;
370 }
371
372 // Bound the new value to the thumb's neighbours.
373 if (disableSwap) {
374 newValue = clamp(newValue, values[activeIndex - 1] || -Infinity, values[activeIndex + 1] || Infinity);
375 }
376 const previousValue = newValue;
377 newValue = setValueIndex({
378 values,
379 newValue,
380 index: activeIndex
381 });
382
383 // Potentially swap the index if needed.
384 if (!(disableSwap && move)) {
385 activeIndex = newValue.indexOf(previousValue);
386 previousIndex.current = activeIndex;
387 }
388 }
389 return {
390 newValue,
391 activeIndex
392 };
393 };
394 const handleTouchMove = (0, _utils.unstable_useEventCallback)(nativeEvent => {
395 const finger = trackFinger(nativeEvent, touchId);
396 if (!finger) {
397 return;
398 }
399 moveCount.current += 1;
400
401 // Cancel move in case some other element consumed a mouseup event and it was not fired.
402 // @ts-ignore buttons doesn't not exists on touch event
403 if (nativeEvent.type === 'mousemove' && nativeEvent.buttons === 0) {
404 // eslint-disable-next-line @typescript-eslint/no-use-before-define
405 handleTouchEnd(nativeEvent);
406 return;
407 }
408 const {
409 newValue,
410 activeIndex
411 } = getFingerNewValue({
412 finger,
413 move: true
414 });
415 focusThumb({
416 sliderRef,
417 activeIndex,
418 setActive
419 });
420 setValueState(newValue);
421 if (!dragging && moveCount.current > INTENTIONAL_DRAG_COUNT_THRESHOLD) {
422 setDragging(true);
423 }
424 if (handleChange && !areValuesEqual(newValue, valueDerived)) {
425 handleChange(nativeEvent, newValue, activeIndex);
426 }
427 });
428 const handleTouchEnd = (0, _utils.unstable_useEventCallback)(nativeEvent => {
429 const finger = trackFinger(nativeEvent, touchId);
430 setDragging(false);
431 if (!finger) {
432 return;
433 }
434 const {
435 newValue
436 } = getFingerNewValue({
437 finger,
438 move: true
439 });
440 setActive(-1);
441 if (nativeEvent.type === 'touchend') {
442 setOpen(-1);
443 }
444 if (onChangeCommitted) {
445 onChangeCommitted(nativeEvent, newValue);
446 }
447 touchId.current = undefined;
448
449 // eslint-disable-next-line @typescript-eslint/no-use-before-define
450 stopListening();
451 });
452 const handleTouchStart = (0, _utils.unstable_useEventCallback)(nativeEvent => {
453 if (disabled) {
454 return;
455 }
456 // If touch-action: none; is not supported we need to prevent the scroll manually.
457 if (!doesSupportTouchActionNone()) {
458 nativeEvent.preventDefault();
459 }
460 const touch = nativeEvent.changedTouches[0];
461 if (touch != null) {
462 // A number that uniquely identifies the current finger in the touch session.
463 touchId.current = touch.identifier;
464 }
465 const finger = trackFinger(nativeEvent, touchId);
466 if (finger !== false) {
467 const {
468 newValue,
469 activeIndex
470 } = getFingerNewValue({
471 finger
472 });
473 focusThumb({
474 sliderRef,
475 activeIndex,
476 setActive
477 });
478 setValueState(newValue);
479 if (handleChange && !areValuesEqual(newValue, valueDerived)) {
480 handleChange(nativeEvent, newValue, activeIndex);
481 }
482 }
483 moveCount.current = 0;
484 const doc = (0, _utils.unstable_ownerDocument)(sliderRef.current);
485 doc.addEventListener('touchmove', handleTouchMove);
486 doc.addEventListener('touchend', handleTouchEnd);
487 });
488 const stopListening = React.useCallback(() => {
489 const doc = (0, _utils.unstable_ownerDocument)(sliderRef.current);
490 doc.removeEventListener('mousemove', handleTouchMove);
491 doc.removeEventListener('mouseup', handleTouchEnd);
492 doc.removeEventListener('touchmove', handleTouchMove);
493 doc.removeEventListener('touchend', handleTouchEnd);
494 }, [handleTouchEnd, handleTouchMove]);
495 React.useEffect(() => {
496 const {
497 current: slider
498 } = sliderRef;
499 slider.addEventListener('touchstart', handleTouchStart, {
500 passive: doesSupportTouchActionNone()
501 });
502 return () => {
503 // @ts-ignore
504 slider.removeEventListener('touchstart', handleTouchStart, {
505 passive: doesSupportTouchActionNone()
506 });
507 stopListening();
508 };
509 }, [stopListening, handleTouchStart]);
510 React.useEffect(() => {
511 if (disabled) {
512 stopListening();
513 }
514 }, [disabled, stopListening]);
515 const createHandleMouseDown = otherHandlers => event => {
516 var _otherHandlers$onMous;
517 (_otherHandlers$onMous = otherHandlers.onMouseDown) == null ? void 0 : _otherHandlers$onMous.call(otherHandlers, event);
518 if (disabled) {
519 return;
520 }
521 if (event.defaultPrevented) {
522 return;
523 }
524
525 // Only handle left clicks
526 if (event.button !== 0) {
527 return;
528 }
529
530 // Avoid text selection
531 event.preventDefault();
532 const finger = trackFinger(event, touchId);
533 if (finger !== false) {
534 const {
535 newValue,
536 activeIndex
537 } = getFingerNewValue({
538 finger
539 });
540 focusThumb({
541 sliderRef,
542 activeIndex,
543 setActive
544 });
545 setValueState(newValue);
546 if (handleChange && !areValuesEqual(newValue, valueDerived)) {
547 handleChange(event, newValue, activeIndex);
548 }
549 }
550 moveCount.current = 0;
551 const doc = (0, _utils.unstable_ownerDocument)(sliderRef.current);
552 doc.addEventListener('mousemove', handleTouchMove);
553 doc.addEventListener('mouseup', handleTouchEnd);
554 };
555 const trackOffset = valueToPercent(range ? values[0] : min, min, max);
556 const trackLeap = valueToPercent(values[values.length - 1], min, max) - trackOffset;
557 const getRootProps = (otherHandlers = {}) => {
558 const ownEventHandlers = {
559 onMouseDown: createHandleMouseDown(otherHandlers || {})
560 };
561 const mergedEventHandlers = (0, _extends2.default)({}, otherHandlers, ownEventHandlers);
562 return (0, _extends2.default)({
563 ref: handleRef
564 }, mergedEventHandlers);
565 };
566 const createHandleMouseOver = otherHandlers => event => {
567 var _otherHandlers$onMous2;
568 (_otherHandlers$onMous2 = otherHandlers.onMouseOver) == null ? void 0 : _otherHandlers$onMous2.call(otherHandlers, event);
569 const index = Number(event.currentTarget.getAttribute('data-index'));
570 setOpen(index);
571 };
572 const createHandleMouseLeave = otherHandlers => event => {
573 var _otherHandlers$onMous3;
574 (_otherHandlers$onMous3 = otherHandlers.onMouseLeave) == null ? void 0 : _otherHandlers$onMous3.call(otherHandlers, event);
575 setOpen(-1);
576 };
577 const getThumbProps = (otherHandlers = {}) => {
578 const ownEventHandlers = {
579 onMouseOver: createHandleMouseOver(otherHandlers || {}),
580 onMouseLeave: createHandleMouseLeave(otherHandlers || {})
581 };
582 return (0, _extends2.default)({}, otherHandlers, ownEventHandlers);
583 };
584 const getHiddenInputProps = (otherHandlers = {}) => {
585 var _parameters$step;
586 const ownEventHandlers = {
587 onChange: createHandleHiddenInputChange(otherHandlers || {}),
588 onFocus: createHandleHiddenInputFocus(otherHandlers || {}),
589 onBlur: createHandleHiddenInputBlur(otherHandlers || {})
590 };
591 const mergedEventHandlers = (0, _extends2.default)({}, otherHandlers, ownEventHandlers);
592 return (0, _extends2.default)({
593 tabIndex,
594 'aria-labelledby': ariaLabelledby,
595 'aria-orientation': orientation,
596 'aria-valuemax': scale(max),
597 'aria-valuemin': scale(min),
598 name,
599 type: 'range',
600 min: parameters.min,
601 max: parameters.max,
602 step: (_parameters$step = parameters.step) != null ? _parameters$step : undefined,
603 disabled
604 }, mergedEventHandlers, {
605 style: (0, _extends2.default)({}, _utils.visuallyHidden, {
606 direction: isRtl ? 'rtl' : 'ltr',
607 // So that VoiceOver's focus indicator matches the thumb's dimensions
608 width: '100%',
609 height: '100%'
610 })
611 });
612 };
613 return {
614 active,
615 axis: axis,
616 axisProps,
617 dragging,
618 focusedThumbIndex,
619 getHiddenInputProps,
620 getRootProps,
621 getThumbProps,
622 marks: marks,
623 open,
624 range,
625 rootRef: handleRef,
626 trackLeap,
627 trackOffset,
628 values
629 };
630}
\No newline at end of file