@hjmds/react 1.1.1 → 1.3.4

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 (203) hide show
  1. package/dist/actions.d.ts.map +1 -1
  2. package/dist/actions.js +12 -6
  3. package/dist/actions.js.map +1 -1
  4. package/dist/advanced-display.d.ts +21 -0
  5. package/dist/advanced-display.d.ts.map +1 -1
  6. package/dist/advanced-display.js +11 -0
  7. package/dist/advanced-display.js.map +1 -1
  8. package/dist/advanced-navigation.d.ts +2 -23
  9. package/dist/advanced-navigation.d.ts.map +1 -1
  10. package/dist/advanced-navigation.js +3 -24
  11. package/dist/advanced-navigation.js.map +1 -1
  12. package/dist/agreement.d.ts +20 -0
  13. package/dist/agreement.d.ts.map +1 -0
  14. package/dist/agreement.js +29 -0
  15. package/dist/agreement.js.map +1 -0
  16. package/dist/anchor.d.ts +24 -0
  17. package/dist/anchor.d.ts.map +1 -0
  18. package/dist/anchor.js +110 -0
  19. package/dist/anchor.js.map +1 -0
  20. package/dist/asset.d.ts +40 -0
  21. package/dist/asset.d.ts.map +1 -0
  22. package/dist/asset.js +26 -0
  23. package/dist/asset.js.map +1 -0
  24. package/dist/auth-screen.d.ts +29 -0
  25. package/dist/auth-screen.d.ts.map +1 -0
  26. package/dist/auth-screen.js +26 -0
  27. package/dist/auth-screen.js.map +1 -0
  28. package/dist/bottom-cta.d.ts +32 -0
  29. package/dist/bottom-cta.d.ts.map +1 -0
  30. package/dist/bottom-cta.js +30 -0
  31. package/dist/bottom-cta.js.map +1 -0
  32. package/dist/bottom-info.d.ts +16 -0
  33. package/dist/bottom-info.d.ts.map +1 -0
  34. package/dist/bottom-info.js +12 -0
  35. package/dist/bottom-info.js.map +1 -0
  36. package/dist/breadcrumb.d.ts +9 -0
  37. package/dist/breadcrumb.d.ts.map +1 -0
  38. package/dist/breadcrumb.js +12 -0
  39. package/dist/breadcrumb.js.map +1 -0
  40. package/dist/calendar.d.ts +30 -0
  41. package/dist/calendar.d.ts.map +1 -0
  42. package/dist/calendar.js +69 -0
  43. package/dist/calendar.js.map +1 -0
  44. package/dist/carousel.d.ts +20 -0
  45. package/dist/carousel.d.ts.map +1 -0
  46. package/dist/carousel.js +70 -0
  47. package/dist/carousel.js.map +1 -0
  48. package/dist/clipboard.d.ts +33 -0
  49. package/dist/clipboard.d.ts.map +1 -0
  50. package/dist/clipboard.js +40 -0
  51. package/dist/clipboard.js.map +1 -0
  52. package/dist/collapsible.d.ts +11 -0
  53. package/dist/collapsible.d.ts.map +1 -0
  54. package/dist/collapsible.js +18 -0
  55. package/dist/collapsible.js.map +1 -0
  56. package/dist/command-palette.d.ts +25 -0
  57. package/dist/command-palette.d.ts.map +1 -0
  58. package/dist/command-palette.js +82 -0
  59. package/dist/command-palette.js.map +1 -0
  60. package/dist/context-menu.d.ts +12 -0
  61. package/dist/context-menu.d.ts.map +1 -0
  62. package/dist/context-menu.js +111 -0
  63. package/dist/context-menu.js.map +1 -0
  64. package/dist/data-table.d.ts +30 -0
  65. package/dist/data-table.d.ts.map +1 -0
  66. package/dist/data-table.js +57 -0
  67. package/dist/data-table.js.map +1 -0
  68. package/dist/date-picker.d.ts +3 -1
  69. package/dist/date-picker.d.ts.map +1 -1
  70. package/dist/date-picker.js +16 -31
  71. package/dist/date-picker.js.map +1 -1
  72. package/dist/date-range.d.ts +24 -0
  73. package/dist/date-range.d.ts.map +1 -0
  74. package/dist/date-range.js +29 -0
  75. package/dist/date-range.js.map +1 -0
  76. package/dist/display.d.ts +16 -0
  77. package/dist/display.d.ts.map +1 -1
  78. package/dist/display.js +13 -1
  79. package/dist/display.js.map +1 -1
  80. package/dist/evidence.d.ts +496 -1
  81. package/dist/evidence.d.ts.map +1 -1
  82. package/dist/evidence.js +48 -2
  83. package/dist/evidence.js.map +1 -1
  84. package/dist/feedback.d.ts +18 -2
  85. package/dist/feedback.d.ts.map +1 -1
  86. package/dist/feedback.js +10 -2
  87. package/dist/feedback.js.map +1 -1
  88. package/dist/floating-action-button.d.ts +32 -0
  89. package/dist/floating-action-button.d.ts.map +1 -0
  90. package/dist/floating-action-button.js +58 -0
  91. package/dist/floating-action-button.js.map +1 -0
  92. package/dist/forms.d.ts +3 -3
  93. package/dist/forms.d.ts.map +1 -1
  94. package/dist/forms.js +18 -7
  95. package/dist/forms.js.map +1 -1
  96. package/dist/heading.d.ts +12 -0
  97. package/dist/heading.d.ts.map +1 -0
  98. package/dist/heading.js +25 -0
  99. package/dist/heading.js.map +1 -0
  100. package/dist/index.d.ts +36 -2
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/index.js +36 -2
  103. package/dist/index.js.map +1 -1
  104. package/dist/layout.d.ts +1 -1
  105. package/dist/mentions.d.ts +28 -0
  106. package/dist/mentions.d.ts.map +1 -0
  107. package/dist/mentions.js +84 -0
  108. package/dist/mentions.js.map +1 -0
  109. package/dist/menubar.d.ts +11 -0
  110. package/dist/menubar.d.ts.map +1 -0
  111. package/dist/menubar.js +137 -0
  112. package/dist/menubar.js.map +1 -0
  113. package/dist/modal.d.ts +67 -0
  114. package/dist/modal.d.ts.map +1 -0
  115. package/dist/modal.js +307 -0
  116. package/dist/modal.js.map +1 -0
  117. package/dist/overlay-stack.d.ts +28 -0
  118. package/dist/overlay-stack.d.ts.map +1 -0
  119. package/dist/overlay-stack.js +47 -0
  120. package/dist/overlay-stack.js.map +1 -0
  121. package/dist/overlays.d.ts +26 -43
  122. package/dist/overlays.d.ts.map +1 -1
  123. package/dist/overlays.js +62 -302
  124. package/dist/overlays.js.map +1 -1
  125. package/dist/pagination.d.ts +17 -0
  126. package/dist/pagination.d.ts.map +1 -0
  127. package/dist/pagination.js +21 -0
  128. package/dist/pagination.js.map +1 -0
  129. package/dist/popover.d.ts +25 -0
  130. package/dist/popover.d.ts.map +1 -0
  131. package/dist/popover.js +226 -0
  132. package/dist/popover.js.map +1 -0
  133. package/dist/portal.d.ts +5 -1
  134. package/dist/portal.d.ts.map +1 -1
  135. package/dist/portal.js +24 -1
  136. package/dist/portal.js.map +1 -1
  137. package/dist/provider-button.d.ts +21 -0
  138. package/dist/provider-button.d.ts.map +1 -0
  139. package/dist/provider-button.js +26 -0
  140. package/dist/provider-button.js.map +1 -0
  141. package/dist/provider.d.ts +13 -1
  142. package/dist/provider.d.ts.map +1 -1
  143. package/dist/provider.js +12 -2
  144. package/dist/provider.js.map +1 -1
  145. package/dist/selection.d.ts +5 -1
  146. package/dist/selection.d.ts.map +1 -1
  147. package/dist/selection.js +2 -2
  148. package/dist/selection.js.map +1 -1
  149. package/dist/side-panel.d.ts +34 -0
  150. package/dist/side-panel.d.ts.map +1 -0
  151. package/dist/side-panel.js +95 -0
  152. package/dist/side-panel.js.map +1 -0
  153. package/dist/sidebar.d.ts +22 -0
  154. package/dist/sidebar.d.ts.map +1 -0
  155. package/dist/sidebar.js +29 -0
  156. package/dist/sidebar.js.map +1 -0
  157. package/dist/skip-nav.d.ts +12 -0
  158. package/dist/skip-nav.d.ts.map +1 -0
  159. package/dist/skip-nav.js +29 -0
  160. package/dist/skip-nav.js.map +1 -0
  161. package/dist/splitter.d.ts +45 -0
  162. package/dist/splitter.d.ts.map +1 -0
  163. package/dist/splitter.js +116 -0
  164. package/dist/splitter.js.map +1 -0
  165. package/dist/styles.css +1105 -26
  166. package/dist/supplemental-display.d.ts +12 -0
  167. package/dist/supplemental-display.d.ts.map +1 -1
  168. package/dist/supplemental-display.js +6 -2
  169. package/dist/supplemental-display.js.map +1 -1
  170. package/dist/tags-input.d.ts +60 -0
  171. package/dist/tags-input.d.ts.map +1 -0
  172. package/dist/tags-input.js +90 -0
  173. package/dist/tags-input.js.map +1 -0
  174. package/dist/text-formats.d.ts +16 -0
  175. package/dist/text-formats.d.ts.map +1 -0
  176. package/dist/text-formats.js +24 -0
  177. package/dist/text-formats.js.map +1 -0
  178. package/dist/toast.d.ts +1 -1
  179. package/dist/toggle-group.d.ts +13 -0
  180. package/dist/toggle-group.d.ts.map +1 -0
  181. package/dist/toggle-group.js +21 -0
  182. package/dist/toggle-group.js.map +1 -0
  183. package/dist/top-bar.d.ts +27 -0
  184. package/dist/top-bar.d.ts.map +1 -0
  185. package/dist/top-bar.js +30 -0
  186. package/dist/top-bar.js.map +1 -0
  187. package/dist/top.d.ts +15 -0
  188. package/dist/top.d.ts.map +1 -0
  189. package/dist/top.js +18 -0
  190. package/dist/top.js.map +1 -0
  191. package/dist/tour.d.ts +21 -0
  192. package/dist/tour.d.ts.map +1 -0
  193. package/dist/tour.js +122 -0
  194. package/dist/tour.js.map +1 -0
  195. package/dist/transfer-list.d.ts +24 -0
  196. package/dist/transfer-list.d.ts.map +1 -0
  197. package/dist/transfer-list.js +109 -0
  198. package/dist/transfer-list.js.map +1 -0
  199. package/dist/tree.d.ts +29 -0
  200. package/dist/tree.d.ts.map +1 -0
  201. package/dist/tree.js +120 -0
  202. package/dist/tree.js.map +1 -0
  203. package/package.json +178 -3
