@hjmds/react 1.1.1 → 1.2.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 (199) 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/bottom-cta.d.ts +32 -0
  25. package/dist/bottom-cta.d.ts.map +1 -0
  26. package/dist/bottom-cta.js +30 -0
  27. package/dist/bottom-cta.js.map +1 -0
  28. package/dist/bottom-info.d.ts +16 -0
  29. package/dist/bottom-info.d.ts.map +1 -0
  30. package/dist/bottom-info.js +12 -0
  31. package/dist/bottom-info.js.map +1 -0
  32. package/dist/breadcrumb.d.ts +9 -0
  33. package/dist/breadcrumb.d.ts.map +1 -0
  34. package/dist/breadcrumb.js +12 -0
  35. package/dist/breadcrumb.js.map +1 -0
  36. package/dist/calendar.d.ts +30 -0
  37. package/dist/calendar.d.ts.map +1 -0
  38. package/dist/calendar.js +69 -0
  39. package/dist/calendar.js.map +1 -0
  40. package/dist/carousel.d.ts +20 -0
  41. package/dist/carousel.d.ts.map +1 -0
  42. package/dist/carousel.js +70 -0
  43. package/dist/carousel.js.map +1 -0
  44. package/dist/clipboard.d.ts +33 -0
  45. package/dist/clipboard.d.ts.map +1 -0
  46. package/dist/clipboard.js +40 -0
  47. package/dist/clipboard.js.map +1 -0
  48. package/dist/collapsible.d.ts +11 -0
  49. package/dist/collapsible.d.ts.map +1 -0
  50. package/dist/collapsible.js +18 -0
  51. package/dist/collapsible.js.map +1 -0
  52. package/dist/command-palette.d.ts +25 -0
  53. package/dist/command-palette.d.ts.map +1 -0
  54. package/dist/command-palette.js +82 -0
  55. package/dist/command-palette.js.map +1 -0
  56. package/dist/context-menu.d.ts +12 -0
  57. package/dist/context-menu.d.ts.map +1 -0
  58. package/dist/context-menu.js +111 -0
  59. package/dist/context-menu.js.map +1 -0
  60. package/dist/data-table.d.ts +30 -0
  61. package/dist/data-table.d.ts.map +1 -0
  62. package/dist/data-table.js +57 -0
  63. package/dist/data-table.js.map +1 -0
  64. package/dist/date-picker.d.ts +3 -1
  65. package/dist/date-picker.d.ts.map +1 -1
  66. package/dist/date-picker.js +16 -31
  67. package/dist/date-picker.js.map +1 -1
  68. package/dist/date-range.d.ts +24 -0
  69. package/dist/date-range.d.ts.map +1 -0
  70. package/dist/date-range.js +29 -0
  71. package/dist/date-range.js.map +1 -0
  72. package/dist/display.d.ts +16 -0
  73. package/dist/display.d.ts.map +1 -1
  74. package/dist/display.js +13 -1
  75. package/dist/display.js.map +1 -1
  76. package/dist/evidence.d.ts +481 -1
  77. package/dist/evidence.d.ts.map +1 -1
  78. package/dist/evidence.js +47 -2
  79. package/dist/evidence.js.map +1 -1
  80. package/dist/feedback.d.ts +18 -2
  81. package/dist/feedback.d.ts.map +1 -1
  82. package/dist/feedback.js +10 -2
  83. package/dist/feedback.js.map +1 -1
  84. package/dist/floating-action-button.d.ts +32 -0
  85. package/dist/floating-action-button.d.ts.map +1 -0
  86. package/dist/floating-action-button.js +58 -0
  87. package/dist/floating-action-button.js.map +1 -0
  88. package/dist/forms.d.ts +3 -3
  89. package/dist/forms.d.ts.map +1 -1
  90. package/dist/forms.js +18 -7
  91. package/dist/forms.js.map +1 -1
  92. package/dist/heading.d.ts +12 -0
  93. package/dist/heading.d.ts.map +1 -0
  94. package/dist/heading.js +25 -0
  95. package/dist/heading.js.map +1 -0
  96. package/dist/index.d.ts +35 -2
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +35 -2
  99. package/dist/index.js.map +1 -1
  100. package/dist/layout.d.ts +1 -1
  101. package/dist/mentions.d.ts +28 -0
  102. package/dist/mentions.d.ts.map +1 -0
  103. package/dist/mentions.js +84 -0
  104. package/dist/mentions.js.map +1 -0
  105. package/dist/menubar.d.ts +11 -0
  106. package/dist/menubar.d.ts.map +1 -0
  107. package/dist/menubar.js +137 -0
  108. package/dist/menubar.js.map +1 -0
  109. package/dist/modal.d.ts +67 -0
  110. package/dist/modal.d.ts.map +1 -0
  111. package/dist/modal.js +307 -0
  112. package/dist/modal.js.map +1 -0
  113. package/dist/overlay-stack.d.ts +28 -0
  114. package/dist/overlay-stack.d.ts.map +1 -0
  115. package/dist/overlay-stack.js +47 -0
  116. package/dist/overlay-stack.js.map +1 -0
  117. package/dist/overlays.d.ts +26 -43
  118. package/dist/overlays.d.ts.map +1 -1
  119. package/dist/overlays.js +62 -302
  120. package/dist/overlays.js.map +1 -1
  121. package/dist/pagination.d.ts +17 -0
  122. package/dist/pagination.d.ts.map +1 -0
  123. package/dist/pagination.js +21 -0
  124. package/dist/pagination.js.map +1 -0
  125. package/dist/popover.d.ts +25 -0
  126. package/dist/popover.d.ts.map +1 -0
  127. package/dist/popover.js +226 -0
  128. package/dist/popover.js.map +1 -0
  129. package/dist/portal.d.ts +5 -1
  130. package/dist/portal.d.ts.map +1 -1
  131. package/dist/portal.js +24 -1
  132. package/dist/portal.js.map +1 -1
  133. package/dist/provider-button.d.ts +21 -0
  134. package/dist/provider-button.d.ts.map +1 -0
  135. package/dist/provider-button.js +26 -0
  136. package/dist/provider-button.js.map +1 -0
  137. package/dist/provider.d.ts +13 -1
  138. package/dist/provider.d.ts.map +1 -1
  139. package/dist/provider.js +12 -2
  140. package/dist/provider.js.map +1 -1
  141. package/dist/selection.d.ts +5 -1
  142. package/dist/selection.d.ts.map +1 -1
  143. package/dist/selection.js +2 -2
  144. package/dist/selection.js.map +1 -1
  145. package/dist/side-panel.d.ts +34 -0
  146. package/dist/side-panel.d.ts.map +1 -0
  147. package/dist/side-panel.js +95 -0
  148. package/dist/side-panel.js.map +1 -0
  149. package/dist/sidebar.d.ts +22 -0
  150. package/dist/sidebar.d.ts.map +1 -0
  151. package/dist/sidebar.js +29 -0
  152. package/dist/sidebar.js.map +1 -0
  153. package/dist/skip-nav.d.ts +12 -0
  154. package/dist/skip-nav.d.ts.map +1 -0
  155. package/dist/skip-nav.js +29 -0
  156. package/dist/skip-nav.js.map +1 -0
  157. package/dist/splitter.d.ts +45 -0
  158. package/dist/splitter.d.ts.map +1 -0
  159. package/dist/splitter.js +116 -0
  160. package/dist/splitter.js.map +1 -0
  161. package/dist/styles.css +1063 -26
  162. package/dist/supplemental-display.d.ts +12 -0
  163. package/dist/supplemental-display.d.ts.map +1 -1
  164. package/dist/supplemental-display.js +6 -2
  165. package/dist/supplemental-display.js.map +1 -1
  166. package/dist/tags-input.d.ts +60 -0
  167. package/dist/tags-input.d.ts.map +1 -0
  168. package/dist/tags-input.js +90 -0
  169. package/dist/tags-input.js.map +1 -0
  170. package/dist/text-formats.d.ts +16 -0
  171. package/dist/text-formats.d.ts.map +1 -0
  172. package/dist/text-formats.js +24 -0
  173. package/dist/text-formats.js.map +1 -0
  174. package/dist/toast.d.ts +1 -1
  175. package/dist/toggle-group.d.ts +13 -0
  176. package/dist/toggle-group.d.ts.map +1 -0
  177. package/dist/toggle-group.js +21 -0
  178. package/dist/toggle-group.js.map +1 -0
  179. package/dist/top-bar.d.ts +27 -0
  180. package/dist/top-bar.d.ts.map +1 -0
  181. package/dist/top-bar.js +30 -0
  182. package/dist/top-bar.js.map +1 -0
  183. package/dist/top.d.ts +15 -0
  184. package/dist/top.d.ts.map +1 -0
  185. package/dist/top.js +18 -0
  186. package/dist/top.js.map +1 -0
  187. package/dist/tour.d.ts +21 -0
  188. package/dist/tour.d.ts.map +1 -0
  189. package/dist/tour.js +122 -0
  190. package/dist/tour.js.map +1 -0
  191. package/dist/transfer-list.d.ts +24 -0
  192. package/dist/transfer-list.d.ts.map +1 -0
  193. package/dist/transfer-list.js +109 -0
  194. package/dist/transfer-list.js.map +1 -0
  195. package/dist/tree.d.ts +29 -0
  196. package/dist/tree.d.ts.map +1 -0
  197. package/dist/tree.js +120 -0
  198. package/dist/tree.js.map +1 -0
  199. package/package.json +173 -3
