UNPKG

@yamada-ui/slider

Version:

Yamada UI slider components

623 lines (621 loc) • 18.4 kB
"use client" // src/slider.tsx import { forwardRef, mergeVars, omitThemeProps, ui, useComponentMultiStyle } from "@yamada-ui/core"; import { formControlProperties, useFormControlProps } from "@yamada-ui/form-control"; import { useControllableState } from "@yamada-ui/use-controllable-state"; import { useLatestRef } from "@yamada-ui/use-latest-ref"; import { usePanEvent } from "@yamada-ui/use-pan-event"; import { useSize } from "@yamada-ui/use-size"; import { clampNumber, createContext, cx, dataAttr, findChild, getValidChildren, handlerAll, includesChildren, isEmpty, isNumber, mergeRefs, omitChildren, percentToValue, pickObject, roundNumberToStep, useCallbackRef, useUpdateEffect, valueToPercent } from "@yamada-ui/utils"; import { useCallback, useRef, useState } from "react"; import { jsx, jsxs } from "react/jsx-runtime"; var getThumbSize = (thumbSize, styles) => { var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t; return (_t = (_s = (_q = (_o = (_m = (_k = (_i = (_g = (_e = (_c = thumbSize != null ? thumbSize : (_a = styles.thumb) == null ? void 0 : _a.boxSize) != null ? _c : (_b = styles.thumb) == null ? void 0 : _b.minBoxSize) != null ? _e : (_d = styles.thumb) == null ? void 0 : _d.width) != null ? _g : (_f = styles.thumb) == null ? void 0 : _f.w) != null ? _i : (_h = styles.thumb) == null ? void 0 : _h.minWidth) != null ? _k : (_j = styles.thumb) == null ? void 0 : _j.minW) != null ? _m : (_l = styles.thumb) == null ? void 0 : _l.height) != null ? _o : (_n = styles.thumb) == null ? void 0 : _n.h) != null ? _q : (_p = styles.thumb) == null ? void 0 : _p.minHeight) != null ? _s : (_r = styles.thumb) == null ? void 0 : _r.minH) != null ? _t : "3.5"; }; var useSlider = ({ focusThumbOnChange = true, ...props }) => { if (!focusThumbOnChange) props.isReadOnly = true; const { id, name, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-valuetext": ariaValueText, defaultValue, getAriaValueText: getAriaValueTextProp, isReversed, max = 100, min = 0, orientation = "horizontal", reversed = isReversed, step = 1, thumbSize: thumbSizeProp, value: valueProp, onChange, onChangeEnd: onChangeEndProp, onChangeStart: onChangeStartProp, ...rest } = useFormControlProps(props); if (max < min) throw new Error("Do not assign a number less than 'min' to 'max'"); const { "aria-readonly": ariaReadonly, disabled, readOnly, required, onBlur, onFocus, ...formControlProps } = pickObject(rest, formControlProperties); const [computedValue, setValue] = useControllableState({ defaultValue: defaultValue != null ? defaultValue : min + (max - min) / 2, value: valueProp, onChange }); const [dragging, setDragging] = useState(false); const [focused, setFocused] = useState(false); const interactive = !(disabled || readOnly); const tenStep = (max - min) / 10; const oneStep = step || (max - min) / 100; const value = clampNumber(computedValue, min, max); const reversedValue = max - value + min; const thumbValue = reversed ? reversedValue : value; const thumbPercent = valueToPercent(thumbValue, min, max); const vertical = orientation === "vertical"; const latestRef = useLatestRef({ focusThumbOnChange, interactive, max, min, step, value }); const eventSourceRef = useRef(null); const containerRef = useRef(null); const trackRef = useRef(null); const thumbRef = useRef(null); const thumbSize = useSize(thumbRef); const onChangeStart = useCallbackRef(onChangeStartProp); const onChangeEnd = useCallbackRef(onChangeEndProp); const getAriaValueText = useCallbackRef(getAriaValueTextProp); usePanEvent(containerRef, { onMove: (ev) => { const { interactive: interactive2 } = latestRef.current; if (!interactive2) return; setValueFromPointer(ev); }, onSessionEnd: () => { const { interactive: interactive2, value: value2 } = latestRef.current; if (!interactive2) return; setDragging(false); onChangeEnd(value2); }, onSessionStart: (ev) => { const { interactive: interactive2, value: value2 } = latestRef.current; if (!interactive2) return; setDragging(true); focusThumb(); setValueFromPointer(ev); onChangeStart(value2); } }); const getValueFromPointer = useCallback( (ev) => { var _a, _b; if (!trackRef.current) return; const { max: max2, min: min2, step: step2 } = latestRef.current; eventSourceRef.current = "pointer"; const { bottom, height, left, width } = trackRef.current.getBoundingClientRect(); const { clientX, clientY } = (_b = (_a = ev.touches) == null ? void 0 : _a[0]) != null ? _b : ev; const diff = vertical ? bottom - clientY : clientX - left; const length = vertical ? height : width; let percent = diff / length; if (reversed) percent = 1 - percent; let nextValue = percentToValue(percent, min2, max2); if (step2) nextValue = parseFloat(roundNumberToStep(nextValue, min2, step2)); nextValue = clampNumber(nextValue, min2, max2); return nextValue; }, [vertical, reversed, latestRef] ); const setValueFromPointer = (ev) => { const { value: value2 } = latestRef.current; const nextValue = getValueFromPointer(ev); if (nextValue != null && nextValue !== value2) setValue(nextValue); }; const focusThumb = useCallback(() => { const { focusThumbOnChange: focusThumbOnChange2 } = latestRef.current; if (focusThumbOnChange2) setTimeout(() => { var _a; return (_a = thumbRef.current) == null ? void 0 : _a.focus(); }); }, [latestRef]); const constrain = useCallback( (value2) => { const { interactive: interactive2, max: max2, min: min2 } = latestRef.current; if (!interactive2) return; value2 = parseFloat(roundNumberToStep(value2, min2, oneStep)); value2 = clampNumber(value2, min2, max2); setValue(value2); }, [oneStep, setValue, latestRef] ); const stepUp = useCallback( (step2 = oneStep) => constrain(reversed ? value - step2 : value + step2), [constrain, reversed, oneStep, value] ); const stepDown = useCallback( (step2 = oneStep) => constrain(reversed ? value + step2 : value - step2), [constrain, reversed, oneStep, value] ); const reset = useCallback( () => constrain(defaultValue || 0), [constrain, defaultValue] ); const stepTo = useCallback((value2) => constrain(value2), [constrain]); const onKeyDown = useCallback( (ev) => { const { max: max2, min: min2 } = latestRef.current; const actions = { ArrowDown: () => stepDown(), ArrowLeft: () => stepDown(), ArrowRight: () => stepUp(), ArrowUp: () => stepUp(), End: () => constrain(max2), Home: () => constrain(min2), PageDown: () => stepDown(tenStep), PageUp: () => stepUp(tenStep) }; const action = actions[ev.key]; if (!action) return; ev.preventDefault(); ev.stopPropagation(); action(ev); eventSourceRef.current = "keyboard"; }, [constrain, latestRef, stepDown, stepUp, tenStep] ); useUpdateEffect(() => { const { value: value2 } = latestRef.current; focusThumb(); if (eventSourceRef.current === "keyboard") onChangeEnd(value2); }, [value, onChangeEnd]); const getContainerProps = useCallback( (props2 = {}, ref = null) => { let { height: h, width: w } = thumbSize != null ? thumbSize : { height: "var(--ui-thumb-size)", width: "var(--ui-thumb-size)" }; if (isNumber(w)) w = `${w}px`; if (isNumber(h)) h = `${h}px`; const paddingStyle = vertical ? { paddingLeft: `calc(${w} / 2)`, paddingRight: `calc(${w} / 2)` } : { paddingBottom: `calc(${h} / 2)`, paddingTop: `calc(${h} / 2)` }; const style = { ...props2.style, outline: 0, position: "relative", touchAction: "none", userSelect: "none", WebkitTapHighlightColor: "rgba(0, 0, 0, 0)", ...paddingStyle }; return { ...rest, ...props2, ref: mergeRefs(ref, containerRef), style, tabIndex: -1, vars: mergeVars(rest.vars, [ { name: "thumb-size", token: "sizes", value: thumbSizeProp, __prefix: "ui" } ]) }; }, [vertical, rest, thumbSize, thumbSizeProp] ); const getInputProps = useCallback( (props2 = {}, ref = null) => ({ "aria-readonly": ariaReadonly, ...formControlProps, ...props2, id, ref, type: "hidden", name, disabled, readOnly, required, value }), [ ariaReadonly, disabled, formControlProps, id, name, readOnly, required, value ] ); const getTrackProps = useCallback( (props2 = {}, ref = null) => { const style = { ...props2.style, position: "absolute", ...vertical ? { height: "100%", left: "50%", transform: "translateX(-50%)" } : { top: "50%", transform: "translateY(-50%)", width: "100%" } }; return { ...formControlProps, ...props2, ref: mergeRefs(ref, trackRef), style }; }, [vertical, formControlProps] ); const getFilledTrackProps = useCallback( (props2 = {}, ref = null) => { const n = Math.abs(reversed ? 100 - thumbPercent : thumbPercent); const style = { ...props2.style, position: "absolute", ...vertical ? { height: `${n}%`, left: "50%", transform: "translateX(-50%)", ...reversed ? { top: "0%" } : { bottom: "0%" } } : { top: "50%", transform: "translateY(-50%)", width: `${n}%`, ...reversed ? { right: "0%" } : { left: "0%" } } }; return { ...formControlProps, ...props2, ref, style }; }, [reversed, vertical, formControlProps, thumbPercent] ); const getMarkProps = useCallback( (props2, ref = null) => { let n = valueToPercent(props2.value, min, max); n = reversed ? 100 - n : n; const style = { ...props2.style, pointerEvents: "none", position: "absolute", ...vertical ? { bottom: `${n}%` } : { left: `${n}%` } }; return { ...formControlProps, ...props2, ref, style, "aria-hidden": true, "data-highlighted": dataAttr(props2.value <= value), "data-invalid": dataAttr(props2.value < min || max < props2.value) }; }, [reversed, vertical, max, min, formControlProps, value] ); const getThumbProps = useCallback( (props2 = {}, ref = null) => { var _a; const n = thumbPercent; let w = "var(--ui-thumb-size)"; let h = "var(--ui-thumb-size)"; if (thumbSize) { w = `${thumbSize.width}px`; h = `${thumbSize.height}px`; } const bottom = `calc(${n}% - (${h} / 2))`; const left = `calc(${n}% - (${w} / 2))`; const style = { ...props2.style, position: "absolute", touchAction: "none", userSelect: "none", ...vertical ? { bottom } : { left } }; return { "aria-label": ariaLabel != null ? ariaLabel : "Slider thumb", "aria-labelledby": ariaLabelledBy, "aria-readonly": ariaReadonly, ...formControlProps, ...props2, ref: mergeRefs(ref, thumbRef), style, "aria-orientation": orientation, "aria-valuemax": max, "aria-valuemin": min, "aria-valuenow": value, "aria-valuetext": (_a = ariaValueText != null ? ariaValueText : getAriaValueText(value)) != null ? _a : value.toString(), "data-active": dataAttr(dragging && focusThumbOnChange), role: "slider", tabIndex: interactive && focusThumbOnChange ? 0 : void 0, onBlur: handlerAll(props2.onBlur, onBlur, () => setFocused(false)), onFocus: handlerAll(props2.onFocus, onFocus, () => setFocused(true)), onKeyDown: handlerAll(props2.onKeyDown, onKeyDown) }; }, [ thumbPercent, thumbSize, vertical, ariaLabel, ariaLabelledBy, ariaReadonly, formControlProps, orientation, max, min, value, ariaValueText, getAriaValueText, dragging, focusThumbOnChange, interactive, onBlur, onFocus, onKeyDown ] ); return { dragging, focused, /** * @deprecated Use `dragging` instead. */ isDragging: dragging, /** * @deprecated Use `focused` instead. */ isFocused: focused, /** * @deprecated Use `vertical` instead. */ isVertical: vertical, reset, stepDown, stepTo, stepUp, value, vertical, getContainerProps, getFilledTrackProps, getInputProps, getMarkProps, getThumbProps, getTrackProps }; }; var [SliderProvider, useSliderContext] = createContext({ name: "SliderContext", errorMessage: `useSliderContext returned is 'undefined'. Seems you forgot to wrap the components in "<Slider />" ` }); var Slider = forwardRef((props, ref) => { const [styles, mergedProps] = useComponentMultiStyle("Slider", props); const { className, children, filledTrackColor, thumbColor, thumbSize, trackColor, trackSize, filledTrackProps, inputProps, thumbProps, trackProps, ...rest } = omitThemeProps(mergedProps); const { isVertical, getContainerProps, getFilledTrackProps, getInputProps, getMarkProps, getThumbProps, getTrackProps } = useSlider({ ...rest, thumbSize: getThumbSize(thumbSize, styles) }); const css = { ...styles.container }; const validChildren = getValidChildren(children); const customSliderTrack = findChild(validChildren, SliderTrack); const customSliderThumb = findChild(validChildren, SliderThumb); const hasSliderThumb = includesChildren(validChildren, SliderThumb); const cloneChildren = !isEmpty(validChildren) ? omitChildren(validChildren, SliderTrack, SliderThumb) : children; return /* @__PURE__ */ jsx( SliderProvider, { value: { filledTrackColor, isVertical, styles, thumbColor, thumbSize, trackColor, trackSize, filledTrackProps, getFilledTrackProps, getMarkProps, getThumbProps, getTrackProps, thumbProps, trackProps }, children: /* @__PURE__ */ jsxs( ui.div, { className: cx("ui-slider", className), __css: css, ...getContainerProps(), children: [ /* @__PURE__ */ jsx(ui.input, { ...getInputProps(inputProps, ref) }), customSliderTrack != null ? customSliderTrack : /* @__PURE__ */ jsx(SliderTrack, {}), cloneChildren, customSliderThumb != null ? customSliderThumb : !hasSliderThumb ? /* @__PURE__ */ jsx(SliderThumb, {}) : null ] } ) } ); }); Slider.displayName = "Slider"; Slider.__ui__ = "Slider"; var SliderTrack = forwardRef( ({ className, children, filledTrackProps, ...rest }, ref) => { const { isVertical, styles, trackColor, trackSize, getTrackProps, trackProps } = useSliderContext(); const css = { ...styles.track }; return /* @__PURE__ */ jsx( ui.div, { className: cx("ui-slider__track", className), __css: css, ...getTrackProps( { ...trackColor ? { bg: trackColor } : {}, ...trackSize ? isVertical ? { w: trackSize } : { h: trackSize } : {}, ...trackProps, ...rest }, ref ), children: children != null ? children : /* @__PURE__ */ jsx(SliderFilledTrack, { ...filledTrackProps }) } ); } ); SliderTrack.displayName = "SliderTrack"; SliderTrack.__ui__ = "SliderTrack"; var SliderFilledTrack = forwardRef( ({ className, ...rest }, ref) => { const { filledTrackColor, styles, filledTrackProps, getFilledTrackProps } = useSliderContext(); const css = { ...styles.filledTrack }; return /* @__PURE__ */ jsx( ui.div, { className: cx("ui-slider__track-filled", className), __css: css, ...getFilledTrackProps( { ...filledTrackColor ? { bg: filledTrackColor } : {}, ...filledTrackProps, ...rest }, ref ) } ); } ); SliderFilledTrack.displayName = "SliderFilledTrack"; SliderFilledTrack.__ui__ = "SliderFilledTrack"; var SliderMark = forwardRef( ({ className, ...rest }, ref) => { const { styles, getMarkProps } = useSliderContext(); const css = { alignItems: "center", display: "inline-flex", justifyContent: "center", ...styles.mark }; return /* @__PURE__ */ jsx( ui.div, { className: cx("ui-slider__mark", className), __css: css, ...getMarkProps(rest, ref) } ); } ); SliderMark.displayName = "SliderMark"; SliderMark.__ui__ = "SliderMark"; var SliderThumb = forwardRef( ({ className, ...rest }, ref) => { const { styles, thumbColor, thumbSize, getThumbProps, thumbProps } = useSliderContext(); const css = { ...styles.thumb }; return /* @__PURE__ */ jsx( ui.div, { className: cx("ui-slider__thumb", className), __css: css, ...getThumbProps( { ...thumbColor ? { bg: thumbColor } : {}, ...thumbSize ? { boxSize: thumbSize } : {}, ...thumbProps, ...rest }, ref ) } ); } ); SliderThumb.displayName = "SliderThumb"; SliderThumb.__ui__ = "SliderThumb"; export { getThumbSize, useSlider, Slider, SliderTrack, SliderFilledTrack, SliderMark, SliderThumb }; //# sourceMappingURL=chunk-FXWAONIG.mjs.map