package/dist/overlays.js CHANGED
@@ -1,309 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { isLargeTextScale } from "@hjmds/design-contracts/components/design-system-provider";
3
2
  import { createAlertDialogSession, getAlertDialogInitialFocus, validateAlertDialogRequest, } from "@hjmds/design-contracts/components/alert-dialog";
4
- import { createSheetLifecycle, sheetBehaviorDefaults, } from "@hjmds/design-contracts/components/sheet";
3
+ import { createSheetLifecycle, resolveNextSheetDetent, sheetBehaviorDefaults, } from "@hjmds/design-contracts/components/sheet";
5
4
  import { resolveTooltipDescriptor, tooltipBehaviorDefaults, } from "@hjmds/design-contracts/components/tooltip";
6
5
  import { dialogRecipe, menuRecipe, sheetRecipe, } from "@hjmds/design-contracts/recipes";
7
6
  import { cloneElement, forwardRef, useCallback, useEffect, useId, useRef, useState, useSyncExternalStore, } from "react";
8
- import { createPortal } from "react-dom";
9
7
  import { Button } from "./actions.js";
10
8
  import { classNames, composeRefs, useControllableState } from "./internal.js";
11
- import { AnchoredPortal, useAnchoredPopup, } from "./portal.js";
12
- import { useOptionalHjmTheme, useTooltipCoordinator } from "./provider.js";
13
- import { createHjmThemeStyle } from "./theme.js";
14
- function containsEventTarget(container, target) {
15
- return target !== null &&
16
- "nodeType" in target &&
17
- container?.contains(target) === true;
18
- }
19
- function HjmPortal({ children, container }) {
20
- const [mounted, setMounted] = useState(false);
21
- const theme = useOptionalHjmTheme();
22
- useEffect(() => setMounted(true), []);
23
- if (!mounted)
24
- return null;
25
- return createPortal(theme ? (_jsx("div", { className: "hjm-root hjm-portal", "data-hjm-portal": "", "data-motion": theme.environment.reducedMotion ? "reduced" : "full", "data-theme": theme.environment.theme, "data-text-scale": theme.environment.textScale, "data-large-text": isLargeTextScale(theme.environment.textScale) ? "true" : undefined, dir: theme.environment.direction, style: createHjmThemeStyle(theme), children: children })) : children, container ?? document.body);
26
- }
27
- function useOpenState({ open, defaultOpen = false, onOpenChange, }) {
28
- const [internalOpen, setInternalOpen] = useState(defaultOpen);
29
- const controlled = open !== undefined;
30
- const currentOpen = controlled ? open : internalOpen;
31
- const pendingRequestRef = useRef(undefined);
32
- useEffect(() => {
33
- if (pendingRequestRef.current === currentOpen) {
34
- pendingRequestRef.current = undefined;
35
- }
36
- }, [currentOpen]);
37
- const changeOpen = useCallback((nextOpen, detail) => {
38
- if (nextOpen === currentOpen ||
39
- pendingRequestRef.current === nextOpen)
40
- return;
41
- pendingRequestRef.current = nextOpen;
42
- if (!controlled)
43
- setInternalOpen(nextOpen);
44
- onOpenChange?.(nextOpen, detail);
45
- if (controlled && nextOpen) {
46
- queueMicrotask(() => {
47
- if (pendingRequestRef.current === true) {
48
- pendingRequestRef.current = undefined;
49
- }
50
- });
51
- }
52
- }, [controlled, currentOpen, onOpenChange]);
53
- return [currentOpen, changeOpen];
54
- }
55
- const focusableSelector = [
56
- "a[href]",
57
- "button:not([disabled])",
58
- "input:not([disabled])",
59
- "select:not([disabled])",
60
- "textarea:not([disabled])",
61
- "[tabindex]:not([tabindex='-1'])",
62
- ].join(",");
63
- function getFocusable(container) {
64
- return [...container.querySelectorAll(focusableSelector)].filter((element) => !element.hidden && element.getAttribute("aria-hidden") !== "true");
65
- }
66
- let bodyLockCount = 0;
67
- let previousBodyOverflow = "";
68
- const activeModalStack = [];
69
- let activeModalOrder = 0;
70
- const isolatedModalBackground = new Map();
71
- let modalIsolationObserver = null;
72
- function getOwnedPopupHosts(modal) {
73
- const ownerId = modal.element.id;
74
- if (ownerId.length === 0)
75
- return [];
76
- return [...document.querySelectorAll("[data-hjm-popup-owner]")]
77
- .filter((host) => host.getAttribute("data-hjm-popup-owner") === ownerId);
78
- }
79
- function modalContainsNode(modal, node) {
80
- return modal.element.contains(node) ||
81
- getOwnedPopupHosts(modal).some((host) => host.contains(node));
82
- }
83
- function getModalFocusable(modal) {
84
- return [
85
- ...getFocusable(modal.element),
86
- ...getOwnedPopupHosts(modal).flatMap((host) => getFocusable(host)),
87
- ];
88
- }
89
- function getModalLayer(priority) {
90
- if (!Number.isSafeInteger(priority)) {
91
- throw new TypeError("modalPriority must be a safe integer");
92
- }
93
- return 1000 + priority;
94
- }
95
- function getTopModal() {
96
- let top;
97
- for (const modal of activeModalStack) {
98
- if (!modal.element.isConnected)
99
- continue;
100
- if (top === undefined ||
101
- modal.priority > top.priority ||
102
- (modal.priority === top.priority && modal.order > top.order))
103
- top = modal;
104
- }
105
- return top;
106
- }
107
- function modalRanksAbove(candidate, reference) {
108
- return candidate.priority > reference.priority ||
109
- (candidate.priority === reference.priority && candidate.order > reference.order);
110
- }
111
- function restoreModalBackground() {
112
- for (const [element, previous] of isolatedModalBackground) {
113
- element.inert = previous.inert;
114
- if (previous.ariaHidden === null)
115
- element.removeAttribute("aria-hidden");
116
- else
117
- element.setAttribute("aria-hidden", previous.ariaHidden);
118
- }
119
- isolatedModalBackground.clear();
120
- }
121
- function isolateModalBackgroundElement(element) {
122
- if (isolatedModalBackground.has(element))
123
- return;
124
- isolatedModalBackground.set(element, {
125
- ariaHidden: element.getAttribute("aria-hidden"),
126
- inert: element.inert,
127
- });
128
- element.inert = true;
129
- element.setAttribute("aria-hidden", "true");
130
- }
131
- /** Keeps only the top modal's ancestor path interactive, including late portals. */
132
- function synchronizeModalBackgroundIsolation() {
133
- restoreModalBackground();
134
- for (let index = activeModalStack.length - 1; index >= 0; index -= 1) {
135
- if (!activeModalStack[index]?.element.isConnected)
136
- activeModalStack.splice(index, 1);
137
- }
138
- const top = getTopModal();
139
- if (!top) {
140
- modalIsolationObserver?.disconnect();
141
- modalIsolationObserver = null;
142
- return;
143
- }
144
- const interactivePath = new Set();
145
- for (const root of [top.element, ...getOwnedPopupHosts(top)]) {
146
- let pathNode = root;
147
- while (pathNode) {
148
- if (pathNode === document.body)
149
- break;
150
- interactivePath.add(pathNode);
151
- pathNode = pathNode.parentElement;
152
- }
153
- }
154
- const inspectedParents = new Set();
155
- for (const pathNode of interactivePath) {
156
- const parent = pathNode.parentElement;
157
- if (!parent || inspectedParents.has(parent))
158
- continue;
159
- inspectedParents.add(parent);
160
- for (const sibling of parent.children) {
161
- if (sibling instanceof HTMLElement && !interactivePath.has(sibling)) {
162
- isolateModalBackgroundElement(sibling);
163
- }
164
- }
165
- }
166
- if (modalIsolationObserver === null) {
167
- modalIsolationObserver = new MutationObserver(() => {
168
- synchronizeModalBackgroundIsolation();
169
- });
170
- modalIsolationObserver.observe(document.body, {
171
- attributes: true,
172
- attributeFilter: ["data-hjm-popup-owner"],
173
- childList: true,
174
- subtree: true,
175
- });
176
- }
177
- }
178
- function lockBodyScroll() {
179
- if (bodyLockCount === 0) {
180
- previousBodyOverflow = document.body.style.overflow;
181
- document.body.style.overflow = "hidden";
182
- }
183
- bodyLockCount += 1;
184
- return () => {
185
- bodyLockCount = Math.max(0, bodyLockCount - 1);
186
- if (bodyLockCount === 0)
187
- document.body.style.overflow = previousBodyOverflow;
188
- };
189
- }
190
- function useModalFocus({ active, priority = 0, contentRef, initialFocusRef, returnFocusRef, fallbackReturnRef, onEscape, }) {
191
- const escapeRef = useRef(onEscape);
192
- escapeRef.current = onEscape;
193
- useEffect(() => {
194
- if (!active)
195
- return;
196
- let retryTimer;
197
- let release;
198
- let cancelled = false;
199
- const activate = () => {
200
- const content = contentRef.current;
201
- if (!content) {
202
- retryTimer = setTimeout(activate, 0);
203
- return;
204
- }
205
- const previouslyFocused = document.activeElement instanceof HTMLElement
206
- ? document.activeElement
207
- : null;
208
- const releaseScroll = lockBodyScroll();
209
- const modal = {
210
- element: content,
211
- order: activeModalOrder += 1,
212
- priority,
213
- };
214
- activeModalStack.push(modal);
215
- synchronizeModalBackgroundIsolation();
216
- const initial = initialFocusRef?.current ?? getFocusable(content)[0] ?? content;
217
- if (getTopModal() === modal)
218
- initial.focus();
219
- const handleKeyDown = (event) => {
220
- if (getTopModal() !== modal)
221
- return;
222
- if (event.key === "Escape") {
223
- event.preventDefault();
224
- escapeRef.current();
225
- return;
226
- }
227
- if (event.key !== "Tab")
228
- return;
229
- const focusable = getModalFocusable(modal);
230
- if (focusable.length === 0) {
231
- event.preventDefault();
232
- content.focus();
233
- return;
234
- }
235
- const first = focusable[0];
236
- const last = focusable.at(-1);
237
- const current = document.activeElement;
238
- if (event.shiftKey && (current === first || !(current instanceof Node) || !modalContainsNode(modal, current))) {
239
- event.preventDefault();
240
- last.focus();
241
- }
242
- else if (!event.shiftKey && (current === last || !(current instanceof Node) || !modalContainsNode(modal, current))) {
243
- event.preventDefault();
244
- first.focus();
245
- }
246
- };
247
- const handleFocusIn = (event) => {
248
- if (getTopModal() !== modal)
249
- return;
250
- if (event.target instanceof Node && !modalContainsNode(modal, event.target)) {
251
- (initialFocusRef?.current ?? getFocusable(content)[0] ?? content).focus();
252
- }
253
- };
254
- document.addEventListener("keydown", handleKeyDown, true);
255
- document.addEventListener("focusin", handleFocusIn, true);
256
- release = () => {
257
- const wasTop = !activeModalStack.some((candidate) => candidate !== modal && candidate.element.isConnected && modalRanksAbove(candidate, modal));
258
- document.removeEventListener("keydown", handleKeyDown, true);
259
- document.removeEventListener("focusin", handleFocusIn, true);
260
- const stackIndex = activeModalStack.lastIndexOf(modal);
261
- if (stackIndex >= 0)
262
- activeModalStack.splice(stackIndex, 1);
263
- synchronizeModalBackgroundIsolation();
264
- releaseScroll();
265
- const returnTarget = returnFocusRef?.current ?? fallbackReturnRef?.current ?? previouslyFocused;
266
- if (wasTop) {
267
- queueMicrotask(() => {
268
- const nextTop = getTopModal();
269
- if (!nextTop) {
270
- returnTarget?.focus();
271
- return;
272
- }
273
- if (returnTarget && modalContainsNode(nextTop, returnTarget))
274
- returnTarget.focus();
275
- else
276
- (getModalFocusable(nextTop)[0] ?? nextTop.element).focus();
277
- });
278
- }
279
- };
280
- if (cancelled)
281
- release();
282
- };
283
- activate();
284
- return () => {
285
- cancelled = true;
286
- if (retryTimer !== undefined)
287
- clearTimeout(retryTimer);
288
- release?.();
289
- };
290
- }, [active, contentRef, fallbackReturnRef, initialFocusRef, priority, returnFocusRef]);
291
- }
292
- function renderTrigger(trigger, triggerRef, open, contentId, popup, onOpen) {
293
- const props = trigger.props;
294
- return cloneElement(trigger, {
295
- ref: composeRefs(props.ref, triggerRef),
296
- "aria-controls": contentId,
297
- "aria-expanded": open,
298
- "aria-haspopup": popup,
299
- onClick: (event) => {
300
- props.onClick?.(event);
301
- if (!event.defaultPrevented && !props.disabled)
302
- onOpen();
303
- },
304
- });
305
- }
306
- export const Dialog = forwardRef(function Dialog({ trigger, title, description, children, footer, size = dialogRecipe.defaults.size, dismissible = dialogRecipe.defaults.dismissible, busy = false, closeLabel, initialFocusRef, returnFocusRef, modalPriority = 0, portalContainer, open: openProp, defaultOpen, onOpenChange, className, }, forwardedRef) {
9
+ import { AnchoredPortal, useAnchoredPopup } from "./portal.js";
10
+ import { useHjmDensityDefault, useTooltipCoordinator } from "./provider.js";
11
+ import { containsEventTarget, getModalLayer, HjmPortal, renderTrigger, useModalFocus, useOpenState, } from "./modal.js";
12
+ export const Dialog = forwardRef(function Dialog({ trigger, title, description, children, footer, size = dialogRecipe.defaults.size, dismissible = dialogRecipe.defaults.dismissible, busy = false, closeLabel, initialFocusRef, returnFocusRef, onDismissComplete, modalPriority = 0, portalContainer, open: openProp, defaultOpen, onOpenChange, className, }, forwardedRef) {
307
13
  const modalLayer = getModalLayer(modalPriority);
308
14
  const [open, changeOpen] = useOpenState({
309
15
  ...(openProp === undefined ? {} : { open: openProp }),
@@ -317,11 +23,52 @@ export const Dialog = forwardRef(function Dialog({ trigger, title, description,
317
23
  const contentId = `${id}-dialog`;
318
24
  const titleId = `${id}-title`;
319
25
  const descriptionId = `${id}-description`;
26
+ const dismissReasonRef = useRef(undefined);
27
+ const wasOpenRef = useRef(open);
28
+ const completeRef = useRef(onDismissComplete);
29
+ completeRef.current = onDismissComplete;
30
+ const openRef = useRef(open);
31
+ openRef.current = open;
320
32
  const requestClose = (reason) => {
321
33
  if (!dismissible || busy)
322
34
  return;
35
+ dismissReasonRef.current = reason;
323
36
  changeOpen(false, { reason });
324
37
  };
38
+ const settle = () => {
39
+ const reason = dismissReasonRef.current ?? "programmatic";
40
+ dismissReasonRef.current = undefined;
41
+ // After the portal is unmounted and `useModalFocus` has restored focus in
42
+ // its own microtask, so the next overlay can open without a 0ms guess.
43
+ queueMicrotask(() => queueMicrotask(() => completeRef.current?.({ reason })));
44
+ };
45
+ useEffect(() => {
46
+ if (open) {
47
+ wasOpenRef.current = true;
48
+ dismissReasonRef.current = undefined;
49
+ return;
50
+ }
51
+ if (!wasOpenRef.current)
52
+ return;
53
+ wasOpenRef.current = false;
54
+ settle();
55
+ }, [open]);
56
+ const epochRef = useRef(0);
57
+ useEffect(() => {
58
+ const epoch = epochRef.current + 1;
59
+ epochRef.current = epoch;
60
+ return () => {
61
+ queueMicrotask(() => {
62
+ // StrictMode re-runs setup right after its probe cleanup; only a real
63
+ // unmount has no later epoch. An open Dialog torn down that way still
64
+ // settles exactly once.
65
+ if (epochRef.current !== epoch || !openRef.current || !wasOpenRef.current)
66
+ return;
67
+ wasOpenRef.current = false;
68
+ settle();
69
+ });
70
+ };
71
+ }, []);
325
72
  useModalFocus({
326
73
  active: open,
327
74
  priority: modalPriority,
@@ -420,7 +167,7 @@ export const AlertDialog = forwardRef(function AlertDialog({ trigger, request, i
420
167
  changeOpen(true, { reason: "trigger" });
421
168
  }), open ? (_jsx(HjmPortal, { ...(portalContainer === undefined ? {} : { container: portalContainer }), children: _jsx("div", { className: "hjm-overlay", "data-kind": "alert-dialog", "data-modal-priority": modalPriority, "data-state": "open", style: { zIndex: modalLayer }, children: _jsxs("div", { ref: composeRefs(contentRef, forwardedRef), id: contentId, role: "alertdialog", "aria-modal": "true", "aria-labelledby": titleId, "aria-describedby": `${descriptionId}${error ? ` ${errorId}` : ""}`, "aria-busy": busy || undefined, tabIndex: -1, className: classNames("hjm-alert-dialog", className), "data-hjm-modal-content": "", "data-size": size, "data-tone": tone, "data-state": busy ? "busy" : error ? "error" : "idle", children: [icon ? _jsx("div", { className: "hjm-alert-dialog__icon", "aria-hidden": "true", children: icon }) : null, _jsx("h2", { id: titleId, className: "hjm-alert-dialog__title", children: request.title }), _jsx("p", { id: descriptionId, className: "hjm-alert-dialog__description", children: request.description }), error ? _jsx("p", { id: errorId, className: "hjm-alert-dialog__error", role: "alert", children: error }) : null, _jsxs("div", { className: "hjm-alert-dialog__actions", children: [request.mode === "confirm" ? (_jsx(Button, { ref: cancelRef, tone: "secondary", disabled: busy || closing, onClick: () => cancel("cancel-action"), children: request.cancelLabel })) : null, _jsx(Button, { ref: confirmRef, tone: tone === "danger" ? "danger" : "primary", loading: busy, disabled: closing, onClick: () => void confirm(), children: request.confirmLabel })] })] }) }) })) : null] }));
422
169
  });
423
- export const Sheet = forwardRef(function Sheet({ trigger, title, description, children, footer, placement = sheetRecipe.defaults.placement, busy = false, dismissPolicy, closeLabel, initialFocusRef, returnFocusRef, onDismissComplete, modalPriority = 0, portalContainer, open: openProp, defaultOpen, onOpenChange, className, }, forwardedRef) {
170
+ export const Sheet = forwardRef(function Sheet({ trigger, title, description, children, footer, placement = sheetRecipe.defaults.placement, detents, activeDetent, onDetentChange, detentLabels, busy = false, dismissPolicy, closeLabel, initialFocusRef, returnFocusRef, onDismissComplete, modalPriority = 0, portalContainer, open: openProp, defaultOpen, onOpenChange, className, }, forwardedRef) {
424
171
  const modalLayer = getModalLayer(modalPriority);
425
172
  const [open, changeOpen] = useOpenState({
426
173
  ...(openProp === undefined ? {} : { open: openProp }),
@@ -494,7 +241,14 @@ export const Sheet = forwardRef(function Sheet({ trigger, title, description, ch
494
241
  return (_jsxs(_Fragment, { children: [trigger === undefined ? null : renderTrigger(trigger, triggerRef, open, contentId, "dialog", () => changeOpen(true, { reason: "trigger" })), open ? (_jsx(HjmPortal, { ...(portalContainer === undefined ? {} : { container: portalContainer }), children: _jsx("div", { className: "hjm-overlay hjm-sheet-positioner", "data-kind": "sheet", "data-modal-priority": modalPriority, "data-placement": placement, "data-state": "open", style: { zIndex: modalLayer }, onMouseDown: (event) => {
495
242
  if (event.target === event.currentTarget)
496
243
  requestClose("outside");
497
- }, children: _jsxs("div", { ref: composeRefs(contentRef, forwardedRef), id: contentId, role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, "aria-describedby": description ? descriptionId : undefined, "aria-busy": busy || undefined, tabIndex: -1, className: classNames("hjm-sheet", className), "data-hjm-modal-content": "", "data-placement": placement, "data-has-footer": footer ? true : undefined, "data-state": busy ? "busy" : "idle", children: [_jsxs("header", { className: "hjm-sheet__header", children: [_jsxs("div", { children: [_jsx("h2", { id: titleId, className: "hjm-sheet__title", children: title }), description ? _jsx("p", { id: descriptionId, className: "hjm-sheet__description", children: description }) : null] }), policy.dismissible ? (_jsx("button", { type: "button", className: "hjm-dialog__close", "aria-label": closeLabel, disabled: busy && !policy.dismissWhileBusy, onClick: () => requestClose("close-action"), children: "\u00D7" })) : null] }), children ? _jsx("div", { className: "hjm-sheet__body", children: children }) : null, footer ? _jsx("footer", { className: "hjm-sheet__footer", children: footer }) : null] }) }) })) : null] }));
244
+ }, children: _jsxs("div", { ref: composeRefs(contentRef, forwardedRef), id: contentId, role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, "aria-describedby": description ? descriptionId : undefined, "aria-busy": busy || undefined, tabIndex: -1, className: classNames("hjm-sheet", className), "data-hjm-modal-content": "", "data-placement": placement, "data-detent": activeDetent, "data-has-footer": footer ? true : undefined, "data-state": busy ? "busy" : "idle", children: [detents && detentLabels && activeDetent ? (_jsx("div", { className: "hjm-sheet__handle-row", children: _jsx("button", { type: "button", className: "hjm-sheet__handle", "aria-label": resolveNextSheetDetent(detents, activeDetent, "expand") === null
245
+ ? detentLabels.collapse
246
+ : detentLabels.expand, onClick: () => {
247
+ const expand = resolveNextSheetDetent(detents, activeDetent, "expand");
248
+ const next = expand ?? resolveNextSheetDetent(detents, activeDetent, "collapse");
249
+ if (next !== null)
250
+ onDetentChange?.(next);
251
+ }, children: _jsx("span", { "aria-hidden": "true", className: "hjm-sheet__handle-bar" }) }) })) : null, _jsxs("header", { className: "hjm-sheet__header", children: [_jsxs("div", { children: [_jsx("h2", { id: titleId, className: "hjm-sheet__title", children: title }), description ? _jsx("p", { id: descriptionId, className: "hjm-sheet__description", children: description }) : null] }), policy.dismissible ? (_jsx("button", { type: "button", className: "hjm-dialog__close", "aria-label": closeLabel, disabled: busy && !policy.dismissWhileBusy, onClick: () => requestClose("close-action"), children: "\u00D7" })) : null] }), children ? _jsx("div", { className: "hjm-sheet__body", children: children }) : null, footer ? _jsx("footer", { className: "hjm-sheet__footer", children: footer }) : null] }) }) })) : null] }));
498
252
  });
499
253
  export const Tooltip = forwardRef(function Tooltip({ trigger, content, placement, align, pointerOpenDelayMs = tooltipBehaviorDefaults.pointerOpenDelayMs, focusOpenDelayMs = tooltipBehaviorDefaults.focusOpenDelayMs, portalContainer, open: openProp, defaultOpen, onOpenChange, className, }, ref) {
500
254
  const descriptor = resolveTooltipDescriptor({
@@ -683,7 +437,13 @@ function validateMenuItems(items, sections, asyncState) {
683
437
  }
684
438
  }
685
439
  export const Menu = forwardRef(function Menu(props, ref) {
686
- const { trigger, label, density = menuRecipe.defaults.density, align = "start", disabled = false, asyncState = { status: "idle" }, onAction, onActionAfterDismiss, portalContainer, open: openProp, defaultOpen, onOpenChange, className, } = props;
440
+ // Unconditional: `??` on the hook call would break the rules of hooks.
441
+ const densityDefault = useHjmDensityDefault({
442
+ comfortable: menuRecipe.defaults.density,
443
+ compact: "compact",
444
+ });
445
+ const { trigger, label, density: densityProp, align = "start", disabled = false, asyncState = { status: "idle" }, onAction, onActionAfterDismiss, portalContainer, open: openProp, defaultOpen, onOpenChange, className, } = props;
446
+ const density = densityProp ?? densityDefault;
687
447
  const sections = props.sections;
688
448
  const items = sections === undefined ? props.items : sections.flatMap((section) => section.items);
689
449
  if (label.trim().length === 0)