@@ -0,0 +1,11 @@
1
+ import { type MenubarDescriptor } from "@hjmds/design-contracts/components/menubar";
2
+ export type MenubarProps<Key extends string = string, MenuKey extends string = string> = Readonly<{
3
+ descriptor: MenubarDescriptor<Key, MenuKey>;
4
+ openMenuId?: MenuKey | null;
5
+ defaultOpenMenuId?: MenuKey | null;
6
+ onOpenMenuIdChange?: (id: MenuKey | null) => void;
7
+ onAction: (id: Key, menuId: MenuKey) => void;
8
+ className?: string;
9
+ }>;
10
+ export declare function Menubar<Key extends string = string, MenuKey extends string = string>({ descriptor, openMenuId: controlledOpen, defaultOpenMenuId, onOpenMenuIdChange, onAction, className, }: MenubarProps<Key, MenuKey>): import("react").JSX.Element;
11
+ //# sourceMappingURL=menubar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menubar.d.ts","sourceRoot":"","sources":["../src/menubar.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,4CAA4C,CAAC;AAIpD,MAAM,MAAM,YAAY,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,EAAE,OAAO,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC;IAChG,UAAU,EAAE,iBAAiB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC5C,UAAU,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5B,iBAAiB,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACnC,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,KAAK,IAAI,CAAC;IAClD,QAAQ,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC,CAAC;AAEH,wBAAgB,OAAO,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,EAAE,OAAO,SAAS,MAAM,GAAG,MAAM,EAAE,EACpF,UAAU,EACV,UAAU,EAAE,cAAc,EAC1B,iBAAiB,EACjB,kBAAkB,EAClB,QAAQ,EACR,SAAS,GACV,EAAE,YAAY,CAAC,GAAG,EAAE,OAAO,CAAC,+BA0J5B"}
@@ -0,0 +1,137 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { menubarRecipe, resolveMenubarNavigation, validateMenubarDescriptor, } from "@hjmds/design-contracts/components/menubar";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { classNames, useControllableState } from "./internal.js";
5
+ export function Menubar({ descriptor, openMenuId: controlledOpen, defaultOpenMenuId, onOpenMenuIdChange, onAction, className, }) {
6
+ validateMenubarDescriptor(descriptor);
7
+ const [openId, setOpenId] = useControllableState({
8
+ ...(controlledOpen === undefined ? {} : { value: controlledOpen }),
9
+ defaultValue: defaultOpenMenuId ?? null,
10
+ ...(onOpenMenuIdChange === undefined ? {} : { onChange: onOpenMenuIdChange }),
11
+ });
12
+ const enabledMenus = descriptor.menus.filter((menu) => menu.disabled !== true);
13
+ // Roving focus: the bar is one tab stop, so the bar — not each label — owns
14
+ // which menu is reachable by Tab.
15
+ const [focusedId, setFocusedId] = useState(enabledMenus[0]?.id ?? descriptor.menus[0].id);
16
+ const [activeItemIndex, setActiveItemIndex] = useState(0);
17
+ const rootRef = useRef(null);
18
+ const labelRefs = useRef(new Map());
19
+ const openMenu = descriptor.menus.find((menu) => menu.id === openId) ?? null;
20
+ const openItems = openMenu?.items.filter((item) => item.disabled !== true) ?? [];
21
+ useEffect(() => {
22
+ if (openId === null)
23
+ return;
24
+ const onPointerDown = (event) => {
25
+ if (event.target instanceof Node && rootRef.current?.contains(event.target))
26
+ return;
27
+ setOpenId(null);
28
+ };
29
+ document.addEventListener("pointerdown", onPointerDown);
30
+ return () => document.removeEventListener("pointerdown", onPointerDown);
31
+ }, [openId, setOpenId]);
32
+ const focusLabel = (id) => {
33
+ setFocusedId(id);
34
+ labelRefs.current.get(id)?.focus();
35
+ };
36
+ const move = (direction) => {
37
+ const next = resolveMenubarNavigation(descriptor, focusedId, direction);
38
+ focusLabel(next);
39
+ // Left/right while a menu is open moves to the neighbour already open, which
40
+ // is the whole reason this is a bar and not three independent Menus.
41
+ if (openId !== null) {
42
+ setOpenId(next);
43
+ setActiveItemIndex(0);
44
+ }
45
+ };
46
+ return (_jsx("div", { ref: rootRef, role: "menubar", "aria-label": descriptor.accessibilityLabel, className: classNames("hjm-menubar", className), style: {
47
+ "--hjm-menubar-min-height": `${menubarRecipe.minHeight}px`,
48
+ "--hjm-menubar-gap": `${menubarRecipe.gap}px`,
49
+ "--hjm-menubar-padding": `${menubarRecipe.paddingHorizontal}px`,
50
+ "--hjm-menubar-label-padding": `${menubarRecipe.label.paddingHorizontal}px`,
51
+ "--hjm-menubar-label-radius": `${menubarRecipe.label.radius}px`,
52
+ }, onKeyDown: (event) => {
53
+ if (event.key === "ArrowRight") {
54
+ event.preventDefault();
55
+ move("next");
56
+ return;
57
+ }
58
+ if (event.key === "ArrowLeft") {
59
+ event.preventDefault();
60
+ move("previous");
61
+ return;
62
+ }
63
+ if (event.key === "Home" || event.key === "End") {
64
+ event.preventDefault();
65
+ const target = (event.key === "Home" ? enabledMenus[0] : enabledMenus.at(-1))?.id;
66
+ if (target !== undefined) {
67
+ focusLabel(target);
68
+ if (openId !== null) {
69
+ setOpenId(target);
70
+ setActiveItemIndex(0);
71
+ }
72
+ }
73
+ return;
74
+ }
75
+ if (event.key === "Escape" && openId !== null) {
76
+ event.preventDefault();
77
+ setOpenId(null);
78
+ focusLabel(focusedId);
79
+ return;
80
+ }
81
+ if (openId === null) {
82
+ if (event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") {
83
+ event.preventDefault();
84
+ setOpenId(focusedId);
85
+ setActiveItemIndex(0);
86
+ }
87
+ return;
88
+ }
89
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
90
+ event.preventDefault();
91
+ setActiveItemIndex((previous) => {
92
+ const next = event.key === "ArrowDown" ? previous + 1 : previous - 1;
93
+ return (next + openItems.length) % Math.max(openItems.length, 1);
94
+ });
95
+ return;
96
+ }
97
+ if (event.key === "Enter" || event.key === " ") {
98
+ event.preventDefault();
99
+ const item = openItems[activeItemIndex];
100
+ if (item) {
101
+ onAction(item.id, openId);
102
+ // Activation runs an action and leaves nothing selected — unlike Tabs.
103
+ setOpenId(null);
104
+ focusLabel(openId);
105
+ }
106
+ }
107
+ }, children: descriptor.menus.map((menu) => {
108
+ const open = menu.id === openId;
109
+ return (_jsxs("div", { className: "hjm-menubar__menu", children: [_jsx("button", { type: "button", ref: (node) => { if (node)
110
+ labelRefs.current.set(menu.id, node);
111
+ else
112
+ labelRefs.current.delete(menu.id); }, role: "menuitem", "aria-haspopup": "menu", "aria-expanded": open, disabled: menu.disabled, tabIndex: menu.id === focusedId ? 0 : -1, "data-open": open || undefined, className: "hjm-menubar__label", onClick: () => {
113
+ setFocusedId(menu.id);
114
+ setOpenId(open ? null : menu.id);
115
+ setActiveItemIndex(0);
116
+ }, onMouseEnter: () => {
117
+ // Once one menu is open, hovering the bar switches menus — the
118
+ // desktop convention; with nothing open, hover opens nothing.
119
+ if (openId !== null && menu.disabled !== true) {
120
+ setOpenId(menu.id);
121
+ setFocusedId(menu.id);
122
+ setActiveItemIndex(0);
123
+ }
124
+ }, children: menu.label }), open ? (_jsx("div", { role: "menu", "aria-label": menu.label, className: "hjm-menubar__panel", children: menu.items.map((item) => {
125
+ const index = openItems.findIndex((candidate) => candidate.id === item.id);
126
+ return (_jsxs("div", { role: "menuitem", "aria-disabled": item.disabled || undefined, "data-active": index === activeItemIndex && item.disabled !== true ? "" : undefined, "data-tone": item.tone, className: "hjm-menubar__item", onMouseEnter: () => { if (index >= 0)
127
+ setActiveItemIndex(index); }, onClick: () => {
128
+ if (item.disabled)
129
+ return;
130
+ onAction(item.id, menu.id);
131
+ setOpenId(null);
132
+ focusLabel(menu.id);
133
+ }, children: [_jsx("span", { children: item.label }), item.shortcut ? _jsx("kbd", { className: "hjm-menubar__shortcut", children: item.shortcut }) : null] }, item.id));
134
+ }) })) : null] }, menu.id));
135
+ }) }));
136
+ }
137
+ //# sourceMappingURL=menubar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menubar.js","sourceRoot":"","sources":["../src/menubar.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n menubarRecipe,\n resolveMenubarNavigation,\n validateMenubarDescriptor,\n type MenubarDescriptor,\n} from \"@hjmds/design-contracts/components/menubar\";\nimport { useEffect, useRef, useState, type CSSProperties } from \"react\";\nimport { classNames, useControllableState } from \"./internal.js\";\n\nexport type MenubarProps<Key extends string = string, MenuKey extends string = string> = Readonly<{\n descriptor: MenubarDescriptor<Key, MenuKey>;\n openMenuId?: MenuKey | null;\n defaultOpenMenuId?: MenuKey | null;\n onOpenMenuIdChange?: (id: MenuKey | null) => void;\n onAction: (id: Key, menuId: MenuKey) => void;\n className?: string;\n}>;\n\nexport function Menubar<Key extends string = string, MenuKey extends string = string>({\n descriptor,\n openMenuId: controlledOpen,\n defaultOpenMenuId,\n onOpenMenuIdChange,\n onAction,\n className,\n}: MenubarProps<Key, MenuKey>) {\n validateMenubarDescriptor(descriptor);\n const [openId, setOpenId] = useControllableState<MenuKey | null>({\n ...(controlledOpen === undefined ? {} : { value: controlledOpen }),\n defaultValue: defaultOpenMenuId ?? null,\n ...(onOpenMenuIdChange === undefined ? {} : { onChange: onOpenMenuIdChange }),\n });\n const enabledMenus = descriptor.menus.filter((menu) => menu.disabled !== true);\n // Roving focus: the bar is one tab stop, so the bar — not each label — owns\n // which menu is reachable by Tab.\n const [focusedId, setFocusedId] = useState<MenuKey>(enabledMenus[0]?.id ?? descriptor.menus[0]!.id);\n const [activeItemIndex, setActiveItemIndex] = useState(0);\n const rootRef = useRef<HTMLDivElement>(null);\n const labelRefs = useRef(new Map<MenuKey, HTMLButtonElement>());\n const openMenu = descriptor.menus.find((menu) => menu.id === openId) ?? null;\n const openItems = openMenu?.items.filter((item) => item.disabled !== true) ?? [];\n\n useEffect(() => {\n if (openId === null) return;\n const onPointerDown = (event: PointerEvent) => {\n if (event.target instanceof Node && rootRef.current?.contains(event.target)) return;\n setOpenId(null);\n };\n document.addEventListener(\"pointerdown\", onPointerDown);\n return () => document.removeEventListener(\"pointerdown\", onPointerDown);\n }, [openId, setOpenId]);\n\n const focusLabel = (id: MenuKey) => {\n setFocusedId(id);\n labelRefs.current.get(id)?.focus();\n };\n const move = (direction: \"next\" | \"previous\") => {\n const next = resolveMenubarNavigation(descriptor, focusedId, direction);\n focusLabel(next);\n // Left/right while a menu is open moves to the neighbour already open, which\n // is the whole reason this is a bar and not three independent Menus.\n if (openId !== null) { setOpenId(next); setActiveItemIndex(0); }\n };\n\n return (\n <div\n ref={rootRef}\n role=\"menubar\"\n aria-label={descriptor.accessibilityLabel}\n className={classNames(\"hjm-menubar\", className)}\n style={{\n \"--hjm-menubar-min-height\": `${menubarRecipe.minHeight}px`,\n \"--hjm-menubar-gap\": `${menubarRecipe.gap}px`,\n \"--hjm-menubar-padding\": `${menubarRecipe.paddingHorizontal}px`,\n \"--hjm-menubar-label-padding\": `${menubarRecipe.label.paddingHorizontal}px`,\n \"--hjm-menubar-label-radius\": `${menubarRecipe.label.radius}px`,\n } as CSSProperties}\n onKeyDown={(event) => {\n if (event.key === \"ArrowRight\") { event.preventDefault(); move(\"next\"); return; }\n if (event.key === \"ArrowLeft\") { event.preventDefault(); move(\"previous\"); return; }\n if (event.key === \"Home\" || event.key === \"End\") {\n event.preventDefault();\n const target = (event.key === \"Home\" ? enabledMenus[0] : enabledMenus.at(-1))?.id;\n if (target !== undefined) { focusLabel(target); if (openId !== null) { setOpenId(target); setActiveItemIndex(0); } }\n return;\n }\n if (event.key === \"Escape\" && openId !== null) {\n event.preventDefault();\n setOpenId(null);\n focusLabel(focusedId);\n return;\n }\n if (openId === null) {\n if (event.key === \"ArrowDown\" || event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n setOpenId(focusedId);\n setActiveItemIndex(0);\n }\n return;\n }\n if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n setActiveItemIndex((previous) => {\n const next = event.key === \"ArrowDown\" ? previous + 1 : previous - 1;\n return (next + openItems.length) % Math.max(openItems.length, 1);\n });\n return;\n }\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n const item = openItems[activeItemIndex];\n if (item) {\n onAction(item.id as Key, openId);\n // Activation runs an action and leaves nothing selected — unlike Tabs.\n setOpenId(null);\n focusLabel(openId);\n }\n }\n }}\n >\n {descriptor.menus.map((menu) => {\n const open = menu.id === openId;\n return (\n <div key={menu.id} className=\"hjm-menubar__menu\">\n <button\n type=\"button\"\n ref={(node) => { if (node) labelRefs.current.set(menu.id, node); else labelRefs.current.delete(menu.id); }}\n role=\"menuitem\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n disabled={menu.disabled}\n tabIndex={menu.id === focusedId ? 0 : -1}\n data-open={open || undefined}\n className=\"hjm-menubar__label\"\n onClick={() => {\n setFocusedId(menu.id);\n setOpenId(open ? null : menu.id);\n setActiveItemIndex(0);\n }}\n onMouseEnter={() => {\n // Once one menu is open, hovering the bar switches menus — the\n // desktop convention; with nothing open, hover opens nothing.\n if (openId !== null && menu.disabled !== true) { setOpenId(menu.id); setFocusedId(menu.id); setActiveItemIndex(0); }\n }}\n >\n {menu.label}\n </button>\n {open ? (\n <div role=\"menu\" aria-label={menu.label} className=\"hjm-menubar__panel\">\n {menu.items.map((item) => {\n const index = openItems.findIndex((candidate) => candidate.id === item.id);\n return (\n <div\n key={item.id}\n role=\"menuitem\"\n aria-disabled={item.disabled || undefined}\n data-active={index === activeItemIndex && item.disabled !== true ? \"\" : undefined}\n data-tone={item.tone}\n className=\"hjm-menubar__item\"\n onMouseEnter={() => { if (index >= 0) setActiveItemIndex(index); }}\n onClick={() => {\n if (item.disabled) return;\n onAction(item.id as Key, menu.id);\n setOpenId(null);\n focusLabel(menu.id);\n }}\n >\n <span>{item.label}</span>\n {item.shortcut ? <kbd className=\"hjm-menubar__shortcut\">{item.shortcut}</kbd> : null}\n </div>\n );\n })}\n </div>\n ) : null}\n </div>\n );\n })}\n </div>\n );\n}\n"]}
@@ -0,0 +1,67 @@
1
+ import { type AriaAttributes, type MouseEventHandler, type ReactElement, type ReactNode, type Ref } from "react";
2
+ export type TriggerElementProps = Readonly<{
3
+ ref?: Ref<HTMLElement>;
4
+ disabled?: boolean;
5
+ onClick?: MouseEventHandler<HTMLElement>;
6
+ onMouseEnter?: MouseEventHandler<HTMLElement>;
7
+ onMouseLeave?: MouseEventHandler<HTMLElement>;
8
+ onPointerEnter?: React.PointerEventHandler<HTMLElement>;
9
+ onPointerLeave?: React.PointerEventHandler<HTMLElement>;
10
+ onFocus?: React.FocusEventHandler<HTMLElement>;
11
+ onBlur?: React.FocusEventHandler<HTMLElement>;
12
+ onKeyDown?: React.KeyboardEventHandler<HTMLElement>;
13
+ "aria-controls"?: string;
14
+ "aria-describedby"?: string;
15
+ "aria-disabled"?: AriaAttributes["aria-disabled"];
16
+ "aria-expanded"?: AriaAttributes["aria-expanded"];
17
+ "aria-haspopup"?: AriaAttributes["aria-haspopup"];
18
+ }>;
19
+ export type OverlayTrigger = ReactElement<TriggerElementProps>;
20
+ export declare function containsEventTarget(container: Node | null, target: EventTarget | null): boolean;
21
+ type ModalPortalProps = Readonly<{
22
+ children: ReactNode;
23
+ container?: HTMLElement;
24
+ }>;
25
+ export declare function HjmPortal({ children, container }: ModalPortalProps): import("react").ReactPortal | null;
26
+ export type OpenState<Detail> = Readonly<{
27
+ open: boolean;
28
+ defaultOpen?: never;
29
+ onOpenChange: (open: boolean, detail: Detail) => void;
30
+ }> | Readonly<{
31
+ open?: never;
32
+ defaultOpen?: boolean;
33
+ onOpenChange?: (open: boolean, detail: Detail) => void;
34
+ }>;
35
+ export type ModalOpenState<Detail> = Readonly<{
36
+ open: boolean;
37
+ defaultOpen?: never;
38
+ onOpenChange: (open: boolean, detail: Detail) => void;
39
+ /** Optional for product-owned, programmatically controlled overlays. */
40
+ trigger?: OverlayTrigger;
41
+ }> | Readonly<{
42
+ open?: never;
43
+ defaultOpen?: boolean;
44
+ onOpenChange?: (open: boolean, detail: Detail) => void;
45
+ /** Uncontrolled overlays need a first-party activation target. */
46
+ trigger: OverlayTrigger;
47
+ }>;
48
+ export declare function useOpenState<Detail>({ open, defaultOpen, onOpenChange, }: Readonly<{
49
+ open?: boolean;
50
+ defaultOpen?: boolean;
51
+ onOpenChange?: (open: boolean, detail: Detail) => void;
52
+ }>): readonly [boolean, (nextOpen: boolean, detail: Detail) => void];
53
+ export declare function getFocusable(container: HTMLElement): HTMLElement[];
54
+ export declare function getModalLayer(priority: number): number;
55
+ type ModalFocusOptions = Readonly<{
56
+ active: boolean;
57
+ priority?: number;
58
+ contentRef: React.RefObject<HTMLElement | null>;
59
+ initialFocusRef?: React.RefObject<HTMLElement | null>;
60
+ returnFocusRef?: React.RefObject<HTMLElement | null>;
61
+ fallbackReturnRef?: React.RefObject<HTMLElement | null>;
62
+ onEscape(): void;
63
+ }>;
64
+ export declare function useModalFocus({ active, priority, contentRef, initialFocusRef, returnFocusRef, fallbackReturnRef, onEscape, }: ModalFocusOptions): void;
65
+ export declare function renderTrigger(trigger: OverlayTrigger, triggerRef: React.RefObject<HTMLElement | null>, open: boolean, contentId: string, popup: "dialog" | "menu", onOpen: () => void): ReactElement;
66
+ export {};
67
+ //# sourceMappingURL=modal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["../src/modal.tsx"],"names":[],"mappings":"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"}
package/dist/modal.js ADDED
@@ -0,0 +1,307 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { isLargeTextScale } from "@hjmds/design-contracts/components/design-system-provider";
3
+ import { cloneElement, useCallback, useEffect, useRef, useState, } from "react";
4
+ import { createPortal } from "react-dom";
5
+ import { composeRefs } from "./internal.js";
6
+ import { getPopoverOwner } from "./portal.js";
7
+ import { useOptionalHjmTheme } from "./provider.js";
8
+ import { createHjmThemeStyle } from "./theme.js";
9
+ export function containsEventTarget(container, target) {
10
+ return target !== null &&
11
+ "nodeType" in target &&
12
+ container?.contains(target) === true;
13
+ }
14
+ export function HjmPortal({ children, container }) {
15
+ const [mounted, setMounted] = useState(false);
16
+ const theme = useOptionalHjmTheme();
17
+ useEffect(() => setMounted(true), []);
18
+ if (!mounted)
19
+ return null;
20
+ 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);
21
+ }
22
+ export function useOpenState({ open, defaultOpen = false, onOpenChange, }) {
23
+ const [internalOpen, setInternalOpen] = useState(defaultOpen);
24
+ const controlled = open !== undefined;
25
+ const currentOpen = controlled ? open : internalOpen;
26
+ const pendingRequestRef = useRef(undefined);
27
+ useEffect(() => {
28
+ if (pendingRequestRef.current === currentOpen) {
29
+ pendingRequestRef.current = undefined;
30
+ }
31
+ }, [currentOpen]);
32
+ const changeOpen = useCallback((nextOpen, detail) => {
33
+ if (nextOpen === currentOpen ||
34
+ pendingRequestRef.current === nextOpen)
35
+ return;
36
+ pendingRequestRef.current = nextOpen;
37
+ if (!controlled)
38
+ setInternalOpen(nextOpen);
39
+ onOpenChange?.(nextOpen, detail);
40
+ if (controlled && nextOpen) {
41
+ queueMicrotask(() => {
42
+ if (pendingRequestRef.current === true) {
43
+ pendingRequestRef.current = undefined;
44
+ }
45
+ });
46
+ }
47
+ }, [controlled, currentOpen, onOpenChange]);
48
+ return [currentOpen, changeOpen];
49
+ }
50
+ const focusableSelector = [
51
+ "a[href]",
52
+ "button:not([disabled])",
53
+ "input:not([disabled])",
54
+ "select:not([disabled])",
55
+ "textarea:not([disabled])",
56
+ "[tabindex]:not([tabindex='-1'])",
57
+ ].join(",");
58
+ export function getFocusable(container) {
59
+ return [...container.querySelectorAll(focusableSelector)].filter((element) => element.tabIndex >= 0 && !element.matches(":disabled") &&
60
+ !element.closest('[hidden],[inert],[aria-hidden="true"]') &&
61
+ element.getClientRects().length > 0 && getComputedStyle(element).visibility !== "hidden");
62
+ }
63
+ let bodyLockCount = 0;
64
+ let previousBodyOverflow = "";
65
+ const activeModalStack = [];
66
+ let activeModalOrder = 0;
67
+ const isolatedModalBackground = new Map();
68
+ let modalIsolationObserver = null;
69
+ function getOwnedPopupHosts(modal) {
70
+ const ownerId = modal.element.id;
71
+ if (ownerId.length === 0)
72
+ return [];
73
+ return [...document.querySelectorAll("[data-hjm-popup-owner]")]
74
+ .filter((host) => host.getAttribute("data-hjm-popup-owner") === ownerId);
75
+ }
76
+ function modalContainsNode(modal, node) {
77
+ return modal.element.contains(node) ||
78
+ getOwnedPopupHosts(modal).some((host) => host.contains(node));
79
+ }
80
+ function getModalFocusable(modal) {
81
+ return [
82
+ ...getFocusable(modal.element),
83
+ ...getOwnedPopupHosts(modal).flatMap((host) => getFocusable(host)),
84
+ ];
85
+ }
86
+ export function getModalLayer(priority) {
87
+ if (!Number.isSafeInteger(priority)) {
88
+ throw new TypeError("modalPriority must be a safe integer");
89
+ }
90
+ return 1000 + priority;
91
+ }
92
+ function getTopModal() {
93
+ let top;
94
+ for (const modal of activeModalStack) {
95
+ if (!modal.element.isConnected)
96
+ continue;
97
+ if (top === undefined ||
98
+ modal.priority > top.priority ||
99
+ (modal.priority === top.priority && modal.order > top.order))
100
+ top = modal;
101
+ }
102
+ return top;
103
+ }
104
+ function modalRanksAbove(candidate, reference) {
105
+ return candidate.priority > reference.priority ||
106
+ (candidate.priority === reference.priority && candidate.order > reference.order);
107
+ }
108
+ function restoreModalBackground() {
109
+ for (const [element, previous] of isolatedModalBackground) {
110
+ element.inert = previous.inert;
111
+ if (previous.ariaHidden === null)
112
+ element.removeAttribute("aria-hidden");
113
+ else
114
+ element.setAttribute("aria-hidden", previous.ariaHidden);
115
+ }
116
+ isolatedModalBackground.clear();
117
+ }
118
+ function isolateModalBackgroundElement(element) {
119
+ if (isolatedModalBackground.has(element))
120
+ return;
121
+ isolatedModalBackground.set(element, {
122
+ ariaHidden: element.getAttribute("aria-hidden"),
123
+ inert: element.inert,
124
+ });
125
+ element.inert = true;
126
+ element.setAttribute("aria-hidden", "true");
127
+ }
128
+ /** Keeps only the top modal's ancestor path interactive, including late portals. */
129
+ function synchronizeModalBackgroundIsolation() {
130
+ restoreModalBackground();
131
+ for (let index = activeModalStack.length - 1; index >= 0; index -= 1) {
132
+ if (!activeModalStack[index]?.element.isConnected)
133
+ activeModalStack.splice(index, 1);
134
+ }
135
+ const top = getTopModal();
136
+ if (!top) {
137
+ modalIsolationObserver?.disconnect();
138
+ modalIsolationObserver = null;
139
+ return;
140
+ }
141
+ const interactivePath = new Set();
142
+ for (const root of [top.element, ...getOwnedPopupHosts(top)]) {
143
+ let pathNode = root;
144
+ while (pathNode) {
145
+ if (pathNode === document.body)
146
+ break;
147
+ interactivePath.add(pathNode);
148
+ pathNode = pathNode.parentElement;
149
+ }
150
+ }
151
+ const inspectedParents = new Set();
152
+ for (const pathNode of interactivePath) {
153
+ const parent = pathNode.parentElement;
154
+ if (!parent || inspectedParents.has(parent))
155
+ continue;
156
+ inspectedParents.add(parent);
157
+ for (const sibling of parent.children) {
158
+ if (sibling instanceof HTMLElement && !interactivePath.has(sibling)) {
159
+ isolateModalBackgroundElement(sibling);
160
+ }
161
+ }
162
+ }
163
+ if (modalIsolationObserver === null) {
164
+ modalIsolationObserver = new MutationObserver(() => {
165
+ synchronizeModalBackgroundIsolation();
166
+ });
167
+ modalIsolationObserver.observe(document.body, {
168
+ attributes: true,
169
+ attributeFilter: ["data-hjm-popup-owner"],
170
+ childList: true,
171
+ subtree: true,
172
+ });
173
+ }
174
+ }
175
+ function lockBodyScroll() {
176
+ if (bodyLockCount === 0) {
177
+ previousBodyOverflow = document.body.style.overflow;
178
+ document.body.style.overflow = "hidden";
179
+ }
180
+ bodyLockCount += 1;
181
+ return () => {
182
+ bodyLockCount = Math.max(0, bodyLockCount - 1);
183
+ if (bodyLockCount === 0)
184
+ document.body.style.overflow = previousBodyOverflow;
185
+ };
186
+ }
187
+ export function useModalFocus({ active, priority = 0, contentRef, initialFocusRef, returnFocusRef, fallbackReturnRef, onEscape, }) {
188
+ const escapeRef = useRef(onEscape);
189
+ escapeRef.current = onEscape;
190
+ useEffect(() => {
191
+ if (!active)
192
+ return;
193
+ let retryTimer;
194
+ let release;
195
+ let cancelled = false;
196
+ const activate = () => {
197
+ const content = contentRef.current;
198
+ if (!content) {
199
+ retryTimer = setTimeout(activate, 0);
200
+ return;
201
+ }
202
+ const previouslyFocused = document.activeElement instanceof HTMLElement
203
+ ? document.activeElement
204
+ : null;
205
+ const releaseScroll = lockBodyScroll();
206
+ const modal = {
207
+ element: content,
208
+ order: activeModalOrder += 1,
209
+ priority,
210
+ };
211
+ activeModalStack.push(modal);
212
+ synchronizeModalBackgroundIsolation();
213
+ const initial = initialFocusRef?.current ?? getFocusable(content)[0] ?? content;
214
+ if (getTopModal() === modal)
215
+ initial.focus();
216
+ const handleKeyDown = (event) => {
217
+ if (getTopModal() !== modal)
218
+ return;
219
+ if (event.key === "Escape") {
220
+ // A focused non-modal child owns its first Escape. Let its content (or
221
+ // nested menu) handle it before dismissing the containing modal.
222
+ if (event.target instanceof Node && getPopoverOwner(event.target))
223
+ return;
224
+ event.preventDefault();
225
+ escapeRef.current();
226
+ return;
227
+ }
228
+ if (event.key !== "Tab")
229
+ return;
230
+ const focusable = getModalFocusable(modal);
231
+ if (focusable.length === 0) {
232
+ event.preventDefault();
233
+ content.focus();
234
+ return;
235
+ }
236
+ const first = focusable[0];
237
+ const last = focusable.at(-1);
238
+ const current = document.activeElement;
239
+ if (event.shiftKey && (current === first || !(current instanceof Node) || !modalContainsNode(modal, current))) {
240
+ event.preventDefault();
241
+ last.focus();
242
+ }
243
+ else if (!event.shiftKey && (current === last || !(current instanceof Node) || !modalContainsNode(modal, current))) {
244
+ event.preventDefault();
245
+ first.focus();
246
+ }
247
+ };
248
+ const handleFocusIn = (event) => {
249
+ if (getTopModal() !== modal)
250
+ return;
251
+ if (event.target instanceof Node && !modalContainsNode(modal, event.target)) {
252
+ (initialFocusRef?.current ?? getFocusable(content)[0] ?? content).focus();
253
+ }
254
+ };
255
+ document.addEventListener("keydown", handleKeyDown, true);
256
+ document.addEventListener("focusin", handleFocusIn, true);
257
+ release = () => {
258
+ const wasTop = !activeModalStack.some((candidate) => candidate !== modal && candidate.element.isConnected && modalRanksAbove(candidate, modal));
259
+ document.removeEventListener("keydown", handleKeyDown, true);
260
+ document.removeEventListener("focusin", handleFocusIn, true);
261
+ const stackIndex = activeModalStack.lastIndexOf(modal);
262
+ if (stackIndex >= 0)
263
+ activeModalStack.splice(stackIndex, 1);
264
+ synchronizeModalBackgroundIsolation();
265
+ releaseScroll();
266
+ const returnTarget = returnFocusRef?.current ?? fallbackReturnRef?.current ?? previouslyFocused;
267
+ if (wasTop) {
268
+ queueMicrotask(() => {
269
+ const nextTop = getTopModal();
270
+ if (!nextTop) {
271
+ returnTarget?.focus();
272
+ return;
273
+ }
274
+ if (returnTarget && modalContainsNode(nextTop, returnTarget))
275
+ returnTarget.focus();
276
+ else
277
+ (getModalFocusable(nextTop)[0] ?? nextTop.element).focus();
278
+ });
279
+ }
280
+ };
281
+ if (cancelled)
282
+ release();
283
+ };
284
+ activate();
285
+ return () => {
286
+ cancelled = true;
287
+ if (retryTimer !== undefined)
288
+ clearTimeout(retryTimer);
289
+ release?.();
290
+ };
291
+ }, [active, contentRef, fallbackReturnRef, initialFocusRef, priority, returnFocusRef]);
292
+ }
293
+ export function renderTrigger(trigger, triggerRef, open, contentId, popup, onOpen) {
294
+ const props = trigger.props;
295
+ return cloneElement(trigger, {
296
+ ref: composeRefs(props.ref, triggerRef),
297
+ "aria-controls": contentId,
298
+ "aria-expanded": open,
299
+ "aria-haspopup": popup,
300
+ onClick: (event) => {
301
+ props.onClick?.(event);
302
+ if (!event.defaultPrevented && !props.disabled)
303
+ onOpen();
304
+ },
305
+ });
306
+ }
307
+ //# sourceMappingURL=modal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modal.js","sourceRoot":"","sources":["../src/modal.tsx"],"names":[],"mappings":"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","sourcesContent":["import { isLargeTextScale } from \"@hjmds/design-contracts/components/design-system-provider\";\nimport {\n cloneElement,\n useCallback,\n useEffect,\n useRef,\n useState,\n type AriaAttributes,\n type MouseEventHandler,\n type ReactElement,\n type ReactNode,\n type Ref,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { composeRefs } from \"./internal.js\";\nimport { getPopoverOwner } from \"./portal.js\";\nimport { useOptionalHjmTheme } from \"./provider.js\";\nimport { createHjmThemeStyle } from \"./theme.js\";\n\n/*\n Shared modal machinery for Dialog, AlertDialog, Sheet, and SidePanel. Keeping it\n inside overlays.tsx would drag the Menu and Tooltip graph into SidePanel's granular\n entry; copying it would split `activeModalStack` in two, so a Dialog and a SidePanel\n would each believe they are topmost. The stack, the body-scroll lock, and background\n isolation must stay single module-level instances, so they live here alone.\n*/\n\nexport type TriggerElementProps = Readonly<{\n ref?: Ref<HTMLElement>;\n disabled?: boolean;\n onClick?: MouseEventHandler<HTMLElement>;\n onMouseEnter?: MouseEventHandler<HTMLElement>;\n onMouseLeave?: MouseEventHandler<HTMLElement>;\n onPointerEnter?: React.PointerEventHandler<HTMLElement>;\n onPointerLeave?: React.PointerEventHandler<HTMLElement>;\n onFocus?: React.FocusEventHandler<HTMLElement>;\n onBlur?: React.FocusEventHandler<HTMLElement>;\n onKeyDown?: React.KeyboardEventHandler<HTMLElement>;\n \"aria-controls\"?: string;\n \"aria-describedby\"?: string;\n \"aria-disabled\"?: AriaAttributes[\"aria-disabled\"];\n \"aria-expanded\"?: AriaAttributes[\"aria-expanded\"];\n \"aria-haspopup\"?: AriaAttributes[\"aria-haspopup\"];\n}>;\n\nexport type OverlayTrigger = ReactElement<TriggerElementProps>;\n\nexport function containsEventTarget(\n container: Node | null,\n target: EventTarget | null,\n): boolean {\n return target !== null &&\n \"nodeType\" in target &&\n container?.contains(target as Node) === true;\n}\n\ntype ModalPortalProps = Readonly<{\n children: ReactNode;\n container?: HTMLElement;\n}>;\n\nexport function HjmPortal({ children, container }: ModalPortalProps) {\n const [mounted, setMounted] = useState(false);\n const theme = useOptionalHjmTheme();\n useEffect(() => setMounted(true), []);\n if (!mounted) return null;\n return createPortal(\n theme ? (\n <div\n className=\"hjm-root hjm-portal\"\n data-hjm-portal=\"\"\n data-motion={theme.environment.reducedMotion ? \"reduced\" : \"full\"}\n data-theme={theme.environment.theme}\n data-text-scale={theme.environment.textScale}\n data-large-text={isLargeTextScale(theme.environment.textScale) ? \"true\" : undefined}\n dir={theme.environment.direction}\n style={createHjmThemeStyle(theme)}\n >\n {children}\n </div>\n ) : children,\n container ?? document.body,\n );\n}\n\nexport type OpenState<Detail> =\n | Readonly<{\n open: boolean;\n defaultOpen?: never;\n onOpenChange: (open: boolean, detail: Detail) => void;\n }>\n | Readonly<{\n open?: never;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean, detail: Detail) => void;\n }>;\n\nexport type ModalOpenState<Detail> =\n | Readonly<{\n open: boolean;\n defaultOpen?: never;\n onOpenChange: (open: boolean, detail: Detail) => void;\n /** Optional for product-owned, programmatically controlled overlays. */\n trigger?: OverlayTrigger;\n }>\n | Readonly<{\n open?: never;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean, detail: Detail) => void;\n /** Uncontrolled overlays need a first-party activation target. */\n trigger: OverlayTrigger;\n }>;\n\nexport function useOpenState<Detail>({\n open,\n defaultOpen = false,\n onOpenChange,\n}: Readonly<{\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean, detail: Detail) => void;\n}>) {\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const controlled = open !== undefined;\n const currentOpen = controlled ? open : internalOpen;\n const pendingRequestRef = useRef<boolean | undefined>(undefined);\n useEffect(() => {\n if (pendingRequestRef.current === currentOpen) {\n pendingRequestRef.current = undefined;\n }\n }, [currentOpen]);\n const changeOpen = useCallback(\n (nextOpen: boolean, detail: Detail) => {\n if (\n nextOpen === currentOpen ||\n pendingRequestRef.current === nextOpen\n ) return;\n pendingRequestRef.current = nextOpen;\n if (!controlled) setInternalOpen(nextOpen);\n onOpenChange?.(nextOpen, detail);\n if (controlled && nextOpen) {\n queueMicrotask(() => {\n if (pendingRequestRef.current === true) {\n pendingRequestRef.current = undefined;\n }\n });\n }\n },\n [controlled, currentOpen, onOpenChange],\n );\n return [currentOpen, changeOpen] as const;\n}\n\nconst focusableSelector = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"textarea:not([disabled])\",\n \"[tabindex]:not([tabindex='-1'])\",\n].join(\",\");\n\nexport function getFocusable(container: HTMLElement): HTMLElement[] {\n return [...container.querySelectorAll<HTMLElement>(focusableSelector)].filter(\n (element) => element.tabIndex >= 0 && !element.matches(\":disabled\") &&\n !element.closest('[hidden],[inert],[aria-hidden=\"true\"]') &&\n element.getClientRects().length > 0 && getComputedStyle(element).visibility !== \"hidden\",\n );\n}\n\nlet bodyLockCount = 0;\nlet previousBodyOverflow = \"\";\ntype ActiveModal = Readonly<{\n element: HTMLElement;\n order: number;\n priority: number;\n}>;\nconst activeModalStack: ActiveModal[] = [];\nlet activeModalOrder = 0;\nconst isolatedModalBackground = new Map<\n HTMLElement,\n Readonly<{ ariaHidden: string | null; inert: boolean }>\n>();\nlet modalIsolationObserver: MutationObserver | null = null;\n\nfunction getOwnedPopupHosts(modal: ActiveModal): HTMLElement[] {\n const ownerId = modal.element.id;\n if (ownerId.length === 0) return [];\n return [...document.querySelectorAll<HTMLElement>(\"[data-hjm-popup-owner]\")]\n .filter((host) => host.getAttribute(\"data-hjm-popup-owner\") === ownerId);\n}\n\nfunction modalContainsNode(modal: ActiveModal, node: Node): boolean {\n return modal.element.contains(node) ||\n getOwnedPopupHosts(modal).some((host) => host.contains(node));\n}\n\nfunction getModalFocusable(modal: ActiveModal): HTMLElement[] {\n return [\n ...getFocusable(modal.element),\n ...getOwnedPopupHosts(modal).flatMap((host) => getFocusable(host)),\n ];\n}\n\nexport function getModalLayer(priority: number): number {\n if (!Number.isSafeInteger(priority)) {\n throw new TypeError(\"modalPriority must be a safe integer\");\n }\n return 1000 + priority;\n}\n\nfunction getTopModal(): ActiveModal | undefined {\n let top: ActiveModal | undefined;\n for (const modal of activeModalStack) {\n if (!modal.element.isConnected) continue;\n if (\n top === undefined ||\n modal.priority > top.priority ||\n (modal.priority === top.priority && modal.order > top.order)\n ) top = modal;\n }\n return top;\n}\n\nfunction modalRanksAbove(candidate: ActiveModal, reference: ActiveModal): boolean {\n return candidate.priority > reference.priority ||\n (candidate.priority === reference.priority && candidate.order > reference.order);\n}\n\nfunction restoreModalBackground(): void {\n for (const [element, previous] of isolatedModalBackground) {\n element.inert = previous.inert;\n if (previous.ariaHidden === null) element.removeAttribute(\"aria-hidden\");\n else element.setAttribute(\"aria-hidden\", previous.ariaHidden);\n }\n isolatedModalBackground.clear();\n}\n\nfunction isolateModalBackgroundElement(element: HTMLElement): void {\n if (isolatedModalBackground.has(element)) return;\n isolatedModalBackground.set(element, {\n ariaHidden: element.getAttribute(\"aria-hidden\"),\n inert: element.inert,\n });\n element.inert = true;\n element.setAttribute(\"aria-hidden\", \"true\");\n}\n\n/** Keeps only the top modal's ancestor path interactive, including late portals. */\nfunction synchronizeModalBackgroundIsolation(): void {\n restoreModalBackground();\n for (let index = activeModalStack.length - 1; index >= 0; index -= 1) {\n if (!activeModalStack[index]?.element.isConnected) activeModalStack.splice(index, 1);\n }\n const top = getTopModal();\n if (!top) {\n modalIsolationObserver?.disconnect();\n modalIsolationObserver = null;\n return;\n }\n\n const interactivePath = new Set<HTMLElement>();\n for (const root of [top.element, ...getOwnedPopupHosts(top)]) {\n let pathNode: HTMLElement | null = root;\n while (pathNode) {\n if (pathNode === document.body) break;\n interactivePath.add(pathNode);\n pathNode = pathNode.parentElement;\n }\n }\n const inspectedParents = new Set<HTMLElement>();\n for (const pathNode of interactivePath) {\n const parent = pathNode.parentElement;\n if (!parent || inspectedParents.has(parent)) continue;\n inspectedParents.add(parent);\n for (const sibling of parent.children) {\n if (sibling instanceof HTMLElement && !interactivePath.has(sibling)) {\n isolateModalBackgroundElement(sibling);\n }\n }\n }\n\n if (modalIsolationObserver === null) {\n modalIsolationObserver = new MutationObserver(() => {\n synchronizeModalBackgroundIsolation();\n });\n modalIsolationObserver.observe(document.body, {\n attributes: true,\n attributeFilter: [\"data-hjm-popup-owner\"],\n childList: true,\n subtree: true,\n });\n }\n}\n\nfunction lockBodyScroll(): () => void {\n if (bodyLockCount === 0) {\n previousBodyOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n }\n bodyLockCount += 1;\n return () => {\n bodyLockCount = Math.max(0, bodyLockCount - 1);\n if (bodyLockCount === 0) document.body.style.overflow = previousBodyOverflow;\n };\n}\n\ntype ModalFocusOptions = Readonly<{\n active: boolean;\n priority?: number;\n contentRef: React.RefObject<HTMLElement | null>;\n initialFocusRef?: React.RefObject<HTMLElement | null>;\n returnFocusRef?: React.RefObject<HTMLElement | null>;\n fallbackReturnRef?: React.RefObject<HTMLElement | null>;\n onEscape(): void;\n}>;\n\nexport function useModalFocus({\n active,\n priority = 0,\n contentRef,\n initialFocusRef,\n returnFocusRef,\n fallbackReturnRef,\n onEscape,\n}: ModalFocusOptions): void {\n const escapeRef = useRef(onEscape);\n escapeRef.current = onEscape;\n\n useEffect(() => {\n if (!active) return;\n let retryTimer: ReturnType<typeof setTimeout> | undefined;\n let release: (() => void) | undefined;\n let cancelled = false;\n\n const activate = () => {\n const content = contentRef.current;\n if (!content) {\n retryTimer = setTimeout(activate, 0);\n return;\n }\n const previouslyFocused = document.activeElement instanceof HTMLElement\n ? document.activeElement\n : null;\n const releaseScroll = lockBodyScroll();\n const modal: ActiveModal = {\n element: content,\n order: activeModalOrder += 1,\n priority,\n };\n activeModalStack.push(modal);\n synchronizeModalBackgroundIsolation();\n const initial = initialFocusRef?.current ?? getFocusable(content)[0] ?? content;\n if (getTopModal() === modal) initial.focus();\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (getTopModal() !== modal) return;\n if (event.key === \"Escape\") {\n // A focused non-modal child owns its first Escape. Let its content (or\n // nested menu) handle it before dismissing the containing modal.\n if (event.target instanceof Node && getPopoverOwner(event.target)) return;\n event.preventDefault();\n escapeRef.current();\n return;\n }\n if (event.key !== \"Tab\") return;\n const focusable = getModalFocusable(modal);\n if (focusable.length === 0) {\n event.preventDefault();\n content.focus();\n return;\n }\n const first = focusable[0]!;\n const last = focusable.at(-1)!;\n const current = document.activeElement;\n if (event.shiftKey && (current === first || !(current instanceof Node) || !modalContainsNode(modal, current))) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && (current === last || !(current instanceof Node) || !modalContainsNode(modal, current))) {\n event.preventDefault();\n first.focus();\n }\n };\n const handleFocusIn = (event: FocusEvent) => {\n if (getTopModal() !== modal) return;\n if (event.target instanceof Node && !modalContainsNode(modal, event.target)) {\n (initialFocusRef?.current ?? getFocusable(content)[0] ?? content).focus();\n }\n };\n document.addEventListener(\"keydown\", handleKeyDown, true);\n document.addEventListener(\"focusin\", handleFocusIn, true);\n release = () => {\n const wasTop = !activeModalStack.some(\n (candidate) =>\n candidate !== modal && candidate.element.isConnected && modalRanksAbove(candidate, modal),\n );\n document.removeEventListener(\"keydown\", handleKeyDown, true);\n document.removeEventListener(\"focusin\", handleFocusIn, true);\n const stackIndex = activeModalStack.lastIndexOf(modal);\n if (stackIndex >= 0) activeModalStack.splice(stackIndex, 1);\n synchronizeModalBackgroundIsolation();\n releaseScroll();\n const returnTarget =\n returnFocusRef?.current ?? fallbackReturnRef?.current ?? previouslyFocused;\n if (wasTop) {\n queueMicrotask(() => {\n const nextTop = getTopModal();\n if (!nextTop) {\n returnTarget?.focus();\n return;\n }\n if (returnTarget && modalContainsNode(nextTop, returnTarget)) returnTarget.focus();\n else (getModalFocusable(nextTop)[0] ?? nextTop.element).focus();\n });\n }\n };\n if (cancelled) release();\n };\n activate();\n return () => {\n cancelled = true;\n if (retryTimer !== undefined) clearTimeout(retryTimer);\n release?.();\n };\n }, [active, contentRef, fallbackReturnRef, initialFocusRef, priority, returnFocusRef]);\n}\n\nexport function renderTrigger(\n trigger: OverlayTrigger,\n triggerRef: React.RefObject<HTMLElement | null>,\n open: boolean,\n contentId: string,\n popup: \"dialog\" | \"menu\",\n onOpen: () => void,\n): ReactElement {\n const props = trigger.props;\n return cloneElement(trigger, {\n ref: composeRefs(props.ref, triggerRef),\n \"aria-controls\": contentId,\n \"aria-expanded\": open,\n \"aria-haspopup\": popup,\n onClick: (event) => {\n props.onClick?.(event);\n if (!event.defaultPrevented && !props.disabled) onOpen();\n },\n });\n}\n\n"]}