@funstack/ui 0.0.0-stage → 0.1.0-alpha.1

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 (299) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +42 -2
  3. package/dist/accordion/accordion-context.d.mts +5 -0
  4. package/dist/accordion/accordion-context.d.mts.map +1 -0
  5. package/dist/accordion/accordion-context.mjs +9 -0
  6. package/dist/accordion/accordion-context.mjs.map +1 -0
  7. package/dist/accordion/accordion-item.d.mts +53 -0
  8. package/dist/accordion/accordion-item.d.mts.map +1 -0
  9. package/dist/accordion/accordion-item.mjs +85 -0
  10. package/dist/accordion/accordion-item.mjs.map +1 -0
  11. package/dist/accordion/accordion-panel.d.mts +40 -0
  12. package/dist/accordion/accordion-panel.d.mts.map +1 -0
  13. package/dist/accordion/accordion-panel.mjs +40 -0
  14. package/dist/accordion/accordion-panel.mjs.map +1 -0
  15. package/dist/accordion/accordion-root.d.mts +90 -0
  16. package/dist/accordion/accordion-root.d.mts.map +1 -0
  17. package/dist/accordion/accordion-root.mjs +229 -0
  18. package/dist/accordion/accordion-root.mjs.map +1 -0
  19. package/dist/accordion/accordion-trigger.d.mts +37 -0
  20. package/dist/accordion/accordion-trigger.d.mts.map +1 -0
  21. package/dist/accordion/accordion-trigger.mjs +81 -0
  22. package/dist/accordion/accordion-trigger.mjs.map +1 -0
  23. package/dist/accordion/index.d.mts +20 -0
  24. package/dist/accordion/index.d.mts.map +1 -0
  25. package/dist/accordion/index.mjs +20 -0
  26. package/dist/accordion/index.mjs.map +1 -0
  27. package/dist/checkbox/checkbox.d.mts +92 -0
  28. package/dist/checkbox/checkbox.d.mts.map +1 -0
  29. package/dist/checkbox/checkbox.mjs +82 -0
  30. package/dist/checkbox/checkbox.mjs.map +1 -0
  31. package/dist/checkbox/index.d.mts +3 -0
  32. package/dist/checkbox/index.mjs +1 -0
  33. package/dist/checkbox-group/checkbox-group-context.mjs +24 -0
  34. package/dist/checkbox-group/checkbox-group-context.mjs.map +1 -0
  35. package/dist/checkbox-group/checkbox-group-item.d.mts +55 -0
  36. package/dist/checkbox-group/checkbox-group-item.d.mts.map +1 -0
  37. package/dist/checkbox-group/checkbox-group-item.mjs +90 -0
  38. package/dist/checkbox-group/checkbox-group-item.mjs.map +1 -0
  39. package/dist/checkbox-group/checkbox-group-label.d.mts +21 -0
  40. package/dist/checkbox-group/checkbox-group-label.d.mts.map +1 -0
  41. package/dist/checkbox-group/checkbox-group-label.mjs +30 -0
  42. package/dist/checkbox-group/checkbox-group-label.mjs.map +1 -0
  43. package/dist/checkbox-group/checkbox-group-parent.d.mts +68 -0
  44. package/dist/checkbox-group/checkbox-group-parent.d.mts.map +1 -0
  45. package/dist/checkbox-group/checkbox-group-parent.mjs +145 -0
  46. package/dist/checkbox-group/checkbox-group-parent.mjs.map +1 -0
  47. package/dist/checkbox-group/checkbox-group-root.d.mts +100 -0
  48. package/dist/checkbox-group/checkbox-group-root.d.mts.map +1 -0
  49. package/dist/checkbox-group/checkbox-group-root.mjs +291 -0
  50. package/dist/checkbox-group/checkbox-group-root.mjs.map +1 -0
  51. package/dist/checkbox-group/index.d.mts +23 -0
  52. package/dist/checkbox-group/index.d.mts.map +1 -0
  53. package/dist/checkbox-group/index.mjs +23 -0
  54. package/dist/checkbox-group/index.mjs.map +1 -0
  55. package/dist/dialog/dialog-close.d.mts +44 -0
  56. package/dist/dialog/dialog-close.d.mts.map +1 -0
  57. package/dist/dialog/dialog-close.mjs +76 -0
  58. package/dist/dialog/dialog-close.mjs.map +1 -0
  59. package/dist/dialog/dialog-context.mjs +13 -0
  60. package/dist/dialog/dialog-context.mjs.map +1 -0
  61. package/dist/dialog/dialog-description.d.mts +22 -0
  62. package/dist/dialog/dialog-description.d.mts.map +1 -0
  63. package/dist/dialog/dialog-description.mjs +31 -0
  64. package/dist/dialog/dialog-description.mjs.map +1 -0
  65. package/dist/dialog/dialog-form.d.mts +58 -0
  66. package/dist/dialog/dialog-form.d.mts.map +1 -0
  67. package/dist/dialog/dialog-form.mjs +108 -0
  68. package/dist/dialog/dialog-form.mjs.map +1 -0
  69. package/dist/dialog/dialog-popup.d.mts +81 -0
  70. package/dist/dialog/dialog-popup.d.mts.map +1 -0
  71. package/dist/dialog/dialog-popup.mjs +367 -0
  72. package/dist/dialog/dialog-popup.mjs.map +1 -0
  73. package/dist/dialog/dialog-root.d.mts +70 -0
  74. package/dist/dialog/dialog-root.d.mts.map +1 -0
  75. package/dist/dialog/dialog-root.mjs +113 -0
  76. package/dist/dialog/dialog-root.mjs.map +1 -0
  77. package/dist/dialog/dialog-title.d.mts +23 -0
  78. package/dist/dialog/dialog-title.d.mts.map +1 -0
  79. package/dist/dialog/dialog-title.mjs +31 -0
  80. package/dist/dialog/dialog-title.mjs.map +1 -0
  81. package/dist/dialog/dialog-trigger.d.mts +39 -0
  82. package/dist/dialog/dialog-trigger.d.mts.map +1 -0
  83. package/dist/dialog/dialog-trigger.mjs +80 -0
  84. package/dist/dialog/dialog-trigger.mjs.map +1 -0
  85. package/dist/dialog/index.d.mts +28 -0
  86. package/dist/dialog/index.d.mts.map +1 -0
  87. package/dist/dialog/index.mjs +28 -0
  88. package/dist/dialog/index.mjs.map +1 -0
  89. package/dist/index.d.mts +60 -0
  90. package/dist/index.mjs +12 -0
  91. package/dist/internal/action-details.mjs +47 -0
  92. package/dist/internal/action-details.mjs.map +1 -0
  93. package/dist/internal/anchor-position.d.mts +7 -0
  94. package/dist/internal/anchor-position.d.mts.map +1 -0
  95. package/dist/internal/anchor-position.mjs +16 -0
  96. package/dist/internal/anchor-position.mjs.map +1 -0
  97. package/dist/internal/data-attr.mjs +13 -0
  98. package/dist/internal/data-attr.mjs.map +1 -0
  99. package/dist/internal/merge-props.mjs +119 -0
  100. package/dist/internal/merge-props.mjs.map +1 -0
  101. package/dist/internal/platform-attributes.d.mts +55 -0
  102. package/dist/internal/platform-attributes.d.mts.map +1 -0
  103. package/dist/internal/sync-presence.mjs +19 -0
  104. package/dist/internal/sync-presence.mjs.map +1 -0
  105. package/dist/internal/transition-boundary.mjs +52 -0
  106. package/dist/internal/transition-boundary.mjs.map +1 -0
  107. package/dist/internal/transition-types.mjs +15 -0
  108. package/dist/internal/transition-types.mjs.map +1 -0
  109. package/dist/internal/types.d.mts +38 -0
  110. package/dist/internal/types.d.mts.map +1 -0
  111. package/dist/internal/use-action-trigger.mjs +58 -0
  112. package/dist/internal/use-action-trigger.mjs.map +1 -0
  113. package/dist/internal/use-controllable-action.mjs +115 -0
  114. package/dist/internal/use-controllable-action.mjs.map +1 -0
  115. package/dist/internal/use-delayed-pair.mjs +40 -0
  116. package/dist/internal/use-delayed-pair.mjs.map +1 -0
  117. package/dist/internal/use-dev-warning.mjs +21 -0
  118. package/dist/internal/use-dev-warning.mjs.map +1 -0
  119. package/dist/internal/use-exit-animation.mjs +55 -0
  120. package/dist/internal/use-exit-animation.mjs.map +1 -0
  121. package/dist/internal/use-form-reset.mjs +50 -0
  122. package/dist/internal/use-form-reset.mjs.map +1 -0
  123. package/dist/internal/use-native-checked.mjs +113 -0
  124. package/dist/internal/use-native-checked.mjs.map +1 -0
  125. package/dist/internal/use-native-group-reconciliation.mjs +64 -0
  126. package/dist/internal/use-native-group-reconciliation.mjs.map +1 -0
  127. package/dist/internal/use-popover-popup.mjs +274 -0
  128. package/dist/internal/use-popover-popup.mjs.map +1 -0
  129. package/dist/internal/use-popover-root.d.mts +2 -0
  130. package/dist/internal/use-popover-root.mjs +112 -0
  131. package/dist/internal/use-popover-root.mjs.map +1 -0
  132. package/dist/internal/use-popover-trigger.mjs +86 -0
  133. package/dist/internal/use-popover-trigger.mjs.map +1 -0
  134. package/dist/internal/use-render.mjs +51 -0
  135. package/dist/internal/use-render.mjs.map +1 -0
  136. package/dist/internal/use-until-found.mjs +40 -0
  137. package/dist/internal/use-until-found.mjs.map +1 -0
  138. package/dist/menu/index.d.mts +34 -0
  139. package/dist/menu/index.d.mts.map +1 -0
  140. package/dist/menu/index.mjs +34 -0
  141. package/dist/menu/index.mjs.map +1 -0
  142. package/dist/menu/menu-checkbox-item.d.mts +42 -0
  143. package/dist/menu/menu-checkbox-item.d.mts.map +1 -0
  144. package/dist/menu/menu-checkbox-item.mjs +71 -0
  145. package/dist/menu/menu-checkbox-item.mjs.map +1 -0
  146. package/dist/menu/menu-context.d.mts +1 -0
  147. package/dist/menu/menu-context.mjs +28 -0
  148. package/dist/menu/menu-context.mjs.map +1 -0
  149. package/dist/menu/menu-dom.mjs +37 -0
  150. package/dist/menu/menu-dom.mjs.map +1 -0
  151. package/dist/menu/menu-group.d.mts +48 -0
  152. package/dist/menu/menu-group.d.mts.map +1 -0
  153. package/dist/menu/menu-group.mjs +69 -0
  154. package/dist/menu/menu-group.mjs.map +1 -0
  155. package/dist/menu/menu-hover.mjs +29 -0
  156. package/dist/menu/menu-hover.mjs.map +1 -0
  157. package/dist/menu/menu-item.d.mts +45 -0
  158. package/dist/menu/menu-item.d.mts.map +1 -0
  159. package/dist/menu/menu-item.mjs +54 -0
  160. package/dist/menu/menu-item.mjs.map +1 -0
  161. package/dist/menu/menu-popup.d.mts +69 -0
  162. package/dist/menu/menu-popup.d.mts.map +1 -0
  163. package/dist/menu/menu-popup.mjs +207 -0
  164. package/dist/menu/menu-popup.mjs.map +1 -0
  165. package/dist/menu/menu-radio-group.d.mts +37 -0
  166. package/dist/menu/menu-radio-group.d.mts.map +1 -0
  167. package/dist/menu/menu-radio-group.mjs +72 -0
  168. package/dist/menu/menu-radio-group.mjs.map +1 -0
  169. package/dist/menu/menu-radio-item.d.mts +33 -0
  170. package/dist/menu/menu-radio-item.d.mts.map +1 -0
  171. package/dist/menu/menu-radio-item.mjs +62 -0
  172. package/dist/menu/menu-radio-item.mjs.map +1 -0
  173. package/dist/menu/menu-root.d.mts +54 -0
  174. package/dist/menu/menu-root.d.mts.map +1 -0
  175. package/dist/menu/menu-root.mjs +102 -0
  176. package/dist/menu/menu-root.mjs.map +1 -0
  177. package/dist/menu/menu-submenu-trigger.d.mts +31 -0
  178. package/dist/menu/menu-submenu-trigger.d.mts.map +1 -0
  179. package/dist/menu/menu-submenu-trigger.mjs +92 -0
  180. package/dist/menu/menu-submenu-trigger.mjs.map +1 -0
  181. package/dist/menu/menu-trigger.d.mts +36 -0
  182. package/dist/menu/menu-trigger.d.mts.map +1 -0
  183. package/dist/menu/menu-trigger.mjs +81 -0
  184. package/dist/menu/menu-trigger.mjs.map +1 -0
  185. package/dist/menu/use-menu-item.mjs +54 -0
  186. package/dist/menu/use-menu-item.mjs.map +1 -0
  187. package/dist/popover/index.d.mts +28 -0
  188. package/dist/popover/index.d.mts.map +1 -0
  189. package/dist/popover/index.mjs +28 -0
  190. package/dist/popover/index.mjs.map +1 -0
  191. package/dist/popover/popover-close.d.mts +41 -0
  192. package/dist/popover/popover-close.d.mts.map +1 -0
  193. package/dist/popover/popover-close.mjs +90 -0
  194. package/dist/popover/popover-close.mjs.map +1 -0
  195. package/dist/popover/popover-context.mjs +26 -0
  196. package/dist/popover/popover-context.mjs.map +1 -0
  197. package/dist/popover/popover-description.d.mts +22 -0
  198. package/dist/popover/popover-description.d.mts.map +1 -0
  199. package/dist/popover/popover-description.mjs +28 -0
  200. package/dist/popover/popover-description.mjs.map +1 -0
  201. package/dist/popover/popover-popup.d.mts +82 -0
  202. package/dist/popover/popover-popup.d.mts.map +1 -0
  203. package/dist/popover/popover-popup.mjs +93 -0
  204. package/dist/popover/popover-popup.mjs.map +1 -0
  205. package/dist/popover/popover-root.d.mts +64 -0
  206. package/dist/popover/popover-root.d.mts.map +1 -0
  207. package/dist/popover/popover-root.mjs +39 -0
  208. package/dist/popover/popover-root.mjs.map +1 -0
  209. package/dist/popover/popover-title.d.mts +23 -0
  210. package/dist/popover/popover-title.d.mts.map +1 -0
  211. package/dist/popover/popover-title.mjs +28 -0
  212. package/dist/popover/popover-title.mjs.map +1 -0
  213. package/dist/popover/popover-trigger.d.mts +37 -0
  214. package/dist/popover/popover-trigger.d.mts.map +1 -0
  215. package/dist/popover/popover-trigger.mjs +40 -0
  216. package/dist/popover/popover-trigger.mjs.map +1 -0
  217. package/dist/radio-group/index.d.mts +20 -0
  218. package/dist/radio-group/index.d.mts.map +1 -0
  219. package/dist/radio-group/index.mjs +20 -0
  220. package/dist/radio-group/index.mjs.map +1 -0
  221. package/dist/radio-group/radio-group-context.mjs +13 -0
  222. package/dist/radio-group/radio-group-context.mjs.map +1 -0
  223. package/dist/radio-group/radio-group-item.d.mts +54 -0
  224. package/dist/radio-group/radio-group-item.d.mts.map +1 -0
  225. package/dist/radio-group/radio-group-item.mjs +83 -0
  226. package/dist/radio-group/radio-group-item.mjs.map +1 -0
  227. package/dist/radio-group/radio-group-label.d.mts +21 -0
  228. package/dist/radio-group/radio-group-label.d.mts.map +1 -0
  229. package/dist/radio-group/radio-group-label.mjs +30 -0
  230. package/dist/radio-group/radio-group-label.mjs.map +1 -0
  231. package/dist/radio-group/radio-group-root.d.mts +92 -0
  232. package/dist/radio-group/radio-group-root.d.mts.map +1 -0
  233. package/dist/radio-group/radio-group-root.mjs +137 -0
  234. package/dist/radio-group/radio-group-root.mjs.map +1 -0
  235. package/dist/switch/index.d.mts +3 -0
  236. package/dist/switch/index.mjs +1 -0
  237. package/dist/switch/switch.d.mts +78 -0
  238. package/dist/switch/switch.d.mts.map +1 -0
  239. package/dist/switch/switch.mjs +77 -0
  240. package/dist/switch/switch.mjs.map +1 -0
  241. package/dist/tabs/index.d.mts +24 -0
  242. package/dist/tabs/index.d.mts.map +1 -0
  243. package/dist/tabs/index.mjs +23 -0
  244. package/dist/tabs/index.mjs.map +1 -0
  245. package/dist/tabs/tabs-context.d.mts +7 -0
  246. package/dist/tabs/tabs-context.d.mts.map +1 -0
  247. package/dist/tabs/tabs-context.mjs +14 -0
  248. package/dist/tabs/tabs-context.mjs.map +1 -0
  249. package/dist/tabs/tabs-ids.mjs +39 -0
  250. package/dist/tabs/tabs-ids.mjs.map +1 -0
  251. package/dist/tabs/tabs-indicator.d.mts +32 -0
  252. package/dist/tabs/tabs-indicator.d.mts.map +1 -0
  253. package/dist/tabs/tabs-indicator.mjs +49 -0
  254. package/dist/tabs/tabs-indicator.mjs.map +1 -0
  255. package/dist/tabs/tabs-list.d.mts +34 -0
  256. package/dist/tabs/tabs-list.d.mts.map +1 -0
  257. package/dist/tabs/tabs-list.mjs +130 -0
  258. package/dist/tabs/tabs-list.mjs.map +1 -0
  259. package/dist/tabs/tabs-panel.d.mts +50 -0
  260. package/dist/tabs/tabs-panel.d.mts.map +1 -0
  261. package/dist/tabs/tabs-panel.mjs +81 -0
  262. package/dist/tabs/tabs-panel.mjs.map +1 -0
  263. package/dist/tabs/tabs-root.d.mts +64 -0
  264. package/dist/tabs/tabs-root.d.mts.map +1 -0
  265. package/dist/tabs/tabs-root.mjs +90 -0
  266. package/dist/tabs/tabs-root.mjs.map +1 -0
  267. package/dist/tabs/tabs-tab.d.mts +49 -0
  268. package/dist/tabs/tabs-tab.d.mts.map +1 -0
  269. package/dist/tabs/tabs-tab.mjs +89 -0
  270. package/dist/tabs/tabs-tab.mjs.map +1 -0
  271. package/dist/toggle/index.d.mts +3 -0
  272. package/dist/toggle/index.mjs +1 -0
  273. package/dist/toggle/toggle.d.mts +57 -0
  274. package/dist/toggle/toggle.d.mts.map +1 -0
  275. package/dist/toggle/toggle.mjs +59 -0
  276. package/dist/toggle/toggle.mjs.map +1 -0
  277. package/dist/tooltip/index.d.mts +20 -0
  278. package/dist/tooltip/index.d.mts.map +1 -0
  279. package/dist/tooltip/index.mjs +19 -0
  280. package/dist/tooltip/index.mjs.map +1 -0
  281. package/dist/tooltip/tooltip-context.d.mts +5 -0
  282. package/dist/tooltip/tooltip-context.d.mts.map +1 -0
  283. package/dist/tooltip/tooltip-context.mjs +13 -0
  284. package/dist/tooltip/tooltip-context.mjs.map +1 -0
  285. package/dist/tooltip/tooltip-popup.d.mts +49 -0
  286. package/dist/tooltip/tooltip-popup.d.mts.map +1 -0
  287. package/dist/tooltip/tooltip-popup.mjs +52 -0
  288. package/dist/tooltip/tooltip-popup.mjs.map +1 -0
  289. package/dist/tooltip/tooltip-root.d.mts +41 -0
  290. package/dist/tooltip/tooltip-root.d.mts.map +1 -0
  291. package/dist/tooltip/tooltip-root.mjs +86 -0
  292. package/dist/tooltip/tooltip-root.mjs.map +1 -0
  293. package/dist/tooltip/tooltip-trigger.d.mts +38 -0
  294. package/dist/tooltip/tooltip-trigger.d.mts.map +1 -0
  295. package/dist/tooltip/tooltip-trigger.mjs +55 -0
  296. package/dist/tooltip/tooltip-trigger.mjs.map +1 -0
  297. package/dist/tooltip/use-tooltip-interaction.mjs +119 -0
  298. package/dist/tooltip/use-tooltip-interaction.mjs.map +1 -0
  299. package/package.json +73 -3
@@ -0,0 +1,112 @@
1
+ "use client";
2
+ import { createActionDetails } from "./action-details.mjs";
3
+ import { transitionTypes } from "./transition-types.mjs";
4
+ import { useControllableAction } from "./use-controllable-action.mjs";
5
+ import { useDevWarning } from "./use-dev-warning.mjs";
6
+ import { useCallback, useId, useInsertionEffect, useRef, useState } from "react";
7
+ //#region src/internal/use-popover-root.ts
8
+ function isPopoverOpen(element) {
9
+ if (element === null) return false;
10
+ try {
11
+ return element.matches(":popover-open");
12
+ } catch {
13
+ return false;
14
+ }
15
+ }
16
+ /** How long a recorded close intent survives when the popover did not close (design 2.3). */
17
+ function clearLater(intentRef, intent, popupRef) {
18
+ setTimeout(() => {
19
+ if (intentRef.current === intent && isPopoverOpen(popupRef.current)) intentRef.current = void 0;
20
+ }, 0);
21
+ }
22
+ /**
23
+ * The Root of a popover-like component without its public props (design 14.1): mode detection
24
+ * (once, from the first render), `useControllableAction<boolean>`, ids, refs, close intents and
25
+ * the intent-aware `requestOpen` / `requestClose` / `notifyClosed`.
26
+ */
27
+ function usePopoverRoot(options) {
28
+ const { component, part, open, defaultOpen, openChangeAction, details, onDispatch } = options;
29
+ const isLibraryNow = options.mode === "library" || open !== void 0 || defaultOpen !== void 0 || openChangeAction !== void 0;
30
+ const [mode] = useState(isLibraryNow ? "library" : "static");
31
+ const isLibrary = mode === "library";
32
+ useDevWarning(isLibrary !== isLibraryNow, `${part}: the mode changed after mount (\`open\`, \`defaultOpen\` and \`openChangeAction\` were all absent on one render and present on another). Switching between JS-free and library mode is not supported.`);
33
+ const { committed, optimistic, pending, dispatch, latestInFlight } = useControllableAction({
34
+ controlled: isLibrary ? open : void 0,
35
+ defaultValue: isLibrary ? defaultOpen : void 0,
36
+ initialValue: false,
37
+ action: isLibrary ? openChangeAction : void 0,
38
+ transitionTypes: (next) => transitionTypes(component, next ? "open" : "close"),
39
+ warn: {
40
+ component: part,
41
+ prop: "open"
42
+ }
43
+ });
44
+ const id = useId();
45
+ const triggerRef = useRef(null);
46
+ const popupRef = useRef(null);
47
+ const closeIntent = useRef(void 0);
48
+ const committedOptimistic = useRef(optimistic);
49
+ useInsertionEffect(() => {
50
+ committedOptimistic.current = optimistic;
51
+ });
52
+ const generation = useRef(0);
53
+ const openGeneration = useCallback(() => generation.current, []);
54
+ const nextOpenGeneration = useCallback(() => {
55
+ generation.current++;
56
+ }, []);
57
+ const recordCloseIntent = useCallback((intent) => {
58
+ closeIntent.current = intent;
59
+ if (intent !== void 0) clearLater(closeIntent, intent, popupRef);
60
+ }, []);
61
+ const takeCloseIntent = useCallback(() => {
62
+ const intent = closeIntent.current;
63
+ closeIntent.current = void 0;
64
+ return intent;
65
+ }, []);
66
+ const intended = () => latestInFlight()?.next ?? committedOptimistic.current;
67
+ const buildDetails = (event, reason) => details !== void 0 ? details(event, reason) : createActionDetails(event, { reason });
68
+ const send = (next, event, reason) => {
69
+ const built = buildDetails(event, reason);
70
+ if (next) nextOpenGeneration();
71
+ onDispatch?.(next, built);
72
+ dispatch(next, built);
73
+ };
74
+ const requestOpen = (event, reason) => {
75
+ if (!isLibrary || intended()) return;
76
+ send(true, event, reason);
77
+ };
78
+ const requestClose = (event, reason) => {
79
+ if (!isLibrary || !intended()) return;
80
+ send(false, event, reason);
81
+ };
82
+ const notifyClosed = (event, reason) => {
83
+ if (!isLibrary || !intended()) return;
84
+ send(false, event, reason);
85
+ };
86
+ return {
87
+ mode,
88
+ ids: {
89
+ popup: id,
90
+ title: `${id}-title`,
91
+ description: `${id}-description`
92
+ },
93
+ committed: isLibrary && committed,
94
+ optimistic: isLibrary && optimistic,
95
+ pending: isLibrary && pending,
96
+ intended,
97
+ openGeneration,
98
+ nextOpenGeneration,
99
+ requestOpen,
100
+ requestClose,
101
+ notifyClosed,
102
+ latestInFlight,
103
+ triggerRef,
104
+ popupRef,
105
+ recordCloseIntent,
106
+ takeCloseIntent
107
+ };
108
+ }
109
+ //#endregion
110
+ export { usePopoverRoot };
111
+
112
+ //# sourceMappingURL=use-popover-root.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-popover-root.mjs","names":[],"sources":["../../src/internal/use-popover-root.ts"],"sourcesContent":["\"use client\";\nimport { useCallback, useId, useInsertionEffect, useRef, useState } from \"react\";\nimport type { RefObject } from \"react\";\nimport { createActionDetails } from \"./action-details.js\";\nimport { transitionTypes } from \"./transition-types.js\";\nimport type { ActionDetails, ChangeAction, ChangeReason } from \"./types.js\";\nimport { useControllableAction } from \"./use-controllable-action.js\";\nimport { useDevWarning } from \"./use-dev-warning.js\";\n\n/**\n * \"static\": JS-free mode, the browser owns open and close through Invoker Commands. \"library\":\n * opening and part-initiated closing are library-owned (transitions keyed on committed state);\n * dismissals are the browser's and notified (design/components/popover.md section 2).\n */\nexport type PopoverMode = \"static\" | \"library\";\n\n/**\n * What caused the next close, recorded just before it happens (design 2.3). The `toggle` event\n * carries no source; without an intent a close is a `\"dismiss\"`.\n */\nexport interface PopoverCloseIntent {\n readonly reason: ChangeReason;\n readonly event: Event | undefined;\n}\n\n/** The `details` every popover-like `openChangeAction` receives. */\nexport interface PopoverLikeDetails extends ActionDetails {\n readonly reason: ChangeReason;\n}\n\n/**\n * The Root's state and machinery, shared by Popover and Menu (design 14). The component puts it\n * in its own context; `usePopoverTrigger` and `usePopoverPopup` take it as their first argument.\n */\nexport interface PopoverRootValue {\n readonly mode: PopoverMode;\n /** `useId`-based ids: the popup, its Title and its Description. */\n readonly ids: { readonly popup: string; readonly title: string; readonly description: string };\n /** Committed open state (library mode); `false` in JS-free mode. */\n readonly committed: boolean;\n /** Optimistic open state (library mode); `false` in JS-free mode. */\n readonly optimistic: boolean;\n /** The Root's transition is pending (library mode). */\n readonly pending: boolean;\n /**\n * What the user already intends: the latest in-flight dispatch's `next`, else the optimistic\n * value of the last commit. Safe to call from handlers and effects (layout effects included,\n * also in the commit that changed it), and after an `await`. Not during render.\n */\n readonly intended: () => boolean;\n /**\n * A counter that grows with every open (a requested open, or the popup being shown). A part\n * whose close runs after an `await` records it first and drops the close when it changed: the\n * user dismissed and re-opened meanwhile (Popover.Close, design 3.4). Handlers only.\n */\n readonly openGeneration: () => number;\n /** Advance `openGeneration` (the Popup's `beforetoggle` for an open). */\n readonly nextOpenGeneration: () => void;\n /**\n * Open through the Root's transition (library mode): a Trigger click, a foreign open caught at\n * `beforetoggle`, or mount reconciliation. A no-op when `true` is already intended.\n */\n readonly requestOpen: (event: Event | undefined, reason: ChangeReason) => void;\n /**\n * A library-owned close (library mode): a Trigger click while open, a Close part, Menu's item\n * activation. `openChangeAction(false)` is awaited and the popup hides in the commit. A no-op\n * when `false` is already intended.\n */\n readonly requestClose: (event: Event | undefined, reason: ChangeReason) => void;\n /**\n * The browser hid the popup (a dismissal or a foreign invoker): dispatch the `false`\n * notification unless `false` is already intended (the library's own echo, or a dismissal\n * during a pending part-initiated close).\n */\n readonly notifyClosed: (event: Event | undefined, reason: ChangeReason) => void;\n /** From `useControllableAction`: the latest in-flight intent, or `null`. */\n readonly latestInFlight: () => { readonly next: boolean } | null;\n /** The registered Trigger: `showPopover({ source })` and the focus-return fallback. */\n readonly triggerRef: RefObject<HTMLElement | null>;\n /** The popup element, set by the Popup's ref; how Close reaches `hidePopover()`. */\n readonly popupRef: RefObject<HTMLElement | null>;\n /** Record what causes the next close. Cleared after a task if the popover did not close. */\n readonly recordCloseIntent: (intent: PopoverCloseIntent | undefined) => void;\n /** Read and clear the recorded intent (the Popup's `toggle` listener). */\n readonly takeCloseIntent: () => PopoverCloseIntent | undefined;\n}\n\nexport interface UsePopoverRootOptions<Details extends PopoverLikeDetails> {\n /** Transition types and messages: `\"popover\"` gives `fsui-popover`, `fsui-popover-open`, … */\n readonly component: string;\n /** Public name of the Root part for development warnings (`\"Popover.Root\"`). */\n readonly part: string;\n readonly open: boolean | undefined;\n readonly defaultOpen: boolean | undefined;\n readonly openChangeAction: ChangeAction<boolean, Details> | undefined;\n /**\n * Builds the `details` of a dispatch. Default: `createActionDetails(event, { reason })`. A\n * component with more fields (Menu) passes its own.\n */\n readonly details?: (event: Event | undefined, reason: ChangeReason) => Details;\n /**\n * `\"library\"` forces library mode whatever the props (Menu, which has no JS-free mode). Default:\n * detected from `open` / `defaultOpen` / `openChangeAction` on the first render.\n */\n readonly mode?: \"library\";\n /**\n * Called synchronously for every dispatch, before the action runs (Menu: cancel hover timers,\n * read the open cause).\n */\n readonly onDispatch?: (next: boolean, details: Details) => void;\n}\n\nfunction isPopoverOpen(element: Element | null): boolean {\n if (element === null) return false;\n try {\n return element.matches(\":popover-open\");\n } catch {\n return false;\n }\n}\n\n/** How long a recorded close intent survives when the popover did not close (design 2.3). */\nfunction clearLater(\n intentRef: { current: PopoverCloseIntent | undefined },\n intent: PopoverCloseIntent,\n popupRef: { current: HTMLElement | null },\n): void {\n setTimeout(() => {\n // Closed: its `toggle` event (queued as a task) consumes the intent. Still open: the close\n // was cancelled or invalid, so the intent is stale.\n if (intentRef.current === intent && isPopoverOpen(popupRef.current)) {\n intentRef.current = undefined;\n }\n }, 0);\n}\n\n/**\n * The Root of a popover-like component without its public props (design 14.1): mode detection\n * (once, from the first render), `useControllableAction<boolean>`, ids, refs, close intents and\n * the intent-aware `requestOpen` / `requestClose` / `notifyClosed`.\n */\nexport function usePopoverRoot<Details extends PopoverLikeDetails>(\n options: UsePopoverRootOptions<Details>,\n): PopoverRootValue {\n const { component, part, open, defaultOpen, openChangeAction, details, onDispatch } = options;\n\n // Mode is decided once, from the first render's props (design 2).\n const isLibraryNow =\n options.mode === \"library\" ||\n open !== undefined ||\n defaultOpen !== undefined ||\n openChangeAction !== undefined;\n const [mode] = useState<PopoverMode>(isLibraryNow ? \"library\" : \"static\");\n const isLibrary = mode === \"library\";\n useDevWarning(\n isLibrary !== isLibraryNow,\n `${part}: the mode changed after mount (\\`open\\`, \\`defaultOpen\\` and \\`openChangeAction\\` ` +\n \"were all absent on one render and present on another). Switching between JS-free and \" +\n \"library mode is not supported.\",\n );\n\n // Hooks cannot be conditional: JS-free mode calls the hook too and never dispatches.\n const { committed, optimistic, pending, dispatch, latestInFlight } = useControllableAction<\n boolean,\n Details\n >({\n controlled: isLibrary ? open : undefined,\n defaultValue: isLibrary ? defaultOpen : undefined,\n initialValue: false,\n action: isLibrary ? openChangeAction : undefined,\n transitionTypes: (next) => transitionTypes(component, next ? \"open\" : \"close\"),\n warn: { component: part, prop: \"open\" },\n });\n\n const id = useId();\n const triggerRef = useRef<HTMLElement | null>(null);\n const popupRef = useRef<HTMLElement | null>(null);\n const closeIntent = useRef<PopoverCloseIntent | undefined>(undefined);\n\n // The optimistic value of the last commit, for callers that run after an `await` (a Close\n // action's continuation) and would otherwise read the value of the render they were created in.\n // An insertion effect runs in the mutation phase, before any layout effect of the same commit\n // (a child's included), so `intended()` is current there too.\n const committedOptimistic = useRef(optimistic);\n useInsertionEffect(() => {\n committedOptimistic.current = optimistic;\n });\n const generation = useRef(0);\n const openGeneration = useCallback(() => generation.current, []);\n const nextOpenGeneration = useCallback(() => {\n generation.current++;\n }, []);\n\n const recordCloseIntent = useCallback((intent: PopoverCloseIntent | undefined) => {\n closeIntent.current = intent;\n if (intent !== undefined) clearLater(closeIntent, intent, popupRef);\n }, []);\n const takeCloseIntent = useCallback(() => {\n const intent = closeIntent.current;\n closeIntent.current = undefined;\n return intent;\n }, []);\n\n // Conventions 3.8 rule 1: judge no-ops against what the user already intends. That also\n // deduplicates the StrictMode re-run of the mount reconciliation and recognises the library's\n // own `hidePopover()` echoing back through `toggle`.\n const intended = (): boolean => latestInFlight()?.next ?? committedOptimistic.current;\n\n const buildDetails = (event: Event | undefined, reason: ChangeReason): Details =>\n details !== undefined\n ? details(event, reason)\n : (createActionDetails(event, { reason }) as unknown as Details);\n\n const send = (next: boolean, event: Event | undefined, reason: ChangeReason): void => {\n const built = buildDetails(event, reason);\n if (next) nextOpenGeneration();\n onDispatch?.(next, built);\n dispatch(next, built);\n };\n\n const requestOpen = (event: Event | undefined, reason: ChangeReason): void => {\n if (!isLibrary || intended()) return;\n send(true, event, reason);\n };\n\n const requestClose = (event: Event | undefined, reason: ChangeReason): void => {\n if (!isLibrary || !intended()) return;\n send(false, event, reason);\n };\n\n // Same guard as `requestClose`; a separate name because the callers mean different things.\n const notifyClosed = (event: Event | undefined, reason: ChangeReason): void => {\n if (!isLibrary || !intended()) return;\n send(false, event, reason);\n };\n\n // Not memoised: `dispatch` is a new function on every render (as Dialog), so parts re-render\n // with the Root, which they do anyway when its state changes.\n return {\n mode,\n ids: { popup: id, title: `${id}-title`, description: `${id}-description` },\n committed: isLibrary && committed,\n optimistic: isLibrary && optimistic,\n pending: isLibrary && pending,\n intended,\n openGeneration,\n nextOpenGeneration,\n requestOpen,\n requestClose,\n notifyClosed,\n latestInFlight,\n triggerRef,\n popupRef,\n recordCloseIntent,\n takeCloseIntent,\n };\n}\n"],"mappings":";;;;;;;AAgHA,SAAS,cAAc,SAAkC;CACvD,IAAI,YAAY,MAAM,OAAO;CAC7B,IAAI;EACF,OAAO,QAAQ,QAAQ,eAAe;CACxC,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,SAAS,WACP,WACA,QACA,UACM;CACN,iBAAiB;EAGf,IAAI,UAAU,YAAY,UAAU,cAAc,SAAS,OAAO,GAChE,UAAU,UAAU,KAAA;CAExB,GAAG,CAAC;AACN;;;;;;AAOA,SAAgB,eACd,SACkB;CAClB,MAAM,EAAE,WAAW,MAAM,MAAM,aAAa,kBAAkB,SAAS,eAAe;CAGtF,MAAM,eACJ,QAAQ,SAAS,aACjB,SAAS,KAAA,KACT,gBAAgB,KAAA,KAChB,qBAAqB,KAAA;CACvB,MAAM,CAAC,QAAQ,SAAsB,eAAe,YAAY,QAAQ;CACxE,MAAM,YAAY,SAAS;CAC3B,cACE,cAAc,cACd,GAAG,KAAK,uMAGV;CAGA,MAAM,EAAE,WAAW,YAAY,SAAS,UAAU,mBAAmB,sBAGnE;EACA,YAAY,YAAY,OAAO,KAAA;EAC/B,cAAc,YAAY,cAAc,KAAA;EACxC,cAAc;EACd,QAAQ,YAAY,mBAAmB,KAAA;EACvC,kBAAkB,SAAS,gBAAgB,WAAW,OAAO,SAAS,OAAO;EAC7E,MAAM;GAAE,WAAW;GAAM,MAAM;EAAO;CACxC,CAAC;CAED,MAAM,KAAK,MAAM;CACjB,MAAM,aAAa,OAA2B,IAAI;CAClD,MAAM,WAAW,OAA2B,IAAI;CAChD,MAAM,cAAc,OAAuC,KAAA,CAAS;CAMpE,MAAM,sBAAsB,OAAO,UAAU;CAC7C,yBAAyB;EACvB,oBAAoB,UAAU;CAChC,CAAC;CACD,MAAM,aAAa,OAAO,CAAC;CAC3B,MAAM,iBAAiB,kBAAkB,WAAW,SAAS,CAAC,CAAC;CAC/D,MAAM,qBAAqB,kBAAkB;EAC3C,WAAW;CACb,GAAG,CAAC,CAAC;CAEL,MAAM,oBAAoB,aAAa,WAA2C;EAChF,YAAY,UAAU;EACtB,IAAI,WAAW,KAAA,GAAW,WAAW,aAAa,QAAQ,QAAQ;CACpE,GAAG,CAAC,CAAC;CACL,MAAM,kBAAkB,kBAAkB;EACxC,MAAM,SAAS,YAAY;EAC3B,YAAY,UAAU,KAAA;EACtB,OAAO;CACT,GAAG,CAAC,CAAC;CAKL,MAAM,iBAA0B,eAAe,CAAC,EAAE,QAAQ,oBAAoB;CAE9E,MAAM,gBAAgB,OAA0B,WAC9C,YAAY,KAAA,IACR,QAAQ,OAAO,MAAM,IACpB,oBAAoB,OAAO,EAAE,OAAO,CAAC;CAE5C,MAAM,QAAQ,MAAe,OAA0B,WAA+B;EACpF,MAAM,QAAQ,aAAa,OAAO,MAAM;EACxC,IAAI,MAAM,mBAAmB;EAC7B,aAAa,MAAM,KAAK;EACxB,SAAS,MAAM,KAAK;CACtB;CAEA,MAAM,eAAe,OAA0B,WAA+B;EAC5E,IAAI,CAAC,aAAa,SAAS,GAAG;EAC9B,KAAK,MAAM,OAAO,MAAM;CAC1B;CAEA,MAAM,gBAAgB,OAA0B,WAA+B;EAC7E,IAAI,CAAC,aAAa,CAAC,SAAS,GAAG;EAC/B,KAAK,OAAO,OAAO,MAAM;CAC3B;CAGA,MAAM,gBAAgB,OAA0B,WAA+B;EAC7E,IAAI,CAAC,aAAa,CAAC,SAAS,GAAG;EAC/B,KAAK,OAAO,OAAO,MAAM;CAC3B;CAIA,OAAO;EACL;EACA,KAAK;GAAE,OAAO;GAAI,OAAO,GAAG,GAAG;GAAS,aAAa,GAAG,GAAG;EAAc;EACzE,WAAW,aAAa;EACxB,YAAY,aAAa;EACzB,SAAS,aAAa;EACtB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF"}
@@ -0,0 +1,86 @@
1
+ "use client";
2
+ import { dataAttr } from "./data-attr.mjs";
3
+ import { rendersButton } from "./use-render.mjs";
4
+ import { useCallback } from "react";
5
+ //#region src/internal/use-popover-trigger.ts
6
+ const isDev = process.env.NODE_ENV !== "production";
7
+ function isPopoverOpen(element) {
8
+ if (element === null) return false;
9
+ try {
10
+ return element.matches(":popover-open");
11
+ } catch {
12
+ return false;
13
+ }
14
+ }
15
+ /**
16
+ * The Trigger of a popover-like component (design/components/popover.md 14.3):
17
+ * `button[commandfor][command="toggle-popover"]` with `aria-expanded`. In JS-free mode the
18
+ * browser runs the command and the Popup keeps `aria-expanded` current on the element. In library
19
+ * mode the click is cancelled (so the command does not run) and toggles against what the user
20
+ * already intends: an open through `requestOpen`, a library-owned close through `requestClose`.
21
+ *
22
+ * Menu adds `aria-haspopup="menu"` and its own key handling on top.
23
+ */
24
+ function usePopoverTrigger(ctx, options) {
25
+ const { part, disabled, render, handlers, activate, disabledAs = "native" } = options;
26
+ const isLibrary = ctx.mode === "library";
27
+ const { ids, triggerRef, popupRef } = ctx;
28
+ const state = {
29
+ open: ctx.optimistic,
30
+ pending: ctx.pending,
31
+ disabled
32
+ };
33
+ const ref = useCallback((element) => {
34
+ if (isDev && !isLibrary && element.tagName !== "BUTTON") console.error(`${part}: rendered a <${element.tagName.toLowerCase()}> in JS-free mode. Only a <button> runs \`command="toggle-popover"\`; this trigger will not open the popover. Render a <button>, or put the component in library mode (\`defaultOpen={false}\`).`);
35
+ triggerRef.current = element;
36
+ if (!isLibrary && popupRef.current !== null) element.setAttribute("aria-expanded", String(isPopoverOpen(popupRef.current)));
37
+ return () => {
38
+ if (triggerRef.current === element) triggerRef.current = null;
39
+ };
40
+ }, [
41
+ isLibrary,
42
+ part,
43
+ triggerRef,
44
+ popupRef
45
+ ]);
46
+ const onClick = (event) => {
47
+ event.preventDefault();
48
+ if (disabled) return;
49
+ if (activate !== void 0) {
50
+ activate(event);
51
+ return;
52
+ }
53
+ const native = event.nativeEvent;
54
+ if (ctx.intended()) ctx.requestClose(native, "activate");
55
+ else ctx.requestOpen(native, "activate");
56
+ };
57
+ const merged = { ...handlers };
58
+ if (isLibrary) {
59
+ const extra = handlers?.onClick;
60
+ merged.onClick = extra === void 0 ? onClick : (event) => {
61
+ extra(event);
62
+ onClick(event);
63
+ };
64
+ }
65
+ const ariaDisabled = disabledAs === "aria";
66
+ return {
67
+ state,
68
+ libraryProps: {
69
+ ...rendersButton(render) && { type: "button" },
70
+ disabled: ariaDisabled ? void 0 : disabled,
71
+ ...ariaDisabled && { "aria-disabled": disabled ? true : void 0 },
72
+ commandfor: ids.popup,
73
+ command: "toggle-popover",
74
+ "aria-expanded": isLibrary ? state.open : false,
75
+ ...merged,
76
+ "data-open": dataAttr(state.open),
77
+ "data-pending": dataAttr(state.pending),
78
+ "data-disabled": dataAttr((isLibrary || ariaDisabled) && disabled)
79
+ },
80
+ ref
81
+ };
82
+ }
83
+ //#endregion
84
+ export { usePopoverTrigger };
85
+
86
+ //# sourceMappingURL=use-popover-trigger.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-popover-trigger.mjs","names":[],"sources":["../../src/internal/use-popover-trigger.ts"],"sourcesContent":["\"use client\";\nimport { useCallback } from \"react\";\nimport type { ComponentPropsWithRef, DOMAttributes, MouseEvent } from \"react\";\nimport { dataAttr } from \"./data-attr.js\";\nimport type { RenderProp } from \"./types.js\";\nimport { rendersButton } from \"./use-render.js\";\nimport type { DataAttributes } from \"./use-render.js\";\nimport type { PopoverRootValue } from \"./use-popover-root.js\";\n\nexport interface PopoverTriggerHookState {\n /** Optimistic open state (library mode); `false` in JS-free mode. */\n readonly open: boolean;\n /** The Root's transition is pending (an open, a part-initiated close or a notification). */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\n/** Event handlers a component layers onto the Trigger's own (Menu's keys and hover intent). */\nexport type PopoverTriggerHandlers = Pick<\n DOMAttributes<HTMLButtonElement>,\n \"onClick\" | \"onKeyDown\" | \"onPointerMove\" | \"onPointerEnter\" | \"onPointerLeave\"\n>;\n\nexport interface UsePopoverTriggerOptions<State> {\n /** Public name of the part for development warnings (`\"Popover.Trigger\"`). */\n readonly part: string;\n readonly disabled: boolean;\n readonly render: RenderProp<ComponentPropsWithRef<\"button\">, State> | undefined;\n /**\n * Extra handlers, merged in front of the Trigger's own: for a key both define, the extra one\n * runs first (the consumer's pass-through handler still runs before both, conventions 1.2).\n */\n readonly handlers?: PopoverTriggerHandlers;\n /**\n * What a click does in library mode, after the command has been cancelled and `disabled`\n * checked. Default: toggle against `intended()` (`requestOpen` / `requestClose`). Menu's\n * SubmenuTrigger opens, or moves focus into an already open submenu, and never closes.\n */\n readonly activate?: (event: MouseEvent<HTMLElement>) => void;\n /**\n * How `disabled` is rendered. `\"native\"` (default): the `disabled` attribute, so the button is\n * no invoker at all. `\"aria\"`: `aria-disabled=\"true\"` and no `disabled` (a menu item stays\n * focusable); the click handler still refuses.\n */\n readonly disabledAs?: \"native\" | \"aria\";\n}\n\nexport interface PopoverTriggerHook {\n readonly state: PopoverTriggerHookState;\n /** Owned props (conventions 1.2) for `useRender`'s `libraryProps`. */\n readonly libraryProps: ComponentPropsWithRef<\"button\"> & DataAttributes;\n /** Registers the element as the Root's Trigger; pass as `useRender`'s `ref`. */\n readonly ref: (element: HTMLElement) => () => void;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nfunction isPopoverOpen(element: Element | null): boolean {\n if (element === null) return false;\n try {\n return element.matches(\":popover-open\");\n } catch {\n return false;\n }\n}\n\n/**\n * The Trigger of a popover-like component (design/components/popover.md 14.3):\n * `button[commandfor][command=\"toggle-popover\"]` with `aria-expanded`. In JS-free mode the\n * browser runs the command and the Popup keeps `aria-expanded` current on the element. In library\n * mode the click is cancelled (so the command does not run) and toggles against what the user\n * already intends: an open through `requestOpen`, a library-owned close through `requestClose`.\n *\n * Menu adds `aria-haspopup=\"menu\"` and its own key handling on top.\n */\nexport function usePopoverTrigger<State>(\n ctx: PopoverRootValue,\n options: UsePopoverTriggerOptions<State>,\n): PopoverTriggerHook {\n const { part, disabled, render, handlers, activate, disabledAs = \"native\" } = options;\n const isLibrary = ctx.mode === \"library\";\n const { ids, triggerRef, popupRef } = ctx;\n\n const state: PopoverTriggerHookState = {\n open: ctx.optimistic,\n // Opens, part-initiated closes and dismissal notifications all belong to the Trigger (4.2).\n pending: ctx.pending,\n disabled,\n };\n\n const ref = useCallback(\n (element: HTMLElement) => {\n if (isDev && !isLibrary && element.tagName !== \"BUTTON\") {\n console.error(\n `${part}: rendered a <${element.tagName.toLowerCase()}> in JS-free mode. Only a ` +\n '<button> runs `command=\"toggle-popover\"`; this trigger will not open the popover. ' +\n \"Render a <button>, or put the component in library mode (`defaultOpen={false}`).\",\n );\n }\n triggerRef.current = element;\n // JS-free mode: the DOM owns `aria-expanded` (design 9). A Trigger that mounts after the\n // Popup (or is re-created) picks up the current state here.\n if (!isLibrary && popupRef.current !== null) {\n element.setAttribute(\"aria-expanded\", String(isPopoverOpen(popupRef.current)));\n }\n return () => {\n if (triggerRef.current === element) triggerRef.current = null;\n };\n },\n [isLibrary, part, triggerRef, popupRef],\n );\n\n const onClick = (event: MouseEvent<HTMLElement>) => {\n // Cancelling the click cancels the command (it runs as the button's activation behaviour,\n // only if the click was not cancelled). Enter and Space synthesise the same click.\n event.preventDefault();\n if (disabled) return;\n if (activate !== undefined) {\n activate(event);\n return;\n }\n const native = event.nativeEvent;\n if (ctx.intended()) ctx.requestClose(native, \"activate\");\n else ctx.requestOpen(native, \"activate\");\n };\n\n // The component's handlers first, then the Trigger's (design/components/menu.md 14). The click\n // handler is only owned when installed, so a render element keeps its own `onClick` in JS-free\n // mode.\n const merged: PopoverTriggerHandlers = { ...handlers };\n if (isLibrary) {\n const extra = handlers?.onClick;\n merged.onClick =\n extra === undefined\n ? onClick\n : (event) => {\n extra(event);\n onClick(event);\n };\n }\n const ariaDisabled = disabledAs === \"aria\";\n\n const libraryProps: ComponentPropsWithRef<\"button\"> & DataAttributes = {\n // `type` only applies to a <button>; on any other element the key is not owned.\n ...(rendersButton(render) && { type: \"button\" as const }),\n // Owned: a render element must not re-enable a disabled Trigger (in JS-free mode the browser\n // would run its command). With `disabledAs: \"aria\"` it is owned as absent.\n disabled: ariaDisabled ? undefined : disabled,\n ...(ariaDisabled && { \"aria-disabled\": disabled ? true : undefined }),\n commandfor: ids.popup,\n command: \"toggle-popover\",\n // Library mode: optimistic. JS-free mode: \"false\" in React; the Popup's `toggle` listener\n // writes the DOM value, and React never rewrites an unchanged prop.\n \"aria-expanded\": isLibrary ? state.open : false,\n ...merged,\n \"data-open\": dataAttr(state.open),\n \"data-pending\": dataAttr(state.pending),\n \"data-disabled\": dataAttr((isLibrary || ariaDisabled) && disabled),\n };\n\n return { state, libraryProps, ref };\n}\n"],"mappings":";;;;;AAuDA,MAAM,QAAQ,QAAQ,IAAI,aAAa;AAEvC,SAAS,cAAc,SAAkC;CACvD,IAAI,YAAY,MAAM,OAAO;CAC7B,IAAI;EACF,OAAO,QAAQ,QAAQ,eAAe;CACxC,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;AAWA,SAAgB,kBACd,KACA,SACoB;CACpB,MAAM,EAAE,MAAM,UAAU,QAAQ,UAAU,UAAU,aAAa,aAAa;CAC9E,MAAM,YAAY,IAAI,SAAS;CAC/B,MAAM,EAAE,KAAK,YAAY,aAAa;CAEtC,MAAM,QAAiC;EACrC,MAAM,IAAI;EAEV,SAAS,IAAI;EACb;CACF;CAEA,MAAM,MAAM,aACT,YAAyB;EACxB,IAAI,SAAS,CAAC,aAAa,QAAQ,YAAY,UAC7C,QAAQ,MACN,GAAG,KAAK,gBAAgB,QAAQ,QAAQ,YAAY,EAAE,iMAGxD;EAEF,WAAW,UAAU;EAGrB,IAAI,CAAC,aAAa,SAAS,YAAY,MACrC,QAAQ,aAAa,iBAAiB,OAAO,cAAc,SAAS,OAAO,CAAC,CAAC;EAE/E,aAAa;GACX,IAAI,WAAW,YAAY,SAAS,WAAW,UAAU;EAC3D;CACF,GACA;EAAC;EAAW;EAAM;EAAY;CAAQ,CACxC;CAEA,MAAM,WAAW,UAAmC;EAGlD,MAAM,eAAe;EACrB,IAAI,UAAU;EACd,IAAI,aAAa,KAAA,GAAW;GAC1B,SAAS,KAAK;GACd;EACF;EACA,MAAM,SAAS,MAAM;EACrB,IAAI,IAAI,SAAS,GAAG,IAAI,aAAa,QAAQ,UAAU;OAClD,IAAI,YAAY,QAAQ,UAAU;CACzC;CAKA,MAAM,SAAiC,EAAE,GAAG,SAAS;CACrD,IAAI,WAAW;EACb,MAAM,QAAQ,UAAU;EACxB,OAAO,UACL,UAAU,KAAA,IACN,WACC,UAAU;GACT,MAAM,KAAK;GACX,QAAQ,KAAK;EACf;CACR;CACA,MAAM,eAAe,eAAe;CAoBpC,OAAO;EAAE;EAAO,cAAA;GAhBd,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;GAGvD,UAAU,eAAe,KAAA,IAAY;GACrC,GAAI,gBAAgB,EAAE,iBAAiB,WAAW,OAAO,KAAA,EAAU;GACnE,YAAY,IAAI;GAChB,SAAS;GAGT,iBAAiB,YAAY,MAAM,OAAO;GAC1C,GAAG;GACH,aAAa,SAAS,MAAM,IAAI;GAChC,gBAAgB,SAAS,MAAM,OAAO;GACtC,iBAAiB,UAAU,aAAa,iBAAiB,QAAQ;EAGxC;EAAG;CAAI;AACpC"}
@@ -0,0 +1,51 @@
1
+ "use client";
2
+ import { mergeProps, mergeRefs } from "./merge-props.mjs";
3
+ import { cloneElement, createElement, isValidElement, useMemo } from "react";
4
+ //#region src/internal/use-render.tsx
5
+ function resolveStateValue(value, state) {
6
+ return typeof value === "function" ? value(state) : value;
7
+ }
8
+ /**
9
+ * Whether a part whose default element is a `<button>` should add `type="button"`: no `render`,
10
+ * a `render` element whose type is the intrinsic `"button"`, or the function form (it receives
11
+ * button props and decides itself what to spread). An element of any other type (an `<a>`, a
12
+ * router `Link`) does not get it: `type` means something else, or nothing, there.
13
+ */
14
+ function rendersButton(render) {
15
+ if (render === void 0 || typeof render === "function") return true;
16
+ return isValidElement(render) && render.type === "button";
17
+ }
18
+ /**
19
+ * Resolves `render` and the function-form `className` / `style` / `children` of a part against
20
+ * its state and produces the element (conventions 1.2, 1.3, 1.4).
21
+ */
22
+ function useRender(options) {
23
+ const { defaultElement, render, state, className, style, children, props, libraryProps, ref } = options;
24
+ const consumerRef = props.ref;
25
+ const renderElement = isValidElement(render) ? render : void 0;
26
+ const elementRef = renderElement?.props.ref;
27
+ const mergedRef = useMemo(() => mergeRefs(ref, consumerRef, elementRef), [
28
+ ref,
29
+ consumerRef,
30
+ elementRef
31
+ ]);
32
+ const partProps = {
33
+ ...props,
34
+ ref: mergedRef,
35
+ className: resolveStateValue(className, state),
36
+ style: resolveStateValue(style, state),
37
+ children: resolveStateValue(children, state)
38
+ };
39
+ const controlled = libraryProps ?? {};
40
+ if (renderElement !== void 0) {
41
+ const { ref: _ignored, ...elementProps } = renderElement.props;
42
+ return cloneElement(renderElement, mergeProps(partProps, elementProps, controlled));
43
+ }
44
+ const resolved = mergeProps(partProps, {}, controlled);
45
+ if (typeof render === "function") return render(resolved, state);
46
+ return createElement(defaultElement, resolved);
47
+ }
48
+ //#endregion
49
+ export { rendersButton, resolveStateValue, useRender };
50
+
51
+ //# sourceMappingURL=use-render.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-render.mjs","names":[],"sources":["../../src/internal/use-render.tsx"],"sourcesContent":["\"use client\";\nimport { cloneElement, createElement, isValidElement, useMemo } from \"react\";\nimport type { CSSProperties, ReactNode, Ref } from \"react\";\nimport { mergeProps, mergeRefs } from \"./merge-props.js\";\nimport type { RenderProp, StateValue } from \"./types.js\";\n\ninterface ElementLikeProps {\n className?: string | undefined;\n style?: CSSProperties | undefined;\n children?: ReactNode;\n ref?: Ref<unknown> | undefined;\n}\n\n/** `data-*` attributes are valid JSX but absent from the element prop types. */\nexport type DataAttributes = { [key: `data-${string}`]: string | undefined };\n\nexport interface UseRenderOptions<Props extends ElementLikeProps, State> {\n /** Intrinsic element rendered when `render` is not given. */\n defaultElement: string;\n render: RenderProp<Props, State> | undefined;\n state: State;\n className: StateValue<string | undefined, State> | undefined;\n style: StateValue<CSSProperties | undefined, State> | undefined;\n children: StateValue<ReactNode, State> | undefined;\n /** The part's pass-through props for the element. A `render` element's own props win over these. */\n props: Omit<Props, \"className\" | \"style\" | \"children\"> & DataAttributes;\n /**\n * Library-controlled attributes the part computes (`role`, `aria-*` state, id wiring, `data-*`\n * state, the library's own event handlers). These win over a `render` element's props\n * (conventions 1.2).\n */\n libraryProps?: Partial<Omit<Props, \"className\" | \"style\" | \"children\">> & DataAttributes;\n /** The library's own ref, if the part needs the DOM node. */\n ref?: Ref<unknown> | undefined;\n}\n\nexport function resolveStateValue<T, State>(value: StateValue<T, State>, state: State): T {\n return typeof value === \"function\" ? (value as (state: State) => T)(state) : value;\n}\n\n/**\n * Whether a part whose default element is a `<button>` should add `type=\"button\"`: no `render`,\n * a `render` element whose type is the intrinsic `\"button\"`, or the function form (it receives\n * button props and decides itself what to spread). An element of any other type (an `<a>`, a\n * router `Link`) does not get it: `type` means something else, or nothing, there.\n */\nexport function rendersButton<Props, State>(render: RenderProp<Props, State> | undefined): boolean {\n if (render === undefined || typeof render === \"function\") return true;\n return isValidElement(render) && render.type === \"button\";\n}\n\n/**\n * Resolves `render` and the function-form `className` / `style` / `children` of a part against\n * its state and produces the element (conventions 1.2, 1.3, 1.4).\n */\nexport function useRender<Props extends ElementLikeProps, State>(\n options: UseRenderOptions<Props, State>,\n): ReactNode {\n const { defaultElement, render, state, className, style, children, props, libraryProps, ref } =\n options;\n\n const consumerRef = (props as ElementLikeProps).ref;\n const renderElement = isValidElement<ElementLikeProps>(render) ? render : undefined;\n const elementRef = renderElement?.props.ref;\n // Memoised so that the callback ref keeps its identity across renders and React does not\n // detach and re-attach the node every time.\n const mergedRef = useMemo(\n () => mergeRefs(ref, consumerRef, elementRef),\n [ref, consumerRef, elementRef],\n );\n\n const partProps = {\n ...props,\n ref: mergedRef,\n className: resolveStateValue(className, state),\n style: resolveStateValue(style, state),\n children: resolveStateValue(children, state),\n } as Record<string, unknown>;\n const controlled = (libraryProps ?? {}) as Record<string, unknown>;\n\n if (renderElement !== undefined) {\n // The element's ref is already part of `mergedRef`.\n const { ref: _ignored, ...elementProps } = renderElement.props as Record<string, unknown>;\n return cloneElement(\n renderElement,\n mergeProps(partProps, elementProps, controlled) as Partial<Props>,\n );\n }\n const resolved = mergeProps(partProps, {}, controlled) as unknown as Props;\n if (typeof render === \"function\") {\n return render(resolved, state);\n }\n return createElement(defaultElement, resolved);\n}\n"],"mappings":";;;;AAoCA,SAAgB,kBAA4B,OAA6B,OAAiB;CACxF,OAAO,OAAO,UAAU,aAAc,MAA8B,KAAK,IAAI;AAC/E;;;;;;;AAQA,SAAgB,cAA4B,QAAuD;CACjG,IAAI,WAAW,KAAA,KAAa,OAAO,WAAW,YAAY,OAAO;CACjE,OAAO,eAAe,MAAM,KAAK,OAAO,SAAS;AACnD;;;;;AAMA,SAAgB,UACd,SACW;CACX,MAAM,EAAE,gBAAgB,QAAQ,OAAO,WAAW,OAAO,UAAU,OAAO,cAAc,QACtF;CAEF,MAAM,cAAe,MAA2B;CAChD,MAAM,gBAAgB,eAAiC,MAAM,IAAI,SAAS,KAAA;CAC1E,MAAM,aAAa,eAAe,MAAM;CAGxC,MAAM,YAAY,cACV,UAAU,KAAK,aAAa,UAAU,GAC5C;EAAC;EAAK;EAAa;CAAU,CAC/B;CAEA,MAAM,YAAY;EAChB,GAAG;EACH,KAAK;EACL,WAAW,kBAAkB,WAAW,KAAK;EAC7C,OAAO,kBAAkB,OAAO,KAAK;EACrC,UAAU,kBAAkB,UAAU,KAAK;CAC7C;CACA,MAAM,aAAc,gBAAgB,CAAC;CAErC,IAAI,kBAAkB,KAAA,GAAW;EAE/B,MAAM,EAAE,KAAK,UAAU,GAAG,iBAAiB,cAAc;EACzD,OAAO,aACL,eACA,WAAW,WAAW,cAAc,UAAU,CAChD;CACF;CACA,MAAM,WAAW,WAAW,WAAW,CAAC,GAAG,UAAU;CACrD,IAAI,OAAO,WAAW,YACpB,OAAO,OAAO,UAAU,KAAK;CAE/B,OAAO,cAAc,gBAAgB,QAAQ;AAC/C"}
@@ -0,0 +1,40 @@
1
+ "use client";
2
+ import { useEffect, useEffectEvent, useLayoutEffect, useRef } from "react";
3
+ //#region src/internal/use-until-found.ts
4
+ /**
5
+ * The single home of the `hidden="until-found"` workaround (conventions 7.4).
6
+ *
7
+ * react-dom 19.3 serialises `hidden` as a boolean attribute (`hidden=""` on client and server),
8
+ * so the part renders a boolean `hidden` and this hook upgrades the attribute to `"until-found"`
9
+ * through a ref in a layout effect. The upgrade is re-asserted after **every** commit while
10
+ * `active`: when the browser reveals the element it removes the attribute itself, and if the
11
+ * resulting action is rejected React sees no prop change (the boolean `hidden` stayed `true`
12
+ * throughout) and would leave the element visible.
13
+ *
14
+ * `beforematch` has no React prop, so a native listener is attached in an effect through the
15
+ * returned ref (conventions 2.6, 7.4). Attach the ref to the element that renders the boolean
16
+ * `hidden`.
17
+ */
18
+ function useUntilFound(options) {
19
+ const { active } = options;
20
+ const node = useRef(null);
21
+ const onMatch = useEffectEvent((event) => {
22
+ options.onMatch(event);
23
+ });
24
+ useLayoutEffect(() => {
25
+ const element = node.current;
26
+ if (element !== null && active && element.getAttribute("hidden") !== "until-found") element.setAttribute("hidden", "until-found");
27
+ });
28
+ useEffect(() => {
29
+ const element = node.current;
30
+ if (element === null) return;
31
+ const listener = (event) => onMatch(event);
32
+ element.addEventListener("beforematch", listener);
33
+ return () => element.removeEventListener("beforematch", listener);
34
+ });
35
+ return node;
36
+ }
37
+ //#endregion
38
+ export { useUntilFound };
39
+
40
+ //# sourceMappingURL=use-until-found.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-until-found.mjs","names":[],"sources":["../../src/internal/use-until-found.ts"],"sourcesContent":["\"use client\";\nimport { useEffect, useEffectEvent, useLayoutEffect, useRef } from \"react\";\nimport type { RefObject } from \"react\";\n\nexport interface UseUntilFoundOptions {\n /**\n * The element should currently be `hidden=\"until-found\"`. The part renders a boolean `hidden`\n * itself as well, so server-rendered HTML hides it (as `hidden=\"\"`) before hydration.\n *\n * Pass `false` while a change the element itself triggered (its `beforematch`) is pending:\n * the browser has already revealed it, and re-asserting would hide it again until the commit.\n * When that change is rejected the owner's optimistic state reverts, `active` becomes `true`\n * again and the next commit re-hides it.\n */\n active: boolean;\n /** Called with the `beforematch` event when find-in-page or a text fragment reveals it. */\n onMatch: (event: Event) => void;\n}\n\n/**\n * The single home of the `hidden=\"until-found\"` workaround (conventions 7.4).\n *\n * react-dom 19.3 serialises `hidden` as a boolean attribute (`hidden=\"\"` on client and server),\n * so the part renders a boolean `hidden` and this hook upgrades the attribute to `\"until-found\"`\n * through a ref in a layout effect. The upgrade is re-asserted after **every** commit while\n * `active`: when the browser reveals the element it removes the attribute itself, and if the\n * resulting action is rejected React sees no prop change (the boolean `hidden` stayed `true`\n * throughout) and would leave the element visible.\n *\n * `beforematch` has no React prop, so a native listener is attached in an effect through the\n * returned ref (conventions 2.6, 7.4). Attach the ref to the element that renders the boolean\n * `hidden`.\n */\nexport function useUntilFound<E extends HTMLElement>(\n options: UseUntilFoundOptions,\n): RefObject<E | null> {\n const { active } = options;\n const node = useRef<E | null>(null);\n const onMatch = useEffectEvent((event: Event) => {\n options.onMatch(event);\n });\n\n // No dependency array on purpose: re-assert after every commit of the owner (see above).\n useLayoutEffect(() => {\n const element = node.current;\n if (element !== null && active && element.getAttribute(\"hidden\") !== \"until-found\") {\n element.setAttribute(\"hidden\", \"until-found\");\n }\n });\n\n // Re-subscribed after every commit so a swapped element (a changed `render`) is covered.\n useEffect(() => {\n const element = node.current;\n if (element === null) return;\n const listener = (event: Event) => onMatch(event);\n element.addEventListener(\"beforematch\", listener);\n return () => element.removeEventListener(\"beforematch\", listener);\n });\n\n return node;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAiCA,SAAgB,cACd,SACqB;CACrB,MAAM,EAAE,WAAW;CACnB,MAAM,OAAO,OAAiB,IAAI;CAClC,MAAM,UAAU,gBAAgB,UAAiB;EAC/C,QAAQ,QAAQ,KAAK;CACvB,CAAC;CAGD,sBAAsB;EACpB,MAAM,UAAU,KAAK;EACrB,IAAI,YAAY,QAAQ,UAAU,QAAQ,aAAa,QAAQ,MAAM,eACnE,QAAQ,aAAa,UAAU,aAAa;CAEhD,CAAC;CAGD,gBAAgB;EACd,MAAM,UAAU,KAAK;EACrB,IAAI,YAAY,MAAM;EACtB,MAAM,YAAY,UAAiB,QAAQ,KAAK;EAChD,QAAQ,iBAAiB,eAAe,QAAQ;EAChD,aAAa,QAAQ,oBAAoB,eAAe,QAAQ;CAClE,CAAC;CAED,OAAO;AACT"}
@@ -0,0 +1,34 @@
1
+ import "../internal/platform-attributes.mjs";
2
+ import { MenuItem, MenuItemDetails, MenuItemProps, MenuItemState } from "./menu-item.mjs";
3
+ import { MenuCheckboxItem, MenuCheckboxItemProps, MenuCheckboxItemState } from "./menu-checkbox-item.mjs";
4
+ import { MenuGroup, MenuGroupLabel, MenuGroupLabelProps, MenuGroupLabelState, MenuGroupProps, MenuGroupState, MenuSeparator, MenuSeparatorProps, MenuSeparatorState } from "./menu-group.mjs";
5
+ import { MenuAlign, MenuPopup, MenuPopupProps, MenuPopupState, MenuSide } from "./menu-popup.mjs";
6
+ import { MenuRadioGroup, MenuRadioGroupProps, MenuRadioGroupState } from "./menu-radio-group.mjs";
7
+ import { MenuRadioItem, MenuRadioItemProps, MenuRadioItemState } from "./menu-radio-item.mjs";
8
+ import { MenuOpenChangeDetails, MenuRoot, MenuRootProps, MenuRootState } from "./menu-root.mjs";
9
+ import { MenuTrigger, MenuTriggerProps, MenuTriggerState } from "./menu-trigger.mjs";
10
+ import { MenuSubmenuTrigger, MenuSubmenuTriggerProps, MenuSubmenuTriggerState } from "./menu-submenu-trigger.mjs";
11
+ //#region src/menu/index.d.ts
12
+ /**
13
+ * Menu button (WAI-ARIA APG Menu Button + Menu) on `popover="auto"`: `Root` (no DOM) > `Trigger`
14
+ * (`button[aria-haspopup=menu]`) + `Popup` (`div[popover=auto][role=menu]`) > `Item`,
15
+ * `CheckboxItem`, `RadioGroup` > `RadioItem`, `Group` > `GroupLabel`, `Separator`, and submenus
16
+ * (a `Root` inside a `Popup`, opened by its `SubmenuTrigger`). An item's `action` runs in its own
17
+ * transition with `data-pending` on the item, and the whole menu closes when it settles.
18
+ */
19
+ export declare const Menu: {
20
+ readonly Root: typeof MenuRoot;
21
+ readonly Trigger: typeof MenuTrigger;
22
+ readonly Popup: typeof MenuPopup;
23
+ readonly Item: typeof MenuItem;
24
+ readonly CheckboxItem: typeof MenuCheckboxItem;
25
+ readonly RadioGroup: typeof MenuRadioGroup;
26
+ readonly RadioItem: typeof MenuRadioItem;
27
+ readonly Group: typeof MenuGroup;
28
+ readonly GroupLabel: typeof MenuGroupLabel;
29
+ readonly Separator: typeof MenuSeparator;
30
+ readonly SubmenuTrigger: typeof MenuSubmenuTrigger;
31
+ };
32
+ //#endregion
33
+ export type { MenuAlign, MenuCheckboxItemProps, MenuCheckboxItemState, MenuGroupLabelProps, MenuGroupLabelState, MenuGroupProps, MenuGroupState, MenuItemDetails, MenuItemProps, MenuItemState, MenuOpenChangeDetails, MenuPopupProps, MenuPopupState, MenuRadioGroupProps, MenuRadioGroupState, MenuRadioItemProps, MenuRadioItemState, MenuRootProps, MenuRootState, MenuSeparatorProps, MenuSeparatorState, MenuSide, MenuSubmenuTriggerProps, MenuSubmenuTriggerState, MenuTriggerProps, MenuTriggerState };
34
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/menu/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;qBAoBa;WACX,aAAI;WACJ,gBAAO;WACP,cAAK;WACL,aAAI;WACJ,qBAAY;WACZ,mBAAU;WACV,kBAAS;WACT,cAAK;WACL,mBAAU;WACV,kBAAS;WACT,uBAAc"}
@@ -0,0 +1,34 @@
1
+ import { MenuCheckboxItem } from "./menu-checkbox-item.mjs";
2
+ import { MenuGroup, MenuGroupLabel, MenuSeparator } from "./menu-group.mjs";
3
+ import { MenuItem } from "./menu-item.mjs";
4
+ import { MenuPopup } from "./menu-popup.mjs";
5
+ import { MenuRadioGroup } from "./menu-radio-group.mjs";
6
+ import { MenuRadioItem } from "./menu-radio-item.mjs";
7
+ import { MenuRoot } from "./menu-root.mjs";
8
+ import { MenuTrigger } from "./menu-trigger.mjs";
9
+ import { MenuSubmenuTrigger } from "./menu-submenu-trigger.mjs";
10
+ //#region src/menu/index.ts
11
+ /**
12
+ * Menu button (WAI-ARIA APG Menu Button + Menu) on `popover="auto"`: `Root` (no DOM) > `Trigger`
13
+ * (`button[aria-haspopup=menu]`) + `Popup` (`div[popover=auto][role=menu]`) > `Item`,
14
+ * `CheckboxItem`, `RadioGroup` > `RadioItem`, `Group` > `GroupLabel`, `Separator`, and submenus
15
+ * (a `Root` inside a `Popup`, opened by its `SubmenuTrigger`). An item's `action` runs in its own
16
+ * transition with `data-pending` on the item, and the whole menu closes when it settles.
17
+ */
18
+ const Menu = {
19
+ Root: MenuRoot,
20
+ Trigger: MenuTrigger,
21
+ Popup: MenuPopup,
22
+ Item: MenuItem,
23
+ CheckboxItem: MenuCheckboxItem,
24
+ RadioGroup: MenuRadioGroup,
25
+ RadioItem: MenuRadioItem,
26
+ Group: MenuGroup,
27
+ GroupLabel: MenuGroupLabel,
28
+ Separator: MenuSeparator,
29
+ SubmenuTrigger: MenuSubmenuTrigger
30
+ };
31
+ //#endregion
32
+ export { Menu };
33
+
34
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/menu/index.ts"],"sourcesContent":["// Ships the platform attribute typings (`command`, `commandfor`, `focusgroup`) with this subpath\n// (conventions 7.4). Type-only: the built JS has no import of it.\nimport \"../internal/platform-attributes.js\";\nimport { MenuCheckboxItem } from \"./menu-checkbox-item.js\";\nimport { MenuGroup, MenuGroupLabel, MenuSeparator } from \"./menu-group.js\";\nimport { MenuItem } from \"./menu-item.js\";\nimport { MenuPopup } from \"./menu-popup.js\";\nimport { MenuRadioGroup } from \"./menu-radio-group.js\";\nimport { MenuRadioItem } from \"./menu-radio-item.js\";\nimport { MenuRoot } from \"./menu-root.js\";\nimport { MenuSubmenuTrigger } from \"./menu-submenu-trigger.js\";\nimport { MenuTrigger } from \"./menu-trigger.js\";\n\n/**\n * Menu button (WAI-ARIA APG Menu Button + Menu) on `popover=\"auto\"`: `Root` (no DOM) > `Trigger`\n * (`button[aria-haspopup=menu]`) + `Popup` (`div[popover=auto][role=menu]`) > `Item`,\n * `CheckboxItem`, `RadioGroup` > `RadioItem`, `Group` > `GroupLabel`, `Separator`, and submenus\n * (a `Root` inside a `Popup`, opened by its `SubmenuTrigger`). An item's `action` runs in its own\n * transition with `data-pending` on the item, and the whole menu closes when it settles.\n */\nexport const Menu = {\n Root: MenuRoot,\n Trigger: MenuTrigger,\n Popup: MenuPopup,\n Item: MenuItem,\n CheckboxItem: MenuCheckboxItem,\n RadioGroup: MenuRadioGroup,\n RadioItem: MenuRadioItem,\n Group: MenuGroup,\n GroupLabel: MenuGroupLabel,\n Separator: MenuSeparator,\n SubmenuTrigger: MenuSubmenuTrigger,\n} as const;\n\nexport type { MenuRootProps, MenuRootState, MenuOpenChangeDetails } from \"./menu-root.js\";\nexport type { MenuTriggerProps, MenuTriggerState } from \"./menu-trigger.js\";\nexport type { MenuPopupProps, MenuPopupState, MenuSide, MenuAlign } from \"./menu-popup.js\";\nexport type { MenuItemProps, MenuItemState, MenuItemDetails } from \"./menu-item.js\";\nexport type { MenuCheckboxItemProps, MenuCheckboxItemState } from \"./menu-checkbox-item.js\";\nexport type { MenuRadioGroupProps, MenuRadioGroupState } from \"./menu-radio-group.js\";\nexport type { MenuRadioItemProps, MenuRadioItemState } from \"./menu-radio-item.js\";\nexport type {\n MenuGroupProps,\n MenuGroupState,\n MenuGroupLabelProps,\n MenuGroupLabelState,\n MenuSeparatorProps,\n MenuSeparatorState,\n} from \"./menu-group.js\";\nexport type { MenuSubmenuTriggerProps, MenuSubmenuTriggerState } from \"./menu-submenu-trigger.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;AAoBA,MAAa,OAAO;CAClB,MAAM;CACN,SAAS;CACT,OAAO;CACP,MAAM;CACN,cAAc;CACd,YAAY;CACZ,WAAW;CACX,OAAO;CACP,YAAY;CACZ,WAAW;CACX,gBAAgB;AAClB"}
@@ -0,0 +1,42 @@
1
+ import { ChangeAction, RenderProp, StateValue } from "../internal/types.mjs";
2
+ import { MenuItemProps } from "./menu-item.mjs";
3
+ import { CSSProperties, ComponentPropsWithRef, ReactNode } from "react";
4
+ //#region src/menu/menu-checkbox-item.d.ts
5
+ export interface MenuCheckboxItemState {
6
+ /** Optimistic checked state (`aria-checked`). */
7
+ readonly checked: boolean;
8
+ /** `checkedChangeAction` is in flight. */
9
+ readonly pending: boolean;
10
+ readonly disabled: boolean;
11
+ }
12
+ export interface MenuCheckboxItemProps extends Omit<MenuItemProps, "action" | "closeOnActivate" | "className" | "style" | "children" | "render"> {
13
+ /** Controlled checked state. `undefined` means uncontrolled. */
14
+ checked?: boolean;
15
+ /** Initial checked state when uncontrolled. Default `false`. */
16
+ defaultChecked?: boolean;
17
+ /**
18
+ * Awaited inside the item's transition; `aria-checked` flips at once and rolls back on
19
+ * rejection (which reaches the nearest Error Boundary above the item). May be a Server
20
+ * Function.
21
+ */
22
+ checkedChangeAction?: ChangeAction<boolean>;
23
+ /** Close the whole menu once the change settles. Default `false`: the menu stays open. */
24
+ closeOnActivate?: boolean;
25
+ className?: StateValue<string | undefined, MenuCheckboxItemState>;
26
+ style?: StateValue<CSSProperties | undefined, MenuCheckboxItemState>;
27
+ children?: StateValue<ReactNode, MenuCheckboxItemState>;
28
+ render?: RenderProp<ComponentPropsWithRef<"button">, MenuCheckboxItemState>;
29
+ }
30
+ /**
31
+ * A checkable menu item: `<button type="button" role="menuitemcheckbox" aria-checked>`. Click,
32
+ * Enter or Space toggles it through its own transition with optimistic `aria-checked` /
33
+ * `data-checked` and `data-pending`; the menu stays open unless `closeOnActivate`.
34
+ *
35
+ * Owned keys (conventions 1.2): those of `Menu.Item`, plus `aria-checked` and `data-checked`.
36
+ */
37
+ export declare function MenuCheckboxItem({ checked, defaultChecked, checkedChangeAction, closeOnActivate, disabled, label, className, style, children, render, ...rest }: MenuCheckboxItemProps): ReactNode;
38
+ export declare namespace MenuCheckboxItem {
39
+ var displayName: string;
40
+ }
41
+ //#endregion
42
+ //# sourceMappingURL=menu-checkbox-item.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu-checkbox-item.d.mts","names":[],"sources":["../../src/menu/menu-checkbox-item.tsx"],"mappings":";;;;iBAaiB;;WAEN;;WAEA;WACA;;iBAGM,8BAA8B,KAC7C;;EAIA;;EAEA;;;;;;EAMA,sBAAsB;;EAEtB;EACA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,iCAAiC;;;;;;;;;wBAUvC,mBACd,SACA,gBACA,qBACA,iBACA,UACA,OACA,WACA,OACA,UACA,WACG,QACF,wBAAqB;yBAZR"}
@@ -0,0 +1,71 @@
1
+ "use client";
2
+ import { createActionDetails } from "../internal/action-details.mjs";
3
+ import { dataAttr } from "../internal/data-attr.mjs";
4
+ import { transitionTypes } from "../internal/transition-types.mjs";
5
+ import { useControllableAction } from "../internal/use-controllable-action.mjs";
6
+ import { rendersButton, useRender } from "../internal/use-render.mjs";
7
+ import { closeStackLater, useMenuContext } from "./menu-context.mjs";
8
+ import { useMenuItem } from "./use-menu-item.mjs";
9
+ import { useRef } from "react";
10
+ //#region src/menu/menu-checkbox-item.tsx
11
+ /**
12
+ * A checkable menu item: `<button type="button" role="menuitemcheckbox" aria-checked>`. Click,
13
+ * Enter or Space toggles it through its own transition with optimistic `aria-checked` /
14
+ * `data-checked` and `data-pending`; the menu stays open unless `closeOnActivate`.
15
+ *
16
+ * Owned keys (conventions 1.2): those of `Menu.Item`, plus `aria-checked` and `data-checked`.
17
+ */
18
+ function MenuCheckboxItem({ checked, defaultChecked, checkedChangeAction, closeOnActivate = false, disabled = false, label, className, style, children, render, ...rest }) {
19
+ const menu = useMenuContext("Menu.CheckboxItem");
20
+ const after = useRef(/* @__PURE__ */ new WeakMap());
21
+ const { optimistic, pending, dispatch } = useControllableAction({
22
+ controlled: checked,
23
+ defaultValue: defaultChecked,
24
+ initialValue: false,
25
+ action: async (next, details) => {
26
+ await checkedChangeAction?.(next, details);
27
+ after.current.get(details)?.();
28
+ },
29
+ transitionTypes: (next) => transitionTypes("menu", next ? "check" : "uncheck"),
30
+ warn: {
31
+ component: "Menu.CheckboxItem",
32
+ prop: "checked"
33
+ }
34
+ });
35
+ const { libraryProps } = useMenuItem({
36
+ part: "Menu.CheckboxItem",
37
+ role: "menuitemcheckbox",
38
+ disabled,
39
+ label,
40
+ activate: (event) => {
41
+ const details = createActionDetails(event, {});
42
+ if (closeOnActivate) after.current.set(details, closeStackLater(menu, event));
43
+ dispatch(!optimistic, details);
44
+ }
45
+ });
46
+ return useRender({
47
+ defaultElement: "button",
48
+ render,
49
+ state: {
50
+ checked: optimistic,
51
+ pending,
52
+ disabled
53
+ },
54
+ className,
55
+ style,
56
+ children,
57
+ props: rest,
58
+ libraryProps: {
59
+ ...rendersButton(render) && { type: "button" },
60
+ ...libraryProps,
61
+ "aria-checked": optimistic,
62
+ "data-checked": dataAttr(optimistic),
63
+ "data-pending": dataAttr(pending)
64
+ }
65
+ });
66
+ }
67
+ MenuCheckboxItem.displayName = "MenuCheckboxItem";
68
+ //#endregion
69
+ export { MenuCheckboxItem };
70
+
71
+ //# sourceMappingURL=menu-checkbox-item.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu-checkbox-item.mjs","names":[],"sources":["../../src/menu/menu-checkbox-item.tsx"],"sourcesContent":["\"use client\";\nimport { useRef } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { transitionTypes } from \"../internal/transition-types.js\";\nimport type { ActionDetails, ChangeAction, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport { closeStackLater, useMenuContext } from \"./menu-context.js\";\nimport type { MenuItemProps } from \"./menu-item.js\";\nimport { useMenuItem } from \"./use-menu-item.js\";\n\nexport interface MenuCheckboxItemState {\n /** Optimistic checked state (`aria-checked`). */\n readonly checked: boolean;\n /** `checkedChangeAction` is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface MenuCheckboxItemProps extends Omit<\n MenuItemProps,\n \"action\" | \"closeOnActivate\" | \"className\" | \"style\" | \"children\" | \"render\"\n> {\n /** Controlled checked state. `undefined` means uncontrolled. */\n checked?: boolean;\n /** Initial checked state when uncontrolled. Default `false`. */\n defaultChecked?: boolean;\n /**\n * Awaited inside the item's transition; `aria-checked` flips at once and rolls back on\n * rejection (which reaches the nearest Error Boundary above the item). May be a Server\n * Function.\n */\n checkedChangeAction?: ChangeAction<boolean>;\n /** Close the whole menu once the change settles. Default `false`: the menu stays open. */\n closeOnActivate?: boolean;\n className?: StateValue<string | undefined, MenuCheckboxItemState>;\n style?: StateValue<CSSProperties | undefined, MenuCheckboxItemState>;\n children?: StateValue<ReactNode, MenuCheckboxItemState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, MenuCheckboxItemState>;\n}\n\n/**\n * A checkable menu item: `<button type=\"button\" role=\"menuitemcheckbox\" aria-checked>`. Click,\n * Enter or Space toggles it through its own transition with optimistic `aria-checked` /\n * `data-checked` and `data-pending`; the menu stays open unless `closeOnActivate`.\n *\n * Owned keys (conventions 1.2): those of `Menu.Item`, plus `aria-checked` and `data-checked`.\n */\nexport function MenuCheckboxItem({\n checked,\n defaultChecked,\n checkedChangeAction,\n closeOnActivate = false,\n disabled = false,\n label,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuCheckboxItemProps) {\n const menu = useMenuContext(\"Menu.CheckboxItem\");\n // The close to run after a dispatch's action resolved, keyed by the dispatch's details.\n const after = useRef(new WeakMap<object, () => void>());\n const { optimistic, pending, dispatch } = useControllableAction<boolean>({\n controlled: checked,\n defaultValue: defaultChecked,\n initialValue: false,\n action: async (next, details) => {\n await checkedChangeAction?.(next, details);\n after.current.get(details)?.();\n },\n transitionTypes: (next) => transitionTypes(\"menu\", next ? \"check\" : \"uncheck\"),\n warn: { component: \"Menu.CheckboxItem\", prop: \"checked\" },\n });\n\n const { libraryProps } = useMenuItem({\n part: \"Menu.CheckboxItem\",\n role: \"menuitemcheckbox\",\n disabled,\n label,\n activate: (event) => {\n const details = createActionDetails(event, {}) as ActionDetails;\n if (closeOnActivate) after.current.set(details, closeStackLater(menu, event));\n dispatch(!optimistic, details);\n },\n });\n\n const state: MenuCheckboxItemState = { checked: optimistic, pending, disabled };\n\n return useRender<ComponentPropsWithRef<\"button\">, MenuCheckboxItemState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n ...(rendersButton(render) && { type: \"button\" as const }),\n ...libraryProps,\n \"aria-checked\": optimistic,\n \"data-checked\": dataAttr(optimistic),\n \"data-pending\": dataAttr(pending),\n },\n });\n}\n\nMenuCheckboxItem.displayName = \"MenuCheckboxItem\";\n"],"mappings":";;;;;;;;;;;;;;;;;AAkDA,SAAgB,iBAAiB,EAC/B,SACA,gBACA,qBACA,kBAAkB,OAClB,WAAW,OACX,OACA,WACA,OACA,UACA,QACA,GAAG,QACqB;CACxB,MAAM,OAAO,eAAe,mBAAmB;CAE/C,MAAM,QAAQ,uBAAO,IAAI,QAA4B,CAAC;CACtD,MAAM,EAAE,YAAY,SAAS,aAAa,sBAA+B;EACvE,YAAY;EACZ,cAAc;EACd,cAAc;EACd,QAAQ,OAAO,MAAM,YAAY;GAC/B,MAAM,sBAAsB,MAAM,OAAO;GACzC,MAAM,QAAQ,IAAI,OAAO,CAAC,GAAG;EAC/B;EACA,kBAAkB,SAAS,gBAAgB,QAAQ,OAAO,UAAU,SAAS;EAC7E,MAAM;GAAE,WAAW;GAAqB,MAAM;EAAU;CAC1D,CAAC;CAED,MAAM,EAAE,iBAAiB,YAAY;EACnC,MAAM;EACN,MAAM;EACN;EACA;EACA,WAAW,UAAU;GACnB,MAAM,UAAU,oBAAoB,OAAO,CAAC,CAAC;GAC7C,IAAI,iBAAiB,MAAM,QAAQ,IAAI,SAAS,gBAAgB,MAAM,KAAK,CAAC;GAC5E,SAAS,CAAC,YAAY,OAAO;EAC/B;CACF,CAAC;CAID,OAAO,UAAkE;EACvE,gBAAgB;EAChB;EACA,OAAA;GALqC,SAAS;GAAY;GAAS;EAK/D;EACJ;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;GACvD,GAAG;GACH,gBAAgB;GAChB,gBAAgB,SAAS,UAAU;GACnC,gBAAgB,SAAS,OAAO;EAClC;CACF,CAAC;AACH;AAEA,iBAAiB,cAAc"}
@@ -0,0 +1 @@
1
+ import "../internal/use-popover-root.mjs";
@@ -0,0 +1,28 @@
1
+ "use client";
2
+ import { createContext, use } from "react";
3
+ //#region src/menu/menu-context.ts
4
+ const MenuContext = createContext(null);
5
+ /**
6
+ * The close an activated item runs once its action settled: the whole stack, through the
7
+ * top-level Root (design 2.2). Recorded at activation: if the menu was dismissed and opened again
8
+ * meanwhile, the close belongs to the previous opening and does nothing (as `useActionTrigger`).
9
+ */
10
+ function closeStackLater(menu, event) {
11
+ const { popover } = menu.root;
12
+ const generation = popover.openGeneration();
13
+ return () => {
14
+ if (popover.openGeneration() === generation) popover.requestClose(event, "activate");
15
+ };
16
+ }
17
+ function useMenuContext(part) {
18
+ const context = use(MenuContext);
19
+ if (context === null) throw new Error(`${part} must be rendered inside a Menu.Root.`);
20
+ return context;
21
+ }
22
+ const MenuListContext = createContext(null);
23
+ const MenuGroupContext = createContext(null);
24
+ const MenuRadioGroupContext = createContext(null);
25
+ //#endregion
26
+ export { MenuContext, MenuGroupContext, MenuListContext, MenuRadioGroupContext, closeStackLater, useMenuContext };
27
+
28
+ //# sourceMappingURL=menu-context.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu-context.mjs","names":[],"sources":["../../src/menu/menu-context.ts"],"sourcesContent":["\"use client\";\nimport { createContext, use } from \"react\";\nimport type { DelayedPair } from \"../internal/use-delayed-pair.js\";\nimport type { PopoverRootValue } from \"../internal/use-popover-root.js\";\n\n/**\n * Where focus goes when the menu opens (design/components/menu.md 6.1): the first enabled item,\n * the last one (ArrowUp on the Trigger), or nowhere (a hover-opened submenu keeps focus on its\n * SubmenuTrigger).\n */\nexport type MenuOpenCause = \"first\" | \"last\" | \"none\";\n\n/** How the open being shown should place focus (design 6.1; review H1). */\nexport interface MenuOpenFocus {\n readonly cause: MenuOpenCause;\n /**\n * The open came from a pointer click (`detail > 0`): the item is focused without the focus\n * ring where the engine supports `focus({ focusVisible: false })`.\n */\n readonly pointer: boolean;\n}\n\n/** One menu level: what its Root provides to its Trigger, Popup and items. */\nexport interface MenuContextValue {\n /** The shared popover machinery (`usePopoverRoot`). */\n readonly popover: PopoverRootValue;\n /** This Root is nested in a `Menu.Popup`. */\n readonly submenu: boolean;\n /** The top-level menu: item activation closes the whole stack through it (design 2.2). */\n readonly root: MenuContextValue;\n /** The enclosing menu of a submenu; `null` at the top level. */\n readonly parent: MenuContextValue | null;\n readonly loop: boolean;\n readonly delay: number;\n readonly closeDelay: number;\n /** The Trigger's id (`aria-labelledby` of the Popup). */\n readonly triggerId: string;\n /** Record the focus target of the next open (and whether a pointer asked for it). */\n readonly setOpenCause: (cause: MenuOpenCause, pointer?: boolean) => void;\n /** Read and clear the focus target of the open being shown (default `\"first\"`, keyboard). */\n readonly takeOpenCause: () => MenuOpenFocus;\n /** Submenu hover-intent timers, shared by the SubmenuTrigger and the Popup. */\n readonly timers: DelayedPair;\n}\n\nexport const MenuContext = createContext<MenuContextValue | null>(null);\n\n/**\n * The close an activated item runs once its action settled: the whole stack, through the\n * top-level Root (design 2.2). Recorded at activation: if the menu was dismissed and opened again\n * meanwhile, the close belongs to the previous opening and does nothing (as `useActionTrigger`).\n */\nexport function closeStackLater(menu: MenuContextValue, event: Event): () => void {\n const { popover } = menu.root;\n const generation = popover.openGeneration();\n return () => {\n if (popover.openGeneration() === generation) popover.requestClose(event, \"activate\");\n };\n}\n\nexport function useMenuContext(part: string): MenuContextValue {\n const context = use(MenuContext);\n if (context === null) {\n throw new Error(`${part} must be rendered inside a Menu.Root.`);\n }\n return context;\n}\n\n/** What a `Menu.Popup` tells the items rendered in it. */\nexport interface MenuListContextValue {\n /** The menu this popup belongs to. */\n readonly menu: MenuContextValue;\n /**\n * `experimental_focusgroup` is on and the engine implements it: items render no `tabindex`\n * (Chromium leaves `tabindex=\"-1\"` elements out of a focusgroup) and the JS arrow keys step\n * aside.\n */\n readonly nativeFocusgroup: boolean;\n /** A typeahead buffer is active: Space is a character, not an activation (design 5.1). */\n readonly typingAhead: () => boolean;\n}\n\nexport const MenuListContext = createContext<MenuListContextValue | null>(null);\n\n/** `Menu.Group` / `Menu.RadioGroup`: the id their `Menu.GroupLabel` renders. */\nexport interface MenuGroupContextValue {\n readonly labelId: string;\n}\n\nexport const MenuGroupContext = createContext<MenuGroupContextValue | null>(null);\n\n/** `Menu.RadioGroup`'s state for its `Menu.RadioItem`s. */\nexport interface MenuRadioGroupContextValue {\n /** Optimistic value. */\n readonly value: string | null;\n readonly pending: boolean;\n /**\n * Dispatch `value` through the group's transition; `then` runs once the change action\n * resolved (an item's `closeOnActivate`).\n */\n readonly select: (value: string, event: Event, then: (() => void) | undefined) => void;\n}\n\nexport const MenuRadioGroupContext = createContext<MenuRadioGroupContextValue | null>(null);\n"],"mappings":";;;AA6CA,MAAa,cAAc,cAAuC,IAAI;;;;;;AAOtE,SAAgB,gBAAgB,MAAwB,OAA0B;CAChF,MAAM,EAAE,YAAY,KAAK;CACzB,MAAM,aAAa,QAAQ,eAAe;CAC1C,aAAa;EACX,IAAI,QAAQ,eAAe,MAAM,YAAY,QAAQ,aAAa,OAAO,UAAU;CACrF;AACF;AAEA,SAAgB,eAAe,MAAgC;CAC7D,MAAM,UAAU,IAAI,WAAW;CAC/B,IAAI,YAAY,MACd,MAAM,IAAI,MAAM,GAAG,KAAK,sCAAsC;CAEhE,OAAO;AACT;AAgBA,MAAa,kBAAkB,cAA2C,IAAI;AAO9E,MAAa,mBAAmB,cAA4C,IAAI;AAchF,MAAa,wBAAwB,cAAiD,IAAI"}