@charcuterie/ui 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/ColorSchemeSwitcher/ColorSchemeSwitcher.d.ts +35 -0
  2. package/dist/ColorSchemeSwitcher/ColorSchemeSwitcher.d.ts.map +1 -0
  3. package/dist/ColorSchemeSwitcher/ColorSchemeSwitcher.js +33 -0
  4. package/dist/ColorSchemeSwitcher/ColorSchemeSwitcher.js.map +1 -0
  5. package/dist/ColorSchemeToggle/ColorSchemeToggle.d.ts +54 -0
  6. package/dist/ColorSchemeToggle/ColorSchemeToggle.d.ts.map +1 -0
  7. package/dist/ColorSchemeToggle/ColorSchemeToggle.js +30 -0
  8. package/dist/ColorSchemeToggle/ColorSchemeToggle.js.map +1 -0
  9. package/dist/Combobox/Combobox.d.ts +54 -0
  10. package/dist/Combobox/Combobox.d.ts.map +1 -0
  11. package/dist/Combobox/Combobox.js +216 -0
  12. package/dist/Combobox/Combobox.js.map +1 -0
  13. package/dist/Combobox/ComboboxOption.d.ts +36 -0
  14. package/dist/Combobox/ComboboxOption.d.ts.map +1 -0
  15. package/dist/Combobox/ComboboxOption.js +32 -0
  16. package/dist/Combobox/ComboboxOption.js.map +1 -0
  17. package/dist/Dialog/Dialog.d.ts +47 -0
  18. package/dist/Dialog/Dialog.d.ts.map +1 -0
  19. package/dist/Dialog/Dialog.js +52 -0
  20. package/dist/Dialog/Dialog.js.map +1 -0
  21. package/dist/Lightbox/Lightbox.d.ts +57 -0
  22. package/dist/Lightbox/Lightbox.d.ts.map +1 -0
  23. package/dist/Lightbox/Lightbox.js +50 -0
  24. package/dist/Lightbox/Lightbox.js.map +1 -0
  25. package/dist/Listbox/Listbox.d.ts +56 -0
  26. package/dist/Listbox/Listbox.d.ts.map +1 -0
  27. package/dist/Listbox/Listbox.js +151 -0
  28. package/dist/Listbox/Listbox.js.map +1 -0
  29. package/dist/Listbox/ListboxOption.d.ts +36 -0
  30. package/dist/Listbox/ListboxOption.d.ts.map +1 -0
  31. package/dist/Listbox/ListboxOption.js +42 -0
  32. package/dist/Listbox/ListboxOption.js.map +1 -0
  33. package/dist/Menu/Menu.d.ts.map +1 -1
  34. package/dist/Menu/Menu.js +47 -97
  35. package/dist/Menu/Menu.js.map +1 -1
  36. package/dist/Modal/Modal.d.ts +11 -59
  37. package/dist/Modal/Modal.d.ts.map +1 -1
  38. package/dist/Modal/Modal.js +28 -101
  39. package/dist/Modal/Modal.js.map +1 -1
  40. package/dist/Overlay/OverlayPanel.d.ts +50 -0
  41. package/dist/Overlay/OverlayPanel.d.ts.map +1 -0
  42. package/dist/Overlay/OverlayPanel.js +72 -0
  43. package/dist/Overlay/OverlayPanel.js.map +1 -0
  44. package/dist/Overlay/OverlayStack.d.ts +47 -0
  45. package/dist/Overlay/OverlayStack.d.ts.map +1 -0
  46. package/dist/Overlay/OverlayStack.js +46 -0
  47. package/dist/Overlay/OverlayStack.js.map +1 -0
  48. package/dist/Overlay/SharedBackdrop.d.ts +24 -0
  49. package/dist/Overlay/SharedBackdrop.d.ts.map +1 -0
  50. package/dist/Overlay/SharedBackdrop.js +28 -0
  51. package/dist/Overlay/SharedBackdrop.js.map +1 -0
  52. package/dist/Overlay/lockScrollBehind.d.ts.map +1 -0
  53. package/dist/Overlay/lockScrollBehind.js.map +1 -0
  54. package/dist/Overlay/overlayPanelClass.d.ts +17 -0
  55. package/dist/Overlay/overlayPanelClass.d.ts.map +1 -0
  56. package/dist/Overlay/overlayPanelClass.js +17 -0
  57. package/dist/Overlay/overlayPanelClass.js.map +1 -0
  58. package/dist/Overlay/useAnchoredOverlay.d.ts +97 -0
  59. package/dist/Overlay/useAnchoredOverlay.d.ts.map +1 -0
  60. package/dist/Overlay/useAnchoredOverlay.js +81 -0
  61. package/dist/Overlay/useAnchoredOverlay.js.map +1 -0
  62. package/dist/Popover/Popover.d.ts +16 -24
  63. package/dist/Popover/Popover.d.ts.map +1 -1
  64. package/dist/Popover/Popover.js +32 -68
  65. package/dist/Popover/Popover.js.map +1 -1
  66. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  67. package/dist/Tooltip/Tooltip.js +25 -25
  68. package/dist/Tooltip/Tooltip.js.map +1 -1
  69. package/dist/index.d.ts +13 -1
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +6 -0
  72. package/dist/index.js.map +1 -1
  73. package/dist/mountStory.testHelpers.d.ts +1 -0
  74. package/dist/mountStory.testHelpers.d.ts.map +1 -1
  75. package/dist/mountStory.testHelpers.js +21 -1
  76. package/dist/mountStory.testHelpers.js.map +1 -1
  77. package/package.json +5 -4
  78. package/src/ColorSchemeSwitcher/ColorSchemeSwitcher.mdx +70 -0
  79. package/src/ColorSchemeSwitcher/ColorSchemeSwitcher.stories.tsx +118 -0
  80. package/src/ColorSchemeSwitcher/ColorSchemeSwitcher.test.tsx +53 -0
  81. package/src/ColorSchemeSwitcher/ColorSchemeSwitcher.tsx +90 -0
  82. package/src/ColorSchemeToggle/ColorSchemeToggle.mdx +48 -0
  83. package/src/ColorSchemeToggle/ColorSchemeToggle.stories.tsx +161 -0
  84. package/src/ColorSchemeToggle/ColorSchemeToggle.test.tsx +80 -0
  85. package/src/ColorSchemeToggle/ColorSchemeToggle.tsx +107 -0
  86. package/src/Combobox/Combobox.mdx +71 -0
  87. package/src/Combobox/Combobox.stories.tsx +245 -0
  88. package/src/Combobox/Combobox.test.tsx +289 -0
  89. package/src/Combobox/Combobox.tsx +513 -0
  90. package/src/Combobox/ComboboxOption.tsx +88 -0
  91. package/src/Dialog/Dialog.mdx +87 -0
  92. package/src/Dialog/Dialog.stories.tsx +347 -0
  93. package/src/Dialog/Dialog.test.tsx +312 -0
  94. package/src/Dialog/Dialog.tsx +127 -0
  95. package/src/Field/Field.test.tsx +6 -4
  96. package/src/Lightbox/Lightbox.mdx +63 -0
  97. package/src/Lightbox/Lightbox.stories.tsx +133 -0
  98. package/src/Lightbox/Lightbox.test.tsx +124 -0
  99. package/src/Lightbox/Lightbox.tsx +143 -0
  100. package/src/Listbox/Listbox.mdx +68 -0
  101. package/src/Listbox/Listbox.stories.tsx +205 -0
  102. package/src/Listbox/Listbox.test.tsx +198 -0
  103. package/src/Listbox/Listbox.tsx +295 -0
  104. package/src/Listbox/ListboxOption.tsx +104 -0
  105. package/src/Menu/Menu.mdx +11 -13
  106. package/src/Menu/Menu.test.tsx +47 -38
  107. package/src/Menu/Menu.tsx +81 -142
  108. package/src/Modal/Modal.mdx +29 -82
  109. package/src/Modal/Modal.stories.tsx +55 -188
  110. package/src/Modal/Modal.test.tsx +44 -177
  111. package/src/Modal/Modal.tsx +54 -204
  112. package/src/Overlay/OverlayPanel.tsx +195 -0
  113. package/src/Overlay/OverlayStack.test.tsx +235 -0
  114. package/src/Overlay/OverlayStack.tsx +132 -0
  115. package/src/Overlay/SharedBackdrop.tsx +40 -0
  116. package/src/Overlay/overlayPanelClass.ts +17 -0
  117. package/src/Overlay/useAnchoredOverlay.ts +197 -0
  118. package/src/Popover/Popover.mdx +17 -20
  119. package/src/Popover/Popover.stories.tsx +33 -0
  120. package/src/Popover/Popover.test.tsx +68 -32
  121. package/src/Popover/Popover.tsx +56 -95
  122. package/src/Tooltip/Tooltip.mdx +12 -9
  123. package/src/Tooltip/Tooltip.test.tsx +17 -19
  124. package/src/Tooltip/Tooltip.tsx +35 -32
  125. package/src/icons.storyHelpers.tsx +22 -0
  126. package/src/index.ts +22 -4
  127. package/src/mountStory.testHelpers.ts +25 -1
  128. package/src/sourceRules.test.ts +26 -1
  129. package/src/storyControls.test.ts +25 -12
  130. package/dist/Modal/lockScrollBehind.d.ts.map +0 -1
  131. package/dist/Modal/lockScrollBehind.js.map +0 -1
  132. /package/dist/{Modal → Overlay}/lockScrollBehind.d.ts +0 -0
  133. /package/dist/{Modal → Overlay}/lockScrollBehind.js +0 -0
  134. /package/src/{Modal → Overlay}/lockScrollBehind.ts +0 -0
package/dist/Menu/Menu.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { selectTabIndex, useClonedChild, useRovingFocus, } from "@charcuterie/logic";
3
- import { autoUpdate, FloatingFocusManager, flip, offset, shift, useDismiss, useFloating, useInteractions, useRole, } from "@floating-ui/react";
2
+ import { selectTabIndex, useRovingFocus, } from "@charcuterie/logic";
3
+ import { FloatingFocusManager, FloatingPortal, } from "@floating-ui/react";
4
4
  import { useEffect, useRef } from "react";
5
+ import { PANEL_SURFACE_CLASS } from "../Overlay/overlayPanelClass.js";
6
+ import { useAnchoredOverlay } from "../Overlay/useAnchoredOverlay.js";
5
7
  import { toClassName } from "../toClassName.js";
6
8
  import { MenuAction } from "./MenuAction.js";
7
9
  /**
@@ -66,49 +68,17 @@ import { MenuAction } from "./MenuAction.js";
66
68
  */
67
69
  export const Menu = ({ className, isVisible, items, onDismiss, placement = "bottom-start", trigger, }) => {
68
70
  const itemElements = useRef(new Map());
69
- const panelElement = useRef(null);
70
71
  const focus = useRovingFocus();
71
72
  const { activeValue, registeredValues, setActiveValue } = focus;
72
73
  const [firstValue] = registeredValues;
73
- const { context, floatingStyles, refs } = useFloating({
74
- open: isVisible,
75
- onOpenChange: (isNextVisible) => {
76
- if (!isNextVisible) {
77
- onDismiss();
78
- }
79
- },
74
+ const { clonedTrigger, context, floatingStyles, getFloatingProps, setFloating, } = useAnchoredOverlay({
75
+ isVisible,
76
+ offsetValue: 4,
77
+ onDismiss,
80
78
  placement,
81
- middleware: [offset(4), flip(), shift({ padding: 8 })],
82
- strategy: "fixed",
83
- whileElementsMounted: autoUpdate,
84
- });
85
- const { getFloatingProps, getReferenceProps } = useInteractions([
86
- useDismiss(context),
87
- useRole(context, { role: "menu" }),
88
- ]);
89
- const clonedTrigger = useClonedChild(trigger, {
90
- ...getReferenceProps(),
91
- ref: refs.setReference,
79
+ role: "menu",
80
+ trigger,
92
81
  });
93
- /**
94
- * The other half of the `isConnected` guard below. If the ref
95
- * fired while the node was still detached, nothing has shown the
96
- * popover — and a `[popover]` that was never shown is
97
- * `display: none`, so the menu renders, passes typecheck, and is
98
- * invisible to `getByRole` and to a user alike.
99
- *
100
- * Effects run after the tree is connected, so this is the
101
- * backstop. It is idempotent: `:popover-open` is the same question
102
- * the ref asks.
103
- */
104
- useEffect(() => {
105
- const panel = panelElement.current;
106
- if (isVisible &&
107
- panel?.isConnected &&
108
- !panel.matches(":popover-open")) {
109
- panel.showPopover();
110
- }
111
- }, [isVisible]);
112
82
  /**
113
83
  * Opening **makes** a member active, and this is the step the
114
84
  * first version was missing.
@@ -137,63 +107,43 @@ export const Menu = ({ className, isVisible, items, onDismiss, placement = "bott
137
107
  }
138
108
  itemElements.current.get(activeValue)?.focus();
139
109
  }, [activeValue, isVisible]);
140
- return (_jsxs(_Fragment, { children: [clonedTrigger, isVisible ? (_jsx(FloatingFocusManager, { context: context,
141
- // `false`, so Tab leaves the menu and lands on the next
142
- // control in the page — which is what closes it, through
143
- // `useDismiss`. A trapped menu is a dialog.
144
- modal: false,
145
- // Focus is moved by the effect above, to whichever item
146
- // `RovingFocus` made active. Letting the manager also aim
147
- // it is two owners for one caret.
148
- initialFocus: -1, children: _jsx("div", { ...getFloatingProps(), className: toClassName("inset-auto m-0 flex min-w-48 flex-col gap-0.5 rounded-md border border-border-default bg-surface-overlay p-1 shadow-medium", className), onKeyDown: (keyEvent) => {
149
- const commands = {
150
- ArrowDown: focus.next,
151
- ArrowUp: focus.previous,
152
- End: focus.last,
153
- Home: focus.first,
154
- };
155
- const command = commands[keyEvent.key];
156
- if (command) {
157
- // Arrow keys scroll the page by default, and a menu
158
- // that scrolls the document behind it while moving
159
- // focus is the hand-rolled version of this
160
- // component.
161
- keyEvent.preventDefault();
162
- command();
163
- }
164
- }, popover: "manual", ref: (node) => {
165
- refs.setFloating(node);
166
- panelElement.current = node;
167
- // A ref callback rather than an effect, for the same
168
- // reason as `Popover`: a `[popover]` is
169
- // `display: none` until shown, and every child effect
170
- // — including the focus move above — would otherwise
171
- // be aiming at a hidden element.
172
- //
173
- // `isConnected` is the guard `Popover` does not need
174
- // and this does. `FloatingFocusManager` renders a pair
175
- // of focus guards around its child, so React commits
176
- // this subtree in an order where the ref can fire
177
- // while the node is still detached — and
178
- // `showPopover()` on a disconnected element throws
179
- // `InvalidStateError`, which surfaces as an unhandled
180
- // rejection rather than as a failing assertion.
181
- if (node?.isConnected &&
182
- !node.matches(":popover-open")) {
183
- node.showPopover();
184
- }
185
- },
186
- // Duplicated from `getFloatingProps()` on purpose, and
187
- // the two cannot differ — `useRole(context, { role:
188
- // "menu" })` above is where it comes from. Stated here
189
- // because a linter cannot see a role through a spread.
190
- role: "menu", style: floatingStyles, children: items.map((item) => (_jsx(MenuAction, { item: item, onDismiss: onDismiss, register: focus.register, tabIndex: selectTabIndex(focus, item.key), trackElement: (key, element) => {
191
- if (element) {
192
- itemElements.current.set(key, element);
193
- }
194
- else {
195
- itemElements.current.delete(key);
110
+ return (_jsxs(_Fragment, { children: [clonedTrigger, isVisible ? (_jsx(FloatingPortal, { children: _jsx(FloatingFocusManager, { context: context,
111
+ // `false`, so Tab leaves the menu and lands on the next
112
+ // control in the page — which is what closes it, through
113
+ // `useDismiss`. A trapped menu is a dialog.
114
+ modal: false,
115
+ // Focus is moved by the effect above, to whichever item
116
+ // `RovingFocus` made active. Letting the manager also aim
117
+ // it is two owners for one caret.
118
+ initialFocus: -1, children: _jsx("div", { ...getFloatingProps(), className: toClassName(PANEL_SURFACE_CLASS, "z-[var(--layer-modal)] flex min-w-48 flex-col gap-0.5 p-1", className), onKeyDown: (keyEvent) => {
119
+ const commands = {
120
+ ArrowDown: focus.next,
121
+ ArrowUp: focus.previous,
122
+ End: focus.last,
123
+ Home: focus.first,
124
+ };
125
+ const command = commands[keyEvent.key];
126
+ if (command) {
127
+ // Arrow keys scroll the page by default, and a menu
128
+ // that scrolls the document behind it while moving
129
+ // focus is the hand-rolled version of this
130
+ // component.
131
+ keyEvent.preventDefault();
132
+ command();
196
133
  }
197
- } }, item.key))) }) })) : null] }));
134
+ }, ref: setFloating,
135
+ // Duplicated from `getFloatingProps()` on purpose, and
136
+ // the two cannot differ — `useRole(context, { role:
137
+ // "menu" })` in the hook is where it comes from. Stated
138
+ // here because a linter cannot see a role through a
139
+ // spread.
140
+ role: "menu", style: floatingStyles, children: items.map((item) => (_jsx(MenuAction, { item: item, onDismiss: onDismiss, register: focus.register, tabIndex: selectTabIndex(focus, item.key), trackElement: (key, element) => {
141
+ if (element) {
142
+ itemElements.current.set(key, element);
143
+ }
144
+ else {
145
+ itemElements.current.delete(key);
146
+ }
147
+ } }, item.key))) }) }) })) : null] }));
198
148
  };
199
149
  //# sourceMappingURL=Menu.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Menu.js","sourceRoot":"","sources":["../../src/Menu/Menu.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Menu.js","sourceRoot":"","sources":["../../src/Menu/Menu.tsx"],"names":[],"mappings":"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"}
@@ -1,66 +1,18 @@
1
- import type { ComponentPropsWithRef, ReactNode } from "react";
2
- export type ModalSize = "full" | "lg" | "md" | "sm" | "xl";
3
- export type ModalProps = Omit<ComponentPropsWithRef<"dialog">, "onClose" | "open"> & {
4
- /** The footer row. Buttons, usually. */
5
- footer?: ReactNode;
6
- heading: string;
7
- headingLevel?: 2 | 3 | 4;
8
- /**
9
- * Escape and a backdrop press both close. Off for the one
10
- * honest case — a destructive confirm the user has to answer —
11
- * and off means Escape is *cancelled* rather than ignored, so
12
- * the browser cannot close a dialog the app still owns.
13
- */
1
+ import type { ReactNode } from "react";
2
+ export type ModalProps = {
3
+ "aria-label"?: string;
4
+ "aria-labelledby"?: string;
5
+ children: ReactNode;
6
+ className?: string;
7
+ /** Escape and a backdrop press both close. */
14
8
  isDismissable?: boolean;
15
9
  isVisible: boolean;
16
10
  /**
17
- * Every route out lands here: the close button, Escape, a
18
- * backdrop press. Wire it to a `useVisibility()`'s `hide`.
11
+ * Every route out lands here: Escape, a backdrop press. Wire it to a
12
+ * `useVisibility()`'s `hide`.
19
13
  */
20
14
  onClose: () => void;
21
- size?: ModalSize;
15
+ role?: "alertdialog" | "dialog";
22
16
  };
23
- /**
24
- * Four repos, and **plex-channels and ripdeck independently
25
- * converged on native `<dialog>`** — which is the design here
26
- * rather than a shortcut. `showModal()` gives a focus trap,
27
- * Escape, `::backdrop`, `inert` on everything behind it, and the
28
- * **top layer**, from the platform, correctly, in every browser
29
- * the fleet runs.
30
- *
31
- * What is not free — and the reason a component exists at all —
32
- * is that a native `<dialog>` is a *self-owning* control: call
33
- * `showModal()` and it decides it is open. That is the same
34
- * conflict Radix has, and it gets the same answer as everywhere
35
- * else in this library: **`isVisible` is the truth, and an effect
36
- * makes the element agree with it.** Nothing here reads
37
- * `dialogElement.open` as state; it is read only to avoid calling
38
- * a method that would throw.
39
- *
40
- * ### Why it is not portalled
41
- *
42
- * The top layer is a *paint* concept: a `<dialog>` renders above
43
- * everything while staying exactly where it is in the DOM. So it
44
- * needs no portal — and that matters well past tidiness, because a
45
- * portal moves the node to `document.body`, out of whatever
46
- * element a test or an agent scoped its queries to.
47
- * `canvas.getByRole("dialog")` finds this one. It would not find a
48
- * portalled one, and the story would have to reach for `screen`
49
- * and lose its scoping.
50
- *
51
- * ### `open:flex`, not `flex`
52
- *
53
- * A closed `<dialog>` is hidden by a UA rule that any
54
- * `display: flex` of ours would beat — so the modal would render
55
- * permanently, in the page flow, with no error. The layout is
56
- * therefore behind Tailwind's `open:` variant, which is the
57
- * `[open]` attribute the browser manages.
58
- *
59
- * ### The accessible name
60
- *
61
- * `heading` is required and is a string, not a `ReactNode`. A
62
- * dialog with no name is announced as "dialog" and nothing else,
63
- * which is what every hand-rolled modal in the inventory does.
64
- */
65
- export declare const Modal: ({ children, className, footer, heading, headingLevel, isDismissable, isVisible, onClose, size, ...dialogProps }: ModalProps) => ReactNode;
17
+ export declare const Modal: ({ "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, children, className, isDismissable, isVisible, onClose, role, }: ModalProps) => ReactNode;
66
18
  //# sourceMappingURL=Modal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../src/Modal/Modal.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,qBAAqB,EACrB,SAAS,EACV,MAAM,OAAO,CAAA;AAOd,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAE1D,MAAM,MAAM,UAAU,GAAG,IAAI,CAC3B,qBAAqB,CAAC,QAAQ,CAAC,EAC/B,SAAS,GAAG,MAAM,CACnB,GAAG;IACF,wCAAwC;IACxC,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACxB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB,CAAA;AAqBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,eAAO,MAAM,KAAK,GAAI,iHAWnB,UAAU,KAAG,SAsHf,CAAA"}
1
+ {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../src/Modal/Modal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,MAAM,MAAM,UAAU,GAAG;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,IAAI,CAAC,EAAE,aAAa,GAAG,QAAQ,CAAA;CAChC,CAAA;AAqCD,eAAO,MAAM,KAAK,GAAI,+HASnB,UAAU,KAAG,SAYf,CAAA"}
@@ -1,111 +1,38 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useUniqueId } from "@charcuterie/logic";
3
- import { useEffect, useRef } from "react";
4
- import { Button } from "../Button/Button.js";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { OverlayPanel } from "../Overlay/OverlayPanel.js";
5
3
  import { toClassName } from "../toClassName.js";
6
- import { lockScrollBehind } from "./lockScrollBehind.js";
7
4
  /**
8
- * `85dvh` rather than `85vh` — on a phone the two differ by the
9
- * address bar, and a dialog whose footer is under it is a dialog
10
- * with no buttons.
11
- */
12
- const SIZE_CLASS = {
13
- sm: "max-h-[85dvh] w-full max-w-sm",
14
- md: "max-h-[85dvh] w-full max-w-md",
15
- lg: "max-h-[85dvh] w-full max-w-2xl",
16
- // M5 added this, because the scale jumped from 42rem straight to
17
- // the whole viewport and rip-deck's capture tail lands in the
18
- // gap: a MakeMKV robot log is fixed-width `MSG:5072,0,1,"…"`
19
- // lines that wrap badly at 42rem, and a full-screen dialog over a
20
- // nine-bay dashboard hides the thing being diagnosed. 56rem is
21
- // what that app's hand-rolled `<dialog>` used before the swap.
22
- xl: "max-h-[85dvh] w-full max-w-4xl",
23
- full: "h-[100dvh] max-h-[100dvh] w-full max-w-full rounded-none",
24
- };
25
- /**
26
- * Four repos, and **plex-channels and ripdeck independently
27
- * converged on native `<dialog>`** — which is the design here
28
- * rather than a shortcut. `showModal()` gives a focus trap,
29
- * Escape, `::backdrop`, `inert` on everything behind it, and the
30
- * **top layer**, from the platform, correctly, in every browser
31
- * the fleet runs.
32
- *
33
- * What is not free — and the reason a component exists at all —
34
- * is that a native `<dialog>` is a *self-owning* control: call
35
- * `showModal()` and it decides it is open. That is the same
36
- * conflict Radix has, and it gets the same answer as everywhere
37
- * else in this library: **`isVisible` is the truth, and an effect
38
- * makes the element agree with it.** Nothing here reads
39
- * `dialogElement.open` as state; it is read only to avoid calling
40
- * a method that would throw.
5
+ * The base layer. Backdrop, dismiss, and portal — and no chrome.
41
6
  *
42
- * ### Why it is not portalled
7
+ * This is the generic modal the library never had while `Modal` was
8
+ * secretly a *dialog*. A dialog is one kind of modal; so are a
9
+ * confirmation, a media lightbox, and anything else that wants a
10
+ * scrim, an Escape, and a click-outside. `Modal` is that foundation,
11
+ * and `Dialog` is `Modal` plus the heading/Close/footer/size chrome
12
+ * the old component shipped.
43
13
  *
44
- * The top layer is a *paint* concept: a `<dialog>` renders above
45
- * everything while staying exactly where it is in the DOM. So it
46
- * needs no portal — and that matters well past tidiness, because a
47
- * portal moves the node to `document.body`, out of whatever
48
- * element a test or an agent scoped its queries to.
49
- * `canvas.getByRole("dialog")` finds this one. It would not find a
50
- * portalled one, and the story would have to reach for `screen`
51
- * and lose its scoping.
14
+ * It renders an `OverlayPanel`: portalled to `document.body` (escaping
15
+ * `overflow: hidden` the top layer could not), focus-trapped, stacked
16
+ * by portal append order, and dismissible only when it is the top of
17
+ * the stack. All `Modal` adds is a sensible surface — a bordered,
18
+ * rounded, elevated box clamped to `85dvh` — that a caller composes
19
+ * over with `className`. `Dialog` replaces the width and layout; a
20
+ * bare `Modal` sizes to its content.
52
21
  *
53
- * ### `open:flex`, not `flex`
22
+ * ### One accessible name, required
54
23
  *
55
- * A closed `<dialog>` is hidden by a UA rule that any
56
- * `display: flex` of ours would beat — so the modal would render
57
- * permanently, in the page flow, with no error. The layout is
58
- * therefore behind Tailwind's `open:` variant, which is the
59
- * `[open]` attribute the browser manages.
24
+ * A `role="dialog"` takes no name from its content, so pass exactly
25
+ * one of `aria-label` / `aria-labelledby`. Missing both is a dev-time
26
+ * warning from `OverlayPanel`, not a throw — the lesson `Spinner`,
27
+ * `Popover`, and the old `Modal` each learned.
60
28
  *
61
- * ### The accessible name
29
+ * ### Stacking is zero-config, until it is not
62
30
  *
63
- * `heading` is required and is a string, not a `ReactNode`. A
64
- * dialog with no name is announced as "dialog" and nothing else,
65
- * which is what every hand-rolled modal in the inventory does.
31
+ * A lone `Modal` renders its own scrim with no setup. Only when modals
32
+ * **stack** — one opened from inside another — does the app need an
33
+ * `OverlayStackProvider` at its root, so the N of them share one scrim
34
+ * and dismiss top-first.
66
35
  */
67
- export const Modal = ({ children, className, footer, heading, headingLevel = 2, isDismissable = true, isVisible, onClose, size = "md", ...dialogProps }) => {
68
- const dialogRef = useRef(null);
69
- const headingId = useUniqueId();
70
- const Heading = `h${headingLevel}`;
71
- useEffect(() => {
72
- const dialogElement = dialogRef.current;
73
- if (!dialogElement) {
74
- return;
75
- }
76
- // Guarded both ways. `showModal()` on an already-open dialog
77
- // throws `InvalidStateError`, and `close()` on a closed one
78
- // fires a spurious `close` event — either turns a re-render
79
- // into a bug.
80
- if (isVisible && !dialogElement.open) {
81
- dialogElement.showModal();
82
- }
83
- if (!isVisible && dialogElement.open) {
84
- dialogElement.close();
85
- }
86
- }, [isVisible]);
87
- useEffect(() => (isVisible ? lockScrollBehind() : undefined), [isVisible]);
88
- return (_jsxs("dialog", { ...dialogProps, "aria-labelledby": headingId, className: toClassName(
89
- // `m-auto` and `p-0` undo the UA stylesheet, which centres
90
- // by margin and adds padding of its own.
91
- "m-auto flex-col overflow-hidden rounded-lg border border-border-default bg-surface-overlay p-0 text-content-primary shadow-high backdrop:bg-scrim open:flex", SIZE_CLASS[size], className), onCancel: (cancelEvent) => {
92
- // Escape. Cancelled rather than ignored when the dialog is
93
- // not dismissable: letting the browser close it would
94
- // leave `isVisible` true and the element shut, which is
95
- // precisely the desync this component exists to prevent.
96
- cancelEvent.preventDefault();
97
- if (isDismissable) {
98
- onClose();
99
- }
100
- }, onClick: (clickEvent) => {
101
- // The backdrop is not an element — a press on it targets
102
- // the `<dialog>` itself, while a press on the content
103
- // targets something inside. Comparing target to
104
- // currentTarget is the whole test.
105
- if (isDismissable &&
106
- clickEvent.target === clickEvent.currentTarget) {
107
- onClose();
108
- }
109
- }, ref: dialogRef, children: [_jsxs("header", { className: "flex shrink-0 items-start justify-between gap-3 border-b border-border-subtle p-4", children: [_jsx(Heading, { className: "font-semibold text-lg leading-tight", id: headingId, children: heading }), isDismissable ? (_jsx(Button, { appearance: "ghost", intent: "neutral", onClick: onClose, size: "sm", children: "Close" })) : null] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-4", children: children }), footer ? (_jsx("footer", { className: "flex shrink-0 flex-wrap justify-end gap-2 border-t border-border-subtle p-4", children: footer })) : null] }));
110
- };
36
+ const MODAL_BOX_CLASS = "flex max-h-[85dvh] flex-col overflow-hidden rounded-lg border border-border-default bg-surface-overlay text-content-primary shadow-high";
37
+ export const Modal = ({ "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, children, className, isDismissable = true, isVisible, onClose, role = "dialog", }) => (_jsx(OverlayPanel, { "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, className: toClassName(MODAL_BOX_CLASS, className), isDismissable: isDismissable, isVisible: isVisible, onClose: onClose, role: role, children: children }));
111
38
  //# sourceMappingURL=Modal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../src/Modal/Modal.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAKhD,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAEzC,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAsB,CAAA;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AA4BxD;;;;GAIG;AACH,MAAM,UAAU,GAA8B;IAC5C,EAAE,EAAE,+BAA+B;IACnC,EAAE,EAAE,+BAA+B;IACnC,EAAE,EAAE,gCAAgC;IACpC,iEAAiE;IACjE,8DAA8D;IAC9D,6DAA6D;IAC7D,kEAAkE;IAClE,+DAA+D;IAC/D,+DAA+D;IAC/D,EAAE,EAAE,gCAAgC;IACpC,IAAI,EAAE,0DAA0D;CACjE,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EACpB,QAAQ,EACR,SAAS,EACT,MAAM,EACN,OAAO,EACP,YAAY,GAAG,CAAC,EAChB,aAAa,GAAG,IAAI,EACpB,SAAS,EACT,OAAO,EACP,IAAI,GAAG,IAAI,EACX,GAAG,WAAW,EACH,EAAa,EAAE;IAC1B,MAAM,SAAS,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAA;IAEjD,MAAM,SAAS,GAAG,WAAW,EAAE,CAAA;IAE/B,MAAM,OAAO,GAAG,IAAI,YAAY,EAAW,CAAA;IAE3C,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,aAAa,GAAG,SAAS,CAAC,OAAO,CAAA;QAEvC,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,OAAM;QACR,CAAC;QAED,6DAA6D;QAC7D,4DAA4D;QAC5D,4DAA4D;QAC5D,cAAc;QACd,IAAI,SAAS,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;YACrC,aAAa,CAAC,SAAS,EAAE,CAAA;QAC3B,CAAC;QAED,IAAI,CAAC,SAAS,IAAI,aAAa,CAAC,IAAI,EAAE,CAAC;YACrC,aAAa,CAAC,KAAK,EAAE,CAAA;QACvB,CAAC;IACH,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAA;IAEf,SAAS,CACP,GAAG,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,EAClD,CAAC,SAAS,CAAC,CACZ,CAAA;IAED,OAAO,CAoBL,qBACM,WAAW,qBACE,SAAS,EAC1B,SAAS,EAAE,WAAW;QACpB,2DAA2D;QAC3D,yCAAyC;QACzC,6JAA6J,EAC7J,UAAU,CAAC,IAAI,CAAC,EAChB,SAAS,CACV,EACD,QAAQ,EAAE,CAAC,WAAW,EAAE,EAAE;YACxB,2DAA2D;YAC3D,sDAAsD;YACtD,wDAAwD;YACxD,yDAAyD;YACzD,WAAW,CAAC,cAAc,EAAE,CAAA;YAE5B,IAAI,aAAa,EAAE,CAAC;gBAClB,OAAO,EAAE,CAAA;YACX,CAAC;QACH,CAAC,EACD,OAAO,EAAE,CAAC,UAAU,EAAE,EAAE;YACtB,yDAAyD;YACzD,sDAAsD;YACtD,gDAAgD;YAChD,mCAAmC;YACnC,IACE,aAAa;gBACb,UAAU,CAAC,MAAM,KAAK,UAAU,CAAC,aAAa,EAC9C,CAAC;gBACD,OAAO,EAAE,CAAA;YACX,CAAC;QACH,CAAC,EACD,GAAG,EAAE,SAAS,aAEd,kBAAQ,SAAS,EAAC,mFAAmF,aACnG,KAAC,OAAO,IACN,SAAS,EAAC,qCAAqC,EAC/C,EAAE,EAAE,SAAS,YAEZ,OAAO,GACA,EAET,aAAa,CAAC,CAAC,CAAC,CACf,KAAC,MAAM,IACL,UAAU,EAAC,OAAO,EAClB,MAAM,EAAC,SAAS,EAChB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAC,IAAI,sBAGF,CACV,CAAC,CAAC,CAAC,IAAI,IACD,EAET,cAAK,SAAS,EAAC,kCAAkC,YAC9C,QAAQ,GACL,EAEL,MAAM,CAAC,CAAC,CAAC,CACR,iBAAQ,SAAS,EAAC,6EAA6E,YAC5F,MAAM,GACA,CACV,CAAC,CAAC,CAAC,IAAI,IACD,CACV,CAAA;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../src/Modal/Modal.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA6B,CAAA;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAkB/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,eAAe,GACnB,yIAAyI,CAAA;AAE3I,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EACpB,YAAY,EAAE,SAAS,EACvB,iBAAiB,EAAE,cAAc,EACjC,QAAQ,EACR,SAAS,EACT,aAAa,GAAG,IAAI,EACpB,SAAS,EACT,OAAO,EACP,IAAI,GAAG,QAAQ,GACJ,EAAa,EAAE,CAAC,CAC3B,KAAC,YAAY,kBACC,SAAS,qBACJ,cAAc,EAC/B,SAAS,EAAE,WAAW,CAAC,eAAe,EAAE,SAAS,CAAC,EAClD,aAAa,EAAE,aAAa,EAC5B,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,IAAI,YAET,QAAQ,GACI,CAChB,CAAA"}
@@ -0,0 +1,50 @@
1
+ import type { ReactNode } from "react";
2
+ /**
3
+ * The portalled, backdrop-backed, focus-trapping panel that `Modal`
4
+ * renders. No chrome — that is `Dialog`'s job — only the four things
5
+ * every backdrop overlay needs: it portals to `document.body`, a
6
+ * scrim stands behind it, focus is trapped inside it, and Escape or an
7
+ * outside press closes it.
8
+ *
9
+ * ### Portalled, and the top layer is gone on purpose
10
+ *
11
+ * This supersedes the `<dialog>.showModal()` top-layer approach. The
12
+ * top layer paints above everything but cannot escape an
13
+ * `overflow: hidden` ancestor's *clipping* in every case the fleet
14
+ * hit; a portal to `document.body` does. The objection the old
15
+ * decision raised — a portal moves the node out of a scoped query —
16
+ * is answered by `useRole` writing `aria-controls`/`aria-labelledby`
17
+ * so the trigger→panel link is followable across the boundary, and by
18
+ * the stories scoping panels to `within(document.body)` while the
19
+ * structural `expectAgentDrivable` needs no change.
20
+ *
21
+ * ### Only the top of the stack is live
22
+ *
23
+ * `useDismiss` (Escape + outside press) is enabled **only when this is
24
+ * the top** of the stack, so an outside press or Escape closes the
25
+ * innermost modal and nothing under it — the same rule the platform's
26
+ * nested `<dialog>` gave for free, without a hand-rolled document
27
+ * keydown racing the focus manager. Everything below the top is
28
+ * `inert`, which is what `expectHiddenFromAgents` asserts and what
29
+ * makes `FloatingFocusManager`'s trap unambiguous.
30
+ *
31
+ * ### One accessible name, required
32
+ *
33
+ * A `role="dialog"` takes no name from its content, so one of
34
+ * `aria-label` / `aria-labelledby` must be present. Missing both is a
35
+ * dev-time warning, not a throw — the lesson `Spinner`, `Popover`, and
36
+ * `Modal` each learned, applied without breaking a render.
37
+ */
38
+ export type OverlayPanelProps = {
39
+ "aria-label"?: string;
40
+ "aria-labelledby"?: string;
41
+ children: ReactNode;
42
+ className?: string;
43
+ /** Escape and an outside press both close. */
44
+ isDismissable?: boolean;
45
+ isVisible: boolean;
46
+ onClose: () => void;
47
+ role?: "alertdialog" | "dialog";
48
+ };
49
+ export declare const OverlayPanel: ({ "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, children, className, isDismissable, isVisible, onClose, role, }: OverlayPanelProps) => ReactNode;
50
+ //# sourceMappingURL=OverlayPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OverlayPanel.d.ts","sourceRoot":"","sources":["../../src/Overlay/OverlayPanel.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAOtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,IAAI,CAAC,EAAE,aAAa,GAAG,QAAQ,CAAA;CAChC,CAAA;AAED,eAAO,MAAM,YAAY,GAAI,+HAS1B,iBAAiB,KAAG,SAsHtB,CAAA"}
@@ -0,0 +1,72 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useLatestRef, useUniqueId, } from "@charcuterie/logic";
3
+ import { FloatingFocusManager, FloatingPortal, useDismiss, useFloating, useInteractions, useRole, } from "@floating-ui/react";
4
+ import { useCallback, useEffect } from "react";
5
+ import { lockScrollBehind } from "./lockScrollBehind.js";
6
+ import { useOverlayStack } from "./OverlayStack.js";
7
+ import { SharedBackdrop } from "./SharedBackdrop.js";
8
+ export const OverlayPanel = ({ "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, children, className, isDismissable = true, isVisible, onClose, role = "dialog", }) => {
9
+ const stack = useOverlayStack();
10
+ const id = useUniqueId();
11
+ // A stable close, so registering it does not churn the stack as the
12
+ // caller's `onClose` closure changes identity between renders.
13
+ const onCloseRef = useLatestRef(onClose);
14
+ const stableOnClose = useCallback(() => {
15
+ onCloseRef.current();
16
+ }, [onCloseRef]);
17
+ const isTop = !stack.isProvided || stack.topId === id;
18
+ const { context, refs } = useFloating({
19
+ open: isVisible,
20
+ onOpenChange: (isNextVisible) => {
21
+ if (!isNextVisible) {
22
+ onClose();
23
+ }
24
+ },
25
+ });
26
+ const { getFloatingProps } = useInteractions([
27
+ useDismiss(context, {
28
+ escapeKey: isDismissable && isTop,
29
+ // The top's outside press is the backdrop click. A per-panel
30
+ // `outsidePress` on a *lower* modal would close it too, so it is
31
+ // scoped to the top — which is also why the scrim needs no click
32
+ // handler of its own.
33
+ outsidePress: isDismissable && isTop,
34
+ }),
35
+ useRole(context, { role }),
36
+ ]);
37
+ const { register, unregister } = stack;
38
+ useEffect(() => {
39
+ if (!isVisible) {
40
+ return;
41
+ }
42
+ register({ id, onClose: stableOnClose });
43
+ return () => {
44
+ unregister(id);
45
+ };
46
+ }, [id, isVisible, register, stableOnClose, unregister]);
47
+ // Ref-counted through `lockScrollBehind`'s module scope, so N
48
+ // stacked panels lock the page once and free it only when the last
49
+ // closes — with or without a provider.
50
+ useEffect(() => (isVisible ? lockScrollBehind() : undefined), [isVisible]);
51
+ useEffect(() => {
52
+ if (isVisible &&
53
+ ariaLabel === undefined &&
54
+ ariaLabelledBy === undefined) {
55
+ console.warn('OverlayPanel: a modal needs an accessible name. Pass `aria-label` or `aria-labelledby` — a `role="dialog"` takes none from its content and is announced as just "dialog".');
56
+ }
57
+ }, [ariaLabel, ariaLabelledBy, isVisible]);
58
+ if (!isVisible) {
59
+ return null;
60
+ }
61
+ return (_jsxs(_Fragment, { children: [stack.isProvided ? null : (_jsx(SharedBackdrop, { isVisible: true })), _jsx(FloatingPortal, { children: _jsx("div", { className: "fixed inset-0 z-[var(--layer-modal)] flex items-center justify-center", children: _jsx(FloatingFocusManager, { context: context,
62
+ // Only the top traps focus; a lower panel is `inert`
63
+ // anyway, and two live traps fight over one caret.
64
+ disabled: !isTop, modal: true, children: _jsx("div", { ...getFloatingProps(), "aria-label": ariaLabel,
65
+ "aria-labelledby": ariaLabelledBy, className: className,
66
+ // The fallback the plan calls for: `inert` on every panel
67
+ // but the top, so a lower modal is unreachable across the
68
+ // portal boundary even where `FloatingFocusManager`'s
69
+ // outside-hiding does not reach a sibling portal.
70
+ inert: isTop ? undefined : true, ref: refs.setFloating, role: role, children: children }) }) }) })] }));
71
+ };
72
+ //# sourceMappingURL=OverlayPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OverlayPanel.js","sourceRoot":"","sources":["../../src/Overlay/OverlayPanel.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import type { ReactNode } from "react";
2
+ /**
3
+ * The ordered stack of open **backdrop** modals, and the one shared
4
+ * scrim that stands behind all of them.
5
+ *
6
+ * ### Why a stack at all
7
+ *
8
+ * A modal must know two things it cannot see for itself: whether it is
9
+ * the **top** of the stack (only the top traps focus and answers
10
+ * Escape / an outside press; everything under it is `inert`), and how
11
+ * **deep** the stack is (the scrim shows while at least one modal is
12
+ * open and hides when the last closes). An `OverlayPanel` registers on
13
+ * open and unregisters on close, and reads `topId`/`depth` back.
14
+ *
15
+ * ### The provider is optional
16
+ *
17
+ * `useOverlayStack()` has a self-hosting default — `isProvided:
18
+ * false`, a no-op register, `depth: 0` — so a lone `Modal` needs no
19
+ * setup: it sees it is unprovided and renders its own scrim, treating
20
+ * itself as the top. Only *stacking* needs `OverlayStackProvider` at
21
+ * the app root, where a single shared scrim and real top-of-stack
22
+ * bookkeeping replace N self-hosted scrims fighting for the same
23
+ * corner.
24
+ */
25
+ export type OverlayStackEntry = {
26
+ id: string;
27
+ onClose: () => void;
28
+ };
29
+ export type OverlayStackValue = {
30
+ depth: number;
31
+ /**
32
+ * `false` in the context default. An `OverlayPanel` reads this to
33
+ * decide whether to render its own scrim (unprovided) or defer to
34
+ * the provider's shared one.
35
+ */
36
+ isProvided: boolean;
37
+ register: (entry: OverlayStackEntry) => void;
38
+ /** Closes the top entry — the imperative twin of an outside press. */
39
+ requestCloseTop: () => void;
40
+ topId: null | string;
41
+ unregister: (id: string) => void;
42
+ };
43
+ export declare const useOverlayStack: () => OverlayStackValue;
44
+ export declare const OverlayStackProvider: ({ children, }: {
45
+ children: ReactNode;
46
+ }) => ReactNode;
47
+ //# sourceMappingURL=OverlayStack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OverlayStack.d.ts","sourceRoot":"","sources":["../../src/Overlay/OverlayStack.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAWtC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,UAAU,EAAE,OAAO,CAAA;IACnB,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAA;IAC5C,sEAAsE;IACtE,eAAe,EAAE,MAAM,IAAI,CAAA;IAC3B,KAAK,EAAE,IAAI,GAAG,MAAM,CAAA;IACpB,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CACjC,CAAA;AAcD,eAAO,MAAM,eAAe,QAAO,iBACF,CAAA;AAEjC,eAAO,MAAM,oBAAoB,GAAI,eAElC;IACD,QAAQ,EAAE,SAAS,CAAA;CACpB,KAAG,SAyDH,CAAA"}