package.dist.index.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of editable Show documentation
Show all versions of editable Show documentation
Core logic for the editable widget implemented as a state machine
The newest version!
'use strict';
var anatomy$1 = require('@zag-js/anatomy');
var domQuery = require('@zag-js/dom-query');
var core = require('@zag-js/core');
var interactOutside = require('@zag-js/interact-outside');
var utils = require('@zag-js/utils');
var types = require('@zag-js/types');
// src/editable.anatomy.ts
var anatomy = anatomy$1.createAnatomy("editable").parts(
"root",
"area",
"label",
"preview",
"input",
"editTrigger",
"submitTrigger",
"cancelTrigger",
"control"
);
var parts = anatomy.build();
var dom = domQuery.createScope({
getRootId: (ctx) => ctx.ids?.root ?? `editable:${ctx.id}`,
getAreaId: (ctx) => ctx.ids?.area ?? `editable:${ctx.id}:area`,
getLabelId: (ctx) => ctx.ids?.label ?? `editable:${ctx.id}:label`,
getPreviewId: (ctx) => ctx.ids?.preview ?? `editable:${ctx.id}:preview`,
getInputId: (ctx) => ctx.ids?.input ?? `editable:${ctx.id}:input`,
getControlId: (ctx) => ctx.ids?.control ?? `editable:${ctx.id}:control`,
getSubmitTriggerId: (ctx) => ctx.ids?.submitTrigger ?? `editable:${ctx.id}:submit`,
getCancelTriggerId: (ctx) => ctx.ids?.cancelTrigger ?? `editable:${ctx.id}:cancel`,
getEditTriggerId: (ctx) => ctx.ids?.editTrigger ?? `editable:${ctx.id}:edit`,
getInputEl: (ctx) => dom.getById(ctx, dom.getInputId(ctx)),
getPreviewEl: (ctx) => dom.getById(ctx, dom.getPreviewId(ctx)),
getSubmitTriggerEl: (ctx) => dom.getById(ctx, dom.getSubmitTriggerId(ctx)),
getCancelTriggerEl: (ctx) => dom.getById(ctx, dom.getCancelTriggerId(ctx)),
getEditTriggerEl: (ctx) => dom.getById(ctx, dom.getEditTriggerId(ctx))
});
// src/editable.connect.ts
function connect(state, send, normalize) {
const disabled = state.context.disabled;
const interactive = state.context.isInteractive;
const readOnly = state.context.readOnly;
const invalid = state.context.invalid;
const autoResize = state.context.autoResize;
const translations = state.context.translations;
const editing = state.matches("edit");
const placeholderProp = state.context.placeholder;
const placeholder = typeof placeholderProp === "string" ? { edit: placeholderProp, preview: placeholderProp } : placeholderProp;
const value = state.context.value;
const empty = value.trim() === "";
const valueText = empty ? placeholder?.preview ?? "" : value;
return {
editing,
empty,
value,
valueText,
setValue(value2) {
send({ type: "VALUE.SET", value: value2, src: "setValue" });
},
clearValue() {
send({ type: "VALUE.SET", value: "", src: "clearValue" });
},
edit() {
if (!interactive) return;
send("EDIT");
},
cancel() {
if (!interactive) return;
send("CANCEL");
},
submit() {
if (!interactive) return;
send("SUBMIT");
},
getRootProps() {
return normalize.element({
...parts.root.attrs,
id: dom.getRootId(state.context),
dir: state.context.dir
});
},
getAreaProps() {
return normalize.element({
...parts.area.attrs,
id: dom.getAreaId(state.context),
dir: state.context.dir,
style: autoResize ? { display: "inline-grid" } : void 0,
"data-focus": domQuery.dataAttr(editing),
"data-disabled": domQuery.dataAttr(disabled),
"data-placeholder-shown": domQuery.dataAttr(empty)
});
},
getLabelProps() {
return normalize.label({
...parts.label.attrs,
id: dom.getLabelId(state.context),
dir: state.context.dir,
htmlFor: dom.getInputId(state.context),
"data-focus": domQuery.dataAttr(editing),
"data-invalid": domQuery.dataAttr(invalid),
onClick() {
if (editing) return;
const previewEl = dom.getPreviewEl(state.context);
previewEl?.focus({ preventScroll: true });
}
});
},
getInputProps() {
return normalize.input({
...parts.input.attrs,
dir: state.context.dir,
"aria-label": translations.input,
name: state.context.name,
form: state.context.form,
id: dom.getInputId(state.context),
hidden: autoResize ? void 0 : !editing,
placeholder: placeholder?.edit,
maxLength: state.context.maxLength,
required: state.context.required,
disabled,
"data-disabled": domQuery.dataAttr(disabled),
readOnly,
"data-readonly": domQuery.dataAttr(readOnly),
"aria-invalid": domQuery.ariaAttr(invalid),
"data-invalid": domQuery.dataAttr(invalid),
"data-autoresize": domQuery.dataAttr(autoResize),
defaultValue: value,
size: autoResize ? 1 : void 0,
onChange(event) {
send({ type: "VALUE.SET", src: "input.change", value: event.currentTarget.value });
},
onKeyDown(event) {
if (event.defaultPrevented) return;
if (domQuery.isComposingEvent(event)) return;
const keyMap = {
Escape() {
send("CANCEL");
event.preventDefault();
},
Enter(event2) {
if (!state.context.submitOnEnter) return;
const { localName } = event2.currentTarget;
if (localName === "textarea") {
const submitMod = domQuery.isApple() ? event2.metaKey : event2.ctrlKey;
if (!submitMod) return;
send({ type: "SUBMIT", src: "keydown.enter" });
return;
}
if (localName === "input" && !event2.shiftKey && !event2.metaKey) {
send({ type: "SUBMIT", src: "keydown.enter" });
event2.preventDefault();
}
}
};
const exec = keyMap[event.key];
if (exec) {
exec(event);
}
},
style: autoResize ? {
gridArea: "1 / 1 / auto / auto",
visibility: !editing ? "hidden" : void 0
} : void 0
});
},
getPreviewProps() {
return normalize.element({
id: dom.getPreviewId(state.context),
...parts.preview.attrs,
dir: state.context.dir,
"data-placeholder-shown": domQuery.dataAttr(empty),
"aria-readonly": domQuery.ariaAttr(readOnly),
"data-readonly": domQuery.dataAttr(disabled),
"data-disabled": domQuery.dataAttr(disabled),
"aria-disabled": domQuery.ariaAttr(disabled),
"aria-invalid": domQuery.ariaAttr(invalid),
"data-invalid": domQuery.dataAttr(invalid),
"aria-label": translations.edit,
"data-autoresize": domQuery.dataAttr(autoResize),
children: valueText,
hidden: autoResize ? void 0 : editing,
tabIndex: interactive ? 0 : void 0,
onClick() {
if (!interactive) return;
if (state.context.activationMode !== "click") return;
send({ type: "EDIT", src: "click" });
},
onFocus() {
if (!interactive) return;
if (state.context.activationMode !== "focus") return;
send({ type: "EDIT", src: "focus" });
},
onDoubleClick(event) {
if (event.defaultPrevented) return;
if (!interactive) return;
if (state.context.activationMode !== "dblclick") return;
send({ type: "EDIT", src: "dblclick" });
},
style: autoResize ? {
whiteSpace: "pre",
userSelect: "none",
gridArea: "1 / 1 / auto / auto",
visibility: editing ? "hidden" : void 0,
// in event the preview overflow's the parent element
overflow: "hidden",
textOverflow: "ellipsis"
} : void 0
});
},
getEditTriggerProps() {
return normalize.button({
...parts.editTrigger.attrs,
id: dom.getEditTriggerId(state.context),
dir: state.context.dir,
"aria-label": translations.edit,
hidden: editing,
type: "button",
disabled,
onClick(event) {
if (event.defaultPrevented) return;
if (!interactive) return;
send({ type: "EDIT", src: "edit.click" });
}
});
},
getControlProps() {
return normalize.element({
id: dom.getControlId(state.context),
...parts.control.attrs,
dir: state.context.dir
});
},
getSubmitTriggerProps() {
return normalize.button({
...parts.submitTrigger.attrs,
dir: state.context.dir,
id: dom.getSubmitTriggerId(state.context),
"aria-label": translations.submit,
hidden: !editing,
disabled,
type: "button",
onClick(event) {
if (event.defaultPrevented) return;
if (!interactive) return;
send({ type: "SUBMIT", src: "submit.click" });
}
});
},
getCancelTriggerProps() {
return normalize.button({
...parts.cancelTrigger.attrs,
dir: state.context.dir,
"aria-label": translations.cancel,
id: dom.getCancelTriggerId(state.context),
hidden: !editing,
type: "button",
disabled,
onClick(event) {
if (event.defaultPrevented) return;
if (!interactive) return;
send({ type: "CANCEL", src: "cancel.click" });
}
});
}
};
}
var submitOnEnter = (ctx) => ["both", "enter"].includes(ctx.submitMode);
var submitOnBlur = (ctx) => ["both", "blur"].includes(ctx.submitMode);
function machine(userContext) {
const ctx = utils.compact(userContext);
return core.createMachine(
{
id: "editable",
initial: ctx.edit ? "edit" : "preview",
entry: ctx.edit ? ["focusInput"] : void 0,
context: {
activationMode: "focus",
submitMode: "both",
value: "",
previousValue: "",
selectOnFocus: true,
disabled: false,
readOnly: false,
...ctx,
translations: {
input: "editable input",
edit: "edit",
submit: "submit",
cancel: "cancel",
...ctx.translations
}
},
watch: {
value: ["syncInputValue"],
edit: ["toggleEditing"]
},
computed: {
submitOnEnter,
submitOnBlur,
isInteractive: (ctx2) => !(ctx2.disabled || ctx2.readOnly)
},
on: {
"VALUE.SET": {
actions: "setValue"
}
},
states: {
preview: {
// https://bugzilla.mozilla.org/show_bug.cgi?id=559561
entry: ["blurInputIfNeeded"],
on: {
"CONTROLLED.EDIT": {
target: "edit",
actions: ["setPreviousValue", "focusInput"]
},
EDIT: [
{
guard: "isEditControlled",
actions: ["invokeOnEdit"]
},
{
target: "edit",
actions: ["setPreviousValue", "focusInput", "invokeOnEdit"]
}
]
}
},
edit: {
activities: ["trackInteractOutside"],
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"]
}
]
}
}
}
},
{
guards: {
isEditControlled: (ctx2) => !!ctx2["edit.controlled"],
isSubmitEvent: (_ctx, evt) => evt.previousEvent?.type === "SUBMIT"
},
activities: {
trackInteractOutside(ctx2, _evt, { send }) {
return interactOutside.trackInteractOutside(dom.getInputEl(ctx2), {
exclude(target) {
const ignore = [dom.getCancelTriggerEl(ctx2), dom.getSubmitTriggerEl(ctx2)];
return ignore.some((el) => domQuery.contains(el, target));
},
onFocusOutside: ctx2.onFocusOutside,
onPointerDownOutside: ctx2.onPointerDownOutside,
onInteractOutside(event) {
ctx2.onInteractOutside?.(event);
if (event.defaultPrevented) return;
const { focusable } = event.detail;
send({ type: submitOnBlur(ctx2) ? "SUBMIT" : "CANCEL", src: "interact-outside", focusable });
}
});
}
},
actions: {
restoreFocus(ctx2, evt) {
if (evt.focusable) return;
domQuery.raf(() => {
const finalEl = ctx2.finalFocusEl?.() ?? dom.getEditTriggerEl(ctx2);
finalEl?.focus({ preventScroll: true });
});
},
focusInput(ctx2) {
domQuery.raf(() => {
const inputEl = dom.getInputEl(ctx2);
if (!inputEl) return;
if (ctx2.selectOnFocus) {
inputEl.select();
} else {
inputEl.focus({ preventScroll: true });
}
});
},
invokeOnCancel(ctx2) {
ctx2.onValueRevert?.({ value: ctx2.previousValue });
},
invokeOnSubmit(ctx2) {
ctx2.onValueCommit?.({ value: ctx2.value });
},
invokeOnEdit(ctx2) {
ctx2.onEditChange?.({ edit: true });
},
invokeOnPreview(ctx2) {
ctx2.onEditChange?.({ edit: false });
},
toggleEditing(ctx2, evt, { send }) {
send({ type: ctx2.edit ? "CONTROLLED.EDIT" : "CONTROLLED.PREVIEW", previousEvent: evt });
},
syncInputValue(ctx2) {
sync.value(ctx2);
},
setValue(ctx2, evt) {
const value = ctx2.maxLength != null ? evt.value.slice(0, ctx2.maxLength) : evt.value;
set.value(ctx2, value);
},
setPreviousValue(ctx2) {
ctx2.previousValue = ctx2.value;
},
revertValue(ctx2) {
set.value(ctx2, ctx2.previousValue);
},
blurInputIfNeeded(ctx2) {
dom.getInputEl(ctx2)?.blur();
}
}
}
);
}
var sync = {
value: (ctx) => {
const inputEl = dom.getInputEl(ctx);
dom.setValue(inputEl, ctx.value);
}
};
var invoke = {
change(ctx) {
ctx.onValueChange?.({ value: ctx.value });
sync.value(ctx);
}
};
var set = {
value(ctx, value) {
if (utils.isEqual(ctx.value, value)) return;
ctx.value = value;
invoke.change(ctx);
}
};
var props = types.createProps()([
"activationMode",
"autoResize",
"dir",
"disabled",
"finalFocusEl",
"form",
"getRootNode",
"id",
"ids",
"invalid",
"maxLength",
"name",
"onEditChange",
"onFocusOutside",
"onInteractOutside",
"onPointerDownOutside",
"onValueChange",
"onValueCommit",
"onValueRevert",
"placeholder",
"readOnly",
"required",
"selectOnFocus",
"edit",
"edit.controlled",
"submitMode",
"translations",
"value"
]);
var splitProps = utils.createSplitProps(props);
exports.anatomy = anatomy;
exports.connect = connect;
exports.machine = machine;
exports.props = props;
exports.splitProps = splitProps;
© 2015 - 2025 Weber Informatics LLC | Privacy Policy