@zag-js/editable
Version:
Core logic for the editable widget implemented as a state machine
262 lines (260 loc) • 8.28 kB
JavaScript
;
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
});