UNPKG

@zag-js/slider

Version:

Core logic for the slider widget implemented as a state machine

921 lines (917 loc) • 31.4 kB
import { createAnatomy } from '@zag-js/anatomy'; import { raf, setElementValue, queryAll, resizeObserverBorderBox, trackPointerMove, trackFormControl, getRelativePoint, dispatchInputValueEvent, dataAttr, isLeftClick, isModifierKey, getEventPoint, ariaAttr, getEventStep, getEventKey } from '@zag-js/dom-query'; import { setValueAtIndex, callAll, getValuePercent, isEqual, createSplitProps, snapValueToStep, clampValue, getValueRanges, getNextStepValue, getPreviousStepValue, getPercentValue, pick, isValueWithinRange, first, last, toPx, getValueTransformer } from '@zag-js/utils'; import { createMachine, memo } from '@zag-js/core'; import { createProps } from '@zag-js/types'; // src/slider.anatomy.ts var anatomy = createAnatomy("slider").parts( "root", "label", "thumb", "valueText", "track", "range", "control", "markerGroup", "marker", "draggingIndicator" ); var parts = anatomy.build(); var getRootId = (ctx) => ctx.ids?.root ?? `slider:${ctx.id}`; var getThumbId = (ctx, index) => ctx.ids?.thumb?.(index) ?? `slider:${ctx.id}:thumb:${index}`; var getHiddenInputId = (ctx, index) => ctx.ids?.hiddenInput?.(index) ?? `slider:${ctx.id}:input:${index}`; var getControlId = (ctx) => ctx.ids?.control ?? `slider:${ctx.id}:control`; var getTrackId = (ctx) => ctx.ids?.track ?? `slider:${ctx.id}:track`; var getRangeId = (ctx) => ctx.ids?.range ?? `slider:${ctx.id}:range`; var getLabelId = (ctx) => ctx.ids?.label ?? `slider:${ctx.id}:label`; var getValueTextId = (ctx) => ctx.ids?.valueText ?? `slider:${ctx.id}:value-text`; var getMarkerId = (ctx, value) => ctx.ids?.marker?.(value) ?? `slider:${ctx.id}:marker:${value}`; var getRootEl = (ctx) => ctx.getById(getRootId(ctx)); var getThumbEl = (ctx, index) => ctx.getById(getThumbId(ctx, index)); var getThumbEls = (ctx) => queryAll(getControlEl(ctx), "[role=slider]"); var getFirstThumbEl = (ctx) => getThumbEls(ctx)[0]; var getHiddenInputEl = (ctx, index) => ctx.getById(getHiddenInputId(ctx, index)); var getControlEl = (ctx) => ctx.getById(getControlId(ctx)); var getPointValue = (params, point) => { const { prop, scope, refs } = params; const controlEl = getControlEl(scope); if (!controlEl) return; const offset = refs.get("thumbDragOffset"); const adjustedPoint = { x: point.x - (offset?.x ?? 0), y: point.y - (offset?.y ?? 0) }; const relativePoint = getRelativePoint(adjustedPoint, controlEl); const percent = relativePoint.getPercentValue({ orientation: prop("orientation"), dir: prop("dir"), inverted: { y: true } }); return getPercentValue(percent, prop("min"), prop("max"), prop("step")); }; var dispatchChangeEvent = (ctx, value) => { value.forEach((value2, index) => { const inputEl = getHiddenInputEl(ctx, index); if (!inputEl) return; dispatchInputValueEvent(inputEl, { value: value2 }); }); }; var getOffsetRect = (el) => ({ left: el?.offsetLeft ?? 0, top: el?.offsetTop ?? 0, width: el?.offsetWidth ?? 0, height: el?.offsetHeight ?? 0 }); function getBounds(value) { const firstValue = value[0]; const lastThumb = value[value.length - 1]; return [firstValue, lastThumb]; } function getRangeOffsets(params) { const { prop, computed } = params; const valuePercent = computed("valuePercent"); const [firstPercent, lastPercent] = getBounds(valuePercent); if (valuePercent.length === 1) { if (prop("origin") === "center") { const isNegative = valuePercent[0] < 50; const start = isNegative ? `${valuePercent[0]}%` : "50%"; const end = isNegative ? "50%" : `${100 - valuePercent[0]}%`; return { start, end }; } if (prop("origin") === "end") { return { start: `${lastPercent}%`, end: "0%" }; } return { start: "0%", end: `${100 - lastPercent}%` }; } return { start: `${firstPercent}%`, end: `${100 - lastPercent}%` }; } function getRangeStyle(params) { const { computed } = params; const isVertical = computed("isVertical"); const isRtl = computed("isRtl"); if (isVertical) { return { position: "absolute", bottom: "var(--slider-range-start)", top: "var(--slider-range-end)" }; } return { position: "absolute", [isRtl ? "right" : "left"]: "var(--slider-range-start)", [isRtl ? "left" : "right"]: "var(--slider-range-end)" }; } function getVerticalThumbOffset(params, value) { const { context, prop } = params; const { height = 0 } = context.get("thumbSize") ?? {}; const getValue = getValueTransformer([prop("min"), prop("max")], [-height / 2, height / 2]); return parseFloat(getValue(value).toFixed(2)); } function getHorizontalThumbOffset(params, value) { const { computed, context, prop } = params; const { width = 0 } = context.get("thumbSize") ?? {}; const isRtl = computed("isRtl"); if (isRtl) { const getValue2 = getValueTransformer([prop("max"), prop("min")], [-width / 2, width / 2]); return -1 * parseFloat(getValue2(value).toFixed(2)); } const getValue = getValueTransformer([prop("min"), prop("max")], [-width / 2, width / 2]); return parseFloat(getValue(value).toFixed(2)); } function getOffset(params, percent, value) { const { computed, prop } = params; if (prop("thumbAlignment") === "center") return `${percent}%`; const offset = computed("isVertical") ? getVerticalThumbOffset(params, value) : getHorizontalThumbOffset(params, value); return `calc(${percent}% - ${offset}px)`; } function getThumbOffset(params, value) { const { prop } = params; const percent = getValuePercent(value, prop("min"), prop("max")) * 100; return getOffset(params, percent, value); } function getVisibility(params) { const { computed, prop } = params; let visibility = "visible"; if (prop("thumbAlignment") === "contain" && !computed("hasMeasuredThumbSize")) { visibility = "hidden"; } return visibility; } function getThumbStyle(params, index) { const { computed, context } = params; const placementProp = computed("isVertical") ? "bottom" : "insetInlineStart"; const focusedIndex = context.get("focusedIndex"); return { visibility: getVisibility(params), position: "absolute", transform: "var(--slider-thumb-transform)", [placementProp]: `var(--slider-thumb-offset-${index})`, zIndex: focusedIndex === index ? 1 : void 0 }; } function getControlStyle() { return { touchAction: "none", userSelect: "none", WebkitUserSelect: "none", position: "relative" }; } function getRootStyle(params) { const { context, computed } = params; const isVertical = computed("isVertical"); const isRtl = computed("isRtl"); const range = getRangeOffsets(params); const thumbSize = context.get("thumbSize"); const offsetStyles = context.get("value").reduce((styles, value, index) => { const offset = getThumbOffset(params, value); return { ...styles, [`--slider-thumb-offset-${index}`]: offset }; }, {}); return { ...offsetStyles, "--slider-thumb-width": toPx(thumbSize?.width), "--slider-thumb-height": toPx(thumbSize?.height), "--slider-thumb-transform": isVertical ? "translateY(50%)" : isRtl ? "translateX(50%)" : "translateX(-50%)", "--slider-range-start": range.start, "--slider-range-end": range.end }; } function getMarkerStyle(params, value) { const { computed } = params; const isHorizontal = computed("isHorizontal"); const isRtl = computed("isRtl"); return { visibility: getVisibility(params), position: "absolute", pointerEvents: "none", [isHorizontal ? "insetInlineStart" : "bottom"]: getThumbOffset(params, value), translate: "var(--translate-x) var(--translate-y)", "--translate-x": isHorizontal ? isRtl ? "50%" : "-50%" : "0%", "--translate-y": !isHorizontal ? "50%" : "0%" }; } function getMarkerGroupStyle() { return { userSelect: "none", WebkitUserSelect: "none", pointerEvents: "none", position: "relative" }; } function normalizeValues(params, nextValues) { return nextValues.map((value, index) => { return constrainValue(params, value, index); }); } function getRangeAtIndex(params, index) { const { context, prop } = params; const step = prop("step") * prop("minStepsBetweenThumbs"); return getValueRanges(context.get("value"), prop("min"), prop("max"), step)[index]; } function constrainValue(params, value, index) { const { prop } = params; const range = getRangeAtIndex(params, index); const snapValue = snapValueToStep(value, prop("min"), prop("max"), prop("step")); return clampValue(snapValue, range.min, range.max); } function decrement(params, index, step) { const { context, prop } = params; const idx = index ?? context.get("focusedIndex"); const range = getRangeAtIndex(params, idx); const nextValues = getPreviousStepValue(idx, { ...range, step: step ?? prop("step"), values: context.get("value") }); nextValues[idx] = clampValue(nextValues[idx], range.min, range.max); return nextValues; } function increment(params, index, step) { const { context, prop } = params; const idx = index ?? context.get("focusedIndex"); const range = getRangeAtIndex(params, idx); const nextValues = getNextStepValue(idx, { ...range, step: step ?? prop("step"), values: context.get("value") }); nextValues[idx] = clampValue(nextValues[idx], range.min, range.max); return nextValues; } function getClosestIndex(params, pointValue) { const { context } = params; const values = context.get("value"); let closestIndex = 0; let minDistance = Math.abs(values[0] - pointValue); for (let i = 1; i < values.length; i++) { const distance = Math.abs(values[i] - pointValue); if (distance <= minDistance) { closestIndex = i; minDistance = distance; } } return selectMovableThumb(params, closestIndex); } function selectMovableThumb(params, index) { const { context, prop } = params; const values = context.get("value"); const max = prop("max"); const thumbValue = values[index]; if (thumbValue === max) { let movableIndex = index; while (movableIndex > 0 && values[movableIndex - 1] === max) { movableIndex -= 1; } return movableIndex; } return index; } // src/slider.connect.ts function connect(service, normalize2) { const { state, send, context, prop, computed, scope } = service; const ariaLabel = prop("aria-label"); const ariaLabelledBy = prop("aria-labelledby"); const sliderValue = context.get("value"); const focusedIndex = context.get("focusedIndex"); const focused = state.matches("focus"); const dragging = state.matches("dragging"); const disabled = computed("isDisabled"); const invalid = prop("invalid"); const interactive = computed("isInteractive"); const isHorizontal = prop("orientation") === "horizontal"; const isVertical = prop("orientation") === "vertical"; function getValuePercentFn(value) { return getValuePercent(value, prop("min"), prop("max")); } function getPercentValueFn(percent) { return getPercentValue(percent, prop("min"), prop("max"), prop("step")); } return { value: sliderValue, dragging, focused, setValue(value) { send({ type: "SET_VALUE", value }); }, getThumbValue(index) { return sliderValue[index]; }, setThumbValue(index, value) { send({ type: "SET_VALUE", index, value }); }, getValuePercent: getValuePercentFn, getPercentValue: getPercentValueFn, getThumbPercent(index) { return getValuePercentFn(sliderValue[index]); }, setThumbPercent(index, percent) { const value = getPercentValueFn(percent); send({ type: "SET_VALUE", index, value }); }, getThumbMin(index) { return getRangeAtIndex(service, index).min; }, getThumbMax(index) { return getRangeAtIndex(service, index).max; }, increment(index) { send({ type: "INCREMENT", index }); }, decrement(index) { send({ type: "DECREMENT", index }); }, focus() { if (!interactive) return; send({ type: "FOCUS", index: 0 }); }, getLabelProps() { return normalize2.label({ ...parts.label.attrs, dir: prop("dir"), "data-disabled": dataAttr(disabled), "data-orientation": prop("orientation"), "data-invalid": dataAttr(invalid), "data-dragging": dataAttr(dragging), "data-focus": dataAttr(focused), id: getLabelId(scope), htmlFor: getHiddenInputId(scope, 0), onClick(event) { if (!interactive) return; event.preventDefault(); getFirstThumbEl(scope)?.focus(); }, style: { userSelect: "none", WebkitUserSelect: "none" } }); }, getRootProps() { return normalize2.element({ ...parts.root.attrs, "data-disabled": dataAttr(disabled), "data-orientation": prop("orientation"), "data-dragging": dataAttr(dragging), "data-invalid": dataAttr(invalid), "data-focus": dataAttr(focused), id: getRootId(scope), dir: prop("dir"), style: getRootStyle(service) }); }, getValueTextProps() { return normalize2.element({ ...parts.valueText.attrs, dir: prop("dir"), "data-disabled": dataAttr(disabled), "data-orientation": prop("orientation"), "data-invalid": dataAttr(invalid), "data-focus": dataAttr(focused), id: getValueTextId(scope) }); }, getTrackProps() { return normalize2.element({ ...parts.track.attrs, dir: prop("dir"), id: getTrackId(scope), "data-disabled": dataAttr(disabled), "data-invalid": dataAttr(invalid), "data-dragging": dataAttr(dragging), "data-orientation": prop("orientation"), "data-focus": dataAttr(focused), style: { position: "relative" } }); }, getThumbProps(props2) { const { index = 0, name } = props2; const value = sliderValue[index]; const range = getRangeAtIndex(service, index); const valueText = prop("getAriaValueText")?.({ value, index }); const _ariaLabel = Array.isArray(ariaLabel) ? ariaLabel[index] : ariaLabel; const _ariaLabelledBy = Array.isArray(ariaLabelledBy) ? ariaLabelledBy[index] : ariaLabelledBy; return normalize2.element({ ...parts.thumb.attrs, dir: prop("dir"), "data-index": index, "data-name": name, id: getThumbId(scope, index), "data-disabled": dataAttr(disabled), "data-orientation": prop("orientation"), "data-focus": dataAttr(focused && focusedIndex === index), "data-dragging": dataAttr(dragging && focusedIndex === index), draggable: false, "aria-disabled": ariaAttr(disabled), "aria-label": _ariaLabel, "aria-labelledby": _ariaLabelledBy ?? getLabelId(scope), "aria-orientation": prop("orientation"), "aria-valuemax": range.max, "aria-valuemin": range.min, "aria-valuenow": sliderValue[index], "aria-valuetext": valueText, role: "slider", tabIndex: disabled ? void 0 : 0, style: getThumbStyle(service, index), onPointerDown(event) { if (!interactive) return; if (!isLeftClick(event)) return; const thumbEl = event.currentTarget; const rect = thumbEl.getBoundingClientRect(); const midpoint = { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }; const offset = { x: event.clientX - midpoint.x, y: event.clientY - midpoint.y }; send({ type: "THUMB_POINTER_DOWN", index, offset }); event.stopPropagation(); }, onBlur() { if (!interactive) return; send({ type: "BLUR" }); }, onFocus() { if (!interactive) return; send({ type: "FOCUS", index }); }, onKeyDown(event) { if (event.defaultPrevented) return; if (!interactive) return; const step = getEventStep(event) * prop("step"); const keyMap = { ArrowUp() { if (isHorizontal) return; send({ type: "ARROW_INC", step, src: "ArrowUp" }); }, ArrowDown() { if (isHorizontal) return; send({ type: "ARROW_DEC", step, src: "ArrowDown" }); }, ArrowLeft() { if (isVertical) return; send({ type: "ARROW_DEC", step, src: "ArrowLeft" }); }, ArrowRight() { if (isVertical) return; send({ type: "ARROW_INC", step, src: "ArrowRight" }); }, PageUp() { send({ type: "ARROW_INC", step, src: "PageUp" }); }, PageDown() { send({ type: "ARROW_DEC", step, src: "PageDown" }); }, Home() { send({ type: "HOME" }); }, End() { send({ type: "END" }); } }; const key = getEventKey(event, { dir: prop("dir"), orientation: prop("orientation") }); const exec = keyMap[key]; if (exec) { exec(event); event.preventDefault(); event.stopPropagation(); } } }); }, getHiddenInputProps(props2) { const { index = 0, name } = props2; return normalize2.input({ name: name ?? (prop("name") ? prop("name") + (sliderValue.length > 1 ? "[]" : "") : void 0), form: prop("form"), type: "text", hidden: true, defaultValue: sliderValue[index], id: getHiddenInputId(scope, index) }); }, getRangeProps() { return normalize2.element({ id: getRangeId(scope), ...parts.range.attrs, dir: prop("dir"), "data-dragging": dataAttr(dragging), "data-focus": dataAttr(focused), "data-invalid": dataAttr(invalid), "data-disabled": dataAttr(disabled), "data-orientation": prop("orientation"), style: getRangeStyle(service) }); }, getControlProps() { return normalize2.element({ ...parts.control.attrs, dir: prop("dir"), id: getControlId(scope), "data-dragging": dataAttr(dragging), "data-disabled": dataAttr(disabled), "data-orientation": prop("orientation"), "data-invalid": dataAttr(invalid), "data-focus": dataAttr(focused), style: getControlStyle(), onPointerDown(event) { if (!interactive) return; if (!isLeftClick(event)) return; if (isModifierKey(event)) return; const point = getEventPoint(event); send({ type: "POINTER_DOWN", point }); event.preventDefault(); event.stopPropagation(); } }); }, getMarkerGroupProps() { return normalize2.element({ ...parts.markerGroup.attrs, role: "presentation", dir: prop("dir"), "aria-hidden": true, "data-orientation": prop("orientation"), style: getMarkerGroupStyle() }); }, getMarkerProps(props2) { const style = getMarkerStyle(service, props2.value); let markerState; if (props2.value < first(sliderValue)) { markerState = "under-value"; } else if (props2.value > last(sliderValue)) { markerState = "over-value"; } else { markerState = "at-value"; } return normalize2.element({ ...parts.marker.attrs, id: getMarkerId(scope, props2.value), role: "presentation", dir: prop("dir"), "data-orientation": prop("orientation"), "data-value": props2.value, "data-disabled": dataAttr(disabled), "data-state": markerState, style }); }, getDraggingIndicatorProps(props2) { const { index = 0 } = props2; const isDragging = index === focusedIndex && dragging; return normalize2.element({ ...parts.draggingIndicator.attrs, role: "presentation", dir: prop("dir"), hidden: !isDragging, "data-orientation": prop("orientation"), "data-state": isDragging ? "open" : "closed", style: getThumbStyle(service, index) }); } }; } var isEqualSize = (a, b) => { return a?.width === b?.width && a?.height === b?.height; }; var normalize = (value, min, max, step, minStepsBetweenThumbs) => { const ranges = getValueRanges(value, min, max, minStepsBetweenThumbs * step); return ranges.map((range) => { const snapValue = snapValueToStep(range.value, range.min, range.max, step); const rangeValue = clampValue(snapValue, range.min, range.max); if (!isValueWithinRange(rangeValue, min, max)) { throw new Error( "[zag-js/slider] The configured `min`, `max`, `step` or `minStepsBetweenThumbs` values are invalid" ); } return rangeValue; }); }; var machine = createMachine({ props({ props: props2 }) { const min = props2.min ?? 0; const max = props2.max ?? 100; const step = props2.step ?? 1; const defaultValue = props2.defaultValue ?? [min]; const minStepsBetweenThumbs = props2.minStepsBetweenThumbs ?? 0; return { dir: "ltr", thumbAlignment: "contain", origin: "start", orientation: "horizontal", minStepsBetweenThumbs, ...props2, defaultValue: normalize(defaultValue, min, max, step, minStepsBetweenThumbs), value: props2.value ? normalize(props2.value, min, max, step, minStepsBetweenThumbs) : void 0, max, step, min }; }, initialState() { return "idle"; }, context({ prop, bindable, getContext }) { return { thumbSize: bindable(() => ({ defaultValue: prop("thumbSize") || null })), value: bindable(() => ({ defaultValue: prop("defaultValue"), value: prop("value"), isEqual, hash(a) { return a.join(","); }, onChange(value) { prop("onValueChange")?.({ value }); } })), focusedIndex: bindable(() => ({ defaultValue: -1, onChange(value) { const ctx = getContext(); prop("onFocusChange")?.({ focusedIndex: value, value: ctx.get("value") }); } })), fieldsetDisabled: bindable(() => ({ defaultValue: false })) }; }, refs() { return { thumbDragOffset: null }; }, computed: { isHorizontal: ({ prop }) => prop("orientation") === "horizontal", isVertical: ({ prop }) => prop("orientation") === "vertical", isRtl: ({ prop }) => prop("orientation") === "horizontal" && prop("dir") === "rtl", isDisabled: ({ context, prop }) => !!prop("disabled") || context.get("fieldsetDisabled"), isInteractive: ({ prop, computed }) => !(prop("readOnly") || computed("isDisabled")), hasMeasuredThumbSize: ({ context }) => context.get("thumbSize") != null, valuePercent: memo( ({ context, prop }) => [context.get("value"), prop("min"), prop("max")], ([value, min, max]) => value.map((value2) => 100 * getValuePercent(value2, min, max)) ) }, watch({ track, action, context, computed, send }) { track([() => context.hash("value")], () => { action(["syncInputElements", "dispatchChangeEvent"]); }); track([() => computed("isDisabled")], () => { if (computed("isDisabled")) { send({ type: "POINTER_CANCEL" }); } }); }, effects: ["trackFormControlState", "trackThumbSize"], on: { SET_VALUE: [ { guard: "hasIndex", actions: ["setValueAtIndex", "invokeOnChangeEnd"] }, { actions: ["setValue", "invokeOnChangeEnd"] } ], INCREMENT: { actions: ["incrementThumbAtIndex", "invokeOnChangeEnd"] }, DECREMENT: { actions: ["decrementThumbAtIndex", "invokeOnChangeEnd"] } }, states: { idle: { on: { POINTER_DOWN: { target: "dragging", actions: ["setClosestThumbIndex", "setPointerValue", "focusActiveThumb"] }, FOCUS: { target: "focus", actions: ["setFocusedIndex"] }, THUMB_POINTER_DOWN: { target: "dragging", actions: ["setFocusedIndex", "setThumbDragOffset", "focusActiveThumb"] } } }, focus: { entry: ["focusActiveThumb"], on: { POINTER_DOWN: { target: "dragging", actions: ["setClosestThumbIndex", "setPointerValue", "focusActiveThumb"] }, THUMB_POINTER_DOWN: { target: "dragging", actions: ["setFocusedIndex", "setThumbDragOffset", "focusActiveThumb"] }, ARROW_DEC: { actions: ["decrementThumbAtIndex", "invokeOnChangeEnd"] }, ARROW_INC: { actions: ["incrementThumbAtIndex", "invokeOnChangeEnd"] }, HOME: { actions: ["setFocusedThumbToMin", "invokeOnChangeEnd"] }, END: { actions: ["setFocusedThumbToMax", "invokeOnChangeEnd"] }, BLUR: { target: "idle", actions: ["clearFocusedIndex"] } } }, dragging: { entry: ["focusActiveThumb"], effects: ["trackPointerMove"], on: { POINTER_UP: { target: "focus", actions: ["invokeOnChangeEnd", "clearThumbDragOffset"] }, POINTER_MOVE: { actions: ["setPointerValue"] }, POINTER_CANCEL: { target: "idle", actions: ["clearFocusedIndex", "clearThumbDragOffset"] } } } }, implementations: { guards: { hasIndex: ({ event }) => event.index != null }, effects: { trackFormControlState({ context, scope }) { return trackFormControl(getRootEl(scope), { onFieldsetDisabledChange(disabled) { context.set("fieldsetDisabled", disabled); }, onFormReset() { context.set("value", context.initial("value")); } }); }, trackPointerMove({ scope, send }) { return trackPointerMove(scope.getDoc(), { onPointerMove(info) { send({ type: "POINTER_MOVE", point: info.point }); }, onPointerUp() { send({ type: "POINTER_UP" }); } }); }, trackThumbSize({ context, scope, prop }) { if (prop("thumbAlignment") !== "contain" || prop("thumbSize")) return; const exec = (el) => { const rect = getOffsetRect(el); const size = pick(rect, ["width", "height"]); if (isEqualSize(context.get("thumbSize"), size)) return; context.set("thumbSize", size); }; const thumbEls = getThumbEls(scope); thumbEls.forEach(exec); const cleanups = thumbEls.map((el) => resizeObserverBorderBox.observe(el, () => exec(el))); return callAll(...cleanups); } }, actions: { dispatchChangeEvent({ context, scope }) { dispatchChangeEvent(scope, context.get("value")); }, syncInputElements({ context, scope }) { context.get("value").forEach((value, index) => { const inputEl = getHiddenInputEl(scope, index); setElementValue(inputEl, value.toString()); }); }, invokeOnChangeEnd({ prop, context }) { queueMicrotask(() => { prop("onValueChangeEnd")?.({ value: context.get("value") }); }); }, setClosestThumbIndex(params) { const { context, event } = params; const pointValue = getPointValue(params, event.point); if (pointValue == null) return; const focusedIndex = getClosestIndex(params, pointValue); context.set("focusedIndex", focusedIndex); }, setFocusedIndex(params) { const { context, event } = params; const movableIndex = selectMovableThumb(params, event.index); context.set("focusedIndex", movableIndex); }, clearFocusedIndex({ context }) { context.set("focusedIndex", -1); }, setThumbDragOffset(params) { const { refs, event } = params; refs.set("thumbDragOffset", event.offset ?? null); }, clearThumbDragOffset({ refs }) { refs.set("thumbDragOffset", null); }, setPointerValue(params) { queueMicrotask(() => { const { context, event } = params; const pointValue = getPointValue(params, event.point); if (pointValue == null) return; const focusedIndex = context.get("focusedIndex"); const value = constrainValue(params, pointValue, focusedIndex); context.set("value", (prev) => setValueAtIndex(prev, focusedIndex, value)); }); }, focusActiveThumb({ scope, context }) { raf(() => { const thumbEl = getThumbEl(scope, context.get("focusedIndex")); thumbEl?.focus({ preventScroll: true }); }); }, decrementThumbAtIndex(params) { const { context, event } = params; const value = decrement(params, event.index, event.step); context.set("value", value); }, incrementThumbAtIndex(params) { const { context, event } = params; const value = increment(params, event.index, event.step); context.set("value", value); }, setFocusedThumbToMin(params) { const { context } = params; const index = context.get("focusedIndex"); const { min } = getRangeAtIndex(params, index); context.set("value", (prev) => setValueAtIndex(prev, index, min)); }, setFocusedThumbToMax(params) { const { context } = params; const index = context.get("focusedIndex"); const { max } = getRangeAtIndex(params, index); context.set("value", (prev) => setValueAtIndex(prev, index, max)); }, setValueAtIndex(params) { const { context, event } = params; const value = constrainValue(params, event.value, event.index); context.set("value", (prev) => setValueAtIndex(prev, event.index, value)); }, setValue(params) { const { context, event } = params; const value = normalizeValues(params, event.value); context.set("value", value); } } } }); var props = createProps()([ "aria-label", "aria-labelledby", "dir", "disabled", "form", "getAriaValueText", "getRootNode", "id", "ids", "invalid", "max", "min", "minStepsBetweenThumbs", "name", "onFocusChange", "onValueChange", "onValueChangeEnd", "orientation", "origin", "readOnly", "step", "thumbAlignment", "thumbAlignment", "thumbSize", "value", "defaultValue" ]); var splitProps = createSplitProps(props); var thumbProps = createProps()(["index", "name"]); var splitThumbProps = createSplitProps(thumbProps); var markerProps = createProps()(["value"]); var splitMarkerProps = createSplitProps(markerProps); export { anatomy, connect, machine, markerProps, props, splitMarkerProps, splitProps, splitThumbProps, thumbProps };