UNPKG

@fluentui/react

Version:

Reusable React components for building web experiences.

327 lines 18.3 kB
define(["require", "exports", "tslib", "react", "@fluentui/react-hooks", "@fluentui/utilities", "../../utilities/dom"], function (require, exports, tslib_1, React, react_hooks_1, utilities_1, dom_1) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useSlider = exports.ONKEYDOWN_TIMEOUT_DURATION = void 0; exports.ONKEYDOWN_TIMEOUT_DURATION = 1000; var getClassNames = (0, utilities_1.classNamesFunction)(); var getSlotStyleFn = function (sty) { return function (value) { var _a; return _a = {}, _a[sty] = "".concat(value, "%"), _a; }; }; var getPercent = function (value, sliderMin, sliderMax) { return sliderMax === sliderMin ? 0 : ((value - sliderMin) / (sliderMax - sliderMin)) * 100; }; var useComponentRef = function (props, sliderBoxRef, value, range) { React.useImperativeHandle(props.componentRef, function () { return ({ get value() { return value; }, get range() { return range; }, focus: function () { var _a; (_a = sliderBoxRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, }); }, [range, sliderBoxRef, value]); }; var useSlider = function (props, ref) { var _a = props.step, step = _a === void 0 ? 1 : _a, className = props.className, _b = props.disabled, disabled = _b === void 0 ? false : _b, label = props.label, _c = props.max, max = _c === void 0 ? 10 : _c, _d = props.min, min = _d === void 0 ? 0 : _d, _e = props.showValue, showValue = _e === void 0 ? true : _e, _f = props.buttonProps, buttonProps = _f === void 0 ? {} : _f, _g = props.vertical, vertical = _g === void 0 ? false : _g, snapToStep = props.snapToStep, valueFormat = props.valueFormat, styles = props.styles, theme = props.theme, originFromZero = props.originFromZero, ariaLabelledBy = props["aria-labelledby"], _h = props.ariaLabel, ariaLabel = _h === void 0 ? props['aria-label'] : _h, ranged = props.ranged, onChange = props.onChange, onChanged = props.onChanged; var disposables = React.useRef([]); var _j = (0, react_hooks_1.useSetTimeout)(), setTimeout = _j.setTimeout, clearTimeout = _j.clearTimeout; var sliderLine = React.useRef(null); var win = (0, dom_1.useWindowEx)(); // Casting here is necessary because useControllableValue expects the event for the change callback // to extend React.SyntheticEvent, when in fact for Slider, the event could be either a React event // or a native browser event depending on the context. var _k = (0, react_hooks_1.useControllableValue)(props.value, props.defaultValue, function (ev, v) { return onChange === null || onChange === void 0 ? void 0 : onChange(v, ranged ? [internalState.latestLowerValue, v] : undefined, ev); }), unclampedValue = _k[0], setValue = _k[1]; var _l = (0, react_hooks_1.useControllableValue)(props.lowerValue, props.defaultLowerValue, function (ev, lv) { return onChange === null || onChange === void 0 ? void 0 : onChange(internalState.latestValue, [lv, internalState.latestValue], ev); }), unclampedLowerValue = _l[0], setLowerValue = _l[1]; // Ensure that value is always a number and is clamped by min/max. var value = Math.max(min, Math.min(max, unclampedValue || 0)); var lowerValue = Math.max(min, Math.min(value, unclampedLowerValue || 0)); var internalState = (0, react_hooks_1.useConst)({ onKeyDownTimer: -1, isAdjustingLowerValue: false, latestValue: value, latestLowerValue: lowerValue, }); // On each render, update this saved value used by callbacks. (This should be safe even if render // is called multiple times, because an event handler or timeout callback will only run once.) internalState.latestValue = value; internalState.latestLowerValue = lowerValue; var id = (0, react_hooks_1.useId)('Slider', props.id || (buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.id)); var classNames = getClassNames(styles, { className: className, disabled: disabled, vertical: vertical, showTransitions: !snapToStep && !internalState.isBetweenSteps, showValue: showValue, ranged: ranged, theme: theme, }); var steps = (max - min) / step; var clearOnKeyDownTimer = function () { clearTimeout(internalState.onKeyDownTimer); internalState.onKeyDownTimer = -1; }; var setOnKeyDownTimer = function (event) { clearOnKeyDownTimer(); if (onChanged) { internalState.onKeyDownTimer = setTimeout(function () { onChanged(event, internalState.latestValue, ranged ? [internalState.latestLowerValue, internalState.latestValue] : undefined); }, exports.ONKEYDOWN_TIMEOUT_DURATION); } }; var getAriaValueText = function (valueProps) { var ariaValueText = props.ariaValueText; if (valueProps !== undefined) { return ariaValueText ? ariaValueText(valueProps) : valueProps.toString(); } return undefined; }; /** * Update `value` or `lowerValue`, including clamping between min/max and rounding to * appropriate precision. * @param newValue - New current value of the slider, possibly rounded to a whole step. * @param newUnroundedValue - Like `newValue` but without the rounding to a step. If this is * provided and not equal to `newValue`, `internalState.isBetweenSteps` will be set, which * may cause thumb movement animations to be disabled. */ var updateValue = function (ev, newValue, newUnroundedValue) { newValue = Math.min(max, Math.max(min, newValue)); newUnroundedValue = newUnroundedValue !== undefined ? Math.min(max, Math.max(min, newUnroundedValue)) : undefined; var numDec = 0; if (isFinite(step)) { while (Math.round(step * Math.pow(10, numDec)) / Math.pow(10, numDec) !== step) { numDec++; } } // Make sure value has correct number of decimal places based on number of decimals in step var roundedValue = parseFloat(newValue.toFixed(numDec)); internalState.isBetweenSteps = newUnroundedValue !== undefined && newUnroundedValue !== roundedValue; if (ranged) { // decided which thumb value to change if (internalState.isAdjustingLowerValue && (originFromZero ? roundedValue <= 0 : roundedValue <= internalState.latestValue)) { setLowerValue(roundedValue, ev); } else if (!internalState.isAdjustingLowerValue && (originFromZero ? roundedValue >= 0 : roundedValue >= internalState.latestLowerValue)) { setValue(roundedValue, ev); } } else { setValue(roundedValue, ev); } }; var onKeyDown = function (event) { var newCurrentValue = internalState.isAdjustingLowerValue ? internalState.latestLowerValue : internalState.latestValue; var diff = 0; // eslint-disable-next-line @typescript-eslint/no-deprecated switch (event.which) { case (0, utilities_1.getRTLSafeKeyCode)(utilities_1.KeyCodes.left, props.theme): case utilities_1.KeyCodes.down: diff = -step; clearOnKeyDownTimer(); setOnKeyDownTimer(event); break; case (0, utilities_1.getRTLSafeKeyCode)(utilities_1.KeyCodes.right, props.theme): case utilities_1.KeyCodes.up: diff = step; clearOnKeyDownTimer(); setOnKeyDownTimer(event); break; case utilities_1.KeyCodes.home: newCurrentValue = min; clearOnKeyDownTimer(); setOnKeyDownTimer(event); break; case utilities_1.KeyCodes.end: newCurrentValue = max; clearOnKeyDownTimer(); setOnKeyDownTimer(event); break; default: return; } updateValue(event, newCurrentValue + diff); event.preventDefault(); event.stopPropagation(); }; var getPosition = function (event, verticalProp) { var currentPosition = 0; switch (event.type) { case 'mousedown': case 'mousemove': currentPosition = !verticalProp ? event.clientX : event.clientY; break; case 'touchstart': case 'touchmove': currentPosition = !verticalProp ? event.touches[0].clientX : event.touches[0].clientY; break; } return currentPosition; }; var calculateCurrentSteps = function (event) { // eslint-disable-next-line @typescript-eslint/no-deprecated var sliderPositionRect = sliderLine.current.getBoundingClientRect(); var sliderLength = !props.vertical ? sliderPositionRect.width : sliderPositionRect.height; var stepLength = sliderLength / steps; var currentSteps; var distance; if (!props.vertical) { var left = getPosition(event, props.vertical); distance = (0, utilities_1.getRTL)(props.theme) ? sliderPositionRect.right - left : left - sliderPositionRect.left; currentSteps = distance / stepLength; } else { var bottom = getPosition(event, props.vertical); distance = sliderPositionRect.bottom - bottom; currentSteps = distance / stepLength; } return currentSteps; }; var onMouseMoveOrTouchMove = function (event, suppressEventCancelation) { var currentSteps = calculateCurrentSteps(event); var newUnroundedValue = min + step * currentSteps; var newCurrentValue = min + step * Math.round(currentSteps); updateValue(event, newCurrentValue, newUnroundedValue); if (!suppressEventCancelation) { event.preventDefault(); event.stopPropagation(); } }; var onMouseDownOrTouchStart = function (event) { if (ranged) { var currentSteps = calculateCurrentSteps(event); var newValue = min + step * currentSteps; internalState.isAdjustingLowerValue = newValue <= internalState.latestLowerValue || newValue - internalState.latestLowerValue <= internalState.latestValue - newValue; } // safe to use `win!` since it can only be called on the client if (event.type === 'mousedown') { disposables.current.push((0, utilities_1.on)(win, 'mousemove', onMouseMoveOrTouchMove, true), (0, utilities_1.on)(win, 'mouseup', onMouseUpOrTouchEnd, true)); } else if (event.type === 'touchstart') { disposables.current.push((0, utilities_1.on)(win, 'touchmove', onMouseMoveOrTouchMove, true), (0, utilities_1.on)(win, 'touchend', onMouseUpOrTouchEnd, true)); } onMouseMoveOrTouchMove(event, true); }; var onMouseUpOrTouchEnd = function (event) { // Done adjusting, so clear this value internalState.isBetweenSteps = undefined; onChanged === null || onChanged === void 0 ? void 0 : onChanged(event, internalState.latestValue, ranged ? [internalState.latestLowerValue, internalState.latestValue] : undefined); disposeListeners(); }; var onThumbFocus = function (event) { internalState.isAdjustingLowerValue = event.target === lowerValueThumbRef.current; }; var disposeListeners = React.useCallback(function () { disposables.current.forEach(function (dispose) { return dispose(); }); disposables.current = []; }, []); React.useEffect(function () { return disposeListeners; }, [disposeListeners]); var lowerValueThumbRef = React.useRef(null); var thumbRef = React.useRef(null); var sliderBoxRef = React.useRef(null); useComponentRef(props, sliderBoxRef, value, ranged ? [lowerValue, value] : undefined); var getPositionStyles = getSlotStyleFn(vertical ? 'bottom' : (0, utilities_1.getRTL)(props.theme) ? 'right' : 'left'); var getTrackStyles = getSlotStyleFn(vertical ? 'height' : 'width'); var originValue = originFromZero ? 0 : min; var valuePercent = getPercent(value, min, max); var lowerValuePercent = getPercent(lowerValue, min, max); var originPercentOfLine = getPercent(originValue, min, max); var activeSectionWidth = ranged ? valuePercent - lowerValuePercent : Math.abs(originPercentOfLine - valuePercent); var topSectionWidth = Math.min(100 - valuePercent, 100 - originPercentOfLine); var bottomSectionWidth = ranged ? lowerValuePercent : Math.min(valuePercent, originPercentOfLine); var rootProps = { className: classNames.root, ref: ref, }; var labelProps = { className: classNames.titleLabel, children: label, disabled: disabled, htmlFor: ariaLabel ? undefined : id, }; var valueLabelProps = showValue ? { className: classNames.valueLabel, children: valueFormat ? valueFormat(value) : value, disabled: disabled, htmlFor: disabled ? id : undefined, } : undefined; var lowerValueLabelProps = ranged && showValue ? { className: classNames.valueLabel, children: valueFormat ? valueFormat(lowerValue) : lowerValue, disabled: disabled, } : undefined; var zeroTickProps = originFromZero ? { className: classNames.zeroTick, style: getPositionStyles(originPercentOfLine), } : undefined; var trackActiveProps = { className: (0, utilities_1.css)(classNames.lineContainer, classNames.activeSection), style: getTrackStyles(activeSectionWidth), }; var trackTopInactiveProps = { className: (0, utilities_1.css)(classNames.lineContainer, classNames.inactiveSection), style: getTrackStyles(topSectionWidth), }; var trackBottomInactiveProps = { className: (0, utilities_1.css)(classNames.lineContainer, classNames.inactiveSection), style: getTrackStyles(bottomSectionWidth), }; var sliderProps = tslib_1.__assign({ 'aria-disabled': disabled, role: 'slider', tabIndex: disabled ? undefined : 0 }, { 'data-is-focusable': !disabled }); var sliderBoxProps = tslib_1.__assign(tslib_1.__assign(tslib_1.__assign({ id: id, className: (0, utilities_1.css)(classNames.slideBox, buttonProps.className), ref: sliderBoxRef }, (!disabled && { onMouseDown: onMouseDownOrTouchStart, onTouchStart: onMouseDownOrTouchStart, onKeyDown: onKeyDown, })), (buttonProps && (0, utilities_1.getNativeProps)(buttonProps, utilities_1.divProperties, ['id', 'className']))), (!ranged && tslib_1.__assign(tslib_1.__assign({}, sliderProps), { 'aria-valuemin': min, 'aria-valuemax': max, 'aria-valuenow': value, 'aria-valuetext': getAriaValueText(value), 'aria-label': ariaLabel || label, 'aria-labelledby': ariaLabelledBy }))); var onFocusProp = disabled ? {} : { onFocus: onThumbFocus }; var thumbProps = tslib_1.__assign({ ref: thumbRef, className: classNames.thumb, style: getPositionStyles(valuePercent) }, (ranged && tslib_1.__assign(tslib_1.__assign(tslib_1.__assign({}, sliderProps), onFocusProp), { id: "max-".concat(id), 'aria-valuemin': lowerValue, 'aria-valuemax': max, 'aria-valuenow': value, 'aria-valuetext': getAriaValueText(value), 'aria-label': "max ".concat(ariaLabel || label) }))); var lowerValueThumbProps = ranged ? tslib_1.__assign(tslib_1.__assign(tslib_1.__assign({ ref: lowerValueThumbRef, className: classNames.thumb, style: getPositionStyles(lowerValuePercent) }, sliderProps), onFocusProp), { id: "min-".concat(id), 'aria-valuemin': min, 'aria-valuemax': value, 'aria-valuenow': lowerValue, 'aria-valuetext': getAriaValueText(lowerValue), 'aria-label': "min ".concat(ariaLabel || label) }) : undefined; var containerProps = { className: classNames.container, }; var sliderLineProps = { ref: sliderLine, className: classNames.line, }; return { root: rootProps, label: labelProps, sliderBox: sliderBoxProps, container: containerProps, valueLabel: valueLabelProps, lowerValueLabel: lowerValueLabelProps, thumb: thumbProps, lowerValueThumb: lowerValueThumbProps, zeroTick: zeroTickProps, activeTrack: trackActiveProps, topInactiveTrack: trackTopInactiveProps, bottomInactiveTrack: trackBottomInactiveProps, sliderLine: sliderLineProps, }; }; exports.useSlider = useSlider; }); //# sourceMappingURL=useSlider.js.map