UNPKG

@zag-js/slider

Version:

Core logic for the slider widget implemented as a state machine

372 lines (370 loc) • 13.5 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/slider.machine.ts var slider_machine_exports = {}; __export(slider_machine_exports, { machine: () => machine }); module.exports = __toCommonJS(slider_machine_exports); var import_core = require("@zag-js/core"); var import_dom_query = require("@zag-js/dom-query"); var import_utils = require("@zag-js/utils"); var dom = __toESM(require("./slider.dom.js")); var import_slider = require("./slider.utils.js"); var isEqualSize = (a, b) => { return a?.width === b?.width && a?.height === b?.height; }; var normalize = (value, min, max, step, minStepsBetweenThumbs) => { const ranges = (0, import_utils.getValueRanges)(value, min, max, minStepsBetweenThumbs * step); return ranges.map((range) => { const snapValue = (0, import_utils.snapValueToStep)(range.value, range.min, range.max, step); const rangeValue = (0, import_utils.clampValue)(snapValue, range.min, range.max); if (!(0, import_utils.isValueWithinRange)(rangeValue, min, max)) { throw new Error( "[zag-js/slider] The configured `min`, `max`, `step` or `minStepsBetweenThumbs` values are invalid" ); } return rangeValue; }); }; var machine = (0, import_core.createMachine)({ props({ props }) { const min = props.min ?? 0; const max = props.max ?? 100; const step = props.step ?? 1; const defaultValue = props.defaultValue ?? [min]; const minStepsBetweenThumbs = props.minStepsBetweenThumbs ?? 0; return { dir: "ltr", thumbAlignment: "contain", origin: "start", orientation: "horizontal", thumbCollisionBehavior: "none", minStepsBetweenThumbs, ...props, largeStep: props.largeStep ?? 10 * step, defaultValue: normalize(defaultValue, min, max, step, minStepsBetweenThumbs), value: props.value ? normalize(props.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: import_utils.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, thumbDragStartValue: 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: (0, import_core.memo)( ({ context, prop }) => [context.get("value"), prop("min"), prop("max")], ([value, min, max]) => value.map((value2) => 100 * (0, import_utils.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", "setThumbDragStartValue", "setPointerValue", "focusActiveThumb"] }, FOCUS: { target: "focus", actions: ["setFocusedIndex"] }, THUMB_POINTER_DOWN: { target: "dragging", actions: ["setFocusedIndex", "setThumbDragOffset", "setThumbDragStartValue", "focusActiveThumb"] } } }, focus: { entry: ["focusActiveThumb"], on: { POINTER_DOWN: { target: "dragging", actions: ["setClosestThumbIndex", "setThumbDragStartValue", "setPointerValue", "focusActiveThumb"] }, THUMB_POINTER_DOWN: { target: "dragging", actions: ["setFocusedIndex", "setThumbDragOffset", "setThumbDragStartValue", "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", "clearThumbDragStartValue"] }, POINTER_MOVE: { actions: ["setPointerValue"] }, POINTER_CANCEL: { target: "idle", actions: ["clearFocusedIndex", "clearThumbDragOffset", "clearThumbDragStartValue"] } } } }, implementations: { guards: { hasIndex: ({ event }) => event.index != null }, effects: { trackFormControlState({ context, scope }) { return (0, import_dom_query.trackFormControl)(dom.getRootEl(scope), { onFieldsetDisabledChange(disabled) { context.set("fieldsetDisabled", disabled); }, onFormReset() { context.set("value", context.initial("value")); } }); }, trackPointerMove({ scope, send }) { return (0, import_dom_query.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 = dom.getOffsetRect(el); const size = (0, import_utils.pick)(rect, ["width", "height"]); if (isEqualSize(context.get("thumbSize"), size)) return; context.set("thumbSize", size); }; const thumbEls = dom.getThumbEls(scope); thumbEls.forEach(exec); const cleanups = thumbEls.map((el) => import_dom_query.resizeObserverBorderBox.observe(el, () => exec(el))); return (0, import_utils.callAll)(...cleanups); } }, actions: { dispatchChangeEvent({ context, scope }) { dom.dispatchChangeEvent(scope, context.get("value")); }, syncInputElements({ context, scope }) { context.get("value").forEach((value, index) => { const inputEl = dom.getHiddenInputEl(scope, index); (0, import_dom_query.setElementValue)(inputEl, value.toString()); }); }, invokeOnChangeEnd({ prop, context }) { queueMicrotask(() => { prop("onValueChangeEnd")?.({ value: context.get("value") }); }); }, setClosestThumbIndex(params) { const { context, event } = params; const pointValue = dom.getPointValue(params, event.point); if (pointValue == null) return; const focusedIndex = (0, import_slider.getClosestIndex)(params, pointValue); context.set("focusedIndex", focusedIndex); }, setFocusedIndex(params) { const { context, event } = params; const movableIndex = (0, import_slider.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); }, setThumbDragStartValue({ refs, context }) { refs.set("thumbDragStartValue", context.get("value").slice()); }, clearThumbDragStartValue({ refs }) { refs.set("thumbDragStartValue", null); }, setPointerValue(params) { queueMicrotask(() => { const { context, event, prop, refs } = params; const pointValue = dom.getPointValue(params, event.point); if (pointValue == null) return; const focusedIndex = context.get("focusedIndex"); const startValues = refs.get("thumbDragStartValue"); const result = (0, import_slider.resolveThumbCollision)( prop("thumbCollisionBehavior"), focusedIndex, pointValue, context.get("value"), prop("min"), prop("max"), prop("step"), prop("minStepsBetweenThumbs"), startValues?.[focusedIndex] ); if (result.swapped) { context.set("focusedIndex", result.index); } context.set("value", result.values); }); }, focusActiveThumb({ scope, context }) { (0, import_dom_query.raf)(() => { const thumbEl = dom.getThumbEl(scope, context.get("focusedIndex")); thumbEl?.focus({ preventScroll: true }); }); }, decrementThumbAtIndex(params) { const { context, event } = params; const value = (0, import_slider.decrement)(params, event.index, event.step); context.set("value", value); }, incrementThumbAtIndex(params) { const { context, event } = params; const value = (0, import_slider.increment)(params, event.index, event.step); context.set("value", value); }, setFocusedThumbToMin(params) { const { context } = params; const index = context.get("focusedIndex"); const { min } = (0, import_slider.getRangeAtIndex)(params, index); context.set("value", (prev) => (0, import_utils.setValueAtIndex)(prev, index, min)); }, setFocusedThumbToMax(params) { const { context } = params; const index = context.get("focusedIndex"); const { max } = (0, import_slider.getRangeAtIndex)(params, index); context.set("value", (prev) => (0, import_utils.setValueAtIndex)(prev, index, max)); }, setValueAtIndex(params) { const { context, event } = params; const value = (0, import_slider.constrainValue)(params, event.value, event.index); context.set("value", (prev) => (0, import_utils.setValueAtIndex)(prev, event.index, value)); }, setValue(params) { const { context, event } = params; const value = (0, import_slider.normalizeValues)(params, event.value); context.set("value", value); } } } }); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { machine });