@julo-ui/sliders
Version:
A React Slider component that implements input[type='range']
350 lines (347 loc) • 9.51 kB
JavaScript
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
};