@zag-js/presence
Version:
Core logic for the presence widget implemented as a state machine
197 lines (195 loc) • 6.62 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/presence.machine.ts
var presence_machine_exports = {};
__export(presence_machine_exports, {
machine: () => machine
});
module.exports = __toCommonJS(presence_machine_exports);
var import_core = require("@zag-js/core");
var import_dom_query = require("@zag-js/dom-query");
var machine = (0, import_core.createMachine)({
props({ props }) {
return { ...props, present: !!props.present };
},
initialState({ prop }) {
return prop("present") ? "mounted" : "unmounted";
},
refs() {
return {
node: null,
styles: null
};
},
context({ bindable }) {
return {
unmountAnimationName: bindable(() => ({ defaultValue: null })),
prevAnimationName: bindable(() => ({ defaultValue: null })),
present: bindable(() => ({ defaultValue: false })),
initial: bindable(() => ({
sync: true,
defaultValue: false
}))
};
},
exit: ["cleanupNode"],
watch({ track, prop, send }) {
track([() => prop("present")], () => {
send({ type: "PRESENCE.CHANGED" });
});
},
on: {
"NODE.SET": {
actions: ["setupNode"]
},
"PRESENCE.CHANGED": {
actions: ["setInitial", "syncPresence"]
}
},
states: {
mounted: {
on: {
UNMOUNT: {
target: "unmounted",
actions: ["clearPrevAnimationName", "invokeOnExitComplete"]
},
"UNMOUNT.SUSPEND": {
target: "unmountSuspended"
}
}
},
unmountSuspended: {
effects: ["trackAnimationEvents"],
on: {
MOUNT: {
target: "mounted",
actions: ["setPrevAnimationName"]
},
UNMOUNT: {
target: "unmounted",
actions: ["clearPrevAnimationName", "invokeOnExitComplete"]
}
}
},
unmounted: {
on: {
MOUNT: {
target: "mounted",
actions: ["setPrevAnimationName"]
}
}
}
},
implementations: {
actions: {
setInitial: ({ context }) => {
if (context.get("initial")) return;
queueMicrotask(() => {
context.set("initial", true);
});
},
invokeOnExitComplete: ({ prop, refs }) => {
prop("onExitComplete")?.();
const node = refs.get("node");
if (!node) return;
const win = (0, import_dom_query.getWindow)(node);
const event = new win.CustomEvent("exitcomplete", { bubbles: false });
node.dispatchEvent(event);
},
setupNode: ({ refs, event }) => {
if (refs.get("node") === event.node) return;
refs.set("node", event.node);
refs.set("styles", (0, import_dom_query.getComputedStyle)(event.node));
},
cleanupNode: ({ refs }) => {
refs.set("node", null);
refs.set("styles", null);
},
syncPresence: ({ context, refs, send, prop }) => {
const presentProp = prop("present");
if (presentProp) {
return send({ type: "MOUNT", src: "presence.changed" });
}
const node = refs.get("node");
if (!presentProp && node?.ownerDocument.visibilityState === "hidden") {
return send({ type: "UNMOUNT", src: "visibilitychange" });
}
(0, import_dom_query.raf)(() => {
const animationName = getAnimationName(refs.get("styles"));
context.set("unmountAnimationName", animationName);
if (animationName === "none" || animationName === context.get("prevAnimationName") || refs.get("styles")?.display === "none" || refs.get("styles")?.animationDuration === "0s") {
send({ type: "UNMOUNT", src: "presence.changed" });
} else {
send({ type: "UNMOUNT.SUSPEND" });
}
});
},
setPrevAnimationName: ({ context, refs }) => {
(0, import_dom_query.raf)(() => {
context.set("prevAnimationName", getAnimationName(refs.get("styles")));
});
},
clearPrevAnimationName: ({ context }) => {
context.set("prevAnimationName", null);
}
},
effects: {
trackAnimationEvents: ({ context, refs, send, prop }) => {
const node = refs.get("node");
if (!node) return;
const onStart = (event) => {
const target = event.composedPath?.()?.[0] ?? event.target;
if (target === node) {
context.set("prevAnimationName", getAnimationName(refs.get("styles")));
}
};
const onEnd = (event) => {
const animationName = getAnimationName(refs.get("styles"));
const target = (0, import_dom_query.getEventTarget)(event);
if (target === node && animationName === context.get("unmountAnimationName") && !prop("present")) {
send({ type: "UNMOUNT", src: "animationend" });
}
};
const onCancel = (event) => {
const target = (0, import_dom_query.getEventTarget)(event);
if (target === node && !prop("present")) {
send({ type: "UNMOUNT", src: "animationcancel" });
}
};
node.addEventListener("animationstart", onStart);
node.addEventListener("animationcancel", onCancel);
node.addEventListener("animationend", onEnd);
const cleanupStyles = (0, import_dom_query.setStyle)(node, { animationFillMode: "forwards" });
return () => {
node.removeEventListener("animationstart", onStart);
node.removeEventListener("animationcancel", onCancel);
node.removeEventListener("animationend", onEnd);
(0, import_dom_query.nextTick)(() => cleanupStyles());
};
}
}
}
});
function getAnimationName(styles) {
return styles?.animationName || "none";
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
machine
});