@funstack/ui 0.1.0-alpha.3 → 0.1.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/dist/accordion/accordion-root.mjs +2 -1
  2. package/dist/accordion/accordion-root.mjs.map +1 -1
  3. package/dist/combobox/combobox-clear.mjs +2 -1
  4. package/dist/combobox/combobox-clear.mjs.map +1 -1
  5. package/dist/combobox/combobox-list.mjs +2 -1
  6. package/dist/combobox/combobox-list.mjs.map +1 -1
  7. package/dist/combobox/combobox-popup.d.mts.map +1 -1
  8. package/dist/combobox/combobox-popup.mjs +7 -2
  9. package/dist/combobox/combobox-popup.mjs.map +1 -1
  10. package/dist/combobox/combobox-root.mjs +1 -1
  11. package/dist/combobox/combobox-trigger.mjs +2 -1
  12. package/dist/combobox/combobox-trigger.mjs.map +1 -1
  13. package/dist/dialog/dialog-close.mjs +2 -1
  14. package/dist/dialog/dialog-close.mjs.map +1 -1
  15. package/dist/dialog/dialog-form.d.mts +14 -8
  16. package/dist/dialog/dialog-form.d.mts.map +1 -1
  17. package/dist/dialog/dialog-form.mjs +36 -5
  18. package/dist/dialog/dialog-form.mjs.map +1 -1
  19. package/dist/dialog/dialog-popup.d.mts.map +1 -1
  20. package/dist/dialog/dialog-popup.mjs +13 -5
  21. package/dist/dialog/dialog-popup.mjs.map +1 -1
  22. package/dist/dialog/dialog-root.mjs +1 -1
  23. package/dist/dialog/dialog-trigger.mjs +2 -1
  24. package/dist/dialog/dialog-trigger.mjs.map +1 -1
  25. package/dist/field/field-root.mjs +5 -3
  26. package/dist/field/field-root.mjs.map +1 -1
  27. package/dist/form/form-root.mjs +2 -1
  28. package/dist/form/form-root.mjs.map +1 -1
  29. package/dist/form/form-submit.mjs +2 -1
  30. package/dist/form/form-submit.mjs.map +1 -1
  31. package/dist/index.d.mts +27 -1
  32. package/dist/index.mjs +6 -1
  33. package/dist/internal/announce.mjs +53 -0
  34. package/dist/internal/announce.mjs.map +1 -0
  35. package/dist/internal/decimal.mjs +27 -0
  36. package/dist/internal/decimal.mjs.map +1 -0
  37. package/dist/internal/dev-warn.mjs +17 -0
  38. package/dist/internal/dev-warn.mjs.map +1 -0
  39. package/dist/internal/field-context.mjs.map +1 -1
  40. package/dist/internal/range-value.mjs +51 -0
  41. package/dist/internal/range-value.mjs.map +1 -0
  42. package/dist/internal/render-element.mjs +54 -0
  43. package/dist/internal/render-element.mjs.map +1 -0
  44. package/dist/internal/toolbar-context.mjs +17 -0
  45. package/dist/internal/toolbar-context.mjs.map +1 -0
  46. package/dist/internal/use-exit-animation.mjs +26 -6
  47. package/dist/internal/use-exit-animation.mjs.map +1 -1
  48. package/dist/internal/use-field-control.mjs +5 -3
  49. package/dist/internal/use-field-control.mjs.map +1 -1
  50. package/dist/internal/use-popover-popup.mjs +3 -2
  51. package/dist/internal/use-popover-popup.mjs.map +1 -1
  52. package/dist/internal/use-popover-trigger.mjs +1 -1
  53. package/dist/internal/use-render.mjs +11 -34
  54. package/dist/internal/use-render.mjs.map +1 -1
  55. package/dist/internal/use-roving-focus.mjs +255 -0
  56. package/dist/internal/use-roving-focus.mjs.map +1 -0
  57. package/dist/menu/menu-checkbox-item.mjs +2 -1
  58. package/dist/menu/menu-checkbox-item.mjs.map +1 -1
  59. package/dist/menu/menu-item.mjs +2 -1
  60. package/dist/menu/menu-item.mjs.map +1 -1
  61. package/dist/menu/menu-popup.d.mts.map +1 -1
  62. package/dist/menu/menu-popup.mjs +7 -2
  63. package/dist/menu/menu-popup.mjs.map +1 -1
  64. package/dist/menu/menu-radio-item.mjs +2 -1
  65. package/dist/menu/menu-radio-item.mjs.map +1 -1
  66. package/dist/menu/menu-root.mjs +1 -1
  67. package/dist/menu/menu-trigger.d.mts +2 -1
  68. package/dist/menu/menu-trigger.d.mts.map +1 -1
  69. package/dist/menu/menu-trigger.mjs +15 -4
  70. package/dist/menu/menu-trigger.mjs.map +1 -1
  71. package/dist/meter/index.d.mts +24 -0
  72. package/dist/meter/index.d.mts.map +1 -0
  73. package/dist/meter/index.mjs +23 -0
  74. package/dist/meter/index.mjs.map +1 -0
  75. package/dist/meter/meter-context.mjs +13 -0
  76. package/dist/meter/meter-context.mjs.map +1 -0
  77. package/dist/meter/meter-indicator.d.mts +19 -0
  78. package/dist/meter/meter-indicator.d.mts.map +1 -0
  79. package/dist/meter/meter-indicator.mjs +25 -0
  80. package/dist/meter/meter-indicator.mjs.map +1 -0
  81. package/dist/meter/meter-label.d.mts +19 -0
  82. package/dist/meter/meter-label.d.mts.map +1 -0
  83. package/dist/meter/meter-label.mjs +28 -0
  84. package/dist/meter/meter-label.mjs.map +1 -0
  85. package/dist/meter/meter-region.d.mts +4 -0
  86. package/dist/meter/meter-region.d.mts.map +1 -0
  87. package/dist/meter/meter-region.mjs +39 -0
  88. package/dist/meter/meter-region.mjs.map +1 -0
  89. package/dist/meter/meter-root.d.mts +71 -0
  90. package/dist/meter/meter-root.d.mts.map +1 -0
  91. package/dist/meter/meter-root.mjs +83 -0
  92. package/dist/meter/meter-root.mjs.map +1 -0
  93. package/dist/meter/meter-track.d.mts +19 -0
  94. package/dist/meter/meter-track.d.mts.map +1 -0
  95. package/dist/meter/meter-track.mjs +25 -0
  96. package/dist/meter/meter-track.mjs.map +1 -0
  97. package/dist/meter/meter-value.d.mts +20 -0
  98. package/dist/meter/meter-value.d.mts.map +1 -0
  99. package/dist/meter/meter-value.mjs +29 -0
  100. package/dist/meter/meter-value.mjs.map +1 -0
  101. package/dist/number-field/index.d.mts +26 -0
  102. package/dist/number-field/index.d.mts.map +1 -0
  103. package/dist/number-field/index.mjs +26 -0
  104. package/dist/number-field/index.mjs.map +1 -0
  105. package/dist/number-field/number-field-arithmetic.mjs +78 -0
  106. package/dist/number-field/number-field-arithmetic.mjs.map +1 -0
  107. package/dist/number-field/number-field-context.d.mts +2 -0
  108. package/dist/number-field/number-field-context.mjs +13 -0
  109. package/dist/number-field/number-field-context.mjs.map +1 -0
  110. package/dist/number-field/number-field-decrement.d.mts +16 -0
  111. package/dist/number-field/number-field-decrement.d.mts.map +1 -0
  112. package/dist/number-field/number-field-decrement.mjs +19 -0
  113. package/dist/number-field/number-field-decrement.mjs.map +1 -0
  114. package/dist/number-field/number-field-format.mjs +279 -0
  115. package/dist/number-field/number-field-format.mjs.map +1 -0
  116. package/dist/number-field/number-field-increment.d.mts +32 -0
  117. package/dist/number-field/number-field-increment.d.mts.map +1 -0
  118. package/dist/number-field/number-field-increment.mjs +68 -0
  119. package/dist/number-field/number-field-increment.mjs.map +1 -0
  120. package/dist/number-field/number-field-input.d.mts +29 -0
  121. package/dist/number-field/number-field-input.d.mts.map +1 -0
  122. package/dist/number-field/number-field-input.mjs +163 -0
  123. package/dist/number-field/number-field-input.mjs.map +1 -0
  124. package/dist/number-field/number-field-label.d.mts +23 -0
  125. package/dist/number-field/number-field-label.d.mts.map +1 -0
  126. package/dist/number-field/number-field-label.mjs +47 -0
  127. package/dist/number-field/number-field-label.mjs.map +1 -0
  128. package/dist/number-field/number-field-root.d.mts +108 -0
  129. package/dist/number-field/number-field-root.d.mts.map +1 -0
  130. package/dist/number-field/number-field-root.mjs +523 -0
  131. package/dist/number-field/number-field-root.mjs.map +1 -0
  132. package/dist/number-field/use-step-button.mjs +107 -0
  133. package/dist/number-field/use-step-button.mjs.map +1 -0
  134. package/dist/popover/popover-close.mjs +2 -1
  135. package/dist/popover/popover-close.mjs.map +1 -1
  136. package/dist/popover/popover-popup.d.mts.map +1 -1
  137. package/dist/popover/popover-popup.mjs +7 -2
  138. package/dist/popover/popover-popup.mjs.map +1 -1
  139. package/dist/popover/popover-root.mjs +1 -1
  140. package/dist/progress/index.d.mts +24 -0
  141. package/dist/progress/index.d.mts.map +1 -0
  142. package/dist/progress/index.mjs +24 -0
  143. package/dist/progress/index.mjs.map +1 -0
  144. package/dist/progress/progress-context.mjs +13 -0
  145. package/dist/progress/progress-context.mjs.map +1 -0
  146. package/dist/progress/progress-indicator.d.mts +19 -0
  147. package/dist/progress/progress-indicator.d.mts.map +1 -0
  148. package/dist/progress/progress-indicator.mjs +29 -0
  149. package/dist/progress/progress-indicator.mjs.map +1 -0
  150. package/dist/progress/progress-label.d.mts +19 -0
  151. package/dist/progress/progress-label.d.mts.map +1 -0
  152. package/dist/progress/progress-label.mjs +30 -0
  153. package/dist/progress/progress-label.mjs.map +1 -0
  154. package/dist/progress/progress-root.d.mts +68 -0
  155. package/dist/progress/progress-root.d.mts.map +1 -0
  156. package/dist/progress/progress-root.mjs +80 -0
  157. package/dist/progress/progress-root.mjs.map +1 -0
  158. package/dist/progress/progress-track.d.mts +19 -0
  159. package/dist/progress/progress-track.d.mts.map +1 -0
  160. package/dist/progress/progress-track.mjs +29 -0
  161. package/dist/progress/progress-track.mjs.map +1 -0
  162. package/dist/progress/progress-value.d.mts +20 -0
  163. package/dist/progress/progress-value.d.mts.map +1 -0
  164. package/dist/progress/progress-value.mjs +33 -0
  165. package/dist/progress/progress-value.mjs.map +1 -0
  166. package/dist/slider/slider-arithmetic.mjs +1 -17
  167. package/dist/slider/slider-arithmetic.mjs.map +1 -1
  168. package/dist/slider/slider-root.mjs +3 -2
  169. package/dist/slider/slider-root.mjs.map +1 -1
  170. package/dist/slider/slider-thumb.mjs +2 -1
  171. package/dist/slider/slider-thumb.mjs.map +1 -1
  172. package/dist/slider/slider-track.mjs +2 -1
  173. package/dist/slider/slider-track.mjs.map +1 -1
  174. package/dist/tabs/tabs-indicator.mjs +2 -1
  175. package/dist/tabs/tabs-indicator.mjs.map +1 -1
  176. package/dist/tabs/tabs-tab.mjs +2 -1
  177. package/dist/tabs/tabs-tab.mjs.map +1 -1
  178. package/dist/toast/toast-action.mjs +2 -1
  179. package/dist/toast/toast-action.mjs.map +1 -1
  180. package/dist/toast/toast-close.mjs +2 -1
  181. package/dist/toast/toast-close.mjs.map +1 -1
  182. package/dist/toast/toast-root.mjs +3 -2
  183. package/dist/toast/toast-root.mjs.map +1 -1
  184. package/dist/toast/toast-viewport.d.mts.map +1 -1
  185. package/dist/toast/toast-viewport.mjs +30 -7
  186. package/dist/toast/toast-viewport.mjs.map +1 -1
  187. package/dist/toast/toast.d.mts.map +1 -1
  188. package/dist/toast/toast.mjs +5 -1
  189. package/dist/toast/toast.mjs.map +1 -1
  190. package/dist/toggle/toggle.mjs +2 -1
  191. package/dist/toggle/toggle.mjs.map +1 -1
  192. package/dist/toggle-group/index.d.mts +16 -0
  193. package/dist/toggle-group/index.d.mts.map +1 -0
  194. package/dist/toggle-group/index.mjs +15 -0
  195. package/dist/toggle-group/index.mjs.map +1 -0
  196. package/dist/toggle-group/toggle-group-context.d.mts +4 -0
  197. package/dist/toggle-group/toggle-group-context.d.mts.map +1 -0
  198. package/dist/toggle-group/toggle-group-context.mjs +13 -0
  199. package/dist/toggle-group/toggle-group-context.mjs.map +1 -0
  200. package/dist/toggle-group/toggle-group-item.d.mts +47 -0
  201. package/dist/toggle-group/toggle-group-item.d.mts.map +1 -0
  202. package/dist/toggle-group/toggle-group-item.mjs +63 -0
  203. package/dist/toggle-group/toggle-group-item.mjs.map +1 -0
  204. package/dist/toggle-group/toggle-group-root.d.mts +95 -0
  205. package/dist/toggle-group/toggle-group-root.d.mts.map +1 -0
  206. package/dist/toggle-group/toggle-group-root.mjs +211 -0
  207. package/dist/toggle-group/toggle-group-root.mjs.map +1 -0
  208. package/dist/toolbar/index.d.mts +18 -0
  209. package/dist/toolbar/index.d.mts.map +1 -0
  210. package/dist/toolbar/index.mjs +18 -0
  211. package/dist/toolbar/index.mjs.map +1 -0
  212. package/dist/toolbar/toolbar-button.d.mts +42 -0
  213. package/dist/toolbar/toolbar-button.d.mts.map +1 -0
  214. package/dist/toolbar/toolbar-button.mjs +50 -0
  215. package/dist/toolbar/toolbar-button.mjs.map +1 -0
  216. package/dist/toolbar/toolbar-root.d.mts +38 -0
  217. package/dist/toolbar/toolbar-root.d.mts.map +1 -0
  218. package/dist/toolbar/toolbar-root.mjs +66 -0
  219. package/dist/toolbar/toolbar-root.mjs.map +1 -0
  220. package/dist/toolbar/toolbar-separator.d.mts +25 -0
  221. package/dist/toolbar/toolbar-separator.d.mts.map +1 -0
  222. package/dist/toolbar/toolbar-separator.mjs +31 -0
  223. package/dist/toolbar/toolbar-separator.mjs.map +1 -0
  224. package/dist/tooltip/tooltip-popup.mjs +2 -1
  225. package/dist/tooltip/tooltip-popup.mjs.map +1 -1
  226. package/dist/tooltip/tooltip-root.mjs +16 -2
  227. package/dist/tooltip/tooltip-root.mjs.map +1 -1
  228. package/dist/tooltip/tooltip-trigger.mjs +2 -1
  229. package/dist/tooltip/tooltip-trigger.mjs.map +1 -1
  230. package/package.json +6 -1
@@ -1 +1 @@
1
- {"version":3,"file":"use-popover-popup.mjs","names":[],"sources":["../../src/internal/use-popover-popup.ts"],"sourcesContent":["\"use client\";\nimport {\n useCallback,\n useEffect,\n useEffectEvent,\n useInsertionEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport type { ReactNode, RefObject, ViewTransitionClass } from \"react\";\nimport { useContentFocusProbe } from \"./content-focus-probe.js\";\nimport { exitAnimations, useExitAnimation } from \"./use-exit-animation.js\";\nimport type { PopoverRootValue } from \"./use-popover-root.js\";\n\nexport interface UsePopoverPopupOptions {\n /** Public name of the part for development warnings (`\"Popover.Popup\"`). */\n readonly part: string;\n /** Element to focus on open: a ref, or a selector resolved inside the popup (design 6.1). */\n readonly initialFocus: RefObject<HTMLElement | null> | string | undefined;\n /**\n * Replaces `initialFocus` with a function called right before each library open (Menu: the\n * first or last item by open cause). `null` leaves the choice to the platform. A\n * `QuietFocusTarget` asks for focus without the focus ring (a pointer-opened menu).\n */\n readonly resolveInitialFocus?: (popup: HTMLElement) => HTMLElement | QuietFocusTarget | null;\n readonly keepMounted: boolean | undefined;\n /** The consumer's `transition`; decides how a library-driven close keeps its content (4.4). */\n readonly transition: ViewTransitionClass | undefined;\n /** Runs after the library's own `beforetoggle` handling (Menu: focus bookkeeping). */\n readonly onBeforeToggle?: (event: ToggleEvent) => void;\n /** Runs after the library's own `toggle` handling (Menu: close the stack on Tab-out). */\n readonly onToggle?: (event: ToggleEvent) => void;\n}\n\n/**\n * An initial focus target opened by a pointer: where the engine reads `focus({ focusVisible })`,\n * the library focuses it with `focusVisible: false` right after `showPopover()` instead of\n * writing `autofocus` (WebKit shows the ring for an `autofocus` target after a click; review H1).\n * Elsewhere it is an ordinary target.\n */\nexport interface QuietFocusTarget {\n readonly element: HTMLElement;\n readonly focusVisible: false;\n}\n\nexport interface PopoverPopupHook {\n /** Callback ref for the popup element; pass as `useRender`'s `ref`. */\n readonly setElement: (element: HTMLElement) => () => void;\n /**\n * Whether the children are shown: committed open, or held while the hide animates (4.4).\n * Library mode only; JS-free mode always renders them.\n */\n readonly visible: boolean;\n /**\n * Wraps the library-mode content: call it only while `visible` (`visible ? wrapContent(x) :\n * null`), or inside the `<Activity>` for `keepMounted`. When a close commit unmounts the\n * content while it holds focus (a library-driven close with `transition`, where the focused\n * element is gone before `hidePopover()` runs and the platform restores nothing), the hide\n * that follows returns focus to the Trigger (design 6.2).\n */\n readonly wrapContent: (children: ReactNode) => ReactNode;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nfunction isPopoverOpen(element: Element): boolean {\n try {\n return element.matches(\":popover-open\");\n } catch {\n return false;\n }\n}\n\nlet focusVisibleOption: boolean | undefined;\n\n/**\n * The engine reads the `focusVisible` member of `focus()`'s options (Chromium 145, Firefox 104,\n * Safari 18.4). Detected once through a getter: the bindings convert the dictionary before the\n * method runs, so a detached element is enough and nothing is focused.\n */\nfunction supportsFocusVisibleOption(doc: Document): boolean {\n if (focusVisibleOption === undefined) {\n let read = false;\n try {\n doc.createElement(\"div\").focus({\n get focusVisible() {\n read = true;\n return false;\n },\n } as FocusOptions);\n } catch {\n read = false;\n }\n focusVisibleOption = read;\n }\n return focusVisibleOption;\n}\n\n/** Resolves `initialFocus` against the popup (a ref, or a selector inside the popup). */\nfunction resolveInitialFocus(\n popup: HTMLElement,\n initialFocus: RefObject<HTMLElement | null> | string,\n): HTMLElement | null {\n if (typeof initialFocus === \"string\") return popup.querySelector<HTMLElement>(initialFocus);\n return initialFocus.current;\n}\n\n/** Whether `element` sits inside a popover that is not shown (an outer popup that closed). */\nfunction insideHiddenPopover(element: Element): boolean {\n for (let node = element.parentElement; node !== null; node = node.parentElement) {\n if (node.hasAttribute(\"popover\") && !isPopoverOpen(node)) return true;\n }\n return false;\n}\n\n/**\n * Everything in a popover-like Popup except rendering (design/components/popover.md 14.2): mount\n * reconciliation, the apply effect, the native `beforetoggle` / `toggle` / `command` listeners,\n * close intents, the focus-return fallback, the JS-free `aria-expanded` sync, the deferred\n * unmount while a hide animates, and `initialFocus`. Shared by Popover and Menu.\n */\nexport function usePopoverPopup(\n ctx: PopoverRootValue,\n options: UsePopoverPopupOptions,\n): PopoverPopupHook {\n const { part, initialFocus, transition, onBeforeToggle: onBeforeToggleOption } = options;\n const onToggleOption = options.onToggle;\n const isLibrary = ctx.mode === \"library\";\n const { popupRef, triggerRef } = ctx;\n\n // The opening `beforetoggle` lets this one through: the library's own `showPopover()`.\n const opening = useRef(false);\n // Set by the synchronous `beforetoggle` of a close, cleared by `toggle` a task later: the\n // popup is hidden but its `toggle` has not arrived, so the apply effect must not re-open it\n // from a commit that lands in between (design 2.5).\n const awaitingClose = useRef(false);\n // Whether focus was inside the popup when its last close began (design 6.2 rule 1).\n const focusWasInside = useRef(false);\n const warnedFocus = useRef(false);\n\n // --- `held`: children stay mounted while the hide animates (design 4.4) -------------------\n\n const exit = useExitAnimation();\n\n // A library-driven close commits `false` first and hides in the layout effect, so without\n // `transition` the children are held through the close commit (\"previous value in state\").\n // With `transition` the close commit unmounts them inside the view transition, whose old\n // snapshot carries the content out.\n const [prevCommitted, setPrevCommitted] = useState(ctx.committed);\n if (prevCommitted !== ctx.committed) {\n setPrevCommitted(ctx.committed);\n if (isLibrary && prevCommitted && transition === undefined) exit.holdDuringRender();\n }\n\n // Whether the content unmounted while holding focus since the popup was last shown (H1).\n const contentProbe = useContentFocusProbe(popupRef);\n const { wrapContent } = contentProbe;\n\n const syncExpanded = (element: HTMLElement): void => {\n // JS-free mode only (design 9): read the DOM, not `newState`, so a stale task cannot lie.\n triggerRef.current?.setAttribute(\"aria-expanded\", String(isPopoverOpen(element)));\n };\n\n /**\n * Design 6.2 rule 2, for a close that began with focus inside: when focus is now on `<body>`\n * or still inside the hidden popup, focus the Trigger (connected, not inside a hidden\n * popover), else blur what holds focus.\n */\n const returnFocus = (element: HTMLElement): void => {\n const doc = element.ownerDocument;\n const active = doc.activeElement;\n if (active !== null && active !== doc.body && !element.contains(active)) return;\n const trigger = triggerRef.current;\n if (trigger !== null && trigger.isConnected && !insideHiddenPopover(trigger)) {\n trigger.focus();\n } else if (active instanceof HTMLElement && active !== doc.body) {\n active.blur();\n }\n };\n\n // --- Opening ------------------------------------------------------------------------------\n\n const resolveFocusTarget = (element: HTMLElement): HTMLElement | QuietFocusTarget | null => {\n if (options.resolveInitialFocus !== undefined) {\n const resolved = options.resolveInitialFocus(element);\n if (resolved === null) return null;\n const target = resolved instanceof HTMLElement ? resolved : resolved.element;\n return element.contains(target) ? resolved : null;\n }\n if (initialFocus === undefined) return null;\n const target = resolveInitialFocus(element, initialFocus);\n if (target !== null && element.contains(target)) return target;\n if (isDev && !warnedFocus.current) {\n warnedFocus.current = true;\n console.error(\n target === null\n ? `${part}: \\`initialFocus\\` resolved to no element; the platform picks the focus target.`\n : `${part}: \\`initialFocus\\` is outside the popup; the platform picks the focus target.`,\n );\n }\n return null;\n };\n\n const showPopup = (element: HTMLElement): void => {\n const resolved = resolveFocusTarget(element);\n const target =\n resolved === null || resolved instanceof HTMLElement ? resolved : resolved.element;\n // A pointer open asks for no focus ring: `focus({ focusVisible: false })` after the show,\n // where the engine reads that option (QuietFocusTarget).\n const quiet =\n target !== null &&\n !(resolved instanceof HTMLElement) &&\n supportsFocusVisibleOption(element.ownerDocument);\n // Otherwise the popover focusing steps honour `autofocus` (and run the dialog focusing steps\n // for a <dialog>, in every engine); React drops `autoFocus` on client-rendered content.\n // Never `.focus()` (6.1) except for the quiet target above.\n const added = target !== null && !quiet && !target.hasAttribute(\"autofocus\");\n if (added) target.setAttribute(\"autofocus\", \"\");\n opening.current = true;\n try {\n const source = triggerRef.current;\n element.showPopover(source !== null && source.isConnected ? { source } : undefined);\n } finally {\n opening.current = false;\n if (added) target.removeAttribute(\"autofocus\");\n }\n if (quiet && isPopoverOpen(element) && element.contains(target)) {\n target.focus({ focusVisible: false } as FocusOptions);\n }\n };\n\n // Mount reconciliation (design 2.4): a click before hydration opened the popover natively.\n // Runs before the apply effect; the dispatch makes the apply effect wait for it.\n const reconcileOnMount = useEffectEvent(() => {\n const element = popupRef.current;\n if (element === null) return;\n if (!isLibrary) {\n syncExpanded(element);\n return;\n }\n if (isPopoverOpen(element)) ctx.requestOpen(undefined, \"platform\");\n });\n useLayoutEffect(() => {\n reconcileOnMount();\n }, []);\n\n // The apply effect (design 2.5): after every commit that re-renders the Popup, once nothing is\n // in flight, the DOM follows committed state. Since the Root's value is memoised (#48) an\n // unrelated Root render does not re-render the Popup, so this does not run for it; every path\n // that makes the DOM diverge from state goes through a dispatch, whose `pending` flips re-render\n // the Popup (review of #48, finding 4). Do not rely on an unrelated render to re-apply state.\n // One effect covers the normal open, `defaultOpen`, a controlled open\n // or close, a library-driven close after `openChangeAction(false)` settled, the re-open of a\n // refused dismissal, and the close after a refused or failed pre-hydration open.\n useLayoutEffect(() => {\n if (!isLibrary) return;\n const element = popupRef.current;\n if (element === null || !element.isConnected) return;\n if (!ctx.pending && ctx.latestInFlight() === null) {\n const shown = isPopoverOpen(element);\n if (ctx.committed && !shown) {\n if (!awaitingClose.current) showPopup(element);\n } else if (!ctx.committed && shown) {\n // An echo: the `toggle` listener finds `false` intended and skips the dispatch.\n ctx.recordCloseIntent(undefined);\n element.hidePopover();\n // The close commit may have unmounted the focused content before the hide (H1): the\n // platform restores focus only from inside the popover, so do it now.\n if (focusWasInside.current) returnFocus(element);\n }\n }\n // Content that unmounted while the popup stays open (not a close): forget it.\n if (ctx.committed && isPopoverOpen(element)) contentProbe.forget();\n // A hold the close commit set (or a dismissal's notification commit) that no hide animation\n // is going to release: the popup is already hidden and its `toggle` has been handled.\n if (\n exit.held &&\n !ctx.committed &&\n !exit.waiting() &&\n !awaitingClose.current &&\n !isPopoverOpen(element)\n ) {\n exit.release();\n }\n });\n\n // --- Native listeners (conventions 7.4; filtered on target: React 19.3.1 emulates bubbling\n // of `toggle`, and a nested popover's events reach this element through the real DOM too) ---\n\n const onBeforeToggle = (event: Event): void => {\n const element = popupRef.current;\n if (element === null || event.target !== element) return;\n const toggle = event as ToggleEvent;\n if (toggle.newState === \"open\") {\n // Every open the library did not start itself is routed through the Root (design 2.2):\n // a foreign `show-popover` / `toggle-popover` invoker, a script `showPopover()`.\n if (isLibrary && !opening.current) {\n event.preventDefault();\n ctx.requestOpen(event, \"activate\");\n } else {\n // A Close action that resolves after this open must not close it (design 3.4).\n ctx.nextOpenGeneration();\n }\n contentProbe.forget();\n } else {\n awaitingClose.current = true;\n const active = element.ownerDocument.activeElement;\n focusWasInside.current =\n (active !== null && element.contains(active)) || contentProbe.hadFocus();\n contentProbe.forget();\n }\n onBeforeToggleOption?.(toggle);\n };\n\n const onToggle = (event: Event): void => {\n const element = popupRef.current;\n if (element === null || event.target !== element) return;\n awaitingClose.current = false;\n if (!isLibrary) syncExpanded(element);\n\n if (!isPopoverOpen(element)) {\n // Focus return for what the platform leaves out (design 6.2): a nested popover restores\n // nothing, and an opener that is gone leaves focus inside the hidden popup.\n const wasInside = focusWasInside.current;\n focusWasInside.current = false;\n if (wasInside) returnFocus(element);\n\n const intent = ctx.takeCloseIntent();\n if (isLibrary) {\n // `false` already intended: the library's own hide (or a dismissal during a pending\n // part-initiated close). With `transition` such a close needs no hold (4.4).\n const echo = !ctx.intended();\n if (!echo || transition === undefined) {\n const animations = exitAnimations(element);\n if (animations.length > 0) {\n // Before the notification, so its commit keeps the children (urgent, internal).\n exit.holdUntil(animations);\n } else {\n // Nothing animates (Firefox, or no CSS exit): release a hold now; the commit is\n // invisible, the popup is already `display: none`.\n exit.release();\n }\n }\n ctx.notifyClosed(intent?.event ?? event, intent?.reason ?? \"dismiss\");\n }\n }\n onToggleOption?.(event as ToggleEvent);\n };\n\n const onCommand = (event: Event): void => {\n const element = popupRef.current;\n if (element === null || event.target !== element || event.defaultPrevented) return;\n const command = String((event as Event & { command?: string }).command ?? \"\").toLowerCase();\n // A foreign invoker closing the popup (design 2.3). Opens are caught at `beforetoggle`.\n if (command === \"hide-popover\" || (command === \"toggle-popover\" && isPopoverOpen(element))) {\n ctx.recordCloseIntent({ reason: \"activate\", event });\n }\n };\n\n // The listeners the callback ref attaches call the latest render's handlers. An insertion\n // effect publishes them before refs attach and before any layout effect (whose\n // `hidePopover()` dispatches `beforetoggle` synchronously).\n const handlers = useRef({ onBeforeToggle, onToggle, onCommand });\n useInsertionEffect(() => {\n handlers.current = { onBeforeToggle, onToggle, onCommand };\n });\n\n // --- Element ------------------------------------------------------------------------------\n\n // The listeners attach in the callback ref, so they are in place from the commit that mounts\n // the element (no passive-effect window, no extra render). JS-free mode: there is no \"right\n // before showPopover()\" (the browser runs the command), so the `autofocus` attribute stays on\n // the `initialFocus` target while it is mounted (as Dialog).\n const setElement = useCallback(\n (element: HTMLElement) => {\n if (isDev && element.tagName !== \"DIALOG\" && element.tagName !== \"DIV\") {\n console.error(\n `${part}: expected to render a <dialog> or a <div> but got ` +\n `<${element.tagName.toLowerCase()}>.`,\n );\n }\n popupRef.current = element;\n const listeners: [string, EventListener][] = [\n [\"beforetoggle\", (event) => handlers.current.onBeforeToggle(event)],\n [\"toggle\", (event) => handlers.current.onToggle(event)],\n [\"command\", (event) => handlers.current.onCommand(event)],\n ];\n for (const [type, listener] of listeners) element.addEventListener(type, listener);\n\n let autofocused: HTMLElement | null = null;\n if (!isLibrary && initialFocus !== undefined) {\n const target = resolveInitialFocus(element, initialFocus);\n if (target !== null && element.contains(target) && !target.hasAttribute(\"autofocus\")) {\n target.setAttribute(\"autofocus\", \"\");\n autofocused = target;\n }\n }\n\n return () => {\n for (const [type, listener] of listeners) element.removeEventListener(type, listener);\n autofocused?.removeAttribute(\"autofocus\");\n if (popupRef.current === element) popupRef.current = null;\n };\n },\n [part, popupRef, isLibrary, initialFocus],\n );\n\n // Development check (design 9): `role=\"dialog\"` needs a name once the popup is (or can be)\n // shown.\n const shown = !isLibrary || ctx.committed;\n const warnedName = useRef(false);\n useEffect(() => {\n const element = popupRef.current;\n if (!isDev || element === null || !shown || warnedName.current) return;\n if (element.hasAttribute(\"aria-label\")) return;\n const labelledBy = (element.getAttribute(\"aria-labelledby\") ?? \"\").split(/\\s+/);\n const doc = element.ownerDocument;\n if (labelledBy.some((id) => id !== \"\" && doc.getElementById(id) !== null)) return;\n const role = element.getAttribute(\"role\");\n if (role !== \"dialog\" && role !== \"alertdialog\" && role !== \"menu\") return;\n warnedName.current = true;\n console.error(\n `${part}: the popup has no accessible name. Render a Title, or give the Popup an ` +\n \"`aria-label`.\",\n );\n }, [popupRef, shown, part]);\n\n return { setElement, visible: ctx.committed || exit.held, wrapContent };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-popover-popup.mjs","names":[],"sources":["../../src/internal/use-popover-popup.ts"],"sourcesContent":["\"use client\";\nimport {\n useCallback,\n useEffect,\n useEffectEvent,\n useInsertionEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport type { ReactNode, RefObject, ViewTransitionClass } from \"react\";\nimport { useContentFocusProbe } from \"./content-focus-probe.js\";\nimport { useExitAnimation } from \"./use-exit-animation.js\";\nimport type { PopoverRootValue } from \"./use-popover-root.js\";\n\nexport interface UsePopoverPopupOptions {\n /** Public name of the part for development warnings (`\"Popover.Popup\"`). */\n readonly part: string;\n /** Element to focus on open: a ref, or a selector resolved inside the popup (design 6.1). */\n readonly initialFocus: RefObject<HTMLElement | null> | string | undefined;\n /**\n * Replaces `initialFocus` with a function called right before each library open (Menu: the\n * first or last item by open cause). `null` leaves the choice to the platform. A\n * `QuietFocusTarget` asks for focus without the focus ring (a pointer-opened menu).\n */\n readonly resolveInitialFocus?: (popup: HTMLElement) => HTMLElement | QuietFocusTarget | null;\n readonly keepMounted: boolean | undefined;\n /** The consumer's `transition`; decides how a library-driven close keeps its content (4.4). */\n readonly transition: ViewTransitionClass | undefined;\n /** Runs after the library's own `beforetoggle` handling (Menu: focus bookkeeping). */\n readonly onBeforeToggle?: (event: ToggleEvent) => void;\n /** Runs after the library's own `toggle` handling (Menu: close the stack on Tab-out). */\n readonly onToggle?: (event: ToggleEvent) => void;\n}\n\n/**\n * An initial focus target opened by a pointer: where the engine reads `focus({ focusVisible })`,\n * the library focuses it with `focusVisible: false` right after `showPopover()` instead of\n * writing `autofocus` (WebKit shows the ring for an `autofocus` target after a click; review H1).\n * Elsewhere it is an ordinary target.\n */\nexport interface QuietFocusTarget {\n readonly element: HTMLElement;\n readonly focusVisible: false;\n}\n\nexport interface PopoverPopupHook {\n /** Callback ref for the popup element; pass as `useRender`'s `ref`. */\n readonly setElement: (element: HTMLElement) => () => void;\n /**\n * Whether the children are shown: committed open, or held while the hide animates (4.4).\n * Library mode only; JS-free mode always renders them.\n */\n readonly visible: boolean;\n /**\n * Wraps the library-mode content: call it only while `visible` (`visible ? wrapContent(x) :\n * null`), or inside the `<Activity>` for `keepMounted`. When a close commit unmounts the\n * content while it holds focus (a library-driven close with `transition`, where the focused\n * element is gone before `hidePopover()` runs and the platform restores nothing), the hide\n * that follows returns focus to the Trigger (design 6.2).\n */\n readonly wrapContent: (children: ReactNode) => ReactNode;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nfunction isPopoverOpen(element: Element): boolean {\n try {\n return element.matches(\":popover-open\");\n } catch {\n return false;\n }\n}\n\nlet focusVisibleOption: boolean | undefined;\n\n/**\n * The engine reads the `focusVisible` member of `focus()`'s options (Chromium 145, Firefox 104,\n * Safari 18.4). Detected once through a getter: the bindings convert the dictionary before the\n * method runs, so a detached element is enough and nothing is focused.\n */\nfunction supportsFocusVisibleOption(doc: Document): boolean {\n if (focusVisibleOption === undefined) {\n let read = false;\n try {\n doc.createElement(\"div\").focus({\n get focusVisible() {\n read = true;\n return false;\n },\n } as FocusOptions);\n } catch {\n read = false;\n }\n focusVisibleOption = read;\n }\n return focusVisibleOption;\n}\n\n/** Resolves `initialFocus` against the popup (a ref, or a selector inside the popup). */\nfunction resolveInitialFocus(\n popup: HTMLElement,\n initialFocus: RefObject<HTMLElement | null> | string,\n): HTMLElement | null {\n if (typeof initialFocus === \"string\") return popup.querySelector<HTMLElement>(initialFocus);\n return initialFocus.current;\n}\n\n/** Whether `element` sits inside a popover that is not shown (an outer popup that closed). */\nfunction insideHiddenPopover(element: Element): boolean {\n for (let node = element.parentElement; node !== null; node = node.parentElement) {\n if (node.hasAttribute(\"popover\") && !isPopoverOpen(node)) return true;\n }\n return false;\n}\n\n/**\n * Everything in a popover-like Popup except rendering (design/components/popover.md 14.2): mount\n * reconciliation, the apply effect, the native `beforetoggle` / `toggle` / `command` listeners,\n * close intents, the focus-return fallback, the JS-free `aria-expanded` sync, the deferred\n * unmount while a hide animates, and `initialFocus`. Shared by Popover and Menu.\n */\nexport function usePopoverPopup(\n ctx: PopoverRootValue,\n options: UsePopoverPopupOptions,\n): PopoverPopupHook {\n const { part, initialFocus, transition, onBeforeToggle: onBeforeToggleOption } = options;\n const onToggleOption = options.onToggle;\n const isLibrary = ctx.mode === \"library\";\n const { popupRef, triggerRef } = ctx;\n\n // The opening `beforetoggle` lets this one through: the library's own `showPopover()`.\n const opening = useRef(false);\n // Set by the synchronous `beforetoggle` of a close, cleared by `toggle` a task later: the\n // popup is hidden but its `toggle` has not arrived, so the apply effect must not re-open it\n // from a commit that lands in between (design 2.5).\n const awaitingClose = useRef(false);\n // Whether focus was inside the popup when its last close began (design 6.2 rule 1).\n const focusWasInside = useRef(false);\n const warnedFocus = useRef(false);\n\n // --- `held`: children stay mounted while the hide animates (design 4.4) -------------------\n\n const exit = useExitAnimation();\n\n // A library-driven close commits `false` first and hides in the layout effect, so without\n // `transition` the children are held through the close commit (\"previous value in state\").\n // With `transition` the close commit unmounts them inside the view transition, whose old\n // snapshot carries the content out.\n const [prevCommitted, setPrevCommitted] = useState(ctx.committed);\n if (prevCommitted !== ctx.committed) {\n setPrevCommitted(ctx.committed);\n if (isLibrary && prevCommitted && transition === undefined) exit.holdDuringRender();\n }\n\n // Whether the content unmounted while holding focus since the popup was last shown (H1).\n const contentProbe = useContentFocusProbe(popupRef);\n const { wrapContent } = contentProbe;\n\n const syncExpanded = (element: HTMLElement): void => {\n // JS-free mode only (design 9): read the DOM, not `newState`, so a stale task cannot lie.\n triggerRef.current?.setAttribute(\"aria-expanded\", String(isPopoverOpen(element)));\n };\n\n /**\n * Design 6.2 rule 2, for a close that began with focus inside: when focus is now on `<body>`\n * or still inside the hidden popup, focus the Trigger (connected, not inside a hidden\n * popover), else blur what holds focus.\n */\n const returnFocus = (element: HTMLElement): void => {\n const doc = element.ownerDocument;\n const active = doc.activeElement;\n if (active !== null && active !== doc.body && !element.contains(active)) return;\n const trigger = triggerRef.current;\n if (trigger !== null && trigger.isConnected && !insideHiddenPopover(trigger)) {\n trigger.focus();\n } else if (active instanceof HTMLElement && active !== doc.body) {\n active.blur();\n }\n };\n\n // --- Opening ------------------------------------------------------------------------------\n\n const resolveFocusTarget = (element: HTMLElement): HTMLElement | QuietFocusTarget | null => {\n if (options.resolveInitialFocus !== undefined) {\n const resolved = options.resolveInitialFocus(element);\n if (resolved === null) return null;\n const target = resolved instanceof HTMLElement ? resolved : resolved.element;\n return element.contains(target) ? resolved : null;\n }\n if (initialFocus === undefined) return null;\n const target = resolveInitialFocus(element, initialFocus);\n if (target !== null && element.contains(target)) return target;\n if (isDev && !warnedFocus.current) {\n warnedFocus.current = true;\n console.error(\n target === null\n ? `${part}: \\`initialFocus\\` resolved to no element; the platform picks the focus target.`\n : `${part}: \\`initialFocus\\` is outside the popup; the platform picks the focus target.`,\n );\n }\n return null;\n };\n\n const showPopup = (element: HTMLElement): void => {\n const resolved = resolveFocusTarget(element);\n const target =\n resolved === null || resolved instanceof HTMLElement ? resolved : resolved.element;\n // A pointer open asks for no focus ring: `focus({ focusVisible: false })` after the show,\n // where the engine reads that option (QuietFocusTarget).\n const quiet =\n target !== null &&\n !(resolved instanceof HTMLElement) &&\n supportsFocusVisibleOption(element.ownerDocument);\n // Otherwise the popover focusing steps honour `autofocus` (and run the dialog focusing steps\n // for a <dialog>, in every engine); React drops `autoFocus` on client-rendered content.\n // Never `.focus()` (6.1) except for the quiet target above.\n const added = target !== null && !quiet && !target.hasAttribute(\"autofocus\");\n if (added) target.setAttribute(\"autofocus\", \"\");\n opening.current = true;\n try {\n const source = triggerRef.current;\n element.showPopover(source !== null && source.isConnected ? { source } : undefined);\n } finally {\n opening.current = false;\n if (added) target.removeAttribute(\"autofocus\");\n }\n if (quiet && isPopoverOpen(element) && element.contains(target)) {\n target.focus({ focusVisible: false } as FocusOptions);\n }\n };\n\n // Mount reconciliation (design 2.4): a click before hydration opened the popover natively.\n // Runs before the apply effect; the dispatch makes the apply effect wait for it.\n const reconcileOnMount = useEffectEvent(() => {\n const element = popupRef.current;\n if (element === null) return;\n if (!isLibrary) {\n syncExpanded(element);\n return;\n }\n if (isPopoverOpen(element)) ctx.requestOpen(undefined, \"platform\");\n });\n useLayoutEffect(() => {\n reconcileOnMount();\n }, []);\n\n // The apply effect (design 2.5): after every commit that re-renders the Popup, once nothing is\n // in flight, the DOM follows committed state. Since the Root's value is memoised (#48) an\n // unrelated Root render does not re-render the Popup, so this does not run for it; every path\n // that makes the DOM diverge from state goes through a dispatch, whose `pending` flips re-render\n // the Popup (review of #48, finding 4). Do not rely on an unrelated render to re-apply state.\n // One effect covers the normal open, `defaultOpen`, a controlled open\n // or close, a library-driven close after `openChangeAction(false)` settled, the re-open of a\n // refused dismissal, and the close after a refused or failed pre-hydration open.\n useLayoutEffect(() => {\n if (!isLibrary) return;\n const element = popupRef.current;\n if (element === null || !element.isConnected) return;\n if (!ctx.pending && ctx.latestInFlight() === null) {\n const shown = isPopoverOpen(element);\n if (ctx.committed && !shown) {\n if (!awaitingClose.current) showPopup(element);\n } else if (!ctx.committed && shown) {\n // An echo: the `toggle` listener finds `false` intended and skips the dispatch.\n ctx.recordCloseIntent(undefined);\n element.hidePopover();\n // The close commit may have unmounted the focused content before the hide (H1): the\n // platform restores focus only from inside the popover, so do it now.\n if (focusWasInside.current) returnFocus(element);\n }\n }\n // Content that unmounted while the popup stays open (not a close): forget it.\n if (ctx.committed && isPopoverOpen(element)) contentProbe.forget();\n // A hold the close commit set (or a dismissal's notification commit) that no hide animation\n // is going to release: the popup is already hidden and its `toggle` has been handled.\n if (\n exit.held &&\n !ctx.committed &&\n !exit.waiting() &&\n !awaitingClose.current &&\n !isPopoverOpen(element)\n ) {\n exit.release();\n }\n });\n\n // --- Native listeners (conventions 7.4; filtered on target: React 19.3.1 emulates bubbling\n // of `toggle`, and a nested popover's events reach this element through the real DOM too) ---\n\n const onBeforeToggle = (event: Event): void => {\n const element = popupRef.current;\n if (element === null || event.target !== element) return;\n const toggle = event as ToggleEvent;\n if (toggle.newState === \"open\") {\n // Every open the library did not start itself is routed through the Root (design 2.2):\n // a foreign `show-popover` / `toggle-popover` invoker, a script `showPopover()`.\n if (isLibrary && !opening.current) {\n event.preventDefault();\n ctx.requestOpen(event, \"activate\");\n } else {\n // A Close action that resolves after this open must not close it (design 3.4).\n ctx.nextOpenGeneration();\n }\n contentProbe.forget();\n } else {\n awaitingClose.current = true;\n exit.recordBaseline(element);\n const active = element.ownerDocument.activeElement;\n focusWasInside.current =\n (active !== null && element.contains(active)) || contentProbe.hadFocus();\n contentProbe.forget();\n }\n onBeforeToggleOption?.(toggle);\n };\n\n const onToggle = (event: Event): void => {\n const element = popupRef.current;\n if (element === null || event.target !== element) return;\n awaitingClose.current = false;\n if (!isLibrary) syncExpanded(element);\n\n if (!isPopoverOpen(element)) {\n // Focus return for what the platform leaves out (design 6.2): a nested popover restores\n // nothing, and an opener that is gone leaves focus inside the hidden popup.\n const wasInside = focusWasInside.current;\n focusWasInside.current = false;\n if (wasInside) returnFocus(element);\n\n const intent = ctx.takeCloseIntent();\n if (isLibrary) {\n // `false` already intended: the library's own hide (or a dismissal during a pending\n // part-initiated close). With `transition` such a close needs no hold (4.4).\n const echo = !ctx.intended();\n if (!echo || transition === undefined) {\n const animations = exit.exitAnimations(element);\n if (animations.length > 0) {\n // Before the notification, so its commit keeps the children (urgent, internal).\n exit.holdUntil(animations);\n } else {\n // Nothing animates (Firefox, or no CSS exit): release a hold now; the commit is\n // invisible, the popup is already `display: none`.\n exit.release();\n }\n }\n ctx.notifyClosed(intent?.event ?? event, intent?.reason ?? \"dismiss\");\n }\n }\n onToggleOption?.(event as ToggleEvent);\n };\n\n const onCommand = (event: Event): void => {\n const element = popupRef.current;\n if (element === null || event.target !== element || event.defaultPrevented) return;\n const command = String((event as Event & { command?: string }).command ?? \"\").toLowerCase();\n // A foreign invoker closing the popup (design 2.3). Opens are caught at `beforetoggle`.\n if (command === \"hide-popover\" || (command === \"toggle-popover\" && isPopoverOpen(element))) {\n ctx.recordCloseIntent({ reason: \"activate\", event });\n }\n };\n\n // The listeners the callback ref attaches call the latest render's handlers. An insertion\n // effect publishes them before refs attach and before any layout effect (whose\n // `hidePopover()` dispatches `beforetoggle` synchronously).\n const handlers = useRef({ onBeforeToggle, onToggle, onCommand });\n useInsertionEffect(() => {\n handlers.current = { onBeforeToggle, onToggle, onCommand };\n });\n\n // --- Element ------------------------------------------------------------------------------\n\n // The listeners attach in the callback ref, so they are in place from the commit that mounts\n // the element (no passive-effect window, no extra render). JS-free mode: there is no \"right\n // before showPopover()\" (the browser runs the command), so the `autofocus` attribute stays on\n // the `initialFocus` target while it is mounted (as Dialog).\n const setElement = useCallback(\n (element: HTMLElement) => {\n if (isDev && element.tagName !== \"DIALOG\" && element.tagName !== \"DIV\") {\n console.error(\n `${part}: expected to render a <dialog> or a <div> but got ` +\n `<${element.tagName.toLowerCase()}>.`,\n );\n }\n popupRef.current = element;\n const listeners: [string, EventListener][] = [\n [\"beforetoggle\", (event) => handlers.current.onBeforeToggle(event)],\n [\"toggle\", (event) => handlers.current.onToggle(event)],\n [\"command\", (event) => handlers.current.onCommand(event)],\n ];\n for (const [type, listener] of listeners) element.addEventListener(type, listener);\n\n let autofocused: HTMLElement | null = null;\n if (!isLibrary && initialFocus !== undefined) {\n const target = resolveInitialFocus(element, initialFocus);\n if (target !== null && element.contains(target) && !target.hasAttribute(\"autofocus\")) {\n target.setAttribute(\"autofocus\", \"\");\n autofocused = target;\n }\n }\n\n return () => {\n for (const [type, listener] of listeners) element.removeEventListener(type, listener);\n autofocused?.removeAttribute(\"autofocus\");\n if (popupRef.current === element) popupRef.current = null;\n };\n },\n [part, popupRef, isLibrary, initialFocus],\n );\n\n // Development check (design 9): `role=\"dialog\"` needs a name once the popup is (or can be)\n // shown.\n const shown = !isLibrary || ctx.committed;\n const warnedName = useRef(false);\n useEffect(() => {\n const element = popupRef.current;\n if (!isDev || element === null || !shown || warnedName.current) return;\n if (element.hasAttribute(\"aria-label\")) return;\n const labelledBy = (element.getAttribute(\"aria-labelledby\") ?? \"\").split(/\\s+/);\n const doc = element.ownerDocument;\n if (labelledBy.some((id) => id !== \"\" && doc.getElementById(id) !== null)) return;\n const role = element.getAttribute(\"role\");\n if (role !== \"dialog\" && role !== \"alertdialog\" && role !== \"menu\") return;\n warnedName.current = true;\n console.error(\n `${part}: the popup has no accessible name. Render a Title, or give the Popup an ` +\n \"`aria-label`.\",\n );\n }, [popupRef, shown, part]);\n\n return { setElement, visible: ctx.committed || exit.held, wrapContent };\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { dataAttr } from "./data-attr.mjs";
3
- import { rendersButton } from "./use-render.mjs";
3
+ import { rendersButton } from "./render-element.mjs";
4
4
  import { useCallback } from "react";
5
5
  //#region src/internal/use-popover-trigger.ts
6
6
  const isDev = process.env.NODE_ENV !== "production";
@@ -1,49 +1,26 @@
1
1
  "use client";
2
- import { mergeProps, mergeRefs } from "./merge-props.mjs";
3
- import { cloneElement, createElement, isValidElement, useMemo } from "react";
2
+ import { mergeRefs } from "./merge-props.mjs";
3
+ import { renderElement, rendersButton, resolveStateValue } from "./render-element.mjs";
4
+ import { isValidElement, useMemo } from "react";
4
5
  //#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
6
  /**
19
7
  * 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).
8
+ * its state and produces the element (conventions 1.2, 1.3, 1.4). The pure work is
9
+ * `renderElement` (`render-element.ts`); this hook adds the memoised ref merge.
21
10
  */
22
11
  function useRender(options) {
23
- const { defaultElement, render, state, className, style, children, props, libraryProps, ref } = options;
12
+ const { render, props, ref } = options;
24
13
  const consumerRef = props.ref;
25
- const renderElement = isValidElement(render) ? render : void 0;
26
- const elementRef = renderElement?.props.ref;
14
+ const elementRef = isValidElement(render) ? render.props.ref : void 0;
27
15
  const mergedRef = useMemo(() => mergeRefs(ref, consumerRef, elementRef), [
28
16
  ref,
29
17
  consumerRef,
30
18
  elementRef
31
19
  ]);
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);
20
+ return renderElement({
21
+ ...options,
22
+ ref: mergedRef
23
+ });
47
24
  }
48
25
  //#endregion
49
26
  export { rendersButton, resolveStateValue, useRender };
@@ -1 +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"}
1
+ {"version":3,"file":"use-render.mjs","names":[],"sources":["../../src/internal/use-render.tsx"],"sourcesContent":["\"use client\";\nimport { isValidElement, useMemo } from \"react\";\nimport type { ReactNode, Ref } from \"react\";\nimport { mergeRefs } from \"./merge-props.js\";\nimport { renderElement } from \"./render-element.js\";\nimport type { ElementLikeProps, RenderElementOptions } from \"./render-element.js\";\n\nexport { rendersButton, resolveStateValue } from \"./render-element.js\";\nexport type { DataAttributes } from \"./render-element.js\";\n\nexport interface UseRenderOptions<Props extends ElementLikeProps, State> extends Omit<\n RenderElementOptions<Props, State>,\n \"ref\"\n> {\n /** The library's own ref, if the part needs the DOM node. */\n ref?: Ref<unknown> | undefined;\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). The pure work is\n * `renderElement` (`render-element.ts`); this hook adds the memoised ref merge.\n */\nexport function useRender<Props extends ElementLikeProps, State>(\n options: UseRenderOptions<Props, State>,\n): ReactNode {\n const { render, props, ref } = options;\n\n const consumerRef = (props as ElementLikeProps).ref;\n const elementRef = isValidElement<ElementLikeProps>(render) ? render.props.ref : undefined;\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 return renderElement<Props, State>({ ...options, ref: mergedRef });\n}\n"],"mappings":";;;;;;;;;;AAuBA,SAAgB,UACd,SACW;CACX,MAAM,EAAE,QAAQ,OAAO,QAAQ;CAE/B,MAAM,cAAe,MAA2B;CAChD,MAAM,aAAa,eAAiC,MAAM,IAAI,OAAO,MAAM,MAAM,KAAA;CAGjF,MAAM,YAAY,cACV,UAAU,KAAK,aAAa,UAAU,GAC5C;EAAC;EAAK;EAAa;CAAU,CAC/B;CAEA,OAAO,cAA4B;EAAE,GAAG;EAAS,KAAK;CAAU,CAAC;AACnE"}
@@ -0,0 +1,255 @@
1
+ "use client";
2
+ import { useEffect, useInsertionEffect, useLayoutEffect, useState } from "react";
3
+ //#region src/internal/use-roving-focus.ts
4
+ const FOCUSABLE = [
5
+ "button",
6
+ "a[href]",
7
+ "input:not([type=\"hidden\"])",
8
+ "select",
9
+ "textarea",
10
+ "summary",
11
+ "[contenteditable]:not([contenteditable=\"false\"])",
12
+ "[tabindex]"
13
+ ].join(",");
14
+ function isVisible(el) {
15
+ if (typeof el.checkVisibility === "function") return el.checkVisibility();
16
+ return el.closest("[hidden]") === null;
17
+ }
18
+ /**
19
+ * Whether `el` or an ancestor below `root` takes it out of `root`'s sequence: a nested toolbar
20
+ * (its own sequence), a popover or a dialog rendered inside the container (a `Menu.Popup`'s items
21
+ * are the menu's).
22
+ */
23
+ function isNestedOut(el, root) {
24
+ for (let node = el; node !== null && node !== root; node = node.parentElement) if (node.getAttribute("role") === "toolbar" || node.hasAttribute("popover") || node.tagName === "DIALOG") return true;
25
+ return false;
26
+ }
27
+ /**
28
+ * The items of `root` in DOM order (design 6.2): its focusable descendants minus `:disabled`,
29
+ * `inert`, invisible, nested-toolbar / popover / dialog content, and elements whose own
30
+ * `tabindex="-1"` was not written by this engine (`managed`). `aria-disabled` elements are items.
31
+ */
32
+ function itemsOf(root, managed) {
33
+ const items = [];
34
+ for (const el of root.querySelectorAll(FOCUSABLE)) {
35
+ if (el.matches(":disabled")) continue;
36
+ if (el.closest("[inert]") !== null) continue;
37
+ if (el.getAttribute("tabindex") === "-1" && !managed.has(el)) continue;
38
+ if (isNestedOut(el, root)) continue;
39
+ if (!isVisible(el)) continue;
40
+ items.push(el);
41
+ }
42
+ return items;
43
+ }
44
+ const BUTTON_LIKE_INPUTS = /* @__PURE__ */ new Set([
45
+ "button",
46
+ "submit",
47
+ "reset",
48
+ "checkbox",
49
+ "radio",
50
+ "image",
51
+ "color",
52
+ "file"
53
+ ]);
54
+ const ARROW_OWNING_ROLES = /* @__PURE__ */ new Set([
55
+ "textbox",
56
+ "combobox",
57
+ "spinbutton",
58
+ "slider"
59
+ ]);
60
+ /**
61
+ * An item that owns the arrow keys (design 6.3): text-like `input`s, `textarea`, `select`,
62
+ * `[contenteditable]`, and `role` `textbox` / `combobox` / `spinbutton` / `slider`.
63
+ */
64
+ function isTextEntryItem(el) {
65
+ const role = el.getAttribute("role");
66
+ if (role !== null && ARROW_OWNING_ROLES.has(role)) return true;
67
+ if (el instanceof HTMLInputElement) return !BUTTON_LIKE_INPUTS.has(el.type);
68
+ if (el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) return true;
69
+ const editable = el.getAttribute("contenteditable");
70
+ if (editable !== null && editable !== "false") return true;
71
+ return el instanceof HTMLElement && el.isContentEditable === true;
72
+ }
73
+ function resolveKey(key, orientation, rtl) {
74
+ switch (key) {
75
+ case "Home": return "first";
76
+ case "End": return "last";
77
+ case "ArrowRight": return orientation === "horizontal" ? rtl ? "prev" : "next" : null;
78
+ case "ArrowLeft": return orientation === "horizontal" ? rtl ? "next" : "prev" : null;
79
+ case "ArrowDown": return orientation === "vertical" ? "next" : null;
80
+ case "ArrowUp": return orientation === "vertical" ? "prev" : null;
81
+ default: return null;
82
+ }
83
+ }
84
+ function focusItem(item) {
85
+ item.focus({ focusVisible: true });
86
+ }
87
+ function createEngine() {
88
+ let current = null;
89
+ let latestOptions = null;
90
+ /**
91
+ * Elements this engine wrote `tabindex` on, with the attribute value they had before the first
92
+ * write (`null`: none). Restored when the engine lets go of an element (native path, element
93
+ * left the container): React does not re-write a `tabIndex` prop it believes it rendered, so a
94
+ * `[tabindex="0"]` widget would otherwise become unreachable.
95
+ */
96
+ const managed = /* @__PURE__ */ new Map();
97
+ const restore = (el) => {
98
+ const original = managed.get(el);
99
+ managed.delete(el);
100
+ if (!el.isConnected || original === void 0) return;
101
+ if (original === null) el.removeAttribute("tabindex");
102
+ else el.setAttribute("tabindex", original);
103
+ };
104
+ const active = (options) => options !== null && options.enabled !== false;
105
+ /** Native path: give back every `tabindex` this engine wrote (a hydration render may have). */
106
+ const release = () => {
107
+ for (const el of [...managed.keys()]) restore(el);
108
+ current = null;
109
+ };
110
+ const apply = (root, focused) => {
111
+ const options = latestOptions;
112
+ if (root === null || !active(options)) return;
113
+ if (options.native) {
114
+ release();
115
+ return;
116
+ }
117
+ const items = itemsOf(root, managed);
118
+ if (!options.memory) current = focused instanceof HTMLElement && items.includes(focused) ? focused : options.entry(items) ?? null;
119
+ else if (current === null || !items.includes(current)) current = options.entry(items) ?? null;
120
+ for (const el of items) {
121
+ if (!managed.has(el)) managed.set(el, el.getAttribute("tabindex"));
122
+ const want = el === current ? "0" : "-1";
123
+ if (el.getAttribute("tabindex") !== want) el.setAttribute("tabindex", want);
124
+ }
125
+ for (const el of [...managed.keys()]) {
126
+ if (items.includes(el)) continue;
127
+ if (!root.contains(el)) restore(el);
128
+ else if (el.getAttribute("tabindex") !== "-1") el.setAttribute("tabindex", "-1");
129
+ }
130
+ };
131
+ const onFocus = (event) => {
132
+ const options = latestOptions;
133
+ if (!active(options) || options.native) return;
134
+ const target = event.target;
135
+ if (!(target instanceof HTMLElement)) return;
136
+ if (!itemsOf(event.currentTarget, managed).includes(target)) return;
137
+ current = target;
138
+ apply(event.currentTarget, target);
139
+ };
140
+ const onBlur = (event) => {
141
+ const options = latestOptions;
142
+ if (!active(options) || options.native || options.memory) return;
143
+ const next = event.relatedTarget;
144
+ if (next instanceof Node && event.currentTarget.contains(next)) return;
145
+ apply(event.currentTarget, null);
146
+ };
147
+ const onPointerDown = (event) => {
148
+ const options = latestOptions;
149
+ if (!active(options) || options.native) return;
150
+ const target = event.target;
151
+ if (!(target instanceof Node)) return;
152
+ const item = itemsOf(event.currentTarget, managed).find((el) => el.contains(target));
153
+ if (item === void 0) return;
154
+ current = item;
155
+ apply(event.currentTarget, options.memory ? item : event.currentTarget.ownerDocument.activeElement);
156
+ };
157
+ const onKeyDown = (event) => {
158
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) return;
159
+ const options = latestOptions;
160
+ if (!active(options) || options.native) return;
161
+ const root = event.currentTarget;
162
+ const target = event.target;
163
+ if (!(target instanceof HTMLElement)) return;
164
+ const items = itemsOf(root, managed);
165
+ const index = items.indexOf(target);
166
+ if (index < 0) return;
167
+ if (isTextEntryItem(target)) {
168
+ if (event.key !== "Tab") return;
169
+ const next = items[index + (event.shiftKey ? -1 : 1)];
170
+ if (next === void 0) return;
171
+ event.preventDefault();
172
+ focusItem(next);
173
+ return;
174
+ }
175
+ const rtl = options.orientation === "horizontal" && getComputedStyle(root).direction === "rtl";
176
+ const move = resolveKey(event.key, options.orientation, rtl);
177
+ if (move === null) return;
178
+ event.preventDefault();
179
+ const last = items.length - 1;
180
+ let nextIndex;
181
+ switch (move) {
182
+ case "first":
183
+ nextIndex = 0;
184
+ break;
185
+ case "last":
186
+ nextIndex = last;
187
+ break;
188
+ case "next":
189
+ nextIndex = index >= last ? options.loop ? 0 : last : index + 1;
190
+ break;
191
+ case "prev": nextIndex = index <= 0 ? options.loop ? last : 0 : index - 1;
192
+ }
193
+ const next = items[nextIndex];
194
+ if (next !== void 0 && next !== target) focusItem(next);
195
+ };
196
+ return {
197
+ setOptions: (options) => {
198
+ latestOptions = options;
199
+ },
200
+ apply,
201
+ handlers: {
202
+ onFocus,
203
+ onBlur,
204
+ onPointerDown,
205
+ onKeyDown
206
+ }
207
+ };
208
+ }
209
+ /**
210
+ * Roving focus over the focusable descendants of `ref`'s element (design 6.2). Writes `tabindex`
211
+ * (`0` on the Tab stop, `-1` on the other items) from a layout effect after every commit of the
212
+ * calling component, from a `MutationObserver` on the subtree and from focus / pointer events;
213
+ * the items render none. Returns the container's handlers for its library props.
214
+ */
215
+ function useRovingFocus(ref, options) {
216
+ const [engine] = useState(createEngine);
217
+ useInsertionEffect(() => {
218
+ engine.setOptions(options);
219
+ });
220
+ const enabled = options.enabled !== false;
221
+ useLayoutEffect(() => {
222
+ const root = ref.current;
223
+ engine.apply(root, root?.ownerDocument.activeElement ?? null);
224
+ });
225
+ useEffect(() => {
226
+ const root = ref.current;
227
+ if (!enabled || root === null) return;
228
+ const observer = new MutationObserver(() => {
229
+ engine.apply(root, root.ownerDocument.activeElement);
230
+ });
231
+ observer.observe(root, {
232
+ childList: true,
233
+ subtree: true,
234
+ attributes: true,
235
+ attributeFilter: [
236
+ "disabled",
237
+ "hidden",
238
+ "inert",
239
+ "aria-disabled"
240
+ ]
241
+ });
242
+ return () => {
243
+ observer.disconnect();
244
+ };
245
+ }, [
246
+ engine,
247
+ enabled,
248
+ ref
249
+ ]);
250
+ return engine.handlers;
251
+ }
252
+ //#endregion
253
+ export { isTextEntryItem, itemsOf, useRovingFocus };
254
+
255
+ //# sourceMappingURL=use-roving-focus.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-roving-focus.mjs","names":[],"sources":["../../src/internal/use-roving-focus.ts"],"sourcesContent":["\"use client\";\nimport { useEffect, useInsertionEffect, useLayoutEffect, useState } from \"react\";\nimport type { FocusEvent, KeyboardEvent, PointerEvent, RefObject } from \"react\";\n\n// The roving-focus engine shared by `Toolbar.Root` and a standalone `ToggleGroup.Root`\n// (design/components/toggle-group.md section 6, conventions 3.2 and 7.4). Items are the focusable\n// descendants of the container, found in the DOM; which one is the Tab stop is urgent library\n// state kept outside React and written to the items as `tabindex` through the DOM.\n\nexport type RovingOrientation = \"horizontal\" | \"vertical\";\n\nexport interface RovingFocusOptions {\n /** `false`: the engine writes nothing and handles nothing (a ToggleGroup inside a Toolbar). Default `true`. */\n readonly enabled?: boolean;\n readonly orientation: RovingOrientation;\n readonly loop: boolean;\n /** The native `focusgroup` path is active: write no `tabindex`, handle no navigation key. */\n readonly native: boolean;\n /** Which item is the Tab stop when there is no usable memory. */\n readonly entry: (items: readonly HTMLElement[]) => HTMLElement | undefined;\n /**\n * `true`: the last focused item stays the Tab stop after focus leaves (Toolbar). `false`: while\n * focus is inside, the focused item is the stop (so Tab and Shift+Tab leave the container);\n * once it leaves, the stop is re-derived from `entry` (ToggleGroup: the pressed Item).\n */\n readonly memory: boolean;\n}\n\nexport interface RovingFocusHandlers {\n readonly onKeyDown: (event: KeyboardEvent<HTMLElement>) => void;\n readonly onFocus: (event: FocusEvent<HTMLElement>) => void;\n readonly onBlur: (event: FocusEvent<HTMLElement>) => void;\n readonly onPointerDown: (event: PointerEvent<HTMLElement>) => void;\n}\n\nconst FOCUSABLE = [\n \"button\",\n \"a[href]\",\n 'input:not([type=\"hidden\"])',\n \"select\",\n \"textarea\",\n \"summary\",\n '[contenteditable]:not([contenteditable=\"false\"])',\n \"[tabindex]\",\n].join(\",\");\n\nfunction isVisible(el: HTMLElement): boolean {\n // jsdom has no `checkVisibility`; `hidden` is the part of it a test can observe there.\n if (typeof el.checkVisibility === \"function\") return el.checkVisibility();\n return el.closest(\"[hidden]\") === null;\n}\n\n/**\n * Whether `el` or an ancestor below `root` takes it out of `root`'s sequence: a nested toolbar\n * (its own sequence), a popover or a dialog rendered inside the container (a `Menu.Popup`'s items\n * are the menu's).\n */\nfunction isNestedOut(el: HTMLElement, root: HTMLElement): boolean {\n for (let node: Element | null = el; node !== null && node !== root; node = node.parentElement) {\n if (\n node.getAttribute(\"role\") === \"toolbar\" ||\n node.hasAttribute(\"popover\") ||\n node.tagName === \"DIALOG\"\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * The items of `root` in DOM order (design 6.2): its focusable descendants minus `:disabled`,\n * `inert`, invisible, nested-toolbar / popover / dialog content, and elements whose own\n * `tabindex=\"-1\"` was not written by this engine (`managed`). `aria-disabled` elements are items.\n */\nexport function itemsOf(root: HTMLElement, managed: { has(el: Element): boolean }): HTMLElement[] {\n const items: HTMLElement[] = [];\n for (const el of root.querySelectorAll<HTMLElement>(FOCUSABLE)) {\n if (el.matches(\":disabled\")) continue;\n if (el.closest(\"[inert]\") !== null) continue;\n if (el.getAttribute(\"tabindex\") === \"-1\" && !managed.has(el)) continue;\n if (isNestedOut(el, root)) continue;\n if (!isVisible(el)) continue;\n items.push(el);\n }\n return items;\n}\n\nconst BUTTON_LIKE_INPUTS = new Set([\n \"button\",\n \"submit\",\n \"reset\",\n \"checkbox\",\n \"radio\",\n \"image\",\n \"color\",\n \"file\",\n]);\nconst ARROW_OWNING_ROLES = new Set([\"textbox\", \"combobox\", \"spinbutton\", \"slider\"]);\n\n/**\n * An item that owns the arrow keys (design 6.3): text-like `input`s, `textarea`, `select`,\n * `[contenteditable]`, and `role` `textbox` / `combobox` / `spinbutton` / `slider`.\n */\nexport function isTextEntryItem(el: Element): boolean {\n const role = el.getAttribute(\"role\");\n if (role !== null && ARROW_OWNING_ROLES.has(role)) return true;\n if (el instanceof HTMLInputElement) return !BUTTON_LIKE_INPUTS.has(el.type);\n if (el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) return true;\n const editable = el.getAttribute(\"contenteditable\");\n if (editable !== null && editable !== \"false\") return true;\n return el instanceof HTMLElement && el.isContentEditable === true;\n}\n\ntype Move = \"next\" | \"prev\" | \"first\" | \"last\";\n\nfunction resolveKey(key: string, orientation: RovingOrientation, rtl: boolean): Move | null {\n switch (key) {\n case \"Home\":\n return \"first\";\n case \"End\":\n return \"last\";\n case \"ArrowRight\":\n return orientation === \"horizontal\" ? (rtl ? \"prev\" : \"next\") : null;\n case \"ArrowLeft\":\n return orientation === \"horizontal\" ? (rtl ? \"next\" : \"prev\") : null;\n case \"ArrowDown\":\n return orientation === \"vertical\" ? \"next\" : null;\n case \"ArrowUp\":\n return orientation === \"vertical\" ? \"prev\" : null;\n default:\n return null;\n }\n}\n\nfunction focusItem(item: HTMLElement): void {\n // Keyboard focus: the ring shows even where the engine's heuristic would not (as Tabs).\n item.focus({ focusVisible: true } as FocusOptions);\n}\n\ninterface Engine {\n /** The options of the last committed render (written from an insertion effect). */\n readonly setOptions: (options: RovingFocusOptions) => void;\n /**\n * Re-derives the Tab stop of `root` and writes `tabindex`. `focused`: the element focus is on,\n * if any.\n */\n readonly apply: (root: HTMLElement | null, focused: Element | null) => void;\n readonly handlers: RovingFocusHandlers;\n}\n\nfunction createEngine(): Engine {\n let current: HTMLElement | null = null;\n let latestOptions: RovingFocusOptions | null = null;\n /**\n * Elements this engine wrote `tabindex` on, with the attribute value they had before the first\n * write (`null`: none). Restored when the engine lets go of an element (native path, element\n * left the container): React does not re-write a `tabIndex` prop it believes it rendered, so a\n * `[tabindex=\"0\"]` widget would otherwise become unreachable.\n */\n const managed = new Map<HTMLElement, string | null>();\n\n const restore = (el: HTMLElement) => {\n const original = managed.get(el);\n managed.delete(el);\n if (!el.isConnected || original === undefined) return;\n if (original === null) el.removeAttribute(\"tabindex\");\n else el.setAttribute(\"tabindex\", original);\n };\n\n const active = (options: RovingFocusOptions | null): options is RovingFocusOptions =>\n options !== null && options.enabled !== false;\n\n /** Native path: give back every `tabindex` this engine wrote (a hydration render may have). */\n const release = () => {\n for (const el of [...managed.keys()]) restore(el);\n current = null;\n };\n\n const apply = (root: HTMLElement | null, focused: Element | null) => {\n const options = latestOptions;\n if (root === null || !active(options)) return;\n if (options.native) {\n release();\n return;\n }\n const items = itemsOf(root, managed);\n if (!options.memory) {\n current =\n focused instanceof HTMLElement && items.includes(focused)\n ? focused\n : (options.entry(items) ?? null);\n } else if (current === null || !items.includes(current)) {\n current = options.entry(items) ?? null;\n }\n for (const el of items) {\n if (!managed.has(el)) managed.set(el, el.getAttribute(\"tabindex\"));\n const want = el === current ? \"0\" : \"-1\";\n if (el.getAttribute(\"tabindex\") !== want) el.setAttribute(\"tabindex\", want);\n }\n // Elements written before that are no longer items: a stale `0` must not stay a Tab stop\n // (it would be one again when re-enabled); one that left the container gets its own back.\n for (const el of [...managed.keys()]) {\n if (items.includes(el)) continue;\n if (!root.contains(el)) {\n restore(el);\n } else if (el.getAttribute(\"tabindex\") !== \"-1\") {\n el.setAttribute(\"tabindex\", \"-1\");\n }\n }\n };\n\n const onFocus = (event: FocusEvent<HTMLElement>) => {\n const options = latestOptions;\n if (!active(options) || options.native) return;\n const target = event.target;\n if (!(target instanceof HTMLElement)) return;\n const items = itemsOf(event.currentTarget, managed);\n if (!items.includes(target)) return;\n current = target;\n apply(event.currentTarget, target);\n };\n\n const onBlur = (event: FocusEvent<HTMLElement>) => {\n const options = latestOptions;\n if (!active(options) || options.native || options.memory) return;\n // Focus moving to another item is handled by its focus event.\n const next = event.relatedTarget;\n if (next instanceof Node && event.currentTarget.contains(next)) return;\n apply(event.currentTarget, null);\n };\n\n const onPointerDown = (event: PointerEvent<HTMLElement>) => {\n const options = latestOptions;\n if (!active(options) || options.native) return;\n const target = event.target;\n if (!(target instanceof Node)) return;\n const items = itemsOf(event.currentTarget, managed);\n // Safari does not focus a button on click; the pointer still makes it the stop.\n const item = items.find((el) => el.contains(target));\n if (item === undefined) return;\n current = item;\n apply(\n event.currentTarget,\n options.memory ? item : event.currentTarget.ownerDocument.activeElement,\n );\n };\n\n const onKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) return;\n const options = latestOptions;\n // The browser moves focus itself where `focusgroup` is implemented.\n if (!active(options) || options.native) return;\n const root = event.currentTarget;\n const target = event.target;\n if (!(target instanceof HTMLElement)) return;\n const items = itemsOf(root, managed);\n const index = items.indexOf(target);\n if (index < 0) return;\n\n if (isTextEntryItem(target)) {\n // The item keeps its arrows, Home and End; Tab / Shift+Tab move to the next / previous item\n // while one exists, as Chromium's `focusgroup=\"toolbar\"` does (design 6.3).\n if (event.key !== \"Tab\") return;\n const next = items[index + (event.shiftKey ? -1 : 1)];\n if (next === undefined) return;\n event.preventDefault();\n focusItem(next);\n return;\n }\n\n const rtl = options.orientation === \"horizontal\" && getComputedStyle(root).direction === \"rtl\";\n const move = resolveKey(event.key, options.orientation, rtl);\n if (move === null) return;\n event.preventDefault();\n const last = items.length - 1;\n let nextIndex: number;\n switch (move) {\n case \"first\":\n nextIndex = 0;\n break;\n case \"last\":\n nextIndex = last;\n break;\n case \"next\":\n nextIndex = index >= last ? (options.loop ? 0 : last) : index + 1;\n break;\n case \"prev\":\n nextIndex = index <= 0 ? (options.loop ? last : 0) : index - 1;\n break;\n }\n const next = items[nextIndex];\n if (next !== undefined && next !== target) focusItem(next);\n };\n\n return {\n setOptions: (options) => {\n latestOptions = options;\n },\n apply,\n handlers: { onFocus, onBlur, onPointerDown, onKeyDown },\n };\n}\n\n/**\n * Roving focus over the focusable descendants of `ref`'s element (design 6.2). Writes `tabindex`\n * (`0` on the Tab stop, `-1` on the other items) from a layout effect after every commit of the\n * calling component, from a `MutationObserver` on the subtree and from focus / pointer events;\n * the items render none. Returns the container's handlers for its library props.\n */\nexport function useRovingFocus(\n ref: RefObject<HTMLElement | null>,\n options: RovingFocusOptions,\n): RovingFocusHandlers {\n const [engine] = useState(createEngine);\n // Before every layout effect of the same commit (as `useLatest`): handlers and the observer\n // read the options of the last committed render.\n useInsertionEffect(() => {\n engine.setOptions(options);\n });\n const enabled = options.enabled !== false;\n\n // After every commit of the container (a deps-less layout effect).\n useLayoutEffect(() => {\n const root = ref.current;\n engine.apply(root, root?.ownerDocument.activeElement ?? null);\n });\n\n // Children that change without the container re-rendering.\n useEffect(() => {\n const root = ref.current;\n if (!enabled || root === null) return;\n const observer = new MutationObserver(() => {\n engine.apply(root, root.ownerDocument.activeElement);\n });\n observer.observe(root, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"disabled\", \"hidden\", \"inert\", \"aria-disabled\"],\n });\n return () => {\n observer.disconnect();\n };\n }, [engine, enabled, ref]);\n\n return engine.handlers;\n}\n"],"mappings":"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"}
@@ -3,7 +3,8 @@ import { createActionDetails } from "../internal/action-details.mjs";
3
3
  import { dataAttr } from "../internal/data-attr.mjs";
4
4
  import { transitionTypes } from "../internal/transition-types.mjs";
5
5
  import { useControllableAction } from "../internal/use-controllable-action.mjs";
6
- import { rendersButton, useRender } from "../internal/use-render.mjs";
6
+ import { rendersButton } from "../internal/render-element.mjs";
7
+ import { useRender } from "../internal/use-render.mjs";
7
8
  import { closeStackLater, useMenuContext } from "./menu-context.mjs";
8
9
  import { useMenuItem } from "./use-menu-item.mjs";
9
10
  import { useRef } from "react";
@@ -1 +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"}
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"}
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  import { createActionDetails } from "../internal/action-details.mjs";
3
3
  import { dataAttr } from "../internal/data-attr.mjs";
4
- import { rendersButton, useRender } from "../internal/use-render.mjs";
4
+ import { rendersButton } from "../internal/render-element.mjs";
5
+ import { useRender } from "../internal/use-render.mjs";
5
6
  import { useActionTrigger } from "../internal/use-action-trigger.mjs";
6
7
  import { useMenuContext } from "./menu-context.mjs";
7
8
  import { useMenuItem } from "./use-menu-item.mjs";
@@ -1 +1 @@
1
- {"version":3,"file":"menu-item.mjs","names":[],"sources":["../../src/menu/menu-item.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { Action, ActionDetails, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useActionTrigger } from \"../internal/use-action-trigger.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport { useMenuContext } from \"./menu-context.js\";\nimport { useMenuItem } from \"./use-menu-item.js\";\n\n/** Details passed to `Menu.Item`'s `action`. */\nexport interface MenuItemDetails extends ActionDetails {}\n\nexport interface MenuItemState {\n /** This item's `action` is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface MenuItemProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"onKeyDown\" | \"onPointerMove\" | \"disabled\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Runs inside this item's own transition (`data-pending` on the item); the menu stays open\n * until it settles, then the whole menu closes (`closeOnActivate`). May be a Server Function\n * (`details.event` is virtual). A rejection leaves the menu open and reaches the nearest Error\n * Boundary above the item. Without `action`, activation only closes.\n */\n action?: Action<[details: MenuItemDetails]>;\n /** Close the whole menu once `action` settles. Default `true`. */\n closeOnActivate?: boolean;\n /** Renders `aria-disabled`: the item stays focusable and never activates. */\n disabled?: boolean;\n /** Text typeahead matches when the item's text content is not representative (an icon). */\n label?: string;\n className?: StateValue<string | undefined, MenuItemState>;\n style?: StateValue<CSSProperties | undefined, MenuItemState>;\n children?: StateValue<ReactNode, MenuItemState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, MenuItemState>;\n}\n\n/**\n * A menu command: `<button type=\"button\" role=\"menuitem\" tabindex=\"-1\">`. Click, Enter or Space\n * runs `action` in the item's own transition; once it resolves the whole menu stack closes\n * through the top-level Root (`openChangeAction(false, { reason: \"activate\" })` awaited, view\n * transition if set). Hovering the item focuses it.\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>`), `role`, `tabIndex`, `disabled`\n * (always absent), `aria-disabled`, `onClick`, `onKeyDown`, `onPointerMove`, `data-pending`,\n * `data-disabled`, `data-typeahead`.\n */\nexport function MenuItem({\n action,\n closeOnActivate = true,\n disabled = false,\n label,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuItemProps) {\n const menu = useMenuContext(\"Menu.Item\");\n // The close belongs to the top-level menu, so its open generation guards it.\n const { pending, run } = useActionTrigger(menu.root.popover.openGeneration);\n\n const { libraryProps } = useMenuItem({\n part: \"Menu.Item\",\n role: \"menuitem\",\n disabled,\n label,\n activate: (event) => {\n const details: MenuItemDetails = createActionDetails(event, {});\n // Repeated activations re-run the action; the first close wins (design 2.3).\n run(\n () => action?.(details),\n closeOnActivate ? () => menu.root.popover.requestClose(event, \"activate\") : undefined,\n );\n },\n });\n\n const state: MenuItemState = { pending, disabled };\n\n return useRender<ComponentPropsWithRef<\"button\">, MenuItemState>({\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 \"data-pending\": dataAttr(pending),\n },\n });\n}\n\nMenuItem.displayName = \"MenuItem\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AAoDA,SAAgB,SAAS,EACvB,QACA,kBAAkB,MAClB,WAAW,OACX,OACA,WACA,OACA,UACA,QACA,GAAG,QACa;CAChB,MAAM,OAAO,eAAe,WAAW;CAEvC,MAAM,EAAE,SAAS,QAAQ,iBAAiB,KAAK,KAAK,QAAQ,cAAc;CAE1E,MAAM,EAAE,iBAAiB,YAAY;EACnC,MAAM;EACN,MAAM;EACN;EACA;EACA,WAAW,UAAU;GACnB,MAAM,UAA2B,oBAAoB,OAAO,CAAC,CAAC;GAE9D,UACQ,SAAS,OAAO,GACtB,wBAAwB,KAAK,KAAK,QAAQ,aAAa,OAAO,UAAU,IAAI,KAAA,CAC9E;EACF;CACF,CAAC;CAID,OAAO,UAA0D;EAC/D,gBAAgB;EAChB;EACA,OAAA;GAL6B;GAAS;EAKlC;EACJ;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;GACvD,GAAG;GACH,gBAAgB,SAAS,OAAO;EAClC;CACF,CAAC;AACH;AAEA,SAAS,cAAc"}
1
+ {"version":3,"file":"menu-item.mjs","names":[],"sources":["../../src/menu/menu-item.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { Action, ActionDetails, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useActionTrigger } from \"../internal/use-action-trigger.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport { useMenuContext } from \"./menu-context.js\";\nimport { useMenuItem } from \"./use-menu-item.js\";\n\n/** Details passed to `Menu.Item`'s `action`. */\nexport interface MenuItemDetails extends ActionDetails {}\n\nexport interface MenuItemState {\n /** This item's `action` is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface MenuItemProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"onKeyDown\" | \"onPointerMove\" | \"disabled\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Runs inside this item's own transition (`data-pending` on the item); the menu stays open\n * until it settles, then the whole menu closes (`closeOnActivate`). May be a Server Function\n * (`details.event` is virtual). A rejection leaves the menu open and reaches the nearest Error\n * Boundary above the item. Without `action`, activation only closes.\n */\n action?: Action<[details: MenuItemDetails]>;\n /** Close the whole menu once `action` settles. Default `true`. */\n closeOnActivate?: boolean;\n /** Renders `aria-disabled`: the item stays focusable and never activates. */\n disabled?: boolean;\n /** Text typeahead matches when the item's text content is not representative (an icon). */\n label?: string;\n className?: StateValue<string | undefined, MenuItemState>;\n style?: StateValue<CSSProperties | undefined, MenuItemState>;\n children?: StateValue<ReactNode, MenuItemState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, MenuItemState>;\n}\n\n/**\n * A menu command: `<button type=\"button\" role=\"menuitem\" tabindex=\"-1\">`. Click, Enter or Space\n * runs `action` in the item's own transition; once it resolves the whole menu stack closes\n * through the top-level Root (`openChangeAction(false, { reason: \"activate\" })` awaited, view\n * transition if set). Hovering the item focuses it.\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>`), `role`, `tabIndex`, `disabled`\n * (always absent), `aria-disabled`, `onClick`, `onKeyDown`, `onPointerMove`, `data-pending`,\n * `data-disabled`, `data-typeahead`.\n */\nexport function MenuItem({\n action,\n closeOnActivate = true,\n disabled = false,\n label,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuItemProps) {\n const menu = useMenuContext(\"Menu.Item\");\n // The close belongs to the top-level menu, so its open generation guards it.\n const { pending, run } = useActionTrigger(menu.root.popover.openGeneration);\n\n const { libraryProps } = useMenuItem({\n part: \"Menu.Item\",\n role: \"menuitem\",\n disabled,\n label,\n activate: (event) => {\n const details: MenuItemDetails = createActionDetails(event, {});\n // Repeated activations re-run the action; the first close wins (design 2.3).\n run(\n () => action?.(details),\n closeOnActivate ? () => menu.root.popover.requestClose(event, \"activate\") : undefined,\n );\n },\n });\n\n const state: MenuItemState = { pending, disabled };\n\n return useRender<ComponentPropsWithRef<\"button\">, MenuItemState>({\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 \"data-pending\": dataAttr(pending),\n },\n });\n}\n\nMenuItem.displayName = \"MenuItem\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAoDA,SAAgB,SAAS,EACvB,QACA,kBAAkB,MAClB,WAAW,OACX,OACA,WACA,OACA,UACA,QACA,GAAG,QACa;CAChB,MAAM,OAAO,eAAe,WAAW;CAEvC,MAAM,EAAE,SAAS,QAAQ,iBAAiB,KAAK,KAAK,QAAQ,cAAc;CAE1E,MAAM,EAAE,iBAAiB,YAAY;EACnC,MAAM;EACN,MAAM;EACN;EACA;EACA,WAAW,UAAU;GACnB,MAAM,UAA2B,oBAAoB,OAAO,CAAC,CAAC;GAE9D,UACQ,SAAS,OAAO,GACtB,wBAAwB,KAAK,KAAK,QAAQ,aAAa,OAAO,UAAU,IAAI,KAAA,CAC9E;EACF;CACF,CAAC;CAID,OAAO,UAA0D;EAC/D,gBAAgB;EAChB;EACA,OAAA;GAL6B;GAAS;EAKlC;EACJ;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;GACvD,GAAG;GACH,gBAAgB,SAAS,OAAO;EAClC;CACF,CAAC;AACH;AAEA,SAAS,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"menu-popup.d.mts","names":[],"sources":["../../src/menu/menu-popup.tsx"],"mappings":";;;;YAiCY,WAAW;YACX,YAAY;iBAEP;;WAEN;WACA,MAAM;WACN,OAAO;;WAEP;;iBAGM,uBAAuB,KACtC;;;;;;;EAuBA,OAAO;;EAEP,QAAQ;;;;;;EAMR;;;;;;;EAOA;;;;;;;EAOA,aAAa;EACb,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,8BAA8B;;;;;;;;;;;;;;;wBA4BpC,YACd,MAAM,UACN,OACA,aACA,yBACA,YACA,WACA,OACA,UACA,WACG,QACF,iBAAc;yBAXD"}
1
+ {"version":3,"file":"menu-popup.d.mts","names":[],"sources":["../../src/menu/menu-popup.tsx"],"mappings":";;;;YAkCY,WAAW;YACX,YAAY;iBAEP;;WAEN;WACA,MAAM;WACN,OAAO;;WAEP;;iBAGM,uBAAuB,KACtC;;;;;;;EAuBA,OAAO;;EAEP,QAAQ;;;;;;EAMR;;;;;;;EAOA;;;;;;;EAOA,aAAa;EACb,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,8BAA8B;;;;;;;;;;;;;;;wBA4BpC,YACd,MAAM,UACN,OACA,aACA,yBACA,YACA,WACA,OACA,UACA,WACG,QACF,iBAAc;yBAXD"}
@@ -1,7 +1,9 @@
1
1
  "use client";
2
2
  import { useTransitionBoundary } from "../internal/transition-boundary.mjs";
3
3
  import { mergeRefs } from "../internal/merge-props.mjs";
4
- import { resolveStateValue, useRender } from "../internal/use-render.mjs";
4
+ import { resolveStateValue } from "../internal/render-element.mjs";
5
+ import { useRender } from "../internal/use-render.mjs";
6
+ import { ToolbarContext } from "../internal/toolbar-context.mjs";
5
7
  import { POSITION_TRY_FALLBACKS, positionArea } from "../internal/anchor-position.mjs";
6
8
  import { usePopoverPopup } from "../internal/use-popover-popup.mjs";
7
9
  import { MenuListContext, useMenuContext } from "./menu-context.mjs";
@@ -180,7 +182,10 @@ function MenuPopup({ side: sideProp, align = "start", keepMounted, experimental_
180
182
  else node = visible ? wrapContent(resolveStateValue(children, s)) : null;
181
183
  return /* @__PURE__ */ jsx(MenuListContext, {
182
184
  value: listContext,
183
- children: node
185
+ children: /* @__PURE__ */ jsx(ToolbarContext, {
186
+ value: null,
187
+ children: node
188
+ })
184
189
  });
185
190
  };
186
191
  const element = useRender({