Packages
corex
0.1.2
0.1.2
0.1.1
0.1.0
0.1.0-rc.1
0.1.0-rc.0
0.1.0-beta.5
0.1.0-beta.4
0.1.0-beta.3
0.1.0-beta.2
0.1.0-beta.1
0.1.0-alpha.33
0.1.0-alpha.32
0.1.0-alpha.31
0.1.0-alpha.30
0.1.0-alpha.29
0.1.0-alpha.28
0.1.0-alpha.27
0.1.0-alpha.26
0.1.0-alpha.25
0.1.0-alpha.24
0.1.0-alpha.23
0.1.0-alpha.22
0.1.0-alpha.21
0.1.0-alpha.20
0.1.0-alpha.19
0.1.0-alpha.18
0.1.0-alpha.17
0.1.0-alpha.16
0.1.0-alpha.15
0.1.0-alpha.14
0.1.0-alpha.13
0.1.0-alpha.12
0.1.0-alpha.11
0.1.0-alpha.10
0.1.0-alpha.9
0.1.0-alpha.8
0.1.0-alpha.7
0.1.0-alpha.6
0.1.0-alpha.5
0.1.0-alpha.4
0.1.0-alpha.3
0.1.0-alpha.2
0.1.0-alpha.1
Accessible and unstyled UI components library written in Elixir and TypeScript that integrates Zag.js state machines into the Phoenix Framework.
Current section
Files
Jump to
Current section
Files
priv/static/editable.mjs
import {
trackInteractOutside
} from "./chunks/chunk-B5L2AGOH.mjs";
import {
notifyPhoenixFormChange
} from "./chunks/chunk-VMKNATWC.mjs";
import {
mountStringBinding,
readEditControlledZagUpdate,
readUpdatedServerString
} from "./chunks/chunk-I2HPUDHJ.mjs";
import {
createDomEventRegistry,
createHookHandleEventRegistry
} from "./chunks/chunk-77HPO22C.mjs";
import {
idMatches,
notifyChange,
readPayloadId,
readPayloadValue
} from "./chunks/chunk-2WCNJX5P.mjs";
import {
Component,
VanillaMachine,
ariaAttr,
canPushEvent,
contains,
createAnatomy,
createMachine,
dataAttr,
getBoolean,
getDir,
getString,
isApple,
isComposingEvent,
raf,
setElementValue,
syncInputFormAssociation
} from "./chunks/chunk-2GQRP3FN.mjs";
// ../node_modules/.pnpm/@zag-js+editable@1.40.0/node_modules/@zag-js/editable/dist/editable.anatomy.mjs
var anatomy = createAnatomy("editable").parts(
"root",
"area",
"label",
"preview",
"input",
"editTrigger",
"submitTrigger",
"cancelTrigger",
"control"
);
var parts = anatomy.build();
// ../node_modules/.pnpm/@zag-js+editable@1.40.0/node_modules/@zag-js/editable/dist/editable.dom.mjs
var getRootId = (ctx) => ctx.ids?.root ?? `editable:${ctx.id}`;
var getAreaId = (ctx) => ctx.ids?.area ?? `editable:${ctx.id}:area`;
var getLabelId = (ctx) => ctx.ids?.label ?? `editable:${ctx.id}:label`;
var getPreviewId = (ctx) => ctx.ids?.preview ?? `editable:${ctx.id}:preview`;
var getInputId = (ctx) => ctx.ids?.input ?? `editable:${ctx.id}:input`;
var getControlId = (ctx) => ctx.ids?.control ?? `editable:${ctx.id}:control`;
var getSubmitTriggerId = (ctx) => ctx.ids?.submitTrigger ?? `editable:${ctx.id}:submit`;
var getCancelTriggerId = (ctx) => ctx.ids?.cancelTrigger ?? `editable:${ctx.id}:cancel`;
var getEditTriggerId = (ctx) => ctx.ids?.editTrigger ?? `editable:${ctx.id}:edit`;
var getInputEl = (ctx) => ctx.getById(getInputId(ctx));
var getPreviewEl = (ctx) => ctx.getById(getPreviewId(ctx));
var getSubmitTriggerEl = (ctx) => ctx.getById(getSubmitTriggerId(ctx));
var getCancelTriggerEl = (ctx) => ctx.getById(getCancelTriggerId(ctx));
var getEditTriggerEl = (ctx) => ctx.getById(getEditTriggerId(ctx));
// ../node_modules/.pnpm/@zag-js+editable@1.40.0/node_modules/@zag-js/editable/dist/editable.connect.mjs
function connect(service, normalize) {
const { state, context, send, prop, scope, computed } = service;
const disabled = !!prop("disabled");
const interactive = computed("isInteractive");
const readOnly = !!prop("readOnly");
const required = !!prop("required");
const invalid = !!prop("invalid");
const autoResize = !!prop("autoResize");
const translations = prop("translations");
const editing = state.matches("edit");
const placeholderProp = prop("placeholder");
const placeholder = typeof placeholderProp === "string" ? { edit: placeholderProp, preview: placeholderProp } : placeholderProp;
const value = context.get("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({ type: "EDIT" });
},
cancel() {
if (!interactive) return;
send({ type: "CANCEL" });
},
submit() {
if (!interactive) return;
send({ type: "SUBMIT" });
},
getRootProps() {
return normalize.element({
...parts.root.attrs,
id: getRootId(scope),
dir: prop("dir")
});
},
getAreaProps() {
return normalize.element({
...parts.area.attrs,
id: getAreaId(scope),
dir: prop("dir"),
style: autoResize ? { display: "inline-grid" } : void 0,
"data-focus": dataAttr(editing),
"data-disabled": dataAttr(disabled),
"data-placeholder-shown": dataAttr(empty)
});
},
getLabelProps() {
return normalize.label({
...parts.label.attrs,
id: getLabelId(scope),
dir: prop("dir"),
htmlFor: getInputId(scope),
"data-focus": dataAttr(editing),
"data-invalid": dataAttr(invalid),
"data-required": dataAttr(required),
onClick() {
if (editing) return;
const previewEl = getPreviewEl(scope);
previewEl?.focus({ preventScroll: true });
}
});
},
getInputProps() {
return normalize.input({
...parts.input.attrs,
dir: prop("dir"),
"aria-label": translations?.input,
name: prop("name"),
form: prop("form"),
id: getInputId(scope),
hidden: autoResize ? void 0 : !editing,
placeholder: placeholder?.edit,
maxLength: prop("maxLength"),
required: prop("required"),
disabled,
"data-disabled": dataAttr(disabled),
readOnly,
"data-readonly": dataAttr(readOnly),
"aria-invalid": ariaAttr(invalid),
"data-invalid": dataAttr(invalid),
"data-autoresize": 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 (isComposingEvent(event)) return;
const keyMap = {
Escape() {
send({ type: "CANCEL" });
event.preventDefault();
},
Enter(event2) {
if (!computed("submitOnEnter")) return;
const { localName } = event2.currentTarget;
if (localName === "textarea") {
const submitMod = 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: getPreviewId(scope),
...parts.preview.attrs,
dir: prop("dir"),
"data-placeholder-shown": dataAttr(empty),
"aria-readonly": ariaAttr(readOnly),
"data-readonly": dataAttr(disabled),
"data-disabled": dataAttr(disabled),
"aria-disabled": ariaAttr(disabled),
"aria-invalid": ariaAttr(invalid),
"data-invalid": dataAttr(invalid),
"aria-label": translations?.edit,
"data-autoresize": dataAttr(autoResize),
children: valueText,
hidden: autoResize ? void 0 : editing,
tabIndex: interactive ? 0 : void 0,
onClick() {
if (!interactive) return;
if (prop("activationMode") !== "click") return;
send({ type: "EDIT", src: "click" });
},
onFocus() {
if (!interactive) return;
if (prop("activationMode") !== "focus") return;
send({ type: "EDIT", src: "focus" });
},
onDoubleClick(event) {
if (event.defaultPrevented) return;
if (!interactive) return;
if (prop("activationMode") !== "dblclick") return;
send({ type: "EDIT", src: "dblclick" });
},
style: autoResize ? {
whiteSpace: "pre",
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: getEditTriggerId(scope),
dir: prop("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: getControlId(scope),
...parts.control.attrs,
dir: prop("dir")
});
},
getSubmitTriggerProps() {
return normalize.button({
...parts.submitTrigger.attrs,
dir: prop("dir"),
id: getSubmitTriggerId(scope),
"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: prop("dir"),
"aria-label": translations?.cancel,
id: getCancelTriggerId(scope),
hidden: !editing,
type: "button",
disabled,
onClick(event) {
if (event.defaultPrevented) return;
if (!interactive) return;
send({ type: "CANCEL", src: "cancel.click" });
}
});
}
};
}
// ../node_modules/.pnpm/@zag-js+editable@1.40.0/node_modules/@zag-js/editable/dist/editable.machine.mjs
var machine = 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 trackInteractOutside(getInputEl(scope), {
exclude(target) {
const ignore = [getCancelTriggerEl(scope), getSubmitTriggerEl(scope)];
return ignore.some((el) => 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;
raf(() => {
const finalEl = prop("finalFocusEl")?.() ?? 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 }) {
raf(() => {
const inputEl = 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 = getInputEl(scope);
if (!inputEl) return;
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 }) {
getInputEl(scope)?.blur();
}
}
}
});
// components/editable.ts
var Editable = class extends Component {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
initMachine(props) {
return new VanillaMachine(machine, props);
}
initApi() {
return this.zagConnect(connect);
}
render() {
const rootEl = this.el.querySelector('[data-scope="editable"][data-part="root"]') ?? this.el;
this.spreadProps(rootEl, this.api.getRootProps());
const controlEl = this.el.querySelector(
'[data-scope="editable"][data-part="control"]'
);
if (controlEl) this.spreadProps(controlEl, this.api.getControlProps());
const areaEl = this.el.querySelector('[data-scope="editable"][data-part="area"]');
if (areaEl) this.spreadProps(areaEl, this.api.getAreaProps());
const labelEl = this.el.querySelector(
'[data-scope="editable"][data-part="label"]'
);
if (labelEl) this.spreadProps(labelEl, this.api.getLabelProps());
const formValueEl = this.el.querySelector(`#${this.el.id}-value`);
if (formValueEl) {
formValueEl.value = this.api.value;
syncInputFormAssociation(formValueEl, this.el);
}
const inputEl = this.el.querySelector(
'[data-scope="editable"][data-part="input"]'
);
if (inputEl) this.spreadProps(inputEl, this.api.getInputProps());
const previewEl = this.el.querySelector(
'[data-scope="editable"][data-part="preview"]'
);
if (previewEl) this.spreadProps(previewEl, this.api.getPreviewProps());
const editTriggerEl = this.el.querySelector(
'[data-scope="editable"][data-part="edit-trigger"]'
);
if (editTriggerEl) this.spreadProps(editTriggerEl, this.api.getEditTriggerProps());
const submitTriggerEl = this.el.querySelector(
'[data-scope="editable"][data-part="submit-trigger"]'
);
if (submitTriggerEl) this.spreadProps(submitTriggerEl, this.api.getSubmitTriggerProps());
const cancelTriggerEl = this.el.querySelector(
'[data-scope="editable"][data-part="cancel-trigger"]'
);
if (cancelTriggerEl) this.spreadProps(cancelTriggerEl, this.api.getCancelTriggerProps());
}
};
// hooks/editable.ts
function dataDefaultValue(el) {
return getString(el, "defaultValue") ?? "";
}
function formValueInput(el) {
return el.querySelector(`#${el.id}-value`);
}
function syncEditableFormValue(el, value, options = {}) {
const hidden = formValueInput(el);
if (hidden) {
notifyPhoenixFormChange(hidden, value, options);
return;
}
const inputEl = el.querySelector('[data-scope="editable"][data-part="input"]');
if (inputEl) {
notifyPhoenixFormChange(inputEl, value, options);
}
}
function notifyEditableValueChange(el, pushEvent, canPush, value, hook) {
syncEditableFormValue(el, value, {
markUsed: hook.allowFormNotify === true
});
notifyChange({
el,
canPushServer: canPush(),
pushEvent,
payload: {
id: el.id,
value
},
serverEventName: getString(el, "onValueChange"),
clientEventName: getString(el, "onValueChangeClient")
});
}
function bindFormSubmitSync(el, zag) {
const form = el.closest("form");
if (!form) return () => {
};
const onSubmit = () => {
if (!zag.api.editing) return;
const inputEl = el.querySelector(
'[data-scope="editable"][data-part="input"]'
);
syncEditableFormValue(el, inputEl?.value ?? zag.api.value, { markUsed: false });
};
form.addEventListener("submit", onSubmit, true);
return () => form.removeEventListener("submit", onSubmit, true);
}
function zagName(el) {
if (formValueInput(el)) return void 0;
return getString(el, "name");
}
var EditableHook = {
mounted() {
const el = this.el;
const pushEvent = this.pushEvent.bind(this);
const canPush = () => canPushEvent(this.liveSocket);
const placeholder = getString(el, "placeholder");
const activationMode = getString(el, "activationMode");
const selectOnFocus = getBoolean(el, "selectOnFocus");
this.allowFormNotify = false;
const valueBinding = mountStringBinding(el, "value", "defaultValue");
const zag = new Editable(el, {
id: el.id,
..."value" in valueBinding ? { value: valueBinding.value ?? "" } : { defaultValue: valueBinding.defaultValue ?? "" },
disabled: getBoolean(el, "disabled"),
readOnly: getBoolean(el, "readonly"),
required: getBoolean(el, "required"),
invalid: getBoolean(el, "invalid"),
name: zagName(el),
form: formValueInput(el) ? void 0 : getString(el, "form"),
dir: getDir(el),
...placeholder !== void 0 ? { placeholder } : {},
...activationMode !== void 0 ? { activationMode } : {},
...selectOnFocus !== void 0 ? { selectOnFocus } : {},
...getBoolean(el, "controlled") ? { edit: getBoolean(el, "edit") } : { defaultEdit: getBoolean(el, "defaultEdit") },
onValueChange: (details) => {
notifyEditableValueChange(el, pushEvent, canPush, details.value, this);
},
onValueCommit: (details) => {
notifyEditableValueChange(el, pushEvent, canPush, details.value, this);
}
});
zag.init();
this.editable = zag;
queueMicrotask(() => {
syncEditableFormValue(el, zag.api.value, { markUsed: false });
this.allowFormNotify = true;
});
this.unbindFormSubmit = bindFormSubmitSync(el, zag);
const domRegistry = createDomEventRegistry(el);
this.domRegistry = domRegistry;
domRegistry.add("corex:editable:set-value", (event) => {
const raw = event.detail?.value;
zag.api.setValue(raw === void 0 || raw === null ? "" : String(raw));
});
const registry = createHookHandleEventRegistry(this);
this.handleRegistry = registry;
registry.add("editable_set_value", (payload) => {
if (!idMatches(el.id, readPayloadId(payload))) return;
zag.api.setValue(readPayloadValue(payload));
});
},
updated() {
const el = this.el;
const valuePatch = readUpdatedServerString(el);
const editPatch = readEditControlledZagUpdate(el);
const props = {
id: el.id,
disabled: getBoolean(el, "disabled"),
readOnly: getBoolean(el, "readonly"),
required: getBoolean(el, "required"),
invalid: getBoolean(el, "invalid"),
name: zagName(el),
form: formValueInput(el) ? void 0 : getString(el, "form"),
dir: getDir(el),
...editPatch
};
if (!this.editable?.api.editing && "value" in valuePatch) {
Object.assign(props, valuePatch);
}
this.editable?.updateProps(props);
},
destroyed() {
this.unbindFormSubmit?.();
this.domRegistry?.teardown();
this.handleRegistry?.teardown();
this.editable?.destroy();
}
};
export {
EditableHook as Editable,
dataDefaultValue
};