package.dist.index.mjs Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dialog Show documentation
Show all versions of dialog Show documentation
Core logic for the dialog widget implemented as a state machine
The newest version!
import { createAnatomy } from '@zag-js/anatomy';
import { createScope, nextTick, raf } from '@zag-js/dom-query';
import { ariaHidden } from '@zag-js/aria-hidden';
import { createMachine } from '@zag-js/core';
import { trackDismissableElement } from '@zag-js/dismissable';
import { preventBodyScroll } from '@zag-js/remove-scroll';
import { createSplitProps, compact } from '@zag-js/utils';
import { createFocusTrap } from 'focus-trap';
import { createProps } from '@zag-js/types';
// src/dialog.anatomy.ts
var anatomy = createAnatomy("dialog").parts(
"trigger",
"backdrop",
"positioner",
"content",
"title",
"description",
"closeTrigger"
);
var parts = anatomy.build();
var dom = createScope({
getPositionerId: (ctx) => ctx.ids?.positioner ?? `dialog:${ctx.id}:positioner`,
getBackdropId: (ctx) => ctx.ids?.backdrop ?? `dialog:${ctx.id}:backdrop`,
getContentId: (ctx) => ctx.ids?.content ?? `dialog:${ctx.id}:content`,
getTriggerId: (ctx) => ctx.ids?.trigger ?? `dialog:${ctx.id}:trigger`,
getTitleId: (ctx) => ctx.ids?.title ?? `dialog:${ctx.id}:title`,
getDescriptionId: (ctx) => ctx.ids?.description ?? `dialog:${ctx.id}:description`,
getCloseTriggerId: (ctx) => ctx.ids?.closeTrigger ?? `dialog:${ctx.id}:close`,
getContentEl: (ctx) => dom.getById(ctx, dom.getContentId(ctx)),
getPositionerEl: (ctx) => dom.getById(ctx, dom.getPositionerId(ctx)),
getBackdropEl: (ctx) => dom.getById(ctx, dom.getBackdropId(ctx)),
getTriggerEl: (ctx) => dom.getById(ctx, dom.getTriggerId(ctx)),
getTitleEl: (ctx) => dom.getById(ctx, dom.getTitleId(ctx)),
getDescriptionEl: (ctx) => dom.getById(ctx, dom.getDescriptionId(ctx)),
getCloseTriggerEl: (ctx) => dom.getById(ctx, dom.getCloseTriggerId(ctx))
});
// src/dialog.connect.ts
function connect(state, send, normalize) {
const ariaLabel = state.context["aria-label"];
const open = state.matches("open");
const rendered = state.context.renderedElements;
return {
open,
setOpen(nextOpen) {
if (nextOpen === open) return;
send(nextOpen ? "OPEN" : "CLOSE");
},
getTriggerProps() {
return normalize.button({
...parts.trigger.attrs,
dir: state.context.dir,
id: dom.getTriggerId(state.context),
"aria-haspopup": "dialog",
type: "button",
"aria-expanded": open,
"data-state": open ? "open" : "closed",
"aria-controls": dom.getContentId(state.context),
onClick(event) {
if (event.defaultPrevented) return;
send("TOGGLE");
}
});
},
getBackdropProps() {
return normalize.element({
...parts.backdrop.attrs,
dir: state.context.dir,
hidden: !open,
id: dom.getBackdropId(state.context),
"data-state": open ? "open" : "closed"
});
},
getPositionerProps() {
return normalize.element({
...parts.positioner.attrs,
dir: state.context.dir,
id: dom.getPositionerId(state.context),
style: {
pointerEvents: open ? void 0 : "none"
}
});
},
getContentProps() {
return normalize.element({
...parts.content.attrs,
dir: state.context.dir,
role: state.context.role,
hidden: !open,
id: dom.getContentId(state.context),
tabIndex: -1,
"data-state": open ? "open" : "closed",
"aria-modal": true,
"aria-label": ariaLabel || void 0,
"aria-labelledby": ariaLabel || !rendered.title ? void 0 : dom.getTitleId(state.context),
"aria-describedby": rendered.description ? dom.getDescriptionId(state.context) : void 0
});
},
getTitleProps() {
return normalize.element({
...parts.title.attrs,
dir: state.context.dir,
id: dom.getTitleId(state.context)
});
},
getDescriptionProps() {
return normalize.element({
...parts.description.attrs,
dir: state.context.dir,
id: dom.getDescriptionId(state.context)
});
},
getCloseTriggerProps() {
return normalize.button({
...parts.closeTrigger.attrs,
dir: state.context.dir,
id: dom.getCloseTriggerId(state.context),
type: "button",
onClick(event) {
if (event.defaultPrevented) return;
event.stopPropagation();
send("CLOSE");
}
});
}
};
}
function machine(userContext) {
const ctx = compact(userContext);
return createMachine(
{
id: "dialog",
initial: ctx.open ? "open" : "closed",
context: {
role: "dialog",
renderedElements: {
title: true,
description: true
},
modal: true,
trapFocus: true,
preventScroll: true,
closeOnInteractOutside: true,
closeOnEscape: true,
restoreFocus: true,
...ctx
},
created: ["setAlertDialogProps"],
watch: {
open: ["toggleVisibility"]
},
states: {
open: {
entry: ["checkRenderedElements", "syncZIndex"],
activities: ["trackDismissableElement", "trapFocus", "preventScroll", "hideContentBelow"],
on: {
"CONTROLLED.CLOSE": {
target: "closed"
},
CLOSE: [
{
guard: "isOpenControlled",
actions: ["invokeOnClose"]
},
{
target: "closed",
actions: ["invokeOnClose"]
}
],
TOGGLE: [
{
guard: "isOpenControlled",
actions: ["invokeOnClose"]
},
{
target: "closed",
actions: ["invokeOnClose"]
}
]
}
},
closed: {
on: {
"CONTROLLED.OPEN": {
target: "open"
},
OPEN: [
{
guard: "isOpenControlled",
actions: ["invokeOnOpen"]
},
{
target: "open",
actions: ["invokeOnOpen"]
}
],
TOGGLE: [
{
guard: "isOpenControlled",
actions: ["invokeOnOpen"]
},
{
target: "open",
actions: ["invokeOnOpen"]
}
]
}
}
}
},
{
guards: {
isOpenControlled: (ctx2) => !!ctx2["open.controlled"]
},
activities: {
trackDismissableElement(ctx2, _evt, { send }) {
const getContentEl = () => dom.getContentEl(ctx2);
return trackDismissableElement(getContentEl, {
defer: true,
pointerBlocking: ctx2.modal,
exclude: [dom.getTriggerEl(ctx2)],
onInteractOutside(event) {
ctx2.onInteractOutside?.(event);
if (!ctx2.closeOnInteractOutside) {
event.preventDefault();
}
},
persistentElements: ctx2.persistentElements,
onFocusOutside: ctx2.onFocusOutside,
onPointerDownOutside: ctx2.onPointerDownOutside,
onEscapeKeyDown(event) {
ctx2.onEscapeKeyDown?.(event);
if (!ctx2.closeOnEscape) {
event.preventDefault();
}
},
onDismiss() {
send({ type: "CLOSE", src: "interact-outside" });
}
});
},
preventScroll(ctx2) {
if (!ctx2.preventScroll) return;
return preventBodyScroll(dom.getDoc(ctx2));
},
trapFocus(ctx2) {
if (!ctx2.trapFocus || !ctx2.modal) return;
let trap;
const cleanup = nextTick(() => {
const contentEl = dom.getContentEl(ctx2);
if (!contentEl) return;
trap = createFocusTrap(contentEl, {
document: dom.getDoc(ctx2),
escapeDeactivates: false,
preventScroll: true,
fallbackFocus: contentEl,
returnFocusOnDeactivate: !!ctx2.restoreFocus,
allowOutsideClick: true,
initialFocus: ctx2.initialFocusEl?.() ?? void 0,
setReturnFocus(triggerEl) {
return ctx2.finalFocusEl?.() ?? triggerEl;
}
});
try {
trap.activate();
} catch {
}
});
return () => {
trap?.deactivate();
cleanup();
};
},
hideContentBelow(ctx2) {
if (!ctx2.modal) return;
const getElements = () => [dom.getContentEl(ctx2)];
return ariaHidden(getElements, { defer: true });
}
},
actions: {
setAlertDialogProps(ctx2) {
if (ctx2.role !== "alertdialog") return;
ctx2.initialFocusEl || (ctx2.initialFocusEl = () => dom.getCloseTriggerEl(ctx2));
ctx2.closeOnInteractOutside = false;
},
checkRenderedElements(ctx2) {
raf(() => {
ctx2.renderedElements.title = !!dom.getTitleEl(ctx2);
ctx2.renderedElements.description = !!dom.getDescriptionEl(ctx2);
});
},
syncZIndex(ctx2) {
raf(() => {
const contentEl = dom.getContentEl(ctx2);
if (!contentEl) return;
const win = dom.getWin(ctx2);
const styles = win.getComputedStyle(contentEl);
const elems = [dom.getPositionerEl(ctx2), dom.getBackdropEl(ctx2)];
elems.forEach((node) => {
node?.style.setProperty("--z-index", styles.zIndex);
});
});
},
invokeOnClose(ctx2) {
ctx2.onOpenChange?.({ open: false });
},
invokeOnOpen(ctx2) {
ctx2.onOpenChange?.({ open: true });
},
toggleVisibility(ctx2, evt, { send }) {
send({ type: ctx2.open ? "CONTROLLED.OPEN" : "CONTROLLED.CLOSE", previousEvent: evt });
}
}
}
);
}
var props = createProps()([
"aria-label",
"closeOnEscape",
"closeOnInteractOutside",
"dir",
"finalFocusEl",
"getRootNode",
"getRootNode",
"id",
"id",
"ids",
"initialFocusEl",
"modal",
"onEscapeKeyDown",
"onFocusOutside",
"onInteractOutside",
"onOpenChange",
"onPointerDownOutside",
"open.controlled",
"open",
"persistentElements",
"preventScroll",
"restoreFocus",
"role",
"trapFocus"
]);
var splitProps = createSplitProps(props);
export { anatomy, connect, machine, props, splitProps };
© 2015 - 2025 Weber Informatics LLC | Privacy Policy