UNPKG

@zag-js/switch

Version:

Core logic for the switch widget implemented as a state machine

301 lines (295 loc) • 8.89 kB
'use strict'; var anatomy$1 = require('@zag-js/anatomy'); var domQuery = require('@zag-js/dom-query'); var focusVisible = require('@zag-js/focus-visible'); var core = require('@zag-js/core'); var types = require('@zag-js/types'); var utils = require('@zag-js/utils'); // src/switch.anatomy.ts var anatomy = anatomy$1.createAnatomy("switch").parts("root", "label", "control", "thumb"); var parts = anatomy.build(); // src/switch.dom.ts var getRootId = (ctx) => ctx.ids?.root ?? `switch:${ctx.id}`; var getLabelId = (ctx) => ctx.ids?.label ?? `switch:${ctx.id}:label`; var getThumbId = (ctx) => ctx.ids?.thumb ?? `switch:${ctx.id}:thumb`; var getControlId = (ctx) => ctx.ids?.control ?? `switch:${ctx.id}:control`; var getHiddenInputId = (ctx) => ctx.ids?.hiddenInput ?? `switch:${ctx.id}:input`; var getRootEl = (ctx) => ctx.getById(getRootId(ctx)); var getHiddenInputEl = (ctx) => ctx.getById(getHiddenInputId(ctx)); // src/switch.connect.ts function connect(service, normalize) { const { context, send, prop, scope } = service; const disabled = !!prop("disabled"); const readOnly = !!prop("readOnly"); const required = !!prop("required"); const checked = !!context.get("checked"); const focused = !disabled && context.get("focused"); const focusVisible$1 = !disabled && context.get("focusVisible"); const active = !disabled && context.get("active"); const dataAttrs = { "data-active": domQuery.dataAttr(active), "data-focus": domQuery.dataAttr(focused), "data-focus-visible": domQuery.dataAttr(focusVisible$1), "data-readonly": domQuery.dataAttr(readOnly), "data-hover": domQuery.dataAttr(context.get("hovered")), "data-disabled": domQuery.dataAttr(disabled), "data-state": checked ? "checked" : "unchecked", "data-invalid": domQuery.dataAttr(prop("invalid")), "data-required": domQuery.dataAttr(required) }; return { checked, disabled, focused, setChecked(checked2) { send({ type: "CHECKED.SET", checked: checked2, isTrusted: false }); }, toggleChecked() { send({ type: "CHECKED.TOGGLE", checked, isTrusted: false }); }, getRootProps() { return normalize.label({ ...parts.root.attrs, ...dataAttrs, dir: prop("dir"), id: getRootId(scope), htmlFor: getHiddenInputId(scope), onPointerMove() { if (disabled) return; send({ type: "CONTEXT.SET", context: { hovered: true } }); }, onPointerLeave() { if (disabled) return; send({ type: "CONTEXT.SET", context: { hovered: false } }); }, onClick(event) { if (disabled) return; const target = domQuery.getEventTarget(event); if (target === getHiddenInputEl(scope)) { event.stopPropagation(); } if (domQuery.isSafari()) { getHiddenInputEl(scope)?.focus(); } } }); }, getLabelProps() { return normalize.element({ ...parts.label.attrs, ...dataAttrs, dir: prop("dir"), id: getLabelId(scope) }); }, getThumbProps() { return normalize.element({ ...parts.thumb.attrs, ...dataAttrs, dir: prop("dir"), id: getThumbId(scope), "aria-hidden": true }); }, getControlProps() { return normalize.element({ ...parts.control.attrs, ...dataAttrs, dir: prop("dir"), id: getControlId(scope), "aria-hidden": true }); }, getHiddenInputProps() { return normalize.input({ id: getHiddenInputId(scope), type: "checkbox", required: prop("required"), defaultChecked: checked, disabled, "aria-labelledby": getLabelId(scope), "aria-invalid": prop("invalid"), name: prop("name"), form: prop("form"), value: prop("value"), style: domQuery.visuallyHiddenStyle, onFocus() { const focusVisible2 = focusVisible.isFocusVisible(); send({ type: "CONTEXT.SET", context: { focused: true, focusVisible: focusVisible2 } }); }, onBlur() { send({ type: "CONTEXT.SET", context: { focused: false, focusVisible: false } }); }, onClick(event) { if (readOnly) { event.preventDefault(); return; } const checked2 = event.currentTarget.checked; send({ type: "CHECKED.SET", checked: checked2, isTrusted: true }); } }); } }; } var { not } = core.createGuards(); var machine = core.createMachine({ props({ props: props2 }) { return { defaultChecked: false, label: "switch", value: "on", ...props2 }; }, initialState() { return "ready"; }, context({ prop, bindable }) { return { checked: bindable(() => ({ defaultValue: prop("defaultChecked"), value: prop("checked"), onChange(value) { prop("onCheckedChange")?.({ checked: value }); } })), fieldsetDisabled: bindable(() => ({ defaultValue: false })), focusVisible: bindable(() => ({ defaultValue: false })), active: bindable(() => ({ defaultValue: false })), focused: bindable(() => ({ defaultValue: false })), hovered: bindable(() => ({ defaultValue: false })) }; }, computed: { isDisabled: ({ context, prop }) => prop("disabled") || context.get("fieldsetDisabled") }, watch({ track, prop, context, action }) { track([() => prop("disabled")], () => { action(["removeFocusIfNeeded"]); }); track([() => context.get("checked")], () => { action(["syncInputElement"]); }); }, effects: ["trackFormControlState", "trackPressEvent", "trackFocusVisible"], on: { "CHECKED.TOGGLE": [ { guard: not("isTrusted"), actions: ["toggleChecked", "dispatchChangeEvent"] }, { actions: ["toggleChecked"] } ], "CHECKED.SET": [ { guard: not("isTrusted"), actions: ["setChecked", "dispatchChangeEvent"] }, { actions: ["setChecked"] } ], "CONTEXT.SET": { actions: ["setContext"] } }, states: { ready: {} }, implementations: { guards: { isTrusted: ({ event }) => !!event.isTrusted }, effects: { trackPressEvent({ computed, scope, context }) { if (computed("isDisabled")) return; return domQuery.trackPress({ pointerNode: getRootEl(scope), keyboardNode: getHiddenInputEl(scope), isValidKey: (event) => event.key === " ", onPress: () => context.set("active", false), onPressStart: () => context.set("active", true), onPressEnd: () => context.set("active", false) }); }, trackFocusVisible({ computed, scope }) { if (computed("isDisabled")) return; return focusVisible.trackFocusVisible({ root: scope.getRootNode() }); }, trackFormControlState({ context, send, scope }) { return domQuery.trackFormControl(getHiddenInputEl(scope), { onFieldsetDisabledChange(disabled) { context.set("fieldsetDisabled", disabled); }, onFormReset() { const checked = context.initial("checked"); send({ type: "CHECKED.SET", checked: !!checked, src: "form-reset" }); } }); } }, actions: { setContext({ context, event }) { for (const key in event.context) { context.set(key, event.context[key]); } }, syncInputElement({ context, scope }) { const inputEl = getHiddenInputEl(scope); if (!inputEl) return; domQuery.setElementChecked(inputEl, !!context.get("checked")); }, removeFocusIfNeeded({ context, prop }) { if (prop("disabled")) { context.set("focused", false); } }, setChecked({ context, event }) { context.set("checked", event.checked); }, toggleChecked({ context }) { context.set("checked", !context.get("checked")); }, dispatchChangeEvent({ context, scope }) { const inputEl = getHiddenInputEl(scope); domQuery.dispatchInputCheckedEvent(inputEl, { checked: context.get("checked") }); } } } }); var props = types.createProps()([ "checked", "defaultChecked", "dir", "disabled", "form", "getRootNode", "id", "ids", "invalid", "label", "name", "onCheckedChange", "readOnly", "required", "value" ]); var splitProps = utils.createSplitProps(props); exports.anatomy = anatomy; exports.connect = connect; exports.machine = machine; exports.props = props; exports.splitProps = splitProps;