UNPKG

@julo-ui/sliders

Version:

A React Slider component that implements input[type='range']

350 lines (347 loc) 9.51 kB
import { use_handle_focus_thumb_default } from "./chunk-6GIWIDFH.mjs"; import { use_handle_pan_event_default } from "./chunk-GE43HSZC.mjs"; import { getDefaultValue } from "./chunk-SOCY7XOC.mjs"; import { use_handle_dragging_default } from "./chunk-ZAZRIR7U.mjs"; import { use_handle_focus_default } from "./chunk-CRNUCIZN.mjs"; import { use_handle_reversed_default } from "./chunk-J3CHJ35F.mjs"; import { use_handle_style_default } from "./chunk-PHTCSOKW.mjs"; import { roundValueToStep, valueToPercent } from "./chunk-Y2VS5NAR.mjs"; // src/slider/use-slider.ts import { useCallback, useId, useMemo, useRef } from "react"; import { callAllFn } from "@julo-ui/function-utils"; import { clampValue } from "@julo-ui/number-utils"; import { ariaAttr, dataAttr, mergeRefs } from "@julo-ui/dom-utils"; import { useCallbackRef } from "@julo-ui/use-callback-ref"; import { useControllableState } from "@julo-ui/use-controllable-state"; import { useWatchElementSize } from "@julo-ui/use-watch-element-size"; function useSlider(props) { var _a; const { min = 0, max = 100, onChange, value: valueProp, defaultValue, isReversed: isReversedProp, direction = "ltr", orientation = "horizontal", id: idProp, isDisabled = false, isReadOnly, onChangeStart: onChangeStartProp, onChangeEnd: onChangeEndProp, step = 1, getAriaValueText: getAriaValueTextProp, "aria-valuetext": ariaValueText, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, name, focusThumbOnChange = true, ...resRootProps } = props; const onChangeStart = useCallbackRef(onChangeStartProp); const onChangeEnd = useCallbackRef(onChangeEndProp); const getAriaValueText = useCallbackRef(getAriaValueTextProp); const isReversed = use_handle_reversed_default({ isReversed: isReversedProp, direction, orientation }); const [computedValue, setComputedValue] = useControllableState({ value: valueProp, defaultValue: defaultValue != null ? defaultValue : getDefaultValue(min, max), onChange }); const isInteractive = !(isDisabled || isReadOnly); const tenSteps = (max - min) / 10; const oneStep = step || (max - min) / 100; const value = clampValue(computedValue, min, max); const reversedValue = max - value + min; const trackValue = isReversed ? reversedValue : value; const thumbPercent = valueToPercent(trackValue, min, max); const isVertical = orientation === "vertical"; const trackRef = useRef(null); const thumbRef = useRef(null); const rootRef = useRef(null); const reactId = useId(); const uuid = idProp != null ? idProp : reactId; const thumbId = `slider-thumb-${uuid}`; const trackId = `slider-track-${uuid}`; const valueText = (_a = getAriaValueText == null ? void 0 : getAriaValueText(value)) != null ? _a : ariaValueText; const thumbSize = useWatchElementSize(thumbRef); const { isDragging, onDraggingStart, onDraggingEnd } = use_handle_dragging_default(); const { isFocused, onBlur: onInputBlur, onFocus: onInputFocus } = use_handle_focus_default(); const sliderStates = useMemo( () => ({ tenSteps, min, max, step: oneStep, isDisabled, value, isInteractive, isReversed, isVertical, eventSource: null, focusThumbOnChange, orientation, isDragging, isFocused }), [ focusThumbOnChange, isDisabled, isDragging, isFocused, isInteractive, isReversed, isVertical, max, min, oneStep, orientation, tenSteps, value ] ); const constrain = useCallback( (value2) => { if (!sliderStates.isInteractive) return; value2 = parseFloat(roundValueToStep(value2, sliderStates.min, oneStep)); value2 = clampValue(value2, sliderStates.min, sliderStates.max); setComputedValue(value2); }, [ oneStep, setComputedValue, sliderStates.isInteractive, sliderStates.max, sliderStates.min ] ); const actions = useMemo( () => ({ stepUp(step2 = oneStep) { const next = isReversed ? value - step2 : value + step2; constrain(next); }, stepDown(step2 = oneStep) { const next = isReversed ? value + step2 : value - step2; constrain(next); }, reset() { constrain(defaultValue || 0); }, stepTo(value2) { constrain(value2); } }), [constrain, isReversed, value, oneStep, defaultValue] ); const { getThumbStyle, getMarkerStyle, innerTrackStyle, rootStyle, trackStyle } = use_handle_style_default({ isReversed: sliderStates.isReversed, orientation: sliderStates.orientation, thumbPercents: [thumbPercent], thumbSizes: [thumbSize] }); const { onFocusThumb } = use_handle_focus_thumb_default({ eventSource: sliderStates.eventSource, focusThumbOnChange: sliderStates.focusThumbOnChange, onChangeEnd, thumbRef, value: sliderStates.value }); use_handle_pan_event_default({ onChangeEnd, onChangeStart, onDraggingEnd, onDraggingStart, onFocusThumb, rootRef, setComputedValue, sliderStates, trackRef }); const onThumbKeyDown = useCallback( (event) => { const keyMap = { ArrowRight: () => actions.stepUp(), ArrowUp: () => actions.stepUp(), ArrowLeft: () => actions.stepDown(), ArrowDown: () => actions.stepDown(), PageUp: () => actions.stepUp(tenSteps), PageDown: () => actions.stepDown(tenSteps), Home: () => constrain(sliderStates.min), End: () => constrain(sliderStates.max) }; const action = keyMap[event.key]; if (action) { event.preventDefault(); event.stopPropagation(); action(event); sliderStates.eventSource = "keyboard"; } }, [actions, constrain, tenSteps, sliderStates] ); const getRootProps = useCallback( (props2 = {}, forwardedRef = null) => ({ ...props2, ...resRootProps, ref: mergeRefs(forwardedRef, rootRef), tabIndex: -1, "aria-disabled": ariaAttr(isDisabled), "data-focused": dataAttr(isFocused), style: { ...props2.style, ...rootStyle } }), [resRootProps, isDisabled, isFocused, rootStyle] ); const getTrackProps = useCallback( (props2 = {}, forwardedRef = null) => ({ ...props2, ref: mergeRefs(forwardedRef, trackRef), id: trackId, "data-disabled": dataAttr(isDisabled), style: { ...props2.style, ...trackStyle } }), [isDisabled, trackId, trackStyle] ); const getInnerTrackProps = useCallback( (props2 = {}, forwardedRef = null) => ({ ...props2, ref: forwardedRef, style: { ...props2.style, ...innerTrackStyle } }), [innerTrackStyle] ); const getThumbProps = useCallback( (props2 = {}, forwardedRef = null) => { const { onKeyDown, onFocus, onBlur, style, ...resProps } = props2; return { ...resProps, ref: mergeRefs(forwardedRef, thumbRef), role: "slider", ...isInteractive && { tabIndex: 0 }, id: thumbId, "data-active": dataAttr(isDragging), "aria-valuetext": valueText, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": value, "aria-orientation": orientation, "aria-disabled": ariaAttr(isDisabled), "aria-readonly": ariaAttr(isReadOnly), "aria-label": ariaLabel, ...!ariaLabel && { "aria-labelledby": ariaLabelledBy }, style: { ...style, ...getThumbStyle(0) }, onKeyDown: callAllFn(onThumbKeyDown, onKeyDown), onFocus: callAllFn(onInputFocus, onFocus), onblur: callAllFn(onInputBlur, onBlur) }; }, [ ariaLabel, ariaLabelledBy, getThumbStyle, isDisabled, isDragging, isInteractive, isReadOnly, max, min, onInputBlur, onInputFocus, onThumbKeyDown, orientation, thumbId, value, valueText ] ); const getMarkerProps = useCallback( (props2, forwardedRef = null) => { const { value: markerValue, style, ...resProps } = props2; const isInRange = !(markerValue < min || markerValue > max); const isHighlighted = value >= markerValue; const markerPercent = valueToPercent(markerValue, min, max); const percent = isReversed ? 100 - markerPercent : markerPercent; return { ...resProps, ref: forwardedRef, role: "presentation", "aria-hidden": true, "data-disabled": dataAttr(isDisabled), "data-invalid": dataAttr(!isInRange), "data-highlighted": dataAttr(isHighlighted), style: { ...style, ...getMarkerStyle(percent) } }; }, [getMarkerStyle, isDisabled, isReversed, max, min, value] ); const getInputProps = useCallback( (props2 = {}, forwardedRef = null) => ({ ...props2, ref: forwardedRef, type: "hidden", value, name }), [name, value] ); return { state: sliderStates, actions, getRootProps, getTrackProps, getInnerTrackProps, getThumbProps, getMarkerProps, getInputProps }; } export { useSlider };