{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"dialog","type":"registry:ui","title":"Dialog","description":"A modal panel with a title, description, body and free-form footer — portals out, traps focus, and locks the page's scroll while open","categories":["overlays"],"registryDependencies":["https://fancy-ui.rama.app/r/internals.json","https://fancy-ui.rama.app/r/internals-dom.json","https://fancy-ui.rama.app/r/internals-motion.json","https://fancy-ui.rama.app/r/sound.json","https://fancy-ui.rama.app/r/utils.json"],"files":[{"path":"react/src/components/dialog/Dialog.tsx","content":"\"use client\";\n\nimport { forwardRef, useEffect, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\n\nimport { cn } from \"../../utils\";\nimport { useFancyId } from \"../../internals/use-id\";\nimport { useSoundCue } from \"../../sound/use-sound\";\nimport { DialogSurface } from \"./DialogSurface\";\n\nexport interface DialogProps {\n\t/** Whether the dialog is open. Controlled when given; the component keeps its own copy either way. */\n\topen?: boolean;\n\t/** Fires whenever `open` changes, from any trigger — the close button, Escape, an outside click, or the optional `trigger` node. */\n\tonOpenChange?: (open: boolean) => void;\n\t/** The heading. Omitted entirely (not just visually) when not given, so `aria-labelledby` never points at nothing. */\n\ttitle?: string;\n\t/** The copy under the title. Same omission rule as `title`. */\n\tdescription?: string;\n\t/** Whether Escape and an outside click close the dialog. The close button always works regardless — see the README. */\n\tdismissible?: boolean;\n\t/** Element to focus once the dialog opens. Defaults to the first focusable descendant — often the close button; pass a form field here for dialogs built around one. */\n\tinitialFocus?: HTMLElement | null;\n\t/** The dialog's body. */\n\tchildren?: ReactNode;\n\t/** The action row under the body. Free-form — callers build their own buttons. */\n\tfooter?: ReactNode;\n\t/** Optional trigger; renders in place and opens the dialog on activation. */\n\ttrigger?: ReactNode;\n\t/** Additional classes for the panel. */\n\tclassName?: string;\n\t/**\n\t * Plays the matching open/close cue through the sound controller. Off by\n\t * default; only audible once the user has enabled sound.\n\t */\n\tsound?: boolean;\n}\n\nexport const Dialog = forwardRef<HTMLDivElement, DialogProps>(function Dialog(\n\t{\n\t\topen: openProp,\n\t\tonOpenChange,\n\t\ttitle,\n\t\tdescription,\n\t\tdismissible = true,\n\t\tinitialFocus = null,\n\t\tchildren,\n\t\tfooter,\n\t\ttrigger,\n\t\tclassName,\n\t\tsound = false,\n\t},\n\tforwardedRef\n) {\n\t// The React shape of the source's `open = $bindable(false)`: an internal\n\t// copy seeded from the prop, re-synced during render whenever the CALLER\n\t// changes the prop, and free to move on its own in between. That is what\n\t// makes all three documented call shapes work off one implementation — a\n\t// caller driving `open` from its own state, a caller who passes only\n\t// `onOpenChange`, and a caller who passes neither and lets the trigger\n\t// run the whole thing.\n\t//\n\t// Re-synced in the render path, not an effect: an effect would paint one\n\t// frame of the stale value first, and the pattern React documents for\n\t// \"adjust state when a prop changes\" is exactly this — set state on the\n\t// component that owns it, during render, and let React restart the render\n\t// before committing anything.\n\tconst [open, setOpenState] = useState(openProp ?? false);\n\tconst [lastOpenProp, setLastOpenProp] = useState(openProp);\n\tif (lastOpenProp !== openProp) {\n\t\tsetLastOpenProp(openProp);\n\t\tsetOpenState(openProp ?? false);\n\t}\n\n\t// A no-op while `sound` is false, so the call sites below stay unguarded.\n\tconst playCue = useSoundCue(sound);\n\n\t// The only place `open` changes on this side of the wire — a plain\n\t// function, not an effect, so it never fights a caller's own controlled\n\t// write and never reads/writes `open` in the same pass.\n\tfunction setOpen(next: boolean) {\n\t\t// A dismiss that changes nothing fires nothing. Belt and braces beside\n\t\t// the dismiss layer's own `active` gate — `active` stops the listener,\n\t\t// this stops the callback — and it fixes a real defect on its own: a\n\t\t// second Escape during the close used to fire `onOpenChange(false)` a\n\t\t// second time.\n\t\t// The same guard is what keeps the cues honest: a redundant call — a\n\t\t// second Escape mid-exit, a dismiss that changes nothing — stays\n\t\t// silent rather than doubling up, and a dialog driven purely by the\n\t\t// `open` prop never reaches this function's open branch, so it opens\n\t\t// silently by design.\n\t\tif (open === next) return;\n\t\tsetOpenState(next);\n\t\tplayCue(next ? \"open\" : \"close\");\n\t\tonOpenChange?.(next);\n\t}\n\n\t// A plain ref, not state: nothing renders off it. It only has to exist by\n\t// the time `useDismissable`'s `exclude` callback runs, and both that and\n\t// `fallbackFocus` below are resolved at event time. Doubles as\n\t// `DialogSurface`'s `fallbackFocus` target — a real, findable place for\n\t// focus to land on close if whatever had focus when the dialog opened is\n\t// no longer in the document by the time it closes.\n\tconst triggerRef = useRef<HTMLElement | null>(null);\n\n\t// The wrapper only listens for the click bubbling up from whatever the\n\t// caller put inside `trigger` — it adds no interactive semantics of its\n\t// own, so the trigger's own content (expected to be a real button or\n\t// similar) is what carries keyboard activation. `exclude` on\n\t// `DialogSurface` below is what stops this same element from reading as\n\t// an \"outside\" pointerdown once the dialog is open, so clicking the\n\t// trigger again while open cannot immediately dismiss what it just — or\n\t// is about to — open.\n\tfunction openFromTrigger() {\n\t\tif (open) return;\n\t\tsetOpen(true);\n\t}\n\n\t// One seed, two suffixes — the same one-generator-per-instance pattern\n\t// FormField and RadioGroup use, safe during SSR. Undefined rather than a\n\t// generated id with nothing pointing at it while the title/description\n\t// they'd label are not rendered.\n\tconst uid = useFancyId();\n\tconst titleId = title ? `${uid}-title` : undefined;\n\tconst descriptionId = description ? `${uid}-description` : undefined;\n\n\t// The wrapper carries no semantics, so the disclosure state goes on what\n\t// the caller rendered inside it — the real button a keyboard or\n\t// screen-reader user lands on. Written imperatively because an arbitrary\n\t// `trigger` node cannot be handed props from here. `aria-controls` only\n\t// while the panel exists, so it never points at an id not in the document.\n\tconst panelId = `${uid}-panel`;\n\tuseEffect(() => {\n\t\tconst el = triggerRef.current?.firstElementChild;\n\t\tif (!el) return;\n\t\tel.setAttribute(\"aria-haspopup\", \"dialog\");\n\t\tel.setAttribute(\"aria-expanded\", String(open));\n\t\tif (open) el.setAttribute(\"aria-controls\", panelId);\n\t\telse el.removeAttribute(\"aria-controls\");\n\t\treturn () => {\n\t\t\tel.removeAttribute(\"aria-haspopup\");\n\t\t\tel.removeAttribute(\"aria-expanded\");\n\t\t\tel.removeAttribute(\"aria-controls\");\n\t\t};\n\t}, [open, panelId, trigger]);\n\n\tconst panelContent = (\n\t\t<>\n\t\t\t<div className=\"flex items-start justify-between gap-3\">\n\t\t\t\t{title ? (\n\t\t\t\t\t<h2 id={titleId} className=\"text-[15px] font-semibold text-balance\">\n\t\t\t\t\t\t{title}\n\t\t\t\t\t</h2>\n\t\t\t\t) : null}\n\t\t\t\t{/*\n\t\t\t\t\tAlways rendered and always functional, independent of `dismissible`:\n\t\t\t\t\tthis is an explicit, deliberate activation — like clicking a Cancel\n\t\t\t\t\tbutton — not the accidental miss-click `dismissible` guards against,\n\t\t\t\t\tso a dialog that turns off Escape/outside-click to protect\n\t\t\t\t\tin-progress work still leaves one unambiguous way out.\n\t\t\t\t*/}\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName=\"ft-dialog-close text-muted-foreground hover:bg-accent hover:text-accent-foreground inline-flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center rounded-md transition-colors focus-visible:ring-[3px] focus-visible:ring-[var(--ft-overlay-accent)]/35 focus-visible:outline-none\"\n\t\t\t\t\tonClick={() => setOpen(false)}\n\t\t\t\t\taria-label=\"Close\"\n\t\t\t\t>\n\t\t\t\t\t<span aria-hidden=\"true\">✕</span>\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t{description ? (\n\t\t\t\t<p id={descriptionId} className=\"text-muted-foreground text-[12.5px] leading-relaxed\">\n\t\t\t\t\t{description}\n\t\t\t\t</p>\n\t\t\t) : null}\n\t\t\t{children}\n\t\t\t{footer ? <div className=\"flex justify-end gap-2\">{footer}</div> : null}\n\t\t</>\n\t);\n\n\treturn (\n\t\t<>\n\t\t\t{trigger ? (\n\t\t\t\t/*\n\t\t\t\t\tThis wrapper adds no semantics of its own on purpose — it only\n\t\t\t\t\tlistens for the click bubbling up from whatever `trigger` renders,\n\t\t\t\t\twhich is expected to be a real interactive element (a Button,\n\t\t\t\t\ttypically) carrying its own keyboard activation. `display: contents`\n\t\t\t\t\tkeeps it out of layout entirely, so it never affects how the\n\t\t\t\t\ttrigger's own content positions itself.\n\t\t\t\t*/\n\t\t\t\t<span\n\t\t\t\t\tref={(node) => {\n\t\t\t\t\t\ttriggerRef.current = node;\n\t\t\t\t\t}}\n\t\t\t\t\tclassName=\"contents\"\n\t\t\t\t\tonClick={openFromTrigger}\n\t\t\t\t>\n\t\t\t\t\t{trigger}\n\t\t\t\t</span>\n\t\t\t) : null}\n\t\t\t<DialogSurface\n\t\t\t\tref={forwardedRef}\n\t\t\t\topen={open}\n\t\t\t\tid={panelId}\n\t\t\t\trole=\"dialog\"\n\t\t\t\ttitleId={titleId}\n\t\t\t\tdescriptionId={descriptionId}\n\t\t\t\tescape={dismissible}\n\t\t\t\toutsideClick={dismissible}\n\t\t\t\tonDismiss={() => setOpen(false)}\n\t\t\t\tinitialFocus={initialFocus}\n\t\t\t\tfallbackFocus={() => triggerRef.current}\n\t\t\t\texclude={() => [triggerRef.current]}\n\t\t\t\tpanelClass={cn(className)}\n\t\t\t>\n\t\t\t\t{panelContent}\n\t\t\t</DialogSurface>\n\t\t</>\n\t);\n});\n\nDialog.displayName = \"Dialog\";\n","type":"registry:ui","target":"@ui/fancy/components/dialog/Dialog.tsx"},{"path":"react/src/components/dialog/DialogSurface.tsx","content":"\"use client\";\n\nimport { forwardRef } from \"react\";\n\nimport { cn } from \"../../utils\";\nimport { Portal } from \"../../internals/Portal\";\nimport { useDismissable } from \"../../internals/dismissable\";\nimport { useFocusTrap } from \"../../internals/focus-trap\";\nimport { useScrollLock } from \"../../internals/scroll-lock\";\nimport { useComposedRefs } from \"../../internals/dom/use-composed-refs\";\nimport { useElementRef } from \"../../internals/dom/use-element-ref\";\nimport { anchored } from \"../../internals/motion/anchored\";\nimport { DURATIONS } from \"../../internals/motion/tokens\";\nimport { usePresence } from \"../../internals/motion/presence\";\nimport type { DialogSurfaceProps } from \"./types\";\nimport \"./dialog-surface.css\";\n\nexport const DialogSurface = forwardRef<HTMLDivElement, DialogSurfaceProps>(function DialogSurface(\n\t{\n\t\topen,\n\t\tid,\n\t\trole,\n\t\ttitleId,\n\t\tdescriptionId,\n\t\tescape,\n\t\toutsideClick,\n\t\tonDismiss,\n\t\tinitialFocus = null,\n\t\tfallbackFocus,\n\t\texclude,\n\t\tpanelClass,\n\t\tchildren,\n\t},\n\tforwardedRef\n) {\n\t// Convention C-1: the NODE, not a ref. The panel is created by\n\t// `presence.mounted`, so a `useRef` + `[]`-deps effect would still be\n\t// holding `null` when the trap's effect fires and the trap would never\n\t// arm — silently.\n\tconst [panel, setPanelNode] = useElementRef<HTMLDivElement>();\n\n\t// Returns the two functions the source hands out through `onActivate`:\n\t// the eager return, and the re-arm. Two module-level `let`s, two handler\n\t// functions and an `onActivate` closure collapse to this one line.\n\tconst trap = useFocusTrap(panel, { initialFocus, fallbackFocus });\n\n\tconst presence = usePresence(open, {\n\t\t// The two halves of the focus handshake, at the two moments the source\n\t\t// puts them: `onintrostart` → rearm, `onoutrostart` → returnFocusNow.\n\t\t//\n\t\t// `returnFocusNow` at the dismiss instant is the whole point: waiting\n\t\t// for the trap's own `destroy()` would leave a keyboard user on\n\t\t// `<body>` for the length of the fade, because the panel is marked\n\t\t// `inert` the instant the exit starts.\n\t\t//\n\t\t// `rearm` is the other half. A dialog reopened DURING its exit\n\t\t// reverses instead of remounting, so the trap is never re-created:\n\t\t// without this the panel would come back `aria-modal` and interactive\n\t\t// with focus left on the trigger behind it, Tab walking the page\n\t\t// rather than the panel, and the eager return already spent for the\n\t\t// life of the instance.\n\t\tonEnterStart: () => trap.rearm(),\n\t\tonExitStart: () => trap.returnFocusNow(),\n\t});\n\n\t// Release timing, which is the only reason this is not keyed on `open`:\n\t// `presence.mounted` stays true for the whole exit, so the page stays\n\t// locked until the backdrop has actually finished fading instead of\n\t// unlocking the instant `open` flips and leaving the page scrollable\n\t// under a scrim still on screen.\n\t//\n\t// CONTRACT divergence, recorded in this folder's README: the internals\n\t// contract's worked example calls a bare `useScrollLock()` here. That is\n\t// only correct for a component whose own mounting scope IS the surface's,\n\t// and this one's is not — `Dialog` renders `DialogSurface`\n\t// unconditionally, exactly as the source does, so a bare call would lock\n\t// the page for every dialog on the page, open or not. `mounted` is the\n\t// mounting scope expressed as a boolean; it is emphatically not `open`.\n\tuseScrollLock(presence.mounted);\n\n\t// `active: open` — a plain boolean where the source needed `() => open`.\n\t// The layer stays ON the stack for its whole exit and stops being TOP of\n\t// it the instant `open` flips, so a second Escape during the fade falls\n\t// through to whatever is underneath.\n\tuseDismissable(panel, {\n\t\tonDismiss,\n\t\tescape,\n\t\toutsideClick,\n\t\texclude,\n\t\tactive: open,\n\t\tkeepFocus: true,\n\t});\n\n\t// Convention C-2: composed ABOVE any conditional. Calling this inside the\n\t// JSX branch below would be a conditional hook and would throw the first\n\t// time `mounted` flips.\n\tconst panelRef = useComposedRefs(\n\t\tsetPanelNode,\n\t\tforwardedRef,\n\t\tpresence.register(\"panel\", anchored, (entering) => ({\n\t\t\tentering,\n\t\t\tduration: DURATIONS.base,\n\t\t\texitDuration: DURATIONS.exit,\n\t\t}))\n\t);\n\n\t// The scrim fades on opacity alone (`scale: false`) — a full-viewport\n\t// fixed element has no business acquiring a compositing layer for a\n\t// transform it does not use. It shares the panel's clock exactly, so the\n\t// two leave together and the \"destroy the subtree when the LAST\n\t// transition finishes\" rule is a tie rather than a straggler.\n\tconst scrimRef = presence.register(\"scrim\", anchored, (entering) => ({\n\t\tentering,\n\t\tscale: false,\n\t\tduration: DURATIONS.base,\n\t\texitDuration: DURATIONS.exit,\n\t}));\n\n\t// CONTRACT divergence, recorded in this folder's README: the `Portal`\n\t// stays mounted and its CHILDREN are what `presence.mounted` gates,\n\t// rather than the whole component returning `null`. `usePortalTarget`\n\t// resolves its container in a layout effect, so a `Portal` that mounts in\n\t// the same commit as the surface renders nothing on that pass — and the\n\t// registered nodes therefore do not exist yet when `usePresence`'s own\n\t// layout effect looks for legs to start, which settles the group\n\t// immediately and skips the entrance outright. Hoisting the `Portal`\n\t// above the gate resolves the container once, at this component's mount,\n\t// so every later open attaches its nodes in the very commit\n\t// `usePresence` is waiting for.\n\treturn (\n\t\t<Portal>\n\t\t\t{presence.mounted ? (\n\t\t\t\t<>\n\t\t\t\t\t<div\n\t\t\t\t\t\tref={scrimRef}\n\t\t\t\t\t\tclassName=\"ft-dialog-scrim fixed inset-0 z-50 bg-black/60\"\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t/>\n\t\t\t\t\t{/*\n\t\t\t\t\t\tNo portal-before-focus-trap ordering ceremony here (divergence\n\t\t\t\t\t\tD-10): `createPortal` commits children into the container before\n\t\t\t\t\t\tany effect runs and refs populate before layout effects, so the\n\t\t\t\t\t\tnode the trap focuses is always connected. The silent-no-op\n\t\t\t\t\t\t`.focus()` hazard the source's comment block warns about cannot\n\t\t\t\t\t\trecur.\n\n\t\t\t\t\t\t`data-state` is an ordinary attribute (divergence D-2) carrying\n\t\t\t\t\t\t`surfaceState`'s TWO values — never `\"opening\"` (convention C-5).\n\t\t\t\t\t\t`inert` is not written by hand either: `usePresence` sets it on\n\t\t\t\t\t\tevery registered node for the whole exit, which is exactly what\n\t\t\t\t\t\ta closing modal wants.\n\t\t\t\t\t*/}\n\t\t\t\t\t<div\n\t\t\t\t\t\tref={panelRef}\n\t\t\t\t\t\tid={id}\n\t\t\t\t\t\trole={role}\n\t\t\t\t\t\taria-modal=\"true\"\n\t\t\t\t\t\taria-labelledby={titleId}\n\t\t\t\t\t\taria-describedby={descriptionId}\n\t\t\t\t\t\ttabIndex={-1}\n\t\t\t\t\t\tdata-state={presence.surfaceState}\n\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\"ft-dialog-panel border-border bg-popover text-popover-foreground fixed top-1/2 left-1/2 z-50 flex max-h-[calc(100vh-2rem)] w-[calc(100%-2rem)] max-w-md -translate-x-1/2 -translate-y-1/2 flex-col gap-3 overflow-y-auto rounded-xl border p-5 shadow-2xl\",\n\t\t\t\t\t\t\t\"focus-visible:outline-none\",\n\t\t\t\t\t\t\tpanelClass\n\t\t\t\t\t\t)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{children}\n\t\t\t\t\t</div>\n\t\t\t\t</>\n\t\t\t) : null}\n\t\t</Portal>\n\t);\n});\n\nDialogSurface.displayName = \"DialogSurface\";\n","type":"registry:ui","target":"@ui/fancy/components/dialog/DialogSurface.tsx"},{"path":"react/src/components/dialog/dialog-surface.css","content":"/*\n * The `<style>` block of the source `DialogSurface`, moved to a colocated\n * stylesheet (PORTING.md §Styling rule 2). The compiler scoped that block;\n * plain CSS is global, so the rule stays anchored on `.ft-dialog-panel` —\n * the class the panel already carries in its own `cn()` call, so no\n * port-added anchor was needed here.\n */\n\n/*\n * The brand accent has no semantic Tailwind token, so it is a scoped\n * custom property with a light-dark() fallback — the same shape Button's\n * own `--ft-btn-accent` uses. Named for the category rather than this one\n * component: every overlay in this wave (Dialog, AlertDialog, Popover,\n * Tooltip, ...) reads the same `--ft-overlay-accent`, so retinting one\n * `--ft-accent` up the tree moves every overlay's focus ring together.\n * Set on the panel, not a wrapper: the custom property still inherits to\n * every descendant that reads it (Dialog's close button, for one), and\n * scoping it here means a page with no open dialog pays nothing for it.\n */\n.ft-dialog-panel {\n\t--ft-overlay-accent: var(\n\t\t--ft-accent,\n\t\tlight-dark(oklch(0.5432 0.2528 300.22), oklch(0.604 0.2606 301.75))\n\t);\n}\n\n/*\n * No `@keyframes` and no `@media (prefers-reduced-motion)` block here any\n * more: both surfaces are driven by the shared JS transition, which\n * collapses its own duration to 0 when the user has asked for reduced\n * motion — `runTransition` then skips `element.animate()` entirely and the\n * dialog appears and disappears instantly, with the close staying\n * synchronous.\n *\n * The keyframes this replaced also carried a bug worth naming: the panel's\n * `from` restated `translate(-50%, -50%)` as a `transform`, on a node\n * whose centring comes from Tailwind v4's separate `translate` property.\n * The two composed, so the panel drifted in from half its own size up and\n * to the left. `transform: scale(…)` alone composes after `translate` and\n * scales the panel about its own centre without touching the centring.\n */\n","type":"registry:ui","target":"@ui/fancy/components/dialog/dialog-surface.css"},{"path":"react/src/components/dialog/index.ts","content":"\"use client\";\n\nexport { Dialog } from \"./Dialog\";\nexport type { DialogProps } from \"./Dialog\";\n","type":"registry:ui","target":"@ui/fancy/components/dialog/index.ts"},{"path":"react/src/components/dialog/types.ts","content":"\"use client\";\n\nimport type { ReactNode } from \"react\";\n\n/**\n * The contract for `DialogSurface`, the modal-surface primitive `Dialog`\n * and `AlertDialog` both render through: portal, backdrop scrim, scroll\n * lock, focus trap, and Escape/outside-click dismissal.\n *\n * This is a \"compound\" in the sense that two public components share one\n * internal implementation, not in the `RadioGroup`/`RadioGroupItem` sense\n * of a root publishing context to children a caller composes by hand.\n * `Dialog` and `AlertDialog` diverge enough in public API — a free-form\n * footer versus a fixed confirm/cancel pair, `dismissible` versus a\n * role-appropriate fixed default, `role=\"dialog\"` versus `\"alertdialog\"` —\n * that neither should be built as a thin variant of the other. What they\n * actually share is this plumbing, so that is what is factored out, as\n * plain props passed straight from each root component to `DialogSurface`.\n * There is no arbitrary caller-authored markup in between for a context to\n * reach across — both call sites are this folder's own root components — so\n * passing props directly says the same thing a context would, with one less\n * indirection.\n *\n * Not exported from `index.ts`: `DialogSurface` is an implementation\n * detail, not part of the package's public surface.\n *\n * One field of the source interface is deliberately absent: `ref`. A\n * bindable element reference is spelled `forwardRef` here (PORTING.md\n * §\"API contract\"), so the panel element arrives through the component's\n * ref channel rather than through a prop.\n */\nexport interface DialogSurfaceProps {\n\t/** Whether the surface is mounted and interactive. */\n\topen: boolean;\n\t/** The panel's `id` — what the trigger's `aria-controls` points at while the surface is open. */\n\tid?: string;\n\t/** Forwarded verbatim to the panel's `role`. */\n\trole: \"dialog\" | \"alertdialog\";\n\t/** Id of the element that labels the surface, or undefined when there is none to point at. */\n\ttitleId?: string;\n\t/** Id of the element that describes the surface, or undefined when there is none to point at. */\n\tdescriptionId?: string;\n\t/** Whether Escape dismisses. Required, not defaulted — every caller must make this call explicitly. */\n\tescape: boolean;\n\t/** Whether an outside pointerdown dismisses. Required for the same reason as `escape`. */\n\toutsideClick: boolean;\n\t/** Called for every dismiss trigger this surface owns (Escape, outside click). */\n\tonDismiss: () => void;\n\t/** Element to focus once the trap activates. Defaults to the first focusable descendant when omitted. */\n\tinitialFocus?: HTMLElement | null;\n\t/**\n\t * Where to send focus back on close if the element that had it before\n\t * the surface opened is no longer in the document by then (removed by a\n\t * re-render, a list row disappearing, a trigger inside a reordering\n\t * list). Forwarded to `useFocusTrap`'s `fallbackFocus` — see that file's\n\t * doc comment for the full fallback chain. Both `Dialog` and\n\t * `AlertDialog` pass their own trigger wrapper here.\n\t */\n\tfallbackFocus?: () => HTMLElement | null | undefined;\n\t/** Elements an outside pointerdown must ignore — typically the trigger that opened this surface. */\n\texclude?: () => (HTMLElement | null)[];\n\t/** Extra classes merged onto the panel element. */\n\tpanelClass?: string;\n\t/** The panel's content. */\n\tchildren?: ReactNode;\n}\n","type":"registry:ui","target":"@ui/fancy/components/dialog/types.ts"}]}