UNPKG

@zag-js/editable

Version:

Core logic for the editable widget implemented as a state machine

262 lines (260 loc) • 8.28 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/editable.machine.ts var editable_machine_exports = {}; __export(editable_machine_exports, { machine: () => machine }); module.exports = __toCommonJS(editable_machine_exports); var import_core = require("@zag-js/core"); var import_dom_query = require("@zag-js/dom-query"); var import_interact_outside = require("@zag-js/interact-outside"); var dom = __toESM(require("./editable.dom.js")); var machine = (0, import_core.createMachine)({ props({ props }) { return { activationMode: "focus", submitMode: "both", defaultValue: "", selectOnFocus: true, ...props, translations: { input: "editable input", edit: "edit", submit: "submit", cancel: "cancel", ...props.translations } }; }, initialState({ prop }) { const edit = prop("edit") || prop("defaultEdit"); return edit ? "edit" : "preview"; }, entry: ["focusInputIfNeeded"], context: ({ bindable, prop }) => { return { value: bindable(() => ({ defaultValue: prop("defaultValue"), value: prop("value"), onChange(value) { return prop("onValueChange")?.({ value }); } })), previousValue: bindable(() => ({ defaultValue: "" })) }; }, watch({ track, action, context, prop }) { track([() => context.get("value")], () => { action(["syncInputValue"]); }); track([() => prop("edit")], () => { action(["toggleEditing"]); }); }, computed: { submitOnEnter({ prop }) { const submitMode = prop("submitMode"); return submitMode === "both" || submitMode === "enter"; }, submitOnBlur({ prop }) { const submitMode = prop("submitMode"); return submitMode === "both" || submitMode === "blur"; }, isInteractive({ prop }) { return !(prop("disabled") || prop("readOnly")); } }, on: { "VALUE.SET": { actions: ["setValue"] } }, states: { preview: { entry: ["blurInput"], on: { "CONTROLLED.EDIT": { target: "edit", actions: ["setPreviousValue", "focusInput"] }, EDIT: [ { guard: "isEditControlled", actions: ["invokeOnEdit"] }, { target: "edit", actions: ["setPreviousValue", "focusInput", "invokeOnEdit"] } ] } }, edit: { effects: ["trackInteractOutside"], entry: ["syncInputValue"], on: { "CONTROLLED.PREVIEW": [ { guard: "isSubmitEvent", target: "preview", actions: ["setPreviousValue", "restoreFocus", "invokeOnSubmit"] }, { target: "preview", actions: ["revertValue", "restoreFocus", "invokeOnCancel"] } ], CANCEL: [ { guard: "isEditControlled", actions: ["invokeOnPreview"] }, { target: "preview", actions: ["revertValue", "restoreFocus", "invokeOnCancel", "invokeOnPreview"] } ], SUBMIT: [ { guard: "isEditControlled", actions: ["invokeOnPreview"] }, { target: "preview", actions: ["setPreviousValue", "restoreFocus", "invokeOnSubmit", "invokeOnPreview"] } ] } } }, implementations: { guards: { isEditControlled: ({ prop }) => prop("edit") != void 0, isSubmitEvent: ({ event }) => event.previousEvent?.type === "SUBMIT" }, effects: { trackInteractOutside({ send, scope, prop, computed }) { return (0, import_interact_outside.trackInteractOutside)(dom.getInputEl(scope), { exclude(target) { const ignore = [dom.getCancelTriggerEl(scope), dom.getSubmitTriggerEl(scope)]; return ignore.some((el) => (0, import_dom_query.contains)(el, target)); }, onFocusOutside: prop("onFocusOutside"), onPointerDownOutside: prop("onPointerDownOutside"), onInteractOutside(event) { prop("onInteractOutside")?.(event); if (event.defaultPrevented) return; const { focusable } = event.detail; send({ type: computed("submitOnBlur") ? "SUBMIT" : "CANCEL", src: "interact-outside", focusable }); } }); } }, actions: { restoreFocus({ event, scope, prop }) { if (event.focusable) return; (0, import_dom_query.raf)(() => { const finalEl = prop("finalFocusEl")?.() ?? dom.getEditTriggerEl(scope); finalEl?.focus({ preventScroll: true }); }); }, clearValue({ context }) { context.set("value", ""); }, focusInputIfNeeded({ action, prop }) { const edit = prop("edit") || prop("defaultEdit"); if (!edit) return; action(["focusInput"]); }, focusInput({ scope, prop }) { (0, import_dom_query.raf)(() => { const inputEl = dom.getInputEl(scope); if (!inputEl) return; if (prop("selectOnFocus")) { inputEl.select(); } else { inputEl.focus({ preventScroll: true }); } }); }, invokeOnCancel({ prop, context }) { const prev = context.get("previousValue"); prop("onValueRevert")?.({ value: prev }); }, invokeOnSubmit({ prop, context }) { const value = context.get("value"); prop("onValueCommit")?.({ value }); }, invokeOnEdit({ prop }) { prop("onEditChange")?.({ edit: true }); }, invokeOnPreview({ prop }) { prop("onEditChange")?.({ edit: false }); }, toggleEditing({ prop, send, event }) { send({ type: prop("edit") ? "CONTROLLED.EDIT" : "CONTROLLED.PREVIEW", previousEvent: event }); }, syncInputValue({ context, scope }) { const inputEl = dom.getInputEl(scope); if (!inputEl) return; (0, import_dom_query.setElementValue)(inputEl, context.get("value")); }, setValue({ context, prop, event }) { const max = prop("maxLength"); const value = max != null ? event.value.slice(0, max) : event.value; context.set("value", value); }, setPreviousValue({ context }) { context.set("previousValue", context.get("value")); }, revertValue({ context }) { const value = context.get("previousValue"); if (!value) return; context.set("value", value); }, blurInput({ scope }) { dom.getInputEl(scope)?.blur(); } } } }); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { machine });