UNPKG

@itwin/itwinui-react

Version:

A react component library for iTwinUI

385 lines (384 loc) 11.9 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true, }); Object.defineProperty(exports, 'Slider', { enumerable: true, get: function () { return Slider; }, }); const _interop_require_default = require('@swc/helpers/_/_interop_require_default'); const _interop_require_wildcard = require('@swc/helpers/_/_interop_require_wildcard'); const _classnames = /*#__PURE__*/ _interop_require_default._( require('classnames'), ); const _react = /*#__PURE__*/ _interop_require_wildcard._(require('react')); const _index = require('../../utils/index.js'); const _Track = require('./Track.js'); const _Thumb = require('./Thumb.js'); const _react1 = require('@floating-ui/react'); const _Tooltip = require('../Tooltip/Tooltip.js'); const getPercentageOfRectangle = (rect, pointerX, pointerY, orientation) => { if ('horizontal' === orientation) { let position = (0, _index.getBoundedValue)(pointerX, rect.left, rect.right); return (position - rect.left) / rect.width; } let position = (0, _index.getBoundedValue)(pointerY, rect.top, rect.bottom); return (rect.bottom - position) / rect.height; }; const getClosestValueIndex = (values, pointerValue) => { if (1 === values.length) return 0; let distances = values.map((value) => Math.abs(value - pointerValue)); let smallest = Math.min(...distances); return distances.indexOf(smallest); }; const getDefaultTrackDisplay = (trackDisplayMode, values) => { if ('auto' !== trackDisplayMode) return trackDisplayMode; return values.length % 2 ? 'even-segments' : 'odd-segments'; }; const roundValueToClosestStep = (value, step, min) => Math.round((value - min) / step) * step + min; const formatNumberValue = (value, step, numDecimals) => { if (Number.isInteger(step)) return value.toFixed(0); return value.toFixed(numDecimals); }; const focusThumb = (sliderContainer, activeIndex) => { let doc = sliderContainer.ownerDocument; if ( !sliderContainer.contains(doc.activeElement) || Number(doc.activeElement?.getAttribute('data-index')) !== activeIndex ) { let thumbToFocus = sliderContainer.querySelector( `[data-index="${activeIndex}"]`, ); thumbToFocus && thumbToFocus.focus(); } }; const Slider = _react.forwardRef((props, ref) => { let { min = 0, max = 100, values, step = 1, tooltipProps, disabled = false, tickLabels, minLabel, maxLabel, trackDisplayMode = 'auto', thumbMode = 'inhibit-crossing', onChange, onUpdate, thumbProps, className, trackContainerProps, minProps, maxProps, trackProps, tickProps, ticksProps, orientation = 'horizontal', ...rest } = props; let [currentValues, setCurrentValues] = _react.useState(values); _react.useEffect(() => { setCurrentValues(values); }, [values]); let [minValueLabel, setMinValueLabel] = _react.useState( () => minLabel ?? min.toString(), ); _react.useEffect(() => { setMinValueLabel(minLabel ?? min.toString()); }, [minLabel, min]); let [maxValueLabel, setMaxValueLabel] = _react.useState( () => maxLabel ?? max.toString(), ); _react.useEffect(() => { setMaxValueLabel(maxLabel ?? max.toString()); }, [maxLabel, max]); let [trackDisplay, setTrackDisplay] = _react.useState(() => getDefaultTrackDisplay(trackDisplayMode, currentValues), ); _react.useEffect(() => { setTrackDisplay(getDefaultTrackDisplay(trackDisplayMode, currentValues)); }, [trackDisplayMode, currentValues]); let containerRef = _react.useRef(null); let getNumDecimalPlaces = _react.useMemo(() => { let stepString = step.toString(); let decimalIndex = stepString.indexOf('.'); return stepString.length - (decimalIndex + 1); }, [step]); let getAllowableThumbRange = _react.useCallback( (index) => { if ('inhibit-crossing' === thumbMode) { let minVal = 0 === index ? min : currentValues[index - 1] + step; let maxVal = index < currentValues.length - 1 ? currentValues[index + 1] - step : max; return [minVal, maxVal]; } return [min, max]; }, [max, min, step, thumbMode, currentValues], ); let [activeThumbIndex, setActiveThumbIndex] = _react.useState(void 0); let updateThumbValue = _react.useCallback( (event, callbackType) => { if (containerRef.current && void 0 !== activeThumbIndex) { let percent = getPercentageOfRectangle( containerRef.current.getBoundingClientRect(), event.clientX, event.clientY, orientation, ); let pointerValue = min + (max - min) * percent; pointerValue = roundValueToClosestStep(pointerValue, step, min); let [minVal, maxVal] = getAllowableThumbRange(activeThumbIndex); pointerValue = (0, _index.getBoundedValue)( pointerValue, minVal, maxVal, ); if (pointerValue !== currentValues[activeThumbIndex]) { let newValues = [...currentValues]; newValues[activeThumbIndex] = pointerValue; setCurrentValues(newValues); 'onChange' === callbackType ? onChange?.(newValues) : onUpdate?.(newValues); } else if ('onChange' === callbackType) onChange?.(currentValues); } }, [ activeThumbIndex, min, max, step, getAllowableThumbRange, currentValues, onUpdate, onChange, orientation, ], ); let handlePointerMove = _react.useCallback( (event) => { if (void 0 === activeThumbIndex) return; event.preventDefault(); event.stopPropagation(); updateThumbValue(event, 'onUpdate'); }, [activeThumbIndex, updateThumbValue], ); let onThumbValueChanged = _react.useCallback( (index, value, keyboardReleased) => { if (currentValues[index] === value && !keyboardReleased) return; if (keyboardReleased) onChange?.(currentValues); else { let newValues = [...currentValues]; newValues[index] = value; onUpdate?.(newValues); setCurrentValues(newValues); } }, [currentValues, onUpdate, onChange], ); let onThumbActivated = _react.useCallback((index) => { setActiveThumbIndex(index); }, []); let handlePointerUp = _react.useCallback( (event) => { if (void 0 === activeThumbIndex) return; updateThumbValue(event, 'onChange'); setActiveThumbIndex(void 0); event.preventDefault(); event.stopPropagation(); }, [activeThumbIndex, updateThumbValue], ); let handlePointerDownOnSlider = _react.useCallback( (event) => { if (containerRef.current) { let percent = getPercentageOfRectangle( containerRef.current.getBoundingClientRect(), event.clientX, event.clientY, orientation, ); let pointerValue = min + (max - min) * percent; pointerValue = roundValueToClosestStep(pointerValue, step, min); let closestValueIndex = getClosestValueIndex( currentValues, pointerValue, ); let [minVal, maxVal] = getAllowableThumbRange(closestValueIndex); pointerValue = (0, _index.getBoundedValue)( pointerValue, minVal, maxVal, ); if (pointerValue === currentValues[closestValueIndex]) return; let newValues = [...currentValues]; newValues[closestValueIndex] = pointerValue; setCurrentValues(newValues); onChange?.(newValues); onUpdate?.(newValues); focusThumb(containerRef.current, closestValueIndex); event.preventDefault(); event.stopPropagation(); } }, [ min, max, step, currentValues, getAllowableThumbRange, onChange, onUpdate, orientation, ], ); (0, _index.useEventListener)( 'pointermove', handlePointerMove, containerRef.current?.ownerDocument, ); (0, _index.useEventListener)( 'pointerup', handlePointerUp, containerRef.current?.ownerDocument, ); let tickMarkArea = _react.useMemo(() => { if (!tickLabels) return null; if (Array.isArray(tickLabels)) return _react.createElement( _index.Box, { as: 'div', ...ticksProps, className: (0, _classnames.default)( 'iui-slider-ticks', ticksProps?.className, ), }, tickLabels.map((label, index) => _react.createElement( _index.Box, { as: 'span', ...tickProps, key: index, className: (0, _classnames.default)( 'iui-slider-tick', tickProps?.className, ), }, label, ), ), ); return tickLabels; }, [tickLabels, tickProps, ticksProps]); let generateTooltipProps = _react.useCallback( (index, val) => { let outProps = tooltipProps ? tooltipProps(index, val, step) : {}; return { ...outProps, content: outProps.content ? outProps.content : formatNumberValue(val, step, getNumDecimalPlaces), }; }, [getNumDecimalPlaces, step, tooltipProps], ); return _react.createElement( _index.Box, { ref: ref, className: (0, _classnames.default)('iui-slider-container', className), 'data-iui-orientation': orientation, 'data-iui-disabled': disabled ? 'true' : void 0, ...rest, }, minValueLabel && _react.createElement( _index.Box, { as: 'span', ...minProps, className: (0, _classnames.default)( 'iui-slider-min', minProps?.className, ), }, minValueLabel, ), _react.createElement( _react1.FloatingDelayGroup, { delay: _Tooltip.defaultTooltipDelay, }, _react.createElement( _index.Box, { ref: containerRef, ...trackContainerProps, className: (0, _classnames.default)( 'iui-slider', { 'iui-grabbing': void 0 !== activeThumbIndex, }, trackContainerProps?.className, ), onPointerDown: handlePointerDownOnSlider, }, currentValues.map((thumbValue, index) => { let [minVal, maxVal] = getAllowableThumbRange(index); let thisThumbProps = thumbProps?.(index); return _react.createElement(_Thumb.Thumb, { key: thisThumbProps?.id ?? index, index: index, disabled: disabled, isActive: activeThumbIndex === index, onThumbActivated: onThumbActivated, onThumbValueChanged: onThumbValueChanged, minVal: minVal, maxVal: maxVal, value: thumbValue, tooltipProps: generateTooltipProps(index, thumbValue), thumbProps: thisThumbProps, step: step, sliderMin: min, sliderMax: max, }); }), _react.createElement(_Track.Track, { trackDisplayMode: trackDisplay, sliderMin: min, sliderMax: max, values: currentValues, orientation: orientation, ...trackProps, }), ), ), tickMarkArea, maxValueLabel && _react.createElement( _index.Box, { as: 'span', ...maxProps, className: (0, _classnames.default)( 'iui-slider-max', maxProps?.className, ), }, maxValueLabel, ), ); }); if ('development' === process.env.NODE_ENV) Slider.displayName = 'Slider';