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

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 (223) hide show
  1. package/dist/accordion/accordion-context.mjs.map +1 -1
  2. package/dist/accordion/accordion-item.mjs +18 -4
  3. package/dist/accordion/accordion-item.mjs.map +1 -1
  4. package/dist/accordion/accordion-panel.mjs +2 -3
  5. package/dist/accordion/accordion-panel.mjs.map +1 -1
  6. package/dist/accordion/accordion-root.d.mts.map +1 -1
  7. package/dist/accordion/accordion-root.mjs +32 -14
  8. package/dist/accordion/accordion-root.mjs.map +1 -1
  9. package/dist/accordion/accordion-trigger.mjs +4 -5
  10. package/dist/accordion/accordion-trigger.mjs.map +1 -1
  11. package/dist/checkbox/checkbox.d.mts +5 -3
  12. package/dist/checkbox/checkbox.d.mts.map +1 -1
  13. package/dist/checkbox/checkbox.mjs +25 -9
  14. package/dist/checkbox/checkbox.mjs.map +1 -1
  15. package/dist/checkbox-group/checkbox-group-context.mjs.map +1 -1
  16. package/dist/checkbox-group/checkbox-group-item.d.mts +2 -1
  17. package/dist/checkbox-group/checkbox-group-item.d.mts.map +1 -1
  18. package/dist/checkbox-group/checkbox-group-item.mjs +3 -1
  19. package/dist/checkbox-group/checkbox-group-item.mjs.map +1 -1
  20. package/dist/checkbox-group/checkbox-group-root.d.mts +5 -2
  21. package/dist/checkbox-group/checkbox-group-root.d.mts.map +1 -1
  22. package/dist/checkbox-group/checkbox-group-root.mjs +24 -5
  23. package/dist/checkbox-group/checkbox-group-root.mjs.map +1 -1
  24. package/dist/combobox/combobox-context.mjs.map +1 -1
  25. package/dist/combobox/combobox-input.d.mts +3 -1
  26. package/dist/combobox/combobox-input.d.mts.map +1 -1
  27. package/dist/combobox/combobox-input.mjs +16 -2
  28. package/dist/combobox/combobox-input.mjs.map +1 -1
  29. package/dist/combobox/combobox-list.mjs +1 -1
  30. package/dist/combobox/combobox-list.mjs.map +1 -1
  31. package/dist/combobox/combobox-root.d.mts.map +1 -1
  32. package/dist/combobox/combobox-root.mjs +19 -4
  33. package/dist/combobox/combobox-root.mjs.map +1 -1
  34. package/dist/dialog/dialog-close.d.mts +17 -10
  35. package/dist/dialog/dialog-close.d.mts.map +1 -1
  36. package/dist/dialog/dialog-close.mjs +31 -20
  37. package/dist/dialog/dialog-close.mjs.map +1 -1
  38. package/dist/dialog/dialog-context.mjs.map +1 -1
  39. package/dist/dialog/dialog-form.d.mts +12 -5
  40. package/dist/dialog/dialog-form.d.mts.map +1 -1
  41. package/dist/dialog/dialog-form.mjs +16 -28
  42. package/dist/dialog/dialog-form.mjs.map +1 -1
  43. package/dist/dialog/dialog-popup.d.mts +10 -4
  44. package/dist/dialog/dialog-popup.d.mts.map +1 -1
  45. package/dist/dialog/dialog-popup.mjs +106 -41
  46. package/dist/dialog/dialog-popup.mjs.map +1 -1
  47. package/dist/dialog/dialog-root.d.mts +14 -8
  48. package/dist/dialog/dialog-root.d.mts.map +1 -1
  49. package/dist/dialog/dialog-root.mjs +66 -11
  50. package/dist/dialog/dialog-root.mjs.map +1 -1
  51. package/dist/dialog/dialog-trigger.d.mts +3 -2
  52. package/dist/dialog/dialog-trigger.d.mts.map +1 -1
  53. package/dist/dialog/dialog-trigger.mjs.map +1 -1
  54. package/dist/dialog/index.d.mts +4 -2
  55. package/dist/dialog/index.d.mts.map +1 -1
  56. package/dist/dialog/index.mjs +4 -2
  57. package/dist/dialog/index.mjs.map +1 -1
  58. package/dist/field/field-control.d.mts +33 -0
  59. package/dist/field/field-control.d.mts.map +1 -0
  60. package/dist/field/field-control.mjs +68 -0
  61. package/dist/field/field-control.mjs.map +1 -0
  62. package/dist/field/field-description.d.mts +24 -0
  63. package/dist/field/field-description.d.mts.map +1 -0
  64. package/dist/field/field-description.mjs +34 -0
  65. package/dist/field/field-description.mjs.map +1 -0
  66. package/dist/field/field-error.d.mts +30 -0
  67. package/dist/field/field-error.d.mts.map +1 -0
  68. package/dist/field/field-error.mjs +42 -0
  69. package/dist/field/field-error.mjs.map +1 -0
  70. package/dist/field/field-label.d.mts +25 -0
  71. package/dist/field/field-label.d.mts.map +1 -0
  72. package/dist/field/field-label.mjs +46 -0
  73. package/dist/field/field-label.mjs.map +1 -0
  74. package/dist/field/field-root.d.mts +53 -0
  75. package/dist/field/field-root.d.mts.map +1 -0
  76. package/dist/field/field-root.mjs +317 -0
  77. package/dist/field/field-root.mjs.map +1 -0
  78. package/dist/field/index.d.mts +18 -0
  79. package/dist/field/index.d.mts.map +1 -0
  80. package/dist/field/index.mjs +17 -0
  81. package/dist/field/index.mjs.map +1 -0
  82. package/dist/form/form-root.d.mts +58 -0
  83. package/dist/form/form-root.d.mts.map +1 -0
  84. package/dist/form/form-root.mjs +146 -0
  85. package/dist/form/form-root.mjs.map +1 -0
  86. package/dist/form/form-submit.d.mts +31 -0
  87. package/dist/form/form-submit.d.mts.map +1 -0
  88. package/dist/form/form-submit.mjs +52 -0
  89. package/dist/form/form-submit.mjs.map +1 -0
  90. package/dist/form/index.d.mts +11 -0
  91. package/dist/form/index.d.mts.map +1 -0
  92. package/dist/form/index.mjs +11 -0
  93. package/dist/form/index.mjs.map +1 -0
  94. package/dist/index.d.mts +27 -1
  95. package/dist/index.mjs +6 -1
  96. package/dist/internal/content-focus-probe.mjs +42 -0
  97. package/dist/internal/content-focus-probe.mjs.map +1 -0
  98. package/dist/internal/field-context.d.mts +19 -0
  99. package/dist/internal/field-context.d.mts.map +1 -0
  100. package/dist/internal/field-context.mjs +14 -0
  101. package/dist/internal/field-context.mjs.map +1 -0
  102. package/dist/internal/focus-first-invalid.mjs +67 -0
  103. package/dist/internal/focus-first-invalid.mjs.map +1 -0
  104. package/dist/internal/form-status-watcher.mjs +31 -0
  105. package/dist/internal/form-status-watcher.mjs.map +1 -0
  106. package/dist/internal/modal-registry.mjs +39 -0
  107. package/dist/internal/modal-registry.mjs.map +1 -0
  108. package/dist/internal/types.d.mts +3 -1
  109. package/dist/internal/types.d.mts.map +1 -1
  110. package/dist/internal/use-controllable-action.mjs +33 -15
  111. package/dist/internal/use-controllable-action.mjs.map +1 -1
  112. package/dist/internal/use-field-control.mjs +107 -0
  113. package/dist/internal/use-field-control.mjs.map +1 -0
  114. package/dist/internal/use-form-owner-event.mjs +44 -0
  115. package/dist/internal/use-form-owner-event.mjs.map +1 -0
  116. package/dist/internal/use-form-reset.mjs +10 -33
  117. package/dist/internal/use-form-reset.mjs.map +1 -1
  118. package/dist/internal/use-latest.mjs +32 -0
  119. package/dist/internal/use-latest.mjs.map +1 -0
  120. package/dist/internal/use-modal-registration.mjs +47 -0
  121. package/dist/internal/use-modal-registration.mjs.map +1 -0
  122. package/dist/internal/use-native-group-reconciliation.mjs +6 -5
  123. package/dist/internal/use-native-group-reconciliation.mjs.map +1 -1
  124. package/dist/internal/use-native-select-value.mjs +7 -5
  125. package/dist/internal/use-native-select-value.mjs.map +1 -1
  126. package/dist/internal/use-popover-popup.mjs +8 -22
  127. package/dist/internal/use-popover-popup.mjs.map +1 -1
  128. package/dist/internal/use-popover-root.mjs +58 -23
  129. package/dist/internal/use-popover-root.mjs.map +1 -1
  130. package/dist/internal/validity.d.mts +24 -0
  131. package/dist/internal/validity.d.mts.map +1 -0
  132. package/dist/internal/validity.mjs +108 -0
  133. package/dist/internal/validity.mjs.map +1 -0
  134. package/dist/menu/menu-group.d.mts.map +1 -1
  135. package/dist/menu/menu-group.mjs +3 -2
  136. package/dist/menu/menu-group.mjs.map +1 -1
  137. package/dist/menu/menu-radio-group.mjs +9 -4
  138. package/dist/menu/menu-radio-group.mjs.map +1 -1
  139. package/dist/menu/menu-root.mjs.map +1 -1
  140. package/dist/radio-group/radio-group-root.d.mts +4 -2
  141. package/dist/radio-group/radio-group-root.d.mts.map +1 -1
  142. package/dist/radio-group/radio-group-root.mjs +34 -8
  143. package/dist/radio-group/radio-group-root.mjs.map +1 -1
  144. package/dist/select/select-group.d.mts.map +1 -1
  145. package/dist/select/select-group.mjs +6 -4
  146. package/dist/select/select-group.mjs.map +1 -1
  147. package/dist/select/select-root.d.mts +4 -2
  148. package/dist/select/select-root.d.mts.map +1 -1
  149. package/dist/select/select-root.mjs +26 -6
  150. package/dist/select/select-root.mjs.map +1 -1
  151. package/dist/slider/index.d.mts +32 -0
  152. package/dist/slider/index.d.mts.map +1 -0
  153. package/dist/slider/index.mjs +27 -0
  154. package/dist/slider/index.mjs.map +1 -0
  155. package/dist/slider/slider-arithmetic.mjs +120 -0
  156. package/dist/slider/slider-arithmetic.mjs.map +1 -0
  157. package/dist/slider/slider-context.mjs +13 -0
  158. package/dist/slider/slider-context.mjs.map +1 -0
  159. package/dist/slider/slider-indicator.d.mts +22 -0
  160. package/dist/slider/slider-indicator.d.mts.map +1 -0
  161. package/dist/slider/slider-indicator.mjs +36 -0
  162. package/dist/slider/slider-indicator.mjs.map +1 -0
  163. package/dist/slider/slider-label.d.mts +23 -0
  164. package/dist/slider/slider-label.d.mts.map +1 -0
  165. package/dist/slider/slider-label.mjs +44 -0
  166. package/dist/slider/slider-label.mjs.map +1 -0
  167. package/dist/slider/slider-root.d.mts +111 -0
  168. package/dist/slider/slider-root.d.mts.map +1 -0
  169. package/dist/slider/slider-root.mjs +582 -0
  170. package/dist/slider/slider-root.mjs.map +1 -0
  171. package/dist/slider/slider-thumb.d.mts +58 -0
  172. package/dist/slider/slider-thumb.d.mts.map +1 -0
  173. package/dist/slider/slider-thumb.mjs +145 -0
  174. package/dist/slider/slider-thumb.mjs.map +1 -0
  175. package/dist/slider/slider-track.d.mts +29 -0
  176. package/dist/slider/slider-track.d.mts.map +1 -0
  177. package/dist/slider/slider-track.mjs +54 -0
  178. package/dist/slider/slider-track.mjs.map +1 -0
  179. package/dist/slider/slider-value.d.mts +34 -0
  180. package/dist/slider/slider-value.d.mts.map +1 -0
  181. package/dist/slider/slider-value.mjs +38 -0
  182. package/dist/slider/slider-value.mjs.map +1 -0
  183. package/dist/switch/switch.d.mts +5 -3
  184. package/dist/switch/switch.d.mts.map +1 -1
  185. package/dist/switch/switch.mjs +24 -8
  186. package/dist/switch/switch.mjs.map +1 -1
  187. package/dist/tabs/tabs-root.mjs +13 -3
  188. package/dist/tabs/tabs-root.mjs.map +1 -1
  189. package/dist/toast/index.d.mts +25 -0
  190. package/dist/toast/index.d.mts.map +1 -0
  191. package/dist/toast/index.mjs +26 -0
  192. package/dist/toast/index.mjs.map +1 -0
  193. package/dist/toast/toast-action.d.mts +39 -0
  194. package/dist/toast/toast-action.d.mts.map +1 -0
  195. package/dist/toast/toast-action.mjs +64 -0
  196. package/dist/toast/toast-action.mjs.map +1 -0
  197. package/dist/toast/toast-close.d.mts +25 -0
  198. package/dist/toast/toast-close.d.mts.map +1 -0
  199. package/dist/toast/toast-close.mjs +46 -0
  200. package/dist/toast/toast-close.mjs.map +1 -0
  201. package/dist/toast/toast-context.mjs +19 -0
  202. package/dist/toast/toast-context.mjs.map +1 -0
  203. package/dist/toast/toast-description.d.mts +24 -0
  204. package/dist/toast/toast-description.d.mts.map +1 -0
  205. package/dist/toast/toast-description.mjs +35 -0
  206. package/dist/toast/toast-description.mjs.map +1 -0
  207. package/dist/toast/toast-root.d.mts +43 -0
  208. package/dist/toast/toast-root.d.mts.map +1 -0
  209. package/dist/toast/toast-root.mjs +204 -0
  210. package/dist/toast/toast-root.mjs.map +1 -0
  211. package/dist/toast/toast-title.d.mts +24 -0
  212. package/dist/toast/toast-title.d.mts.map +1 -0
  213. package/dist/toast/toast-title.mjs +33 -0
  214. package/dist/toast/toast-title.mjs.map +1 -0
  215. package/dist/toast/toast-viewport.d.mts +52 -0
  216. package/dist/toast/toast-viewport.d.mts.map +1 -0
  217. package/dist/toast/toast-viewport.mjs +621 -0
  218. package/dist/toast/toast-viewport.mjs.map +1 -0
  219. package/dist/toast/toast.d.mts +119 -0
  220. package/dist/toast/toast.d.mts.map +1 -0
  221. package/dist/toast/toast.mjs +153 -0
  222. package/dist/toast/toast.mjs.map +1 -0
  223. package/package.json +5 -1
@@ -1 +1 @@
1
- {"version":3,"file":"combobox-root.mjs","names":[],"sources":["../../src/combobox/combobox-root.tsx"],"sourcesContent":["\"use client\";\nimport {\n useCallback,\n useDeferredValue,\n useEffect,\n useId,\n useInsertionEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { flushSync } from \"react-dom\";\nimport type { ReactNode, RefObject } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport type {\n ActionDetails,\n ChangeAction,\n ChangeReason,\n StateValue,\n UrgentChangeAction,\n} from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useFormReset } from \"../internal/use-form-reset.js\";\nimport type { FormAssociatedElement } from \"../internal/use-form-reset.js\";\nimport { usePopoverRoot } from \"../internal/use-popover-root.js\";\nimport { resolveStateValue } from \"../internal/use-render.js\";\nimport { ComboboxContext } from \"./combobox-context.js\";\nimport type { ComboboxContextValue, ComboboxOptionEntry } from \"./combobox-context.js\";\n\n/** Details passed as the second argument of `Combobox.Root`'s `valueChangeAction`. */\nexport interface ComboboxValueChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user accepted an option (Enter, click; `details.event` is the `keydown` or\n * `click`), committed free text with `allowCustomValue` (Enter: the `keydown`; focus leaving:\n * the `focusout`), or pressed Clear (`click`).\n * `\"platform\"`: a form reset (`details.event` is the `reset` event).\n */\n readonly reason: ChangeReason;\n /** `true` when `next` is free text committed under `allowCustomValue`, not an option's value. */\n readonly custom: boolean;\n}\n\n/** Details passed as the second argument of `Combobox.Root`'s `inputValueChangeAction`. */\nexport interface ComboboxInputValueChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: typing (the `input` event), an accepted option's label, Clear.\n * `\"dismiss\"`: a revert: focus left with unmatched text, or the selection rolled back.\n * `\"platform\"`: form reset (the `reset` event), or text found in the input at mount\n * (`undefined`).\n */\n readonly reason: ChangeReason;\n}\n\n/** Details passed as the second argument of `Combobox.Root`'s `openChangeAction`. */\nexport interface ComboboxOpenChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: typing, ArrowDown / ArrowUp / Alt+ArrowDown, the Trigger, a click on the input\n * with `openOnClick`, an accepted option (the close after it), a foreign `showPopover()`.\n * `\"dismiss\"`: Escape, light dismiss, focus leaving the combobox, another top-layer element.\n * `\"platform\"`: the popup was found open on mount (only script can cause it).\n */\n readonly reason: ChangeReason;\n}\n\nexport interface ComboboxRootState {\n /** Optimistic selection. Content rendered from it must not suspend. */\n readonly value: string | null;\n /** The text in the input (urgent). */\n readonly inputValue: string;\n /** The deferred text the mounted results are for. */\n readonly query: string;\n /** `inputValue !== query`: the results shown are for an older text. */\n readonly stale: boolean;\n /** The value of the option `aria-activedescendant` points at, or `null`. */\n readonly activeValue: string | null;\n /** Optimistic open state. */\n readonly open: boolean;\n /** A `valueChangeAction` or `openChangeAction` dispatched through this Combobox is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface ComboboxRootProps {\n /** Controlled selection. `null`: nothing selected. `undefined`: uncontrolled. */\n value?: string | null;\n /** Initial selection in uncontrolled mode. Default `null`. */\n defaultValue?: string | null;\n /**\n * Runs inside the Root's transition when the user accepts an option, commits free text\n * (`allowCustomValue`), presses Clear, or the form resets. Awaited; the listbox closes in the\n * commit that ends it; a rejection rolls the input text and `data-selected` back and leaves\n * the listbox open. May be a Server Function. A `setState` after your own `await` needs\n * `startTransition`.\n */\n valueChangeAction?: ChangeAction<string | null, ComboboxValueChangeDetails>;\n\n /** Controlled input text. `undefined`: uncontrolled. */\n inputValue?: string;\n /**\n * Initial input text. Default `\"\"`. Pass the selected value's label when `defaultValue` is\n * set: the library never derives the text from the value.\n */\n defaultInputValue?: string;\n /**\n * The urgent lane: called synchronously, outside any Transition, on every change of the text\n * (typing, and the library's own writes: an accepted option's label, a revert, Clear, a form\n * reset, text typed before hydration). Not awaited; nothing is pending for it. A controlled\n * parent updates its state in the body (an urgent update, which is what a controlled text\n * input needs).\n */\n inputValueChangeAction?: UrgentChangeAction<string, ComboboxInputValueChangeDetails>;\n\n /** Controlled open state. `undefined`: uncontrolled. */\n open?: boolean;\n /** Initial open state when uncontrolled. Opens after hydration. */\n defaultOpen?: boolean;\n /**\n * Awaited for opens and for the close after an accepted option (or the Trigger); a\n * notification for dismissals (Escape, a click outside, focus leaving).\n */\n openChangeAction?: ChangeAction<boolean, ComboboxOpenChangeDetails>;\n\n /**\n * Free text is a value: Enter with no active option, or focus leaving with text that is not\n * the selected option's label, dispatches the text with `details.custom: true` (empty text\n * dispatches `null`). Default `false`: unmatched text reverts to the selection's label when\n * focus leaves.\n */\n allowCustomValue?: boolean;\n /**\n * Make the first enabled option active when the results for an open or an edit settle (APG\n * \"automatic selection\"). Stale results are never highlighted. Default `false`.\n */\n autoHighlight?: boolean;\n /** Open the listbox on a pointer click in the input (not on focus). Default `false`. */\n openOnClick?: boolean;\n /** Wrap from the last option to the first and back with the arrow keys. Default `false`. */\n loop?: boolean;\n /**\n * The message `ariaNotify()` announces on the List when the results settle, from the number\n * of mounted options. Default (English): `\"No results\"`, `\"1 result\"`, `\"N results\"`. Override\n * for localisation or a richer message; return `null` to announce nothing. Ignored where\n * `ariaNotify` is missing.\n */\n announce?: (count: number, state: ComboboxRootState) => string | null;\n\n /** Form participation through a hidden input with this name (value `\"\"` for `null`). */\n name?: string;\n /** The `form` attribute of the hidden input. */\n form?: string;\n /** Disables the Input, Trigger and Clear; nothing dispatches for user interaction. */\n disabled?: boolean;\n children?: StateValue<ReactNode, ComboboxRootState>;\n}\n\n/** The default announcement (design 6.4; conventions 11: English, always overridable). */\nexport function defaultComboboxAnnouncement(count: number): string {\n if (count === 0) return \"No results\";\n if (count === 1) return \"1 result\";\n return `${count} results`;\n}\n\nconst VALUE_TRANSITION_TYPES = [\"fsui-combobox\", \"fsui-combobox-select\"] as const;\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nfunction isPopoverOpen(element: Element | null): boolean {\n if (element === null) return false;\n try {\n return element.matches(\":popover-open\");\n } catch {\n return false;\n }\n}\n\nfunction isThenable(value: unknown): boolean {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\n/** What a value dispatch carries for the Root's own follow-up (by `details` identity). */\ninterface DispatchMeta {\n readonly id: number;\n /** The open this dispatch belongs to (Popover 3.4): a newer open drops the close. */\n readonly generation: number;\n /** Close the listbox once the action settled (an accepted option, Enter on free text). */\n readonly close: boolean;\n /** The text the library wrote with the dispatch: typing since then keeps the listbox open. */\n readonly text: string | undefined;\n}\n\n/** The text the library wrote for a user dispatch, reverted if the value does not land. */\ninterface TextRecord {\n readonly id: number;\n readonly value: string | null;\n readonly text: string;\n settled: boolean;\n}\n\ninterface OrderedOption extends ComboboxOptionEntry {\n readonly value: string;\n}\n\n/**\n * Owns the two states of a combobox (design/components/combobox.md 2): the selection (`value`,\n * a transition with `useOptimistic`, like every other value) and the text (`inputValue`, the\n * library's **urgent** lane: `inputValueChangeAction` is called synchronously, outside any\n * transition). The results render from `useDeferredValue(inputValue)`; the active option is\n * urgent virtual focus (`aria-activedescendant`); the listbox is a `popover=\"auto\"` opened by the\n * library with the input as `source`. Renders no DOM, except one `<input type=\"hidden\">` when\n * `name` is given.\n */\nexport function ComboboxRoot(props: ComboboxRootProps) {\n const {\n value,\n defaultValue,\n valueChangeAction,\n inputValue: inputValueProp,\n defaultInputValue,\n open,\n defaultOpen,\n openChangeAction,\n allowCustomValue = false,\n autoHighlight = false,\n openOnClick = false,\n loop = false,\n name,\n form,\n disabled = false,\n children,\n } = props;\n\n const id = useId();\n const ids = useMemo(\n () => ({\n input: `${id}-input`,\n label: `${id}-label`,\n list: `${id}-list`,\n optionPrefix: `${id}-option-`,\n }),\n [id],\n );\n\n const popover = usePopoverRoot<ComboboxOpenChangeDetails>({\n component: \"combobox\",\n part: \"Combobox.Root\",\n mode: \"library\",\n open,\n defaultOpen,\n openChangeAction,\n });\n\n // --- Refs the handlers read after an `await` or from a timer ------------------------------\n\n const propsRef = useRef(props);\n const popoverRef = useRef(popover);\n useInsertionEffect(() => {\n propsRef.current = props;\n popoverRef.current = popover;\n });\n\n const inputRef = useRef<HTMLInputElement | null>(null);\n const listRef = useRef<HTMLElement | null>(null);\n const hiddenRef = useRef<HTMLInputElement | null>(null);\n\n // --- Text: the urgent lane (design 4) ------------------------------------------------------\n\n const [internalText, setInternalText] = useState(() => defaultInputValue ?? inputValueProp ?? \"\");\n const text = inputValueProp ?? internalText;\n const query = useDeferredValue(text);\n const stale = text !== query;\n const textRef = useRef(text);\n useInsertionEffect(() => {\n textRef.current = text;\n });\n\n const warnedThenable = useRef(false);\n /** Writes the text (urgent) and calls `inputValueChangeAction` synchronously (design 2.2). */\n const writeText = useCallback(\n (next: string, reason: ChangeReason, event: Event | undefined): void => {\n const latest = propsRef.current;\n if (latest.inputValue === undefined) setInternalText(next);\n textRef.current = next;\n const action = latest.inputValueChangeAction;\n if (action === undefined) return;\n const result: unknown = action(next, createActionDetails(event, { reason }));\n if (isDev && !warnedThenable.current && isThenable(result)) {\n warnedThenable.current = true;\n console.error(\n \"Combobox.Root: `inputValueChangeAction` returned a promise. It is the urgent lane: \" +\n \"called synchronously and never awaited. Put async work in `valueChangeAction`, or \" +\n \"derive the results from the deferred `query`.\",\n );\n }\n },\n [],\n );\n\n // --- Labels (design 2.3) -------------------------------------------------------------------\n\n const [labels] = useState(() => {\n const map = new Map<string, string>();\n const initialValue = value !== undefined ? value : (defaultValue ?? null);\n if (initialValue !== null) map.set(initialValue, defaultInputValue ?? inputValueProp ?? \"\");\n return map;\n });\n const labelFor = useCallback(\n (v: string | null): string => (v === null ? \"\" : (labels.get(v) ?? \"\")),\n [labels],\n );\n\n // --- Selection: a transition (design 3.1, 5.2) --------------------------------------------\n\n const dispatchSeq = useRef(0);\n const [dispatchMeta] = useState(() => new WeakMap<object, DispatchMeta>());\n const textRecord = useRef<TextRecord | null>(null);\n const committedRef = useRef<string | null>(null);\n\n const runValueAction = async (\n next: string | null,\n details: ComboboxValueChangeDetails,\n ): Promise<void> => {\n const meta = dispatchMeta.get(details);\n try {\n await valueChangeAction?.(next, details);\n } catch (error) {\n // Walkthrough 4: the text rolls back with the optimistic value. A synchronous throw lands\n // here inside the Root's `startTransition` callback; yield once so the write (and the\n // consumer's `inputValueChangeAction`) is urgent, as every text write (conventions 2.6).\n await undefined;\n const record = textRecord.current;\n if (meta !== undefined && record !== null && record.id === meta.id) {\n textRecord.current = null;\n if (textRef.current === record.text) {\n writeText(labelFor(committedRef.current), \"dismiss\", undefined);\n }\n }\n throw error;\n }\n const record = textRecord.current;\n if (meta !== undefined && record !== null && record.id === meta.id) record.settled = true;\n // Menu decision 3: close in the commit that ends the action, unless a newer dispatch took\n // over, the user dismissed and re-opened meanwhile, or the user is typing a new search.\n const current = popoverRef.current;\n if (\n meta !== undefined &&\n meta.close &&\n meta.id === dispatchSeq.current &&\n current.openGeneration() === meta.generation &&\n (meta.text === undefined || textRef.current === meta.text)\n ) {\n current.requestClose(details.event, \"activate\");\n }\n };\n\n const selection = useControllableAction<string | null, ComboboxValueChangeDetails>({\n controlled: value,\n defaultValue,\n initialValue: null,\n action: runValueAction,\n transitionTypes: () => VALUE_TRANSITION_TYPES,\n warn: { component: \"Combobox.Root\", prop: \"value\" },\n });\n useInsertionEffect(() => {\n committedRef.current = selection.committed;\n });\n\n const dispatchValue = (\n next: string | null,\n event: Event | undefined,\n fields: { reason: ChangeReason; custom: boolean },\n options: { close: boolean; text: string | undefined },\n ): void => {\n const details = createActionDetails(event, fields);\n const dispatchId = ++dispatchSeq.current;\n dispatchMeta.set(details, {\n id: dispatchId,\n generation: popover.openGeneration(),\n close: options.close,\n text: options.text,\n });\n textRecord.current =\n options.text === undefined\n ? null\n : { id: dispatchId, value: next, text: options.text, settled: false };\n selection.dispatch(next, details);\n };\n\n // --- Option registry and the active option (design 2, 6.5) --------------------------------\n\n const [registry] = useState(() => new Map<string, ComboboxOptionEntry>());\n const [optionCount, setOptionCount] = useState(0);\n const [statusCount, setStatusCount] = useState(0);\n // The same count for the announcement effect: the open commit's passive effects can run before\n // the re-render the Status's registration scheduled (the race `registry` avoids for options).\n const statusCountRef = useRef(0);\n const [labelCount, setLabelCount] = useState(0);\n const warnedDuplicate = useRef(false);\n\n const registerOption = useCallback(\n (optionValue: string, entry: ComboboxOptionEntry) => {\n const existing = registry.get(optionValue);\n if (\n isDev &&\n !warnedDuplicate.current &&\n existing !== undefined &&\n existing.element !== entry.element &&\n existing.element.isConnected\n ) {\n warnedDuplicate.current = true;\n console.error(\n `Combobox.Option: two options have the value ${JSON.stringify(optionValue)}. ` +\n \"Option ids are derived from the value, so they must be unique within one Combobox.\",\n );\n }\n registry.set(optionValue, entry);\n labels.set(optionValue, entry.label ?? (entry.element.textContent ?? \"\").trim());\n setOptionCount(registry.size);\n return () => {\n if (registry.get(optionValue) === entry) registry.delete(optionValue);\n setOptionCount(registry.size);\n };\n },\n [registry, labels],\n );\n\n const registerStatus = useCallback(() => {\n statusCountRef.current += 1;\n setStatusCount((count) => count + 1);\n return () => {\n statusCountRef.current -= 1;\n setStatusCount((count) => count - 1);\n };\n }, []);\n\n const registerLabel = useCallback(() => {\n setLabelCount((count) => count + 1);\n return () => setLabelCount((count) => count - 1);\n }, []);\n\n const [activeValue, setActiveValue] = useState<string | null>(null);\n const activeRef = useRef<string | null>(null);\n const setActive = useCallback((next: string | null) => {\n activeRef.current = next;\n setActiveValue(next);\n }, []);\n // The edge to activate once the open has options (ArrowDown / ArrowUp while closed).\n const pendingEdge = useRef<\"first\" | \"last\" | null>(null);\n // `autoHighlight` (APG automatic selection) highlights once per open and once per edit, when\n // the results for it settle. Home / End / Left / Right clear the active option without\n // re-arming it, so the caret keys really return visual focus to the input.\n const autoArmed = useRef(true);\n\n const orderedOptions = useCallback((): OrderedOption[] => {\n const options: OrderedOption[] = [];\n for (const [optionValue, entry] of registry) options.push({ value: optionValue, ...entry });\n options.sort((a, b) =>\n a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1,\n );\n return options;\n }, [registry]);\n\n // A Status inside the List is a Suspense fallback: the results are loading (design 6.3).\n const loading = statusCount > 0;\n const empty = optionCount === 0 && !stale && !loading;\n\n // Revalidation (design 6.5): urgent, in a layout effect after every commit; never dispatches.\n useLayoutEffect(() => {\n const current = activeRef.current;\n if (!popover.committed) {\n // Keep the edge of an open that is on its way (the optimistic commit comes first).\n if (!popover.intended()) pendingEdge.current = null;\n autoArmed.current = true;\n if (current !== null) setActive(null);\n return;\n }\n const enabled = orderedOptions().filter((option) => !option.disabled);\n let next = current;\n if (pendingEdge.current !== null && enabled.length > 0) {\n next = (pendingEdge.current === \"first\" ? enabled[0] : enabled.at(-1))!.value;\n pendingEdge.current = null;\n } else if (next !== null && !enabled.some((option) => option.value === next)) {\n next = null;\n }\n if (next !== null) {\n // Something is active (keys, hover): nothing to highlight for these results.\n autoArmed.current = false;\n } else if (autoHighlight && autoArmed.current && !stale && enabled.length > 0) {\n // Only settled results: a stale list's first option is never auto-highlighted, so a fast\n // Enter commits the typed text or does nothing (design L5).\n next = enabled[0]!.value;\n autoArmed.current = false;\n }\n if (next !== current) setActive(next);\n });\n\n // The selection did not land (a controlled parent refused it, or the caught-inside variant of\n // a controlled parent): the text goes back to the committed value's label (design 2.2).\n // Labels for values set from outside are learnt from the text the consumer passed with them.\n const prevCommitted = useRef(selection.committed);\n useLayoutEffect(() => {\n const record = textRecord.current;\n if (\n record !== null &&\n record.settled &&\n !selection.pending &&\n selection.latestInFlight() === null\n ) {\n textRecord.current = null;\n if (selection.committed !== record.value && textRef.current === record.text) {\n writeText(labelFor(selection.committed), \"dismiss\", undefined);\n }\n }\n if (prevCommitted.current !== selection.committed) {\n prevCommitted.current = selection.committed;\n const committed = selection.committed;\n if (committed !== null && textRecord.current === null && !labels.has(committed)) {\n labels.set(committed, textRef.current);\n }\n }\n });\n\n // --- Announcements (design 6.4) -----------------------------------------------------------\n\n const state: ComboboxRootState = {\n value: selection.optimistic,\n inputValue: text,\n query,\n stale,\n activeValue,\n open: popover.optimistic,\n pending: selection.pending || popover.pending,\n disabled,\n };\n\n const lastAnnounced = useRef<string | null>(null);\n useEffect(() => {\n if (!popover.committed) {\n lastAnnounced.current = null;\n return;\n }\n if (stale || statusCountRef.current > 0) return;\n // The registry, not `optionCount`: a commit's passive effects can run before the re-render\n // that the Options' registration scheduled.\n const count = registry.size;\n const key = `${count}\\u0000${query}`;\n if (lastAnnounced.current === key) return;\n lastAnnounced.current = key;\n const list = listRef.current as (HTMLElement & { ariaNotify?: (m: string) => void }) | null;\n if (list === null || typeof list.ariaNotify !== \"function\") return;\n const announce = propsRef.current.announce ?? defaultComboboxAnnouncement;\n const message = announce(count, state);\n if (message !== null) list.ariaNotify(message);\n });\n\n // --- Form: hidden input, reset, mount reconciliation (design 11.1, walkthroughs 10, 11) ----\n\n const [mountValue] = useState(value);\n const [mountText] = useState(inputValueProp);\n // Reset targets (conventions 3.8 rule 6): uncontrolled, the latest default props; controlled,\n // the mount-time values.\n const valueResetTarget = value !== undefined ? (mountValue ?? null) : (defaultValue ?? null);\n const inputDefaultValue =\n inputValueProp !== undefined ? (mountText ?? \"\") : (defaultInputValue ?? \"\");\n // After a form reset a controlled parent may not adopt the text: put React's value back into\n // the DOM (react-dom only does that after its own input events).\n const [resync, setResync] = useState(0);\n useLayoutEffect(() => {\n const input = inputRef.current;\n if (resync > 0 && input !== null && input.value !== textRef.current) {\n input.value = textRef.current;\n }\n }, [resync]);\n\n const formElement = useMemo<RefObject<FormAssociatedElement | null>>(\n () => ({\n get current() {\n return hiddenRef.current ?? inputRef.current;\n },\n set current(_ignored) {},\n }),\n [],\n );\n useFormReset(\n formElement,\n (event) => {\n const input = inputRef.current;\n const domText = input?.value ?? inputDefaultValue;\n if (domText !== textRef.current) writeText(domText, \"platform\", event);\n // A controlled parent that does not adopt the text gets the DOM re-synced.\n setResync((n) => n + 1);\n if (valueResetTarget !== selection.optimistic) {\n dispatchValue(\n valueResetTarget,\n event,\n { reason: \"platform\", custom: false },\n { close: false, text: undefined },\n );\n }\n },\n () => {\n // react-dom writes `defaultValue = value` after every update of a controlled input, so the\n // reset target is written here, right before the browser's reset algorithm reads it.\n const input = inputRef.current;\n if (input !== null && input.defaultValue !== inputDefaultValue) {\n input.defaultValue = inputDefaultValue;\n }\n },\n );\n\n // Walkthrough 11: react-dom keeps a hydrated text input's DOM value and fires no `onChange`.\n const reconciled = useRef(false);\n useLayoutEffect(() => {\n if (reconciled.current) return;\n reconciled.current = true;\n const input = inputRef.current;\n if (input !== null && input.value !== textRef.current) {\n writeText(input.value, \"platform\", undefined);\n }\n }, [writeText]);\n\n const [missingText] = useState(\n () =>\n (value ?? defaultValue ?? null) !== null &&\n inputValueProp === undefined &&\n defaultInputValue === undefined,\n );\n useDevWarning(\n missingText,\n \"Combobox.Root: `value` / `defaultValue` is set but neither `inputValue` nor \" +\n \"`defaultInputValue` is: the input will show no text for the selection. The library never \" +\n \"derives the text from the value; pass the selected option's label as well.\",\n );\n\n // --- Interaction (design 5, 8) -------------------------------------------------------------\n\n const accept = (optionValue: string, event: Event): void => {\n if (disabled) return;\n const entry = registry.get(optionValue);\n if (entry === undefined || entry.disabled) return;\n const label = entry.label ?? (entry.element.textContent ?? \"\").trim();\n labels.set(optionValue, label);\n pendingEdge.current = null;\n if (activeRef.current !== optionValue) setActive(optionValue);\n writeText(label, \"activate\", event);\n // Re-accepting the selected option dispatches nothing and closes (conventions 2.3 item 8).\n if (optionValue === selection.optimistic) {\n popover.requestClose(event, \"activate\");\n return;\n }\n dispatchValue(\n optionValue,\n event,\n { reason: \"activate\", custom: false },\n { close: true, text: label },\n );\n };\n\n /** Free text as the value (design 5.3). Returns whether it dispatched. */\n const commitText = (event: Event, close: boolean): boolean => {\n const current = selection.optimistic;\n const typed = textRef.current;\n if (typed === labelFor(current)) return false;\n const next = typed === \"\" ? null : typed;\n if (next === current) return false;\n if (next !== null) labels.set(next, typed);\n dispatchValue(\n next,\n event,\n { reason: \"activate\", custom: next !== null },\n { close, text: typed },\n );\n return true;\n };\n\n const clear = (event: Event): void => {\n if (disabled) return;\n if (textRef.current !== \"\") writeText(\"\", \"activate\", event);\n if (selection.optimistic !== null) {\n dispatchValue(null, event, { reason: \"activate\", custom: false }, { close: false, text: \"\" });\n }\n inputRef.current?.focus();\n };\n\n const typed = (next: string, event: Event): void => {\n if (disabled) return;\n writeText(next, \"activate\", event);\n pendingEdge.current = null;\n // APG: editing moves visual focus back to the input; `autoHighlight` picks the first option\n // again once the results for the new text settle.\n if (activeRef.current !== null) setActive(null);\n autoArmed.current = true;\n popover.requestOpen(event, \"activate\");\n };\n\n const move = (direction: 1 | -1): void => {\n const enabled = orderedOptions().filter((option) => !option.disabled);\n if (enabled.length === 0) return;\n const last = enabled.length - 1;\n const index = enabled.findIndex((option) => option.value === activeRef.current);\n let next: number;\n if (direction === 1) next = index < 0 ? 0 : index >= last ? (loop ? 0 : last) : index + 1;\n else next = index < 0 ? last : index <= 0 ? (loop ? last : 0) : index - 1;\n const target = enabled[next]!;\n setActive(target.value);\n if (typeof target.element.scrollIntoView === \"function\") {\n target.element.scrollIntoView({ block: \"nearest\" });\n }\n };\n\n const activeEnabled = (): string | null => {\n const current = activeRef.current;\n if (current === null) return null;\n const entry = registry.get(current);\n return entry !== undefined && !entry.disabled ? current : null;\n };\n\n const keyDown = (event: KeyboardEvent): boolean => {\n if (disabled || event.ctrlKey || event.metaKey) return false;\n // IME: the key belongs to the composition (Enter commits it, arrows pick a candidate).\n if (event.isComposing || event.keyCode === 229) return false;\n // What the user sees, not what is intended: right after Escape the platform has hidden the\n // popup but its `toggle` (and the library's notification) is still queued, and an open that\n // is still committing shows nothing yet. Enter must submit and arrows must open in both.\n const isOpen = isPopoverOpen(popover.popupRef.current);\n switch (event.key) {\n case \"ArrowDown\":\n event.preventDefault();\n if (event.altKey) {\n if (!isOpen) popover.requestOpen(event, \"activate\");\n } else if (!isOpen) {\n pendingEdge.current = \"first\";\n popover.requestOpen(event, \"activate\");\n } else {\n move(1);\n }\n return true;\n case \"ArrowUp\":\n event.preventDefault();\n if (event.altKey) {\n if (isOpen) {\n const active = activeEnabled();\n if (active !== null) accept(active, event);\n else popover.requestClose(event, \"activate\");\n }\n } else if (!isOpen) {\n pendingEdge.current = \"last\";\n popover.requestOpen(event, \"activate\");\n } else {\n move(-1);\n }\n return true;\n case \"Enter\": {\n if (isOpen) {\n // An open listbox means the user is choosing: never submit from here.\n event.preventDefault();\n const active = activeEnabled();\n if (active !== null) accept(active, event);\n else if (allowCustomValue && !commitText(event, true)) {\n popover.requestClose(event, \"activate\");\n }\n return true;\n }\n // Closed: not prevented, so a form still submits. The key's default action (implicit\n // submission) must see the optimistic hidden value: Firefox submits before React's\n // scheduled flush of the optimistic render, so it is flushed here (design 5.3).\n if (allowCustomValue) flushSync(() => commitText(event, false));\n return false;\n }\n case \"Home\":\n case \"End\":\n case \"ArrowLeft\":\n case \"ArrowRight\":\n // APG: the caret keys move visual focus to the input (not prevented: the caret moves).\n if (activeRef.current !== null) setActive(null);\n return false;\n default:\n return false;\n }\n };\n\n /** Focus has left the input and the popup (design 5.4). */\n const leave = (event: Event): void => {\n // `disabled`: nothing dispatches (disabling a focused input blurs it).\n if (disabled) return;\n const popup = popover.popupRef.current;\n if (popup !== null && isPopoverOpen(popup)) {\n popover.recordCloseIntent({ reason: \"dismiss\", event });\n popup.hidePopover();\n }\n pendingEdge.current = null;\n if (allowCustomValue) {\n commitText(event, false);\n return;\n }\n const label = labelFor(selection.optimistic);\n if (textRef.current !== label) writeText(label, \"dismiss\", event);\n };\n const leaveRef = useRef(leave);\n useInsertionEffect(() => {\n leaveRef.current = leave;\n });\n\n const focusOut = (event: FocusEvent): void => {\n const input = inputRef.current;\n const popup = popover.popupRef.current;\n const next = event.relatedTarget;\n if (next instanceof Node) {\n if (next === input || (popup !== null && popup.contains(next))) return;\n leave(event);\n return;\n }\n // No related target: the window lost focus (nothing to do), or a click landed on something\n // that takes no focus (the page itself: focus moved to <body>). Decide after the click.\n if (input === null) return;\n const doc = input.ownerDocument;\n setTimeout(() => {\n if (!doc.hasFocus()) return;\n const active = doc.activeElement;\n if (active === input || (active !== null && popup !== null && popup.contains(active))) {\n return;\n }\n if (input.isConnected) leaveRef.current(event);\n }, 0);\n };\n\n const inputClick = (event: MouseEvent): void => {\n if (disabled || !openOnClick) return;\n popover.requestOpen(event, \"activate\");\n };\n\n const context: ComboboxContextValue = {\n popover,\n ids,\n value: selection.optimistic,\n valuePending: selection.pending,\n pending: state.pending,\n inputValue: text,\n query,\n stale,\n activeValue,\n empty,\n loading,\n hasLabel: labelCount > 0,\n disabled,\n inputRef,\n listRef,\n registerOption,\n registerStatus,\n registerLabel,\n setActive,\n accept,\n clear,\n typed,\n keyDown,\n focusOut,\n inputClick,\n };\n\n return (\n <ComboboxContext value={context}>\n {name !== undefined && (\n <input\n ref={hiddenRef}\n type=\"hidden\"\n name={name}\n form={form}\n value={selection.optimistic ?? \"\"}\n />\n )}\n {resolveStateValue(children, state)}\n </ComboboxContext>\n );\n}\n\nComboboxRoot.displayName = \"ComboboxRoot\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"combobox-root.mjs","names":[],"sources":["../../src/combobox/combobox-root.tsx"],"sourcesContent":["\"use client\";\nimport {\n use,\n useCallback,\n useDeferredValue,\n useEffect,\n useId,\n useInsertionEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { flushSync } from \"react-dom\";\nimport type { ReactNode, RefObject } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { FieldContext } from \"../internal/field-context.js\";\nimport { useFieldValueChange } from \"../internal/use-field-control.js\";\nimport type {\n ActionDetails,\n ChangeAction,\n ChangeReason,\n StateValue,\n UrgentChangeAction,\n} from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useFormReset } from \"../internal/use-form-reset.js\";\nimport type { FormAssociatedElement } from \"../internal/use-form-reset.js\";\nimport { usePopoverRoot } from \"../internal/use-popover-root.js\";\nimport { resolveStateValue } from \"../internal/use-render.js\";\nimport { ComboboxContext } from \"./combobox-context.js\";\nimport type { ComboboxContextValue, ComboboxOptionEntry } from \"./combobox-context.js\";\n\n/** Details passed as the second argument of `Combobox.Root`'s `valueChangeAction`. */\nexport interface ComboboxValueChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user accepted an option (Enter, click; `details.event` is the `keydown` or\n * `click`), committed free text with `allowCustomValue` (Enter: the `keydown`; focus leaving:\n * the `focusout`), or pressed Clear (`click`).\n * `\"platform\"`: a form reset (`details.event` is the `reset` event).\n */\n readonly reason: ChangeReason;\n /** `true` when `next` is free text committed under `allowCustomValue`, not an option's value. */\n readonly custom: boolean;\n}\n\n/** Details passed as the second argument of `Combobox.Root`'s `inputValueChangeAction`. */\nexport interface ComboboxInputValueChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: typing (the `input` event), an accepted option's label, Clear.\n * `\"dismiss\"`: a revert: focus left with unmatched text, or the selection rolled back.\n * `\"platform\"`: form reset (the `reset` event), or text found in the input at mount\n * (`undefined`).\n */\n readonly reason: ChangeReason;\n}\n\n/** Details passed as the second argument of `Combobox.Root`'s `openChangeAction`. */\nexport interface ComboboxOpenChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: typing, ArrowDown / ArrowUp / Alt+ArrowDown, the Trigger, a click on the input\n * with `openOnClick`, an accepted option (the close after it), a foreign `showPopover()`.\n * `\"dismiss\"`: Escape, light dismiss, focus leaving the combobox, another top-layer element.\n * `\"platform\"`: the popup was found open on mount (only script can cause it).\n */\n readonly reason: ChangeReason;\n}\n\nexport interface ComboboxRootState {\n /** Optimistic selection. Content rendered from it must not suspend. */\n readonly value: string | null;\n /** The text in the input (urgent). */\n readonly inputValue: string;\n /** The deferred text the mounted results are for. */\n readonly query: string;\n /** `inputValue !== query`: the results shown are for an older text. */\n readonly stale: boolean;\n /** The value of the option `aria-activedescendant` points at, or `null`. */\n readonly activeValue: string | null;\n /** Optimistic open state. */\n readonly open: boolean;\n /** A `valueChangeAction` or `openChangeAction` dispatched through this Combobox is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface ComboboxRootProps {\n /** Controlled selection. `null`: nothing selected. `undefined`: uncontrolled. */\n value?: string | null;\n /** Initial selection in uncontrolled mode. Default `null`. */\n defaultValue?: string | null;\n /**\n * Runs inside the Root's transition when the user accepts an option, commits free text\n * (`allowCustomValue`), presses Clear, or the form resets. Awaited; the listbox closes in the\n * commit that ends it; a rejection rolls the input text and `data-selected` back and leaves\n * the listbox open. May be a Server Function. A `setState` after your own `await` needs\n * `startTransition`.\n */\n valueChangeAction?: ChangeAction<string | null, ComboboxValueChangeDetails>;\n\n /** Controlled input text. `undefined`: uncontrolled. */\n inputValue?: string;\n /**\n * Initial input text. Default `\"\"`. Pass the selected value's label when `defaultValue` is\n * set: the library never derives the text from the value.\n */\n defaultInputValue?: string;\n /**\n * The urgent lane: called synchronously, outside any Transition, on every change of the text\n * (typing, and the library's own writes: an accepted option's label, a revert, Clear, a form\n * reset, text typed before hydration). Not awaited; nothing is pending for it. A controlled\n * parent updates its state in the body (an urgent update, which is what a controlled text\n * input needs).\n */\n inputValueChangeAction?: UrgentChangeAction<string, ComboboxInputValueChangeDetails>;\n\n /** Controlled open state. `undefined`: uncontrolled. */\n open?: boolean;\n /** Initial open state when uncontrolled. Opens after hydration. */\n defaultOpen?: boolean;\n /**\n * Awaited for opens and for the close after an accepted option (or the Trigger); a\n * notification for dismissals (Escape, a click outside, focus leaving).\n */\n openChangeAction?: ChangeAction<boolean, ComboboxOpenChangeDetails>;\n\n /**\n * Free text is a value: Enter with no active option, or focus leaving with text that is not\n * the selected option's label, dispatches the text with `details.custom: true` (empty text\n * dispatches `null`). Default `false`: unmatched text reverts to the selection's label when\n * focus leaves.\n */\n allowCustomValue?: boolean;\n /**\n * Make the first enabled option active when the results for an open or an edit settle (APG\n * \"automatic selection\"). Stale results are never highlighted. Default `false`.\n */\n autoHighlight?: boolean;\n /** Open the listbox on a pointer click in the input (not on focus). Default `false`. */\n openOnClick?: boolean;\n /** Wrap from the last option to the first and back with the arrow keys. Default `false`. */\n loop?: boolean;\n /**\n * The message `ariaNotify()` announces on the List when the results settle, from the number\n * of mounted options. Default (English): `\"No results\"`, `\"1 result\"`, `\"N results\"`. Override\n * for localisation or a richer message; return `null` to announce nothing. Ignored where\n * `ariaNotify` is missing.\n */\n announce?: (count: number, state: ComboboxRootState) => string | null;\n\n /** Form participation through a hidden input with this name (value `\"\"` for `null`). */\n name?: string;\n /** The `form` attribute of the hidden input. */\n form?: string;\n /** Disables the Input, Trigger and Clear; nothing dispatches for user interaction. */\n disabled?: boolean;\n children?: StateValue<ReactNode, ComboboxRootState>;\n}\n\n/** The default announcement (design 6.4; conventions 11: English, always overridable). */\nexport function defaultComboboxAnnouncement(count: number): string {\n if (count === 0) return \"No results\";\n if (count === 1) return \"1 result\";\n return `${count} results`;\n}\n\nconst VALUE_TRANSITION_TYPES = [\"fsui-combobox\", \"fsui-combobox-select\"] as const;\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nfunction isPopoverOpen(element: Element | null): boolean {\n if (element === null) return false;\n try {\n return element.matches(\":popover-open\");\n } catch {\n return false;\n }\n}\n\nfunction isThenable(value: unknown): boolean {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\n/** What a value dispatch carries for the Root's own follow-up (by `details` identity). */\ninterface DispatchMeta {\n readonly id: number;\n /** The open this dispatch belongs to (Popover 3.4): a newer open drops the close. */\n readonly generation: number;\n /** Close the listbox once the action settled (an accepted option, Enter on free text). */\n readonly close: boolean;\n /** The text the library wrote with the dispatch: typing since then keeps the listbox open. */\n readonly text: string | undefined;\n}\n\n/** The text the library wrote for a user dispatch, reverted if the value does not land. */\ninterface TextRecord {\n readonly id: number;\n readonly value: string | null;\n readonly text: string;\n settled: boolean;\n}\n\ninterface OrderedOption extends ComboboxOptionEntry {\n readonly value: string;\n}\n\n/**\n * Owns the two states of a combobox (design/components/combobox.md 2): the selection (`value`,\n * a transition with `useOptimistic`, like every other value) and the text (`inputValue`, the\n * library's **urgent** lane: `inputValueChangeAction` is called synchronously, outside any\n * transition). The results render from `useDeferredValue(inputValue)`; the active option is\n * urgent virtual focus (`aria-activedescendant`); the listbox is a `popover=\"auto\"` opened by the\n * library with the input as `source`. Renders no DOM, except one `<input type=\"hidden\">` when\n * `name` is given.\n */\nexport function ComboboxRoot(props: ComboboxRootProps) {\n const {\n value,\n defaultValue,\n valueChangeAction,\n inputValue: inputValueProp,\n defaultInputValue,\n open,\n defaultOpen,\n openChangeAction,\n allowCustomValue = false,\n autoHighlight = false,\n openOnClick = false,\n loop = false,\n form,\n children,\n } = props;\n\n // Inside a Field.Root (design/components/field-form.md 7.2): the Input is the Field's control\n // (its id is the Field's, so `Field.Label` points at it), the hidden input's `name` and\n // `disabled` default from the Field, and the List is named by `Field.Label` when no\n // `Combobox.Label` is mounted.\n const field = use(FieldContext);\n const name = props.name ?? field?.name;\n const disabled = (props.disabled ?? false) || (field?.disabled ?? false);\n const fieldControlId = field?.ids.control;\n\n const id = useId();\n const ids = useMemo(\n () => ({\n input: fieldControlId ?? `${id}-input`,\n label: `${id}-label`,\n list: `${id}-list`,\n optionPrefix: `${id}-option-`,\n }),\n [id, fieldControlId],\n );\n\n const popover = usePopoverRoot<ComboboxOpenChangeDetails>({\n component: \"combobox\",\n part: \"Combobox.Root\",\n mode: \"library\",\n open,\n defaultOpen,\n openChangeAction,\n });\n\n // --- Refs the handlers read after an `await` or from a timer ------------------------------\n\n const propsRef = useRef(props);\n const popoverRef = useRef(popover);\n useInsertionEffect(() => {\n propsRef.current = props;\n popoverRef.current = popover;\n });\n\n const inputRef = useRef<HTMLInputElement | null>(null);\n const listRef = useRef<HTMLElement | null>(null);\n const hiddenRef = useRef<HTMLInputElement | null>(null);\n\n // --- Text: the urgent lane (design 4) ------------------------------------------------------\n\n const [internalText, setInternalText] = useState(() => defaultInputValue ?? inputValueProp ?? \"\");\n const text = inputValueProp ?? internalText;\n const query = useDeferredValue(text);\n const stale = text !== query;\n const textRef = useRef(text);\n useInsertionEffect(() => {\n textRef.current = text;\n });\n\n const warnedThenable = useRef(false);\n /** Writes the text (urgent) and calls `inputValueChangeAction` synchronously (design 2.2). */\n const writeText = useCallback(\n (next: string, reason: ChangeReason, event: Event | undefined): void => {\n const latest = propsRef.current;\n if (latest.inputValue === undefined) setInternalText(next);\n textRef.current = next;\n const action = latest.inputValueChangeAction;\n if (action === undefined) return;\n const result: unknown = action(next, createActionDetails(event, { reason }));\n if (isDev && !warnedThenable.current && isThenable(result)) {\n warnedThenable.current = true;\n console.error(\n \"Combobox.Root: `inputValueChangeAction` returned a promise. It is the urgent lane: \" +\n \"called synchronously and never awaited. Put async work in `valueChangeAction`, or \" +\n \"derive the results from the deferred `query`.\",\n );\n }\n },\n [],\n );\n\n // --- Labels (design 2.3) -------------------------------------------------------------------\n\n const [labels] = useState(() => {\n const map = new Map<string, string>();\n const initialValue = value !== undefined ? value : (defaultValue ?? null);\n if (initialValue !== null) map.set(initialValue, defaultInputValue ?? inputValueProp ?? \"\");\n return map;\n });\n const labelFor = useCallback(\n (v: string | null): string => (v === null ? \"\" : (labels.get(v) ?? \"\")),\n [labels],\n );\n\n // --- Selection: a transition (design 3.1, 5.2) --------------------------------------------\n\n const dispatchSeq = useRef(0);\n const [dispatchMeta] = useState(() => new WeakMap<object, DispatchMeta>());\n const textRecord = useRef<TextRecord | null>(null);\n const committedRef = useRef<string | null>(null);\n\n const runValueAction = async (\n next: string | null,\n details: ComboboxValueChangeDetails,\n ): Promise<void> => {\n const meta = dispatchMeta.get(details);\n try {\n await valueChangeAction?.(next, details);\n } catch (error) {\n // Walkthrough 4: the text rolls back with the optimistic value. A synchronous throw lands\n // here inside the Root's `startTransition` callback; yield once so the write (and the\n // consumer's `inputValueChangeAction`) is urgent, as every text write (conventions 2.6).\n await undefined;\n const record = textRecord.current;\n if (meta !== undefined && record !== null && record.id === meta.id) {\n textRecord.current = null;\n if (textRef.current === record.text) {\n writeText(labelFor(committedRef.current), \"dismiss\", undefined);\n }\n }\n throw error;\n }\n const record = textRecord.current;\n if (meta !== undefined && record !== null && record.id === meta.id) record.settled = true;\n // Menu decision 3: close in the commit that ends the action, unless a newer dispatch took\n // over, the user dismissed and re-opened meanwhile, or the user is typing a new search.\n const current = popoverRef.current;\n if (\n meta !== undefined &&\n meta.close &&\n meta.id === dispatchSeq.current &&\n current.openGeneration() === meta.generation &&\n (meta.text === undefined || textRef.current === meta.text)\n ) {\n current.requestClose(details.event, \"activate\");\n }\n };\n\n const selection = useControllableAction<string | null, ComboboxValueChangeDetails>({\n controlled: value,\n defaultValue,\n initialValue: null,\n action: runValueAction,\n transitionTypes: () => VALUE_TRANSITION_TYPES,\n warn: { component: \"Combobox.Root\", prop: \"value\" },\n });\n useInsertionEffect(() => {\n committedRef.current = selection.committed;\n });\n\n /** The next change of the optimistic selection comes from the user (a dispatch). */\n const userEdit = useRef(false);\n const dispatchValue = (\n next: string | null,\n event: Event | undefined,\n fields: { reason: ChangeReason; custom: boolean },\n options: { close: boolean; text: string | undefined },\n ): void => {\n const details = createActionDetails(event, fields);\n const dispatchId = ++dispatchSeq.current;\n dispatchMeta.set(details, {\n id: dispatchId,\n generation: popover.openGeneration(),\n close: options.close,\n text: options.text,\n });\n textRecord.current =\n options.text === undefined\n ? null\n : { id: dispatchId, value: next, text: options.text, settled: false };\n if (next !== selection.optimistic) userEdit.current = true;\n selection.dispatch(next, details);\n };\n\n // Inside a Field: React writes the hidden input with no `input` / `change` event, so the Field\n // is told after the commit (its `validate` reads the hidden input; a user's pick dismisses a\n // shown server error like typing would).\n useFieldValueChange(selection.optimistic, () => {\n const edit = userEdit.current;\n userEdit.current = false;\n return edit;\n });\n\n // --- Option registry and the active option (design 2, 6.5) --------------------------------\n\n const [registry] = useState(() => new Map<string, ComboboxOptionEntry>());\n const [optionCount, setOptionCount] = useState(0);\n const [statusCount, setStatusCount] = useState(0);\n // The same count for the announcement effect: the open commit's passive effects can run before\n // the re-render the Status's registration scheduled (the race `registry` avoids for options).\n const statusCountRef = useRef(0);\n const [labelCount, setLabelCount] = useState(0);\n const warnedDuplicate = useRef(false);\n\n const registerOption = useCallback(\n (optionValue: string, entry: ComboboxOptionEntry) => {\n const existing = registry.get(optionValue);\n if (\n isDev &&\n !warnedDuplicate.current &&\n existing !== undefined &&\n existing.element !== entry.element &&\n existing.element.isConnected\n ) {\n warnedDuplicate.current = true;\n console.error(\n `Combobox.Option: two options have the value ${JSON.stringify(optionValue)}. ` +\n \"Option ids are derived from the value, so they must be unique within one Combobox.\",\n );\n }\n registry.set(optionValue, entry);\n labels.set(optionValue, entry.label ?? (entry.element.textContent ?? \"\").trim());\n setOptionCount(registry.size);\n return () => {\n if (registry.get(optionValue) === entry) registry.delete(optionValue);\n setOptionCount(registry.size);\n };\n },\n [registry, labels],\n );\n\n const registerStatus = useCallback(() => {\n statusCountRef.current += 1;\n setStatusCount((count) => count + 1);\n return () => {\n statusCountRef.current -= 1;\n setStatusCount((count) => count - 1);\n };\n }, []);\n\n const registerLabel = useCallback(() => {\n setLabelCount((count) => count + 1);\n return () => setLabelCount((count) => count - 1);\n }, []);\n\n const [activeValue, setActiveValue] = useState<string | null>(null);\n const activeRef = useRef<string | null>(null);\n const setActive = useCallback((next: string | null) => {\n activeRef.current = next;\n setActiveValue(next);\n }, []);\n // The edge to activate once the open has options (ArrowDown / ArrowUp while closed).\n const pendingEdge = useRef<\"first\" | \"last\" | null>(null);\n // `autoHighlight` (APG automatic selection) highlights once per open and once per edit, when\n // the results for it settle. Home / End / Left / Right clear the active option without\n // re-arming it, so the caret keys really return visual focus to the input.\n const autoArmed = useRef(true);\n\n const orderedOptions = useCallback((): OrderedOption[] => {\n const options: OrderedOption[] = [];\n for (const [optionValue, entry] of registry) options.push({ value: optionValue, ...entry });\n options.sort((a, b) =>\n a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1,\n );\n return options;\n }, [registry]);\n\n // A Status inside the List is a Suspense fallback: the results are loading (design 6.3).\n const loading = statusCount > 0;\n const empty = optionCount === 0 && !stale && !loading;\n\n // Revalidation (design 6.5): urgent, in a layout effect after every commit; never dispatches.\n useLayoutEffect(() => {\n const current = activeRef.current;\n if (!popover.committed) {\n // Keep the edge of an open that is on its way (the optimistic commit comes first).\n if (!popover.intended()) pendingEdge.current = null;\n autoArmed.current = true;\n if (current !== null) setActive(null);\n return;\n }\n const enabled = orderedOptions().filter((option) => !option.disabled);\n let next = current;\n if (pendingEdge.current !== null && enabled.length > 0) {\n next = (pendingEdge.current === \"first\" ? enabled[0] : enabled.at(-1))!.value;\n pendingEdge.current = null;\n } else if (next !== null && !enabled.some((option) => option.value === next)) {\n next = null;\n }\n if (next !== null) {\n // Something is active (keys, hover): nothing to highlight for these results.\n autoArmed.current = false;\n } else if (autoHighlight && autoArmed.current && !stale && enabled.length > 0) {\n // Only settled results: a stale list's first option is never auto-highlighted, so a fast\n // Enter commits the typed text or does nothing (design L5).\n next = enabled[0]!.value;\n autoArmed.current = false;\n }\n if (next !== current) setActive(next);\n });\n\n // The selection did not land (a controlled parent refused it, or the caught-inside variant of\n // a controlled parent): the text goes back to the committed value's label (design 2.2).\n // Labels for values set from outside are learnt from the text the consumer passed with them.\n const prevCommitted = useRef(selection.committed);\n useLayoutEffect(() => {\n const record = textRecord.current;\n if (\n record !== null &&\n record.settled &&\n !selection.pending &&\n selection.latestInFlight() === null\n ) {\n textRecord.current = null;\n if (selection.committed !== record.value && textRef.current === record.text) {\n writeText(labelFor(selection.committed), \"dismiss\", undefined);\n }\n }\n if (prevCommitted.current !== selection.committed) {\n prevCommitted.current = selection.committed;\n const committed = selection.committed;\n if (committed !== null && textRecord.current === null && !labels.has(committed)) {\n labels.set(committed, textRef.current);\n }\n }\n });\n\n // --- Announcements (design 6.4) -----------------------------------------------------------\n\n const state: ComboboxRootState = {\n value: selection.optimistic,\n inputValue: text,\n query,\n stale,\n activeValue,\n open: popover.optimistic,\n pending: selection.pending || popover.pending,\n disabled,\n };\n\n const lastAnnounced = useRef<string | null>(null);\n useEffect(() => {\n if (!popover.committed) {\n lastAnnounced.current = null;\n return;\n }\n if (stale || statusCountRef.current > 0) return;\n // The registry, not `optionCount`: a commit's passive effects can run before the re-render\n // that the Options' registration scheduled.\n const count = registry.size;\n const key = `${count}\\u0000${query}`;\n if (lastAnnounced.current === key) return;\n lastAnnounced.current = key;\n const list = listRef.current as (HTMLElement & { ariaNotify?: (m: string) => void }) | null;\n if (list === null || typeof list.ariaNotify !== \"function\") return;\n const announce = propsRef.current.announce ?? defaultComboboxAnnouncement;\n const message = announce(count, state);\n if (message !== null) list.ariaNotify(message);\n });\n\n // --- Form: hidden input, reset, mount reconciliation (design 11.1, walkthroughs 10, 11) ----\n\n const [mountValue] = useState(value);\n const [mountText] = useState(inputValueProp);\n // Reset targets (conventions 3.8 rule 6): uncontrolled, the latest default props; controlled,\n // the mount-time values.\n const valueResetTarget = value !== undefined ? (mountValue ?? null) : (defaultValue ?? null);\n const inputDefaultValue =\n inputValueProp !== undefined ? (mountText ?? \"\") : (defaultInputValue ?? \"\");\n // After a form reset a controlled parent may not adopt the text: put React's value back into\n // the DOM (react-dom only does that after its own input events).\n const [resync, setResync] = useState(0);\n useLayoutEffect(() => {\n const input = inputRef.current;\n if (resync > 0 && input !== null && input.value !== textRef.current) {\n input.value = textRef.current;\n }\n }, [resync]);\n\n const formElement = useMemo<RefObject<FormAssociatedElement | null>>(\n () => ({\n get current() {\n return hiddenRef.current ?? inputRef.current;\n },\n set current(_ignored) {},\n }),\n [],\n );\n useFormReset(\n formElement,\n (event) => {\n const input = inputRef.current;\n const domText = input?.value ?? inputDefaultValue;\n if (domText !== textRef.current) writeText(domText, \"platform\", event);\n // A controlled parent that does not adopt the text gets the DOM re-synced.\n setResync((n) => n + 1);\n if (valueResetTarget !== selection.optimistic) {\n dispatchValue(\n valueResetTarget,\n event,\n { reason: \"platform\", custom: false },\n { close: false, text: undefined },\n );\n }\n },\n () => {\n // react-dom writes `defaultValue = value` after every update of a controlled input, so the\n // reset target is written here, right before the browser's reset algorithm reads it.\n const input = inputRef.current;\n if (input !== null && input.defaultValue !== inputDefaultValue) {\n input.defaultValue = inputDefaultValue;\n }\n },\n );\n\n // Walkthrough 11: react-dom keeps a hydrated text input's DOM value and fires no `onChange`.\n const reconciled = useRef(false);\n useLayoutEffect(() => {\n if (reconciled.current) return;\n reconciled.current = true;\n const input = inputRef.current;\n if (input !== null && input.value !== textRef.current) {\n writeText(input.value, \"platform\", undefined);\n }\n }, [writeText]);\n\n const [missingText] = useState(\n () =>\n (value ?? defaultValue ?? null) !== null &&\n inputValueProp === undefined &&\n defaultInputValue === undefined,\n );\n useDevWarning(\n missingText,\n \"Combobox.Root: `value` / `defaultValue` is set but neither `inputValue` nor \" +\n \"`defaultInputValue` is: the input will show no text for the selection. The library never \" +\n \"derives the text from the value; pass the selected option's label as well.\",\n );\n\n // --- Interaction (design 5, 8) -------------------------------------------------------------\n\n const accept = (optionValue: string, event: Event): void => {\n if (disabled) return;\n const entry = registry.get(optionValue);\n if (entry === undefined || entry.disabled) return;\n const label = entry.label ?? (entry.element.textContent ?? \"\").trim();\n labels.set(optionValue, label);\n pendingEdge.current = null;\n if (activeRef.current !== optionValue) setActive(optionValue);\n writeText(label, \"activate\", event);\n // Re-accepting the selected option dispatches nothing and closes (conventions 2.3 item 8).\n if (optionValue === selection.optimistic) {\n popover.requestClose(event, \"activate\");\n return;\n }\n dispatchValue(\n optionValue,\n event,\n { reason: \"activate\", custom: false },\n { close: true, text: label },\n );\n };\n\n /** Free text as the value (design 5.3). Returns whether it dispatched. */\n const commitText = (event: Event, close: boolean): boolean => {\n const current = selection.optimistic;\n const typed = textRef.current;\n if (typed === labelFor(current)) return false;\n const next = typed === \"\" ? null : typed;\n if (next === current) return false;\n if (next !== null) labels.set(next, typed);\n dispatchValue(\n next,\n event,\n { reason: \"activate\", custom: next !== null },\n { close, text: typed },\n );\n return true;\n };\n\n const clear = (event: Event): void => {\n if (disabled) return;\n if (textRef.current !== \"\") writeText(\"\", \"activate\", event);\n if (selection.optimistic !== null) {\n dispatchValue(null, event, { reason: \"activate\", custom: false }, { close: false, text: \"\" });\n }\n inputRef.current?.focus();\n };\n\n const typed = (next: string, event: Event): void => {\n if (disabled) return;\n writeText(next, \"activate\", event);\n pendingEdge.current = null;\n // APG: editing moves visual focus back to the input; `autoHighlight` picks the first option\n // again once the results for the new text settle.\n if (activeRef.current !== null) setActive(null);\n autoArmed.current = true;\n popover.requestOpen(event, \"activate\");\n };\n\n const move = (direction: 1 | -1): void => {\n const enabled = orderedOptions().filter((option) => !option.disabled);\n if (enabled.length === 0) return;\n const last = enabled.length - 1;\n const index = enabled.findIndex((option) => option.value === activeRef.current);\n let next: number;\n if (direction === 1) next = index < 0 ? 0 : index >= last ? (loop ? 0 : last) : index + 1;\n else next = index < 0 ? last : index <= 0 ? (loop ? last : 0) : index - 1;\n const target = enabled[next]!;\n setActive(target.value);\n if (typeof target.element.scrollIntoView === \"function\") {\n target.element.scrollIntoView({ block: \"nearest\" });\n }\n };\n\n const activeEnabled = (): string | null => {\n const current = activeRef.current;\n if (current === null) return null;\n const entry = registry.get(current);\n return entry !== undefined && !entry.disabled ? current : null;\n };\n\n const keyDown = (event: KeyboardEvent): boolean => {\n if (disabled || event.ctrlKey || event.metaKey) return false;\n // IME: the key belongs to the composition (Enter commits it, arrows pick a candidate).\n if (event.isComposing || event.keyCode === 229) return false;\n // What the user sees, not what is intended: right after Escape the platform has hidden the\n // popup but its `toggle` (and the library's notification) is still queued, and an open that\n // is still committing shows nothing yet. Enter must submit and arrows must open in both.\n const isOpen = isPopoverOpen(popover.popupRef.current);\n switch (event.key) {\n case \"ArrowDown\":\n event.preventDefault();\n if (event.altKey) {\n if (!isOpen) popover.requestOpen(event, \"activate\");\n } else if (!isOpen) {\n pendingEdge.current = \"first\";\n popover.requestOpen(event, \"activate\");\n } else {\n move(1);\n }\n return true;\n case \"ArrowUp\":\n event.preventDefault();\n if (event.altKey) {\n if (isOpen) {\n const active = activeEnabled();\n if (active !== null) accept(active, event);\n else popover.requestClose(event, \"activate\");\n }\n } else if (!isOpen) {\n pendingEdge.current = \"last\";\n popover.requestOpen(event, \"activate\");\n } else {\n move(-1);\n }\n return true;\n case \"Enter\": {\n if (isOpen) {\n // An open listbox means the user is choosing: never submit from here.\n event.preventDefault();\n const active = activeEnabled();\n if (active !== null) accept(active, event);\n else if (allowCustomValue && !commitText(event, true)) {\n popover.requestClose(event, \"activate\");\n }\n return true;\n }\n // Closed: not prevented, so a form still submits. The key's default action (implicit\n // submission) must see the optimistic hidden value: Firefox submits before React's\n // scheduled flush of the optimistic render, so it is flushed here (design 5.3).\n if (allowCustomValue) flushSync(() => commitText(event, false));\n return false;\n }\n case \"Home\":\n case \"End\":\n case \"ArrowLeft\":\n case \"ArrowRight\":\n // APG: the caret keys move visual focus to the input (not prevented: the caret moves).\n if (activeRef.current !== null) setActive(null);\n return false;\n default:\n return false;\n }\n };\n\n /** Focus has left the input and the popup (design 5.4). */\n const leave = (event: Event): void => {\n // `disabled`: nothing dispatches (disabling a focused input blurs it).\n if (disabled) return;\n const popup = popover.popupRef.current;\n if (popup !== null && isPopoverOpen(popup)) {\n popover.recordCloseIntent({ reason: \"dismiss\", event });\n popup.hidePopover();\n }\n pendingEdge.current = null;\n if (allowCustomValue) {\n commitText(event, false);\n return;\n }\n const label = labelFor(selection.optimistic);\n if (textRef.current !== label) writeText(label, \"dismiss\", event);\n };\n const leaveRef = useRef(leave);\n useInsertionEffect(() => {\n leaveRef.current = leave;\n });\n\n const focusOut = (event: FocusEvent): void => {\n const input = inputRef.current;\n const popup = popover.popupRef.current;\n const next = event.relatedTarget;\n if (next instanceof Node) {\n if (next === input || (popup !== null && popup.contains(next))) return;\n leave(event);\n return;\n }\n // No related target: the window lost focus (nothing to do), or a click landed on something\n // that takes no focus (the page itself: focus moved to <body>). Decide after the click.\n if (input === null) return;\n const doc = input.ownerDocument;\n setTimeout(() => {\n if (!doc.hasFocus()) return;\n const active = doc.activeElement;\n if (active === input || (active !== null && popup !== null && popup.contains(active))) {\n return;\n }\n if (input.isConnected) leaveRef.current(event);\n }, 0);\n };\n\n const inputClick = (event: MouseEvent): void => {\n if (disabled || !openOnClick) return;\n popover.requestOpen(event, \"activate\");\n };\n\n const context: ComboboxContextValue = {\n popover,\n ids,\n value: selection.optimistic,\n valuePending: selection.pending,\n pending: state.pending,\n inputValue: text,\n query,\n stale,\n activeValue,\n empty,\n loading,\n hasLabel: labelCount > 0,\n fieldLabelId: field?.ids.label,\n disabled,\n inputRef,\n listRef,\n registerOption,\n registerStatus,\n registerLabel,\n setActive,\n accept,\n clear,\n typed,\n keyDown,\n focusOut,\n inputClick,\n };\n\n return (\n <ComboboxContext value={context}>\n {name !== undefined && (\n <input\n ref={hiddenRef}\n type=\"hidden\"\n name={name}\n form={form}\n value={selection.optimistic ?? \"\"}\n />\n )}\n {resolveStateValue(children, state)}\n </ComboboxContext>\n );\n}\n\nComboboxRoot.displayName = \"ComboboxRoot\";\n"],"mappings":"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"}
@@ -13,13 +13,18 @@ export interface DialogCloseDetails extends ActionDetails {
13
13
  }
14
14
  export interface DialogCloseProps extends Omit<ComponentPropsWithRef<"button">, "onClick" | "value" | "className" | "style" | "children"> {
15
15
  /**
16
- * Runs inside this part's own transition, then closes the dialog with `close(value)`. A
17
- * rejection reaches the nearest Error Boundary above the Close part and leaves the dialog open.
18
- * Without `action` the button is a native `command="request-close"` invoker (JS-free).
16
+ * Runs inside this part's own transition (`data-pending` on the button), then closes the
17
+ * dialog with `value` as `returnValue`: in library mode through the Root
18
+ * (`openChangeAction(false)` awaited, view transition if set), in JS-free mode with
19
+ * `close(value)`. A rejection reaches the nearest Error Boundary above the Close part and
20
+ * leaves the dialog open. If the dialog was dismissed and opened again while the action ran,
21
+ * the close is dropped: it belonged to the earlier open. Without `action` in JS-free mode the
22
+ * button is a native `command="request-close"` invoker.
19
23
  */
20
24
  action?: Action<[details: DialogCloseDetails]>;
21
- /** Becomes `dialog.returnValue` (and `details.returnValue` of the close notification). */
25
+ /** Becomes `dialog.returnValue` (and `details.returnValue` of `openChangeAction(false)`). */
22
26
  value?: string;
27
+ /** Native `disabled`; owned, so a `render` element cannot re-enable the button. */
23
28
  disabled?: boolean;
24
29
  className?: StateValue<string | undefined, DialogCloseState>;
25
30
  style?: StateValue<CSSProperties | undefined, DialogCloseState>;
@@ -27,14 +32,16 @@ export interface DialogCloseProps extends Omit<ComponentPropsWithRef<"button">,
27
32
  render?: RenderProp<ComponentPropsWithRef<"button">, DialogCloseState>;
28
33
  }
29
34
  /**
30
- * A close button: `<button type="button" commandfor command="request-close">`. Without `action`
31
- * the browser closes the dialog (a close request: `cancel`, then `close`), with `value` as the
32
- * `returnValue`. With `action` the click is cancelled, the action runs in this part's own
33
- * transition (`data-pending` on the button) and the dialog closes with `close(value)` once it
34
- * resolves; a rejection leaves the dialog open.
35
+ * A close button: `<button type="button" commandfor command="request-close">`. In JS-free mode
36
+ * without `action` the browser closes the dialog (a close request: `cancel`, then `close`), with
37
+ * `value` as the `returnValue`. In library mode the click is always cancelled and the close goes
38
+ * through the Root (`openChangeAction(false)` awaited, then `close(value)` in the commit, inside
39
+ * the view transition if the Popup's `transition` is set). With `action` the action runs first in
40
+ * this part's own transition (`data-pending` on the button); a rejection leaves the dialog open.
35
41
  *
36
42
  * Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `commandfor`,
37
- * `command`, `value`, `onClick` (with `action`), `data-pending`, `data-disabled`.
43
+ * `command`, `value`, `onClick` (library mode, or with `action`), `data-pending`,
44
+ * `data-disabled`.
38
45
  */
39
46
  export declare function DialogClose({ action, value, disabled, className, style, children, render, ...rest }: DialogCloseProps): ReactNode;
40
47
  export declare namespace DialogClose {
@@ -1 +1 @@
1
- {"version":3,"file":"dialog-close.d.mts","names":[],"sources":["../../src/dialog/dialog-close.tsx"],"mappings":";;;iBAWiB;;WAEN;WACA;;;iBAIM,2BAA2B;;WAEjC;;iBAGM,yBAAyB,KACxC;;;;;;EAQA,SAAS,QAAQ,SAAS;;EAE1B;EACA;EACA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,iCAAiC;;;;;;;;;;;;wBAevC,cACd,QACA,OACA,UACA,WACA,OACA,UACA,WACG,QACF,mBAAgB;yBATH"}
1
+ {"version":3,"file":"dialog-close.d.mts","names":[],"sources":["../../src/dialog/dialog-close.tsx"],"mappings":";;;iBAWiB;;WAEN;WACA;;;iBAIM,2BAA2B;;WAEjC;;iBAGM,yBAAyB,KACxC;;;;;;;;;;EAYA,SAAS,QAAQ,SAAS;;EAE1B;;EAEA;EACA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,iCAAiC;;;;;;;;;;;;;;wBAiBvC,cACd,QACA,OACA,UACA,WACA,OACA,UACA,WACG,QACF,mBAAgB;yBATH"}
@@ -8,44 +8,55 @@ import { useCallback } from "react";
8
8
  //#region src/dialog/dialog-close.tsx
9
9
  const isDev = process.env.NODE_ENV !== "production";
10
10
  /**
11
- * A close button: `<button type="button" commandfor command="request-close">`. Without `action`
12
- * the browser closes the dialog (a close request: `cancel`, then `close`), with `value` as the
13
- * `returnValue`. With `action` the click is cancelled, the action runs in this part's own
14
- * transition (`data-pending` on the button) and the dialog closes with `close(value)` once it
15
- * resolves; a rejection leaves the dialog open.
11
+ * A close button: `<button type="button" commandfor command="request-close">`. In JS-free mode
12
+ * without `action` the browser closes the dialog (a close request: `cancel`, then `close`), with
13
+ * `value` as the `returnValue`. In library mode the click is always cancelled and the close goes
14
+ * through the Root (`openChangeAction(false)` awaited, then `close(value)` in the commit, inside
15
+ * the view transition if the Popup's `transition` is set). With `action` the action runs first in
16
+ * this part's own transition (`data-pending` on the button); a rejection leaves the dialog open.
16
17
  *
17
18
  * Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `commandfor`,
18
- * `command`, `value`, `onClick` (with `action`), `data-pending`, `data-disabled`.
19
+ * `command`, `value`, `onClick` (library mode, or with `action`), `data-pending`,
20
+ * `data-disabled`.
19
21
  */
20
22
  function DialogClose({ action, value, disabled = false, className, style, children, render, ...rest }) {
21
23
  const ctx = useDialogContext("Dialog.Close");
22
24
  const { pending, run } = useActionTrigger(ctx.openGeneration);
23
25
  const { ids, dialogRef, recordCloseIntent } = ctx;
24
- const hasAction = action !== void 0;
26
+ const isLibrary = ctx.mode === "library";
27
+ const installClick = isLibrary || action !== void 0;
25
28
  const state = {
26
29
  pending,
27
30
  disabled
28
31
  };
29
32
  const devRef = useCallback((element) => {
30
- if (isDev && !hasAction && element.tagName !== "BUTTON") console.error(`Dialog.Close: rendered a <${element.tagName.toLowerCase()}> without \`action\`. Only a <button> runs \`command="request-close"\`; this element will not close the dialog.`);
33
+ if (isDev && !installClick && element.tagName !== "BUTTON") console.error(`Dialog.Close: rendered a <${element.tagName.toLowerCase()}> in JS-free mode without \`action\`. Only a <button> runs \`command="request-close"\`; this element will not close the dialog.`);
31
34
  return () => {};
32
- }, [hasAction]);
35
+ }, [installClick]);
36
+ const close = (event, returnValue) => {
37
+ if (isLibrary) {
38
+ ctx.requestClose(event, "activate", returnValue);
39
+ return;
40
+ }
41
+ const dialog = dialogRef.current;
42
+ if (dialog === null || !dialog.open) return;
43
+ recordCloseIntent({
44
+ reason: "activate",
45
+ event
46
+ });
47
+ dialog.close(returnValue);
48
+ };
33
49
  const onClick = (event) => {
34
- if (action === void 0) return;
35
50
  event.preventDefault();
36
51
  if (disabled) return;
37
52
  const native = event.nativeEvent;
38
53
  const returnValue = value ?? "";
54
+ if (action === void 0) {
55
+ close(native, returnValue);
56
+ return;
57
+ }
39
58
  const details = createActionDetails(native, { returnValue });
40
- run(() => action(details), () => {
41
- const dialog = dialogRef.current;
42
- if (dialog === null || !dialog.open) return;
43
- recordCloseIntent({
44
- reason: "activate",
45
- event: native
46
- });
47
- dialog.close(returnValue);
48
- });
59
+ run(() => action(details), () => close(native, returnValue));
49
60
  };
50
61
  const libraryProps = {
51
62
  ...rendersButton(render) && { type: "button" },
@@ -53,7 +64,7 @@ function DialogClose({ action, value, disabled = false, className, style, childr
53
64
  commandfor: ids.dialog,
54
65
  command: "request-close",
55
66
  value,
56
- ...hasAction && { onClick },
67
+ ...installClick && { onClick },
57
68
  "data-pending": dataAttr(pending),
58
69
  "data-disabled": dataAttr(disabled)
59
70
  };
@@ -1 +1 @@
1
- {"version":3,"file":"dialog-close.mjs","names":[],"sources":["../../src/dialog/dialog-close.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, MouseEvent, 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 type { DataAttributes } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogCloseState {\n /** The `action` is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\n/** Details passed to `Dialog.Close`'s `action`. */\nexport interface DialogCloseDetails extends ActionDetails {\n /** The `value` the dialog closes with (`dialog.returnValue`); `\"\"` without `value`. */\n readonly returnValue: string;\n}\n\nexport interface DialogCloseProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"value\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Runs inside this part's own transition, then closes the dialog with `close(value)`. A\n * rejection reaches the nearest Error Boundary above the Close part and leaves the dialog open.\n * Without `action` the button is a native `command=\"request-close\"` invoker (JS-free).\n */\n action?: Action<[details: DialogCloseDetails]>;\n /** Becomes `dialog.returnValue` (and `details.returnValue` of the close notification). */\n value?: string;\n disabled?: boolean;\n className?: StateValue<string | undefined, DialogCloseState>;\n style?: StateValue<CSSProperties | undefined, DialogCloseState>;\n children?: StateValue<ReactNode, DialogCloseState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, DialogCloseState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * A close button: `<button type=\"button\" commandfor command=\"request-close\">`. Without `action`\n * the browser closes the dialog (a close request: `cancel`, then `close`), with `value` as the\n * `returnValue`. With `action` the click is cancelled, the action runs in this part's own\n * transition (`data-pending` on the button) and the dialog closes with `close(value)` once it\n * resolves; a rejection leaves the dialog open.\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `commandfor`,\n * `command`, `value`, `onClick` (with `action`), `data-pending`, `data-disabled`.\n */\nexport function DialogClose({\n action,\n value,\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogCloseProps) {\n const ctx = useDialogContext(\"Dialog.Close\");\n // The open-generation guard: a close from a previous opening must not close a new one.\n const { pending, run } = useActionTrigger(ctx.openGeneration);\n const { ids, dialogRef, recordCloseIntent } = ctx;\n const hasAction = action !== undefined;\n\n const state: DialogCloseState = { pending, disabled };\n\n const devRef = useCallback(\n (element: HTMLElement) => {\n if (isDev && !hasAction && element.tagName !== \"BUTTON\") {\n console.error(\n `Dialog.Close: rendered a <${element.tagName.toLowerCase()}> without \\`action\\`. Only a ` +\n '<button> runs `command=\"request-close\"`; this element will not close the dialog.',\n );\n }\n // A cleanup, so the merged ref never calls back with `null`.\n return () => {};\n },\n [hasAction],\n );\n\n const onClick = (event: MouseEvent<HTMLElement>) => {\n if (action === undefined) return;\n // Cancelling the click cancels the command.\n event.preventDefault();\n if (disabled) return;\n const native = event.nativeEvent;\n const returnValue = value ?? \"\";\n const details = createActionDetails(native, { returnValue });\n run(\n () => action(details),\n () => {\n // Repeated clicks re-run the action; the first `close()` wins (design 3.4).\n const dialog = dialogRef.current;\n if (dialog === null || !dialog.open) return;\n recordCloseIntent({ reason: \"activate\", event: native });\n dialog.close(returnValue);\n },\n );\n };\n\n const libraryProps: ComponentPropsWithRef<\"button\"> & DataAttributes = {\n ...(rendersButton(render) && { type: \"button\" as const }),\n // Owned: a render element must not re-enable a disabled Close.\n disabled,\n commandfor: ids.dialog,\n command: \"request-close\",\n value,\n ...(hasAction && { onClick }),\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n };\n\n return useRender<ComponentPropsWithRef<\"button\">, DialogCloseState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps,\n ref: devRef,\n });\n}\n\nDialogClose.displayName = \"DialogClose\";\n"],"mappings":";;;;;;;;AA0CA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;;;;;;AAYvC,SAAgB,YAAY,EAC1B,QACA,OACA,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACgB;CACnB,MAAM,MAAM,iBAAiB,cAAc;CAE3C,MAAM,EAAE,SAAS,QAAQ,iBAAiB,IAAI,cAAc;CAC5D,MAAM,EAAE,KAAK,WAAW,sBAAsB;CAC9C,MAAM,YAAY,WAAW,KAAA;CAE7B,MAAM,QAA0B;EAAE;EAAS;CAAS;CAEpD,MAAM,SAAS,aACZ,YAAyB;EACxB,IAAI,SAAS,CAAC,aAAa,QAAQ,YAAY,UAC7C,QAAQ,MACN,6BAA6B,QAAQ,QAAQ,YAAY,EAAE,gHAE7D;EAGF,aAAa,CAAC;CAChB,GACA,CAAC,SAAS,CACZ;CAEA,MAAM,WAAW,UAAmC;EAClD,IAAI,WAAW,KAAA,GAAW;EAE1B,MAAM,eAAe;EACrB,IAAI,UAAU;EACd,MAAM,SAAS,MAAM;EACrB,MAAM,cAAc,SAAS;EAC7B,MAAM,UAAU,oBAAoB,QAAQ,EAAE,YAAY,CAAC;EAC3D,UACQ,OAAO,OAAO,SACd;GAEJ,MAAM,SAAS,UAAU;GACzB,IAAI,WAAW,QAAQ,CAAC,OAAO,MAAM;GACrC,kBAAkB;IAAE,QAAQ;IAAY,OAAO;GAAO,CAAC;GACvD,OAAO,MAAM,WAAW;EAC1B,CACF;CACF;CAEA,MAAM,eAAiE;EACrE,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;EAEvD;EACA,YAAY,IAAI;EAChB,SAAS;EACT;EACA,GAAI,aAAa,EAAE,QAAQ;EAC3B,gBAAgB,SAAS,OAAO;EAChC,iBAAiB,SAAS,QAAQ;CACpC;CAEA,OAAO,UAA6D;EAClE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;EACA,KAAK;CACP,CAAC;AACH;AAEA,YAAY,cAAc"}
1
+ {"version":3,"file":"dialog-close.mjs","names":[],"sources":["../../src/dialog/dialog-close.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, MouseEvent, 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 type { DataAttributes } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogCloseState {\n /** The `action` is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\n/** Details passed to `Dialog.Close`'s `action`. */\nexport interface DialogCloseDetails extends ActionDetails {\n /** The `value` the dialog closes with (`dialog.returnValue`); `\"\"` without `value`. */\n readonly returnValue: string;\n}\n\nexport interface DialogCloseProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"value\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Runs inside this part's own transition (`data-pending` on the button), then closes the\n * dialog with `value` as `returnValue`: in library mode through the Root\n * (`openChangeAction(false)` awaited, view transition if set), in JS-free mode with\n * `close(value)`. A rejection reaches the nearest Error Boundary above the Close part and\n * leaves the dialog open. If the dialog was dismissed and opened again while the action ran,\n * the close is dropped: it belonged to the earlier open. Without `action` in JS-free mode the\n * button is a native `command=\"request-close\"` invoker.\n */\n action?: Action<[details: DialogCloseDetails]>;\n /** Becomes `dialog.returnValue` (and `details.returnValue` of `openChangeAction(false)`). */\n value?: string;\n /** Native `disabled`; owned, so a `render` element cannot re-enable the button. */\n disabled?: boolean;\n className?: StateValue<string | undefined, DialogCloseState>;\n style?: StateValue<CSSProperties | undefined, DialogCloseState>;\n children?: StateValue<ReactNode, DialogCloseState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, DialogCloseState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * A close button: `<button type=\"button\" commandfor command=\"request-close\">`. In JS-free mode\n * without `action` the browser closes the dialog (a close request: `cancel`, then `close`), with\n * `value` as the `returnValue`. In library mode the click is always cancelled and the close goes\n * through the Root (`openChangeAction(false)` awaited, then `close(value)` in the commit, inside\n * the view transition if the Popup's `transition` is set). With `action` the action runs first in\n * this part's own transition (`data-pending` on the button); a rejection leaves the dialog open.\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `commandfor`,\n * `command`, `value`, `onClick` (library mode, or with `action`), `data-pending`,\n * `data-disabled`.\n */\nexport function DialogClose({\n action,\n value,\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogCloseProps) {\n const ctx = useDialogContext(\"Dialog.Close\");\n // The open-generation guard: a close from a previous opening must not close a new one.\n const { pending, run } = useActionTrigger(ctx.openGeneration);\n const { ids, dialogRef, recordCloseIntent } = ctx;\n const isLibrary = ctx.mode === \"library\";\n const hasAction = action !== undefined;\n const installClick = isLibrary || hasAction;\n\n const state: DialogCloseState = { pending, disabled };\n\n const devRef = useCallback(\n (element: HTMLElement) => {\n if (isDev && !installClick && element.tagName !== \"BUTTON\") {\n console.error(\n `Dialog.Close: rendered a <${element.tagName.toLowerCase()}> in JS-free mode without ` +\n '`action`. Only a <button> runs `command=\"request-close\"`; this element will not ' +\n \"close the dialog.\",\n );\n }\n // A cleanup, so the merged ref never calls back with `null`.\n return () => {};\n },\n [installClick],\n );\n\n const close = (event: Event, returnValue: string): void => {\n if (isLibrary) {\n // A library-owned close (design 2.3): awaited, then `close(returnValue)` in the commit. A\n // no-op when `false` is already intended (a repeated click, or a dismissal meanwhile).\n ctx.requestClose(event, \"activate\", returnValue);\n return;\n }\n // JS-free mode with `action`: a browser-owned close; there is no Root state to go through.\n const dialog = dialogRef.current;\n if (dialog === null || !dialog.open) return;\n recordCloseIntent({ reason: \"activate\", event });\n dialog.close(returnValue);\n };\n\n const onClick = (event: MouseEvent<HTMLElement>) => {\n // Cancelling the click cancels the command.\n event.preventDefault();\n if (disabled) return;\n const native = event.nativeEvent;\n const returnValue = value ?? \"\";\n if (action === undefined) {\n close(native, returnValue);\n return;\n }\n const details = createActionDetails(native, { returnValue });\n // Repeated clicks re-run the action; the first close wins (design 3.4). A close whose open\n // was superseded by a newer one (dismissed and re-opened meanwhile) is dropped by the hook.\n run(\n () => action(details),\n () => close(native, returnValue),\n );\n };\n\n const libraryProps: ComponentPropsWithRef<\"button\"> & DataAttributes = {\n ...(rendersButton(render) && { type: \"button\" as const }),\n // Owned: a render element must not re-enable a disabled Close.\n disabled,\n commandfor: ids.dialog,\n command: \"request-close\",\n value,\n ...(installClick && { onClick }),\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n };\n\n return useRender<ComponentPropsWithRef<\"button\">, DialogCloseState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps,\n ref: devRef,\n });\n}\n\nDialogClose.displayName = \"DialogClose\";\n"],"mappings":";;;;;;;;AA+CA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;;;;;;;;AAcvC,SAAgB,YAAY,EAC1B,QACA,OACA,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACgB;CACnB,MAAM,MAAM,iBAAiB,cAAc;CAE3C,MAAM,EAAE,SAAS,QAAQ,iBAAiB,IAAI,cAAc;CAC5D,MAAM,EAAE,KAAK,WAAW,sBAAsB;CAC9C,MAAM,YAAY,IAAI,SAAS;CAE/B,MAAM,eAAe,aADH,WAAW,KAAA;CAG7B,MAAM,QAA0B;EAAE;EAAS;CAAS;CAEpD,MAAM,SAAS,aACZ,YAAyB;EACxB,IAAI,SAAS,CAAC,gBAAgB,QAAQ,YAAY,UAChD,QAAQ,MACN,6BAA6B,QAAQ,QAAQ,YAAY,EAAE,gIAG7D;EAGF,aAAa,CAAC;CAChB,GACA,CAAC,YAAY,CACf;CAEA,MAAM,SAAS,OAAc,gBAA8B;EACzD,IAAI,WAAW;GAGb,IAAI,aAAa,OAAO,YAAY,WAAW;GAC/C;EACF;EAEA,MAAM,SAAS,UAAU;EACzB,IAAI,WAAW,QAAQ,CAAC,OAAO,MAAM;EACrC,kBAAkB;GAAE,QAAQ;GAAY;EAAM,CAAC;EAC/C,OAAO,MAAM,WAAW;CAC1B;CAEA,MAAM,WAAW,UAAmC;EAElD,MAAM,eAAe;EACrB,IAAI,UAAU;EACd,MAAM,SAAS,MAAM;EACrB,MAAM,cAAc,SAAS;EAC7B,IAAI,WAAW,KAAA,GAAW;GACxB,MAAM,QAAQ,WAAW;GACzB;EACF;EACA,MAAM,UAAU,oBAAoB,QAAQ,EAAE,YAAY,CAAC;EAG3D,UACQ,OAAO,OAAO,SACd,MAAM,QAAQ,WAAW,CACjC;CACF;CAEA,MAAM,eAAiE;EACrE,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;EAEvD;EACA,YAAY,IAAI;EAChB,SAAS;EACT;EACA,GAAI,gBAAgB,EAAE,QAAQ;EAC9B,gBAAgB,SAAS,OAAO;EAChC,iBAAiB,SAAS,QAAQ;CACpC;CAEA,OAAO,UAA6D;EAClE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;EACA,KAAK;CACP,CAAC;AACH;AAEA,YAAY,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"dialog-context.mjs","names":[],"sources":["../../src/dialog/dialog-context.ts"],"sourcesContent":["\"use client\";\nimport { createContext, use } from \"react\";\nimport type { RefObject } from \"react\";\nimport type { ChangeReason } from \"../internal/types.js\";\n\n/**\n * \"static\": JS-free mode, the browser owns open and close through Invoker Commands. \"library\":\n * open is library-owned (a transition keyed on committed state), close is browser-owned and\n * notified (design section 2).\n */\nexport type DialogMode = \"static\" | \"library\";\n\n/**\n * What caused the next `close` event, recorded just before the close happens (design 2.3). The\n * `close` event carries no source; without an intent the close is a `\"dismiss\"`.\n */\nexport interface DialogCloseIntent {\n readonly reason: ChangeReason;\n readonly event: Event | undefined;\n}\n\nexport interface DialogContextValue {\n readonly mode: DialogMode;\n /** `useId`-based ids: the `<dialog>`, its Title and its Description. */\n readonly ids: { readonly dialog: string; readonly title: string; readonly description: string };\n readonly modal: boolean;\n /** Committed open state (library mode); `false` in static mode. */\n readonly committed: boolean;\n /** Optimistic open state (library mode); `false` in static mode. */\n readonly optimistic: boolean;\n /** The Root's transition is pending (library mode). */\n readonly pending: boolean;\n /**\n * Open the dialog through the Root's transition (library mode): a Trigger click, a foreign\n * `show-modal` command, or mount reconciliation. A no-op when the user already intends `true`.\n */\n readonly requestOpen: (event: Event | undefined, reason: ChangeReason) => void;\n /**\n * The dialog closed natively: dispatch the `false` notification unless the user (or the\n * library's own echo) already intends `false`.\n */\n readonly notifyClosed: (event: Event, reason: ChangeReason, returnValue: string) => void;\n /** From `useControllableAction`: the latest in-flight intent, or `null`. */\n readonly latestInFlight: () => { readonly next: boolean } | null;\n /** The registered Trigger element, for the focus-return fallback (design 6.2). */\n readonly triggerRef: RefObject<HTMLElement | null>;\n /** The `<dialog>`, set by the Popup's ref; how Close and Form reach `close(value)`. */\n readonly dialogRef: RefObject<HTMLDialogElement | null>;\n /** Record what causes the next close. Cleared after a task if the dialog did not close. */\n readonly recordCloseIntent: (intent: DialogCloseIntent | undefined) => void;\n /** Read and clear the recorded intent (the Popup's `close` listener). */\n readonly takeCloseIntent: () => DialogCloseIntent | undefined;\n /**\n * Counts the opens of this dialog. A Close action reads it when it starts and skips its close\n * if a newer open started meanwhile (the dialog was dismissed and opened again).\n */\n readonly openGeneration: () => number;\n /** Advance `openGeneration` (an open dispatch, the `<dialog>`'s native `beforetoggle` to open). */\n readonly nextOpenGeneration: () => void;\n}\n\nexport const DialogContext = createContext<DialogContextValue | null>(null);\n\nexport function useDialogContext(part: string): DialogContextValue {\n const context = use(DialogContext);\n if (context === null) {\n throw new Error(`${part} must be rendered inside a Dialog.Root.`);\n }\n return context;\n}\n"],"mappings":";;;AA6DA,MAAa,gBAAgB,cAAyC,IAAI;AAE1E,SAAgB,iBAAiB,MAAkC;CACjE,MAAM,UAAU,IAAI,aAAa;CACjC,IAAI,YAAY,MACd,MAAM,IAAI,MAAM,GAAG,KAAK,wCAAwC;CAElE,OAAO;AACT"}
1
+ {"version":3,"file":"dialog-context.mjs","names":[],"sources":["../../src/dialog/dialog-context.ts"],"sourcesContent":["\"use client\";\nimport { createContext, use } from \"react\";\nimport type { RefObject } from \"react\";\nimport type { ChangeReason } from \"../internal/types.js\";\n\n/**\n * \"static\": JS-free mode, the browser owns open and close through Invoker Commands. \"library\":\n * opening and the closes a part asks for are library-owned (transitions keyed on committed\n * state); dismissals are browser-owned and notified (design section 2).\n */\nexport type DialogMode = \"static\" | \"library\";\n\n/**\n * What caused the next `close` event, recorded just before the close happens (design 2.3). The\n * `close` event carries no source; without an intent the close is a `\"dismiss\"`.\n */\nexport interface DialogCloseIntent {\n readonly reason: ChangeReason;\n readonly event: Event | undefined;\n}\n\nexport interface DialogContextValue {\n readonly mode: DialogMode;\n /** `useId`-based ids: the `<dialog>`, its Title and its Description. */\n readonly ids: { readonly dialog: string; readonly title: string; readonly description: string };\n readonly modal: boolean;\n /** Committed open state (library mode); `false` in static mode. */\n readonly committed: boolean;\n /** Optimistic open state (library mode); `false` in static mode. */\n readonly optimistic: boolean;\n /** The Root's transition is pending (library mode). */\n readonly pending: boolean;\n /**\n * Open the dialog through the Root's transition (library mode): a Trigger click, a foreign\n * `show-modal` command, or mount reconciliation. A no-op when the user already intends `true`.\n */\n readonly requestOpen: (event: Event | undefined, reason: ChangeReason) => void;\n /**\n * The dialog closed natively (a dismissal, not the library's own `close()`): dispatch the\n * `false` notification unless the user already intends `false`.\n */\n readonly notifyClosed: (event: Event, reason: ChangeReason, returnValue: string) => void;\n /**\n * What the user already intends: the latest in-flight dispatch's `next`, else the optimistic\n * value of the last commit. Safe to call from handlers and effects (layout effects included,\n * also in the commit that changed it), and after an `await`. Not during render.\n */\n readonly intended: () => boolean;\n /**\n * A library-owned close (library mode, design 2.3): a Close part, a `method=\"dialog\"` submit,\n * a Form's settle close, a foreign close command. Records `returnValue` for the apply effect's\n * `close()` and dispatches `false` (`openChangeAction(false)` awaited). A no-op when `false` is\n * already intended.\n */\n readonly requestClose: (\n event: Event | undefined,\n reason: ChangeReason,\n returnValue: string,\n ) => void;\n /**\n * Read and clear the `returnValue` of the pending part-initiated close, for the apply effect's\n * `close()`; `\"\"` when there is none (a controlled close).\n */\n readonly takeCloseReturnValue: () => string;\n /** From `useControllableAction`: the latest in-flight intent, or `null`. */\n readonly latestInFlight: () => { readonly next: boolean } | null;\n /** The registered Trigger element, for the focus-return fallback (design 6.2). */\n readonly triggerRef: RefObject<HTMLElement | null>;\n /** The `<dialog>`, set by the Popup's ref; how Close and Form reach `close(value)`. */\n readonly dialogRef: RefObject<HTMLDialogElement | null>;\n /** Record what causes the next close. Cleared after a task if the dialog did not close. */\n readonly recordCloseIntent: (intent: DialogCloseIntent | undefined) => void;\n /** Read and clear the recorded intent (the Popup's `close` listener). */\n readonly takeCloseIntent: () => DialogCloseIntent | undefined;\n /**\n * Counts the opens of this dialog. A Close action reads it when it starts and skips its close\n * if a newer open started meanwhile (the dialog was dismissed and opened again).\n */\n readonly openGeneration: () => number;\n /** Advance `openGeneration` (an open dispatch, the `<dialog>`'s native `beforetoggle` to open). */\n readonly nextOpenGeneration: () => void;\n}\n\nexport const DialogContext = createContext<DialogContextValue | null>(null);\n\nexport function useDialogContext(part: string): DialogContextValue {\n const context = use(DialogContext);\n if (context === null) {\n throw new Error(`${part} must be rendered inside a Dialog.Root.`);\n }\n return context;\n}\n"],"mappings":";;;AAmFA,MAAa,gBAAgB,cAAyC,IAAI;AAE1E,SAAgB,iBAAiB,MAAkC;CACjE,MAAM,UAAU,IAAI,aAAa;CACjC,IAAI,YAAY,MACd,MAAM,IAAI,MAAM,GAAG,KAAK,wCAAwC;CAElE,OAAO;AACT"}
@@ -9,9 +9,13 @@ export interface DialogFormProps extends Omit<ComponentPropsWithRef<"form">, "me
9
9
  /**
10
10
  * React form action (`(formData) => unknown`, may be a Server Function). Passed to the form
11
11
  * unwrapped so React's progressive enhancement is kept; the dialog closes with the submitter's
12
- * `value` as `returnValue` once the action's transition settles (see `closeOnSettle`). A
13
- * rejection leaves the dialog open. Without `action` the form renders `method="dialog"`:
14
- * submitting closes the dialog natively. A string `action` (a URL) is a plain navigating form.
12
+ * `value` as `returnValue` once the action's transition settles (see `closeOnSettle`): in
13
+ * library mode through the Root (`openChangeAction(false)` awaited, view transition if set),
14
+ * in JS-free mode with `close(value)`. A rejection leaves the dialog open. Without `action`
15
+ * the form renders `method="dialog"`: submitting closes the dialog, natively in JS-free mode
16
+ * and through the Root in library mode (a `preventDefault()` in your own `onSubmit` blocks
17
+ * both; `stopPropagation()` there leaves the close to the browser, notified as a dismissal).
18
+ * A string `action` (a URL) is a plain navigating form.
15
19
  */
16
20
  action?: string | ((formData: FormData) => unknown);
17
21
  /**
@@ -21,7 +25,9 @@ export interface DialogFormProps extends Omit<ComponentPropsWithRef<"form">, "me
21
25
  * Any result that does not throw counts as success, including a `useActionState`-style result
22
26
  * that reports validation errors: with the default the dialog closes and those errors are never
23
27
  * seen. Throw to keep the dialog open, or set `closeOnSettle={false}` and close it yourself:
24
- * a `Dialog.Close`, a submitter with `formMethod="dialog"`, or a controlled `open`.
28
+ * a `Dialog.Close`, a submit button in a separate `method="dialog"` form, or a controlled
29
+ * `open`. (A `formMethod="dialog"` submitter inside this form cannot close it: React prevents
30
+ * every submit of a form with a function `action` and runs the action again.)
25
31
  *
26
32
  * The dialog is not closed either when it was closed (Escape, a Close part) while the action
27
33
  * ran, also if the user re-opened it meanwhile.
@@ -39,7 +45,8 @@ export interface DialogFormProps extends Omit<ComponentPropsWithRef<"form">, "me
39
45
  }
40
46
  /**
41
47
  * A form that completes the dialog. Without `action`: `<form method="dialog">`, so a submit
42
- * closes the dialog natively (JS-free) with the submitter's `value` as `returnValue`. With a
48
+ * closes the dialog with the submitter's `value` as `returnValue` (natively in JS-free mode;
49
+ * in library mode the Popup cancels the submission and closes through the Root). With a
43
50
  * function `action`: `<form action={action}>` (React overrides the method) and a status watcher
44
51
  * that closes the dialog once the action has settled, with the submitter's `value`, unless
45
52
  * `closeOnSettle` is `false` or the dialog was closed while the action ran.
@@ -1 +1 @@
1
- {"version":3,"file":"dialog-form.d.mts","names":[],"sources":["../../src/dialog/dialog-form.tsx"],"mappings":";;;iBAQiB;;WAEN;;iBAGM,wBAAwB,KACvC;;;;;;;;EAUA,oBAAoB,UAAU;;;;;;;;;;;;;EAa9B;;;;;EAKA,SAAS;EACT,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,+BAA+B;;;;;;;;;;;;;;;wBAwCrC,aACd,QACA,eACA,QACA,WACA,OACA,UACA,WACG,QACF,kBAAe;yBATF"}
1
+ {"version":3,"file":"dialog-form.d.mts","names":[],"sources":["../../src/dialog/dialog-form.tsx"],"mappings":";;;iBAQiB;;WAEN;;iBAGM,wBAAwB,KACvC;;;;;;;;;;;;EAcA,oBAAoB,UAAU;;;;;;;;;;;;;;;EAe9B;;;;;EAKA,SAAS;EACT,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,+BAA+B;;;;;;;;;;;;;;;;wBAiBrC,aACd,QACA,eACA,QACA,WACA,OACA,UACA,WACG,QACF,kBAAe;yBATF"}
@@ -1,35 +1,14 @@
1
1
  "use client";
2
2
  import { resolveStateValue, useRender } from "../internal/use-render.mjs";
3
3
  import { useDialogContext } from "./dialog-context.mjs";
4
- import { useCallback, useEffect, useRef, useState } from "react";
4
+ import { FormStatusWatcher } from "../internal/form-status-watcher.mjs";
5
+ import { useCallback, useRef, useState } from "react";
5
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
- import { useFormStatus } from "react-dom";
7
7
  //#region src/dialog/dialog-form.tsx
8
8
  /**
9
- * Observes React's form channel (design 8.1): `useFormStatus().pending` covers the action's
10
- * whole transition, including the Suspense wait of what it re-renders. On `true → false` the
11
- * submission succeeded (a rejection unmounts the form through the nearest Error Boundary, so the
12
- * edge is never seen).
13
- */
14
- function FormStatusWatcher({ onPendingChange, onSettled }) {
15
- const { pending } = useFormStatus();
16
- const previous = useRef(false);
17
- useEffect(() => {
18
- const was = previous.current;
19
- previous.current = pending;
20
- if (was === pending) return;
21
- onPendingChange(pending);
22
- if (was && !pending) onSettled();
23
- }, [
24
- pending,
25
- onPendingChange,
26
- onSettled
27
- ]);
28
- return null;
29
- }
30
- /**
31
9
  * A form that completes the dialog. Without `action`: `<form method="dialog">`, so a submit
32
- * closes the dialog natively (JS-free) with the submitter's `value` as `returnValue`. With a
10
+ * closes the dialog with the submitter's `value` as `returnValue` (natively in JS-free mode;
11
+ * in library mode the Popup cancels the submission and closes through the Root). With a
33
12
  * function `action`: `<form action={action}>` (React overrides the method) and a status watcher
34
13
  * that closes the dialog once the action has settled, with the submitter's `value`, unless
35
14
  * `closeOnSettle` is `false` or the dialog was closed while the action ran.
@@ -41,7 +20,9 @@ function FormStatusWatcher({ onPendingChange, onSettled }) {
41
20
  * Owned keys (conventions 1.2): `method` (except with a string `action`), `action`.
42
21
  */
43
22
  function DialogForm({ action, closeOnSettle = true, method, className, style, children, render, ...rest }) {
44
- const { dialogRef, recordCloseIntent } = useDialogContext("Dialog.Form");
23
+ const ctx = useDialogContext("Dialog.Form");
24
+ const { dialogRef, recordCloseIntent, requestClose } = ctx;
25
+ const isLibrary = ctx.mode === "library";
45
26
  const [pending, setPending] = useState(false);
46
27
  const submission = useRef(null);
47
28
  const closedSinceSubmit = useRef(false);
@@ -73,15 +54,22 @@ function DialogForm({ action, closeOnSettle = true, method, className, style, ch
73
54
  submission.current = null;
74
55
  if (!closeOnSettle || closedSinceSubmit.current) return;
75
56
  if (dialog === null || !dialog.open) return;
57
+ const returnValue = submitted?.returnValue ?? "";
58
+ if (isLibrary) {
59
+ requestClose(submitted?.event, "activate", returnValue);
60
+ return;
61
+ }
76
62
  recordCloseIntent({
77
63
  reason: "activate",
78
64
  event: submitted?.event
79
65
  });
80
- dialog.close(submitted?.returnValue ?? "");
66
+ dialog.close(returnValue);
81
67
  }, [
82
68
  closeOnSettle,
83
69
  dialogRef,
84
- recordCloseIntent
70
+ isLibrary,
71
+ recordCloseIntent,
72
+ requestClose
85
73
  ]);
86
74
  return useRender({
87
75
  defaultElement: "form",
@@ -1 +1 @@
1
- {"version":3,"file":"dialog-form.mjs","names":[],"sources":["../../src/dialog/dialog-form.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { useFormStatus } from \"react-dom\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogFormState {\n /** `useFormStatus().pending` of this form, exposed for `className` / `children` functions. */\n readonly pending: boolean;\n}\n\nexport interface DialogFormProps extends Omit<\n ComponentPropsWithRef<\"form\">,\n \"method\" | \"action\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * React form action (`(formData) => unknown`, may be a Server Function). Passed to the form\n * unwrapped so React's progressive enhancement is kept; the dialog closes with the submitter's\n * `value` as `returnValue` once the action's transition settles (see `closeOnSettle`). A\n * rejection leaves the dialog open. Without `action` the form renders `method=\"dialog\"`:\n * submitting closes the dialog natively. A string `action` (a URL) is a plain navigating form.\n */\n action?: string | ((formData: FormData) => unknown);\n /**\n * With a function `action`: close the dialog once the action's transition settles without\n * throwing. Default `true`.\n *\n * Any result that does not throw counts as success, including a `useActionState`-style result\n * that reports validation errors: with the default the dialog closes and those errors are never\n * seen. Throw to keep the dialog open, or set `closeOnSettle={false}` and close it yourself:\n * a `Dialog.Close`, a submitter with `formMethod=\"dialog\"`, or a controlled `open`.\n *\n * The dialog is not closed either when it was closed (Escape, a Close part) while the action\n * ran, also if the user re-opened it meanwhile.\n */\n closeOnSettle?: boolean;\n /**\n * Only with a string `action` (a navigating form, for example `\"post\"`). Owned otherwise:\n * `\"dialog\"` without `action`, React's own with a function `action`.\n */\n method?: ComponentPropsWithRef<\"form\">[\"method\"];\n className?: StateValue<string | undefined, DialogFormState>;\n style?: StateValue<CSSProperties | undefined, DialogFormState>;\n children?: StateValue<ReactNode, DialogFormState>;\n render?: RenderProp<ComponentPropsWithRef<\"form\">, DialogFormState>;\n}\n\ninterface FormStatusWatcherProps {\n readonly onPendingChange: (pending: boolean) => void;\n readonly onSettled: () => void;\n}\n\n/**\n * Observes React's form channel (design 8.1): `useFormStatus().pending` covers the action's\n * whole transition, including the Suspense wait of what it re-renders. On `true → false` the\n * submission succeeded (a rejection unmounts the form through the nearest Error Boundary, so the\n * edge is never seen).\n */\nfunction FormStatusWatcher({ onPendingChange, onSettled }: FormStatusWatcherProps) {\n const { pending } = useFormStatus();\n const previous = useRef(false);\n useEffect(() => {\n const was = previous.current;\n previous.current = pending;\n if (was === pending) return;\n onPendingChange(pending);\n if (was && !pending) onSettled();\n }, [pending, onPendingChange, onSettled]);\n return null;\n}\n\n/**\n * A form that completes the dialog. Without `action`: `<form method=\"dialog\">`, so a submit\n * closes the dialog natively (JS-free) with the submitter's `value` as `returnValue`. With a\n * function `action`: `<form action={action}>` (React overrides the method) and a status watcher\n * that closes the dialog once the action has settled, with the submitter's `value`, unless\n * `closeOnSettle` is `false` or the dialog was closed while the action ran.\n *\n * `DialogFormState.pending` is this form's own status; it is not emitted as `data-pending` and\n * is not folded into any control's `pending` (conventions 2.7). Submit buttons read\n * `useFormStatus()` themselves.\n *\n * Owned keys (conventions 1.2): `method` (except with a string `action`), `action`.\n */\nexport function DialogForm({\n action,\n closeOnSettle = true,\n method,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogFormProps) {\n const ctx = useDialogContext(\"Dialog.Form\");\n const { dialogRef, recordCloseIntent } = ctx;\n const [pending, setPending] = useState(false);\n const submission = useRef<{ event: Event; returnValue: string } | null>(null);\n // The dialog closed after the last submit (Escape, a Close part): the settle must not close it\n // again, also when the user re-opened it meanwhile (the review's walkthrough 11.8 variant).\n const closedSinceSubmit = useRef(false);\n const watches = typeof action === \"function\";\n\n // The submitter's `value` comes from the native `submit` event (capture, before React's\n // handler runs the action).\n const libraryRef = useCallback((form: HTMLFormElement) => {\n const onSubmit = (event: Event) => {\n if (event.target !== form) return;\n const submitter = (event as SubmitEvent).submitter as\n | HTMLButtonElement\n | HTMLInputElement\n | null;\n submission.current = { event, returnValue: submitter?.value ?? \"\" };\n closedSinceSubmit.current = false;\n };\n const dialog = form.closest(\"dialog\");\n const onClose = (event: Event) => {\n if (event.target === dialog) closedSinceSubmit.current = true;\n };\n form.addEventListener(\"submit\", onSubmit, true);\n dialog?.addEventListener(\"close\", onClose);\n return () => {\n form.removeEventListener(\"submit\", onSubmit, true);\n dialog?.removeEventListener(\"close\", onClose);\n };\n }, []);\n\n const onSettled = useCallback(() => {\n const dialog = dialogRef.current;\n const submitted = submission.current;\n submission.current = null;\n if (!closeOnSettle || closedSinceSubmit.current) return;\n if (dialog === null || !dialog.open) return;\n recordCloseIntent({ reason: \"activate\", event: submitted?.event });\n dialog.close(submitted?.returnValue ?? \"\");\n }, [closeOnSettle, dialogRef, recordCloseIntent]);\n\n const state: DialogFormState = { pending };\n\n return useRender<ComponentPropsWithRef<\"form\">, DialogFormState>({\n defaultElement: \"form\",\n render,\n state,\n className,\n style,\n children: (s) => (\n <>\n {resolveStateValue(children, s)}\n {watches && <FormStatusWatcher onPendingChange={setPending} onSettled={onSettled} />}\n </>\n ),\n props: rest,\n libraryProps: {\n method: action === undefined ? \"dialog\" : typeof action === \"string\" ? method : undefined,\n // React's own type wants `void | Promise<void>`; the library's actions return `unknown`\n // (conventions 2.2) and React ignores the result.\n action: action as ComponentPropsWithRef<\"form\">[\"action\"],\n },\n ref: libraryRef,\n });\n}\n\nDialogForm.displayName = \"DialogForm\";\n"],"mappings":";;;;;;;;;;;;;AA4DA,SAAS,kBAAkB,EAAE,iBAAiB,aAAqC;CACjF,MAAM,EAAE,YAAY,cAAc;CAClC,MAAM,WAAW,OAAO,KAAK;CAC7B,gBAAgB;EACd,MAAM,MAAM,SAAS;EACrB,SAAS,UAAU;EACnB,IAAI,QAAQ,SAAS;EACrB,gBAAgB,OAAO;EACvB,IAAI,OAAO,CAAC,SAAS,UAAU;CACjC,GAAG;EAAC;EAAS;EAAiB;CAAS,CAAC;CACxC,OAAO;AACT;;;;;;;;;;;;;;AAeA,SAAgB,WAAW,EACzB,QACA,gBAAgB,MAChB,QACA,WACA,OACA,UACA,QACA,GAAG,QACe;CAElB,MAAM,EAAE,WAAW,sBADP,iBAAiB,aACc;CAC3C,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,MAAM,aAAa,OAAqD,IAAI;CAG5E,MAAM,oBAAoB,OAAO,KAAK;CACtC,MAAM,UAAU,OAAO,WAAW;CAIlC,MAAM,aAAa,aAAa,SAA0B;EACxD,MAAM,YAAY,UAAiB;GACjC,IAAI,MAAM,WAAW,MAAM;GAC3B,MAAM,YAAa,MAAsB;GAIzC,WAAW,UAAU;IAAE;IAAO,aAAa,WAAW,SAAS;GAAG;GAClE,kBAAkB,UAAU;EAC9B;EACA,MAAM,SAAS,KAAK,QAAQ,QAAQ;EACpC,MAAM,WAAW,UAAiB;GAChC,IAAI,MAAM,WAAW,QAAQ,kBAAkB,UAAU;EAC3D;EACA,KAAK,iBAAiB,UAAU,UAAU,IAAI;EAC9C,QAAQ,iBAAiB,SAAS,OAAO;EACzC,aAAa;GACX,KAAK,oBAAoB,UAAU,UAAU,IAAI;GACjD,QAAQ,oBAAoB,SAAS,OAAO;EAC9C;CACF,GAAG,CAAC,CAAC;CAEL,MAAM,YAAY,kBAAkB;EAClC,MAAM,SAAS,UAAU;EACzB,MAAM,YAAY,WAAW;EAC7B,WAAW,UAAU;EACrB,IAAI,CAAC,iBAAiB,kBAAkB,SAAS;EACjD,IAAI,WAAW,QAAQ,CAAC,OAAO,MAAM;EACrC,kBAAkB;GAAE,QAAQ;GAAY,OAAO,WAAW;EAAM,CAAC;EACjE,OAAO,MAAM,WAAW,eAAe,EAAE;CAC3C,GAAG;EAAC;EAAe;EAAW;CAAiB,CAAC;CAIhD,OAAO,UAA0D;EAC/D,gBAAgB;EAChB;EACA,OAAA,EAL+B,QAK3B;EACJ;EACA;EACA,WAAW,MACT,qBAAA,UAAA,EAAA,UAAA,CACG,kBAAkB,UAAU,CAAC,GAC7B,WAAW,oBAAC,mBAAD;GAAmB,iBAAiB;GAAuB;EAAY,CAAA,CACnF,EAAA,CAAA;EAEJ,OAAO;EACP,cAAc;GACZ,QAAQ,WAAW,KAAA,IAAY,WAAW,OAAO,WAAW,WAAW,SAAS,KAAA;GAGxE;EACV;EACA,KAAK;CACP,CAAC;AACH;AAEA,WAAW,cAAc"}
1
+ {"version":3,"file":"dialog-form.mjs","names":[],"sources":["../../src/dialog/dialog-form.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useRef, useState } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { FormStatusWatcher } from \"../internal/form-status-watcher.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogFormState {\n /** `useFormStatus().pending` of this form, exposed for `className` / `children` functions. */\n readonly pending: boolean;\n}\n\nexport interface DialogFormProps extends Omit<\n ComponentPropsWithRef<\"form\">,\n \"method\" | \"action\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * React form action (`(formData) => unknown`, may be a Server Function). Passed to the form\n * unwrapped so React's progressive enhancement is kept; the dialog closes with the submitter's\n * `value` as `returnValue` once the action's transition settles (see `closeOnSettle`): in\n * library mode through the Root (`openChangeAction(false)` awaited, view transition if set),\n * in JS-free mode with `close(value)`. A rejection leaves the dialog open. Without `action`\n * the form renders `method=\"dialog\"`: submitting closes the dialog, natively in JS-free mode\n * and through the Root in library mode (a `preventDefault()` in your own `onSubmit` blocks\n * both; `stopPropagation()` there leaves the close to the browser, notified as a dismissal).\n * A string `action` (a URL) is a plain navigating form.\n */\n action?: string | ((formData: FormData) => unknown);\n /**\n * With a function `action`: close the dialog once the action's transition settles without\n * throwing. Default `true`.\n *\n * Any result that does not throw counts as success, including a `useActionState`-style result\n * that reports validation errors: with the default the dialog closes and those errors are never\n * seen. Throw to keep the dialog open, or set `closeOnSettle={false}` and close it yourself:\n * a `Dialog.Close`, a submit button in a separate `method=\"dialog\"` form, or a controlled\n * `open`. (A `formMethod=\"dialog\"` submitter inside this form cannot close it: React prevents\n * every submit of a form with a function `action` and runs the action again.)\n *\n * The dialog is not closed either when it was closed (Escape, a Close part) while the action\n * ran, also if the user re-opened it meanwhile.\n */\n closeOnSettle?: boolean;\n /**\n * Only with a string `action` (a navigating form, for example `\"post\"`). Owned otherwise:\n * `\"dialog\"` without `action`, React's own with a function `action`.\n */\n method?: ComponentPropsWithRef<\"form\">[\"method\"];\n className?: StateValue<string | undefined, DialogFormState>;\n style?: StateValue<CSSProperties | undefined, DialogFormState>;\n children?: StateValue<ReactNode, DialogFormState>;\n render?: RenderProp<ComponentPropsWithRef<\"form\">, DialogFormState>;\n}\n\n/**\n * A form that completes the dialog. Without `action`: `<form method=\"dialog\">`, so a submit\n * closes the dialog with the submitter's `value` as `returnValue` (natively in JS-free mode;\n * in library mode the Popup cancels the submission and closes through the Root). With a\n * function `action`: `<form action={action}>` (React overrides the method) and a status watcher\n * that closes the dialog once the action has settled, with the submitter's `value`, unless\n * `closeOnSettle` is `false` or the dialog was closed while the action ran.\n *\n * `DialogFormState.pending` is this form's own status; it is not emitted as `data-pending` and\n * is not folded into any control's `pending` (conventions 2.7). Submit buttons read\n * `useFormStatus()` themselves.\n *\n * Owned keys (conventions 1.2): `method` (except with a string `action`), `action`.\n */\nexport function DialogForm({\n action,\n closeOnSettle = true,\n method,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogFormProps) {\n const ctx = useDialogContext(\"Dialog.Form\");\n const { dialogRef, recordCloseIntent, requestClose } = ctx;\n const isLibrary = ctx.mode === \"library\";\n const [pending, setPending] = useState(false);\n const submission = useRef<{ event: Event; returnValue: string } | null>(null);\n // The dialog closed after the last submit (Escape, a Close part): the settle must not close it\n // again, also when the user re-opened it meanwhile (the review's walkthrough 11.8 variant).\n const closedSinceSubmit = useRef(false);\n const watches = typeof action === \"function\";\n\n // The submitter's `value` comes from the native `submit` event (capture, before React's\n // handler runs the action).\n const libraryRef = useCallback((form: HTMLFormElement) => {\n const onSubmit = (event: Event) => {\n if (event.target !== form) return;\n const submitter = (event as SubmitEvent).submitter as\n | HTMLButtonElement\n | HTMLInputElement\n | null;\n submission.current = { event, returnValue: submitter?.value ?? \"\" };\n closedSinceSubmit.current = false;\n };\n const dialog = form.closest(\"dialog\");\n const onClose = (event: Event) => {\n if (event.target === dialog) closedSinceSubmit.current = true;\n };\n form.addEventListener(\"submit\", onSubmit, true);\n dialog?.addEventListener(\"close\", onClose);\n return () => {\n form.removeEventListener(\"submit\", onSubmit, true);\n dialog?.removeEventListener(\"close\", onClose);\n };\n }, []);\n\n const onSettled = useCallback(() => {\n const dialog = dialogRef.current;\n const submitted = submission.current;\n submission.current = null;\n if (!closeOnSettle || closedSinceSubmit.current) return;\n if (dialog === null || !dialog.open) return;\n const returnValue = submitted?.returnValue ?? \"\";\n if (isLibrary) {\n // A library-owned close (design 2.3): `openChangeAction(false)` awaited, then\n // `close(returnValue)` in the commit, inside the view transition if set.\n requestClose(submitted?.event, \"activate\", returnValue);\n return;\n }\n recordCloseIntent({ reason: \"activate\", event: submitted?.event });\n dialog.close(returnValue);\n }, [closeOnSettle, dialogRef, isLibrary, recordCloseIntent, requestClose]);\n\n const state: DialogFormState = { pending };\n\n return useRender<ComponentPropsWithRef<\"form\">, DialogFormState>({\n defaultElement: \"form\",\n render,\n state,\n className,\n style,\n children: (s) => (\n <>\n {resolveStateValue(children, s)}\n {watches && <FormStatusWatcher onPendingChange={setPending} onSettled={onSettled} />}\n </>\n ),\n props: rest,\n libraryProps: {\n method: action === undefined ? \"dialog\" : typeof action === \"string\" ? method : undefined,\n // React's own type wants `void | Promise<void>`; the library's actions return `unknown`\n // (conventions 2.2) and React ignores the result.\n action: action as ComponentPropsWithRef<\"form\">[\"action\"],\n },\n ref: libraryRef,\n });\n}\n\nDialogForm.displayName = \"DialogForm\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAqEA,SAAgB,WAAW,EACzB,QACA,gBAAgB,MAChB,QACA,WACA,OACA,UACA,QACA,GAAG,QACe;CAClB,MAAM,MAAM,iBAAiB,aAAa;CAC1C,MAAM,EAAE,WAAW,mBAAmB,iBAAiB;CACvD,MAAM,YAAY,IAAI,SAAS;CAC/B,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,MAAM,aAAa,OAAqD,IAAI;CAG5E,MAAM,oBAAoB,OAAO,KAAK;CACtC,MAAM,UAAU,OAAO,WAAW;CAIlC,MAAM,aAAa,aAAa,SAA0B;EACxD,MAAM,YAAY,UAAiB;GACjC,IAAI,MAAM,WAAW,MAAM;GAC3B,MAAM,YAAa,MAAsB;GAIzC,WAAW,UAAU;IAAE;IAAO,aAAa,WAAW,SAAS;GAAG;GAClE,kBAAkB,UAAU;EAC9B;EACA,MAAM,SAAS,KAAK,QAAQ,QAAQ;EACpC,MAAM,WAAW,UAAiB;GAChC,IAAI,MAAM,WAAW,QAAQ,kBAAkB,UAAU;EAC3D;EACA,KAAK,iBAAiB,UAAU,UAAU,IAAI;EAC9C,QAAQ,iBAAiB,SAAS,OAAO;EACzC,aAAa;GACX,KAAK,oBAAoB,UAAU,UAAU,IAAI;GACjD,QAAQ,oBAAoB,SAAS,OAAO;EAC9C;CACF,GAAG,CAAC,CAAC;CAEL,MAAM,YAAY,kBAAkB;EAClC,MAAM,SAAS,UAAU;EACzB,MAAM,YAAY,WAAW;EAC7B,WAAW,UAAU;EACrB,IAAI,CAAC,iBAAiB,kBAAkB,SAAS;EACjD,IAAI,WAAW,QAAQ,CAAC,OAAO,MAAM;EACrC,MAAM,cAAc,WAAW,eAAe;EAC9C,IAAI,WAAW;GAGb,aAAa,WAAW,OAAO,YAAY,WAAW;GACtD;EACF;EACA,kBAAkB;GAAE,QAAQ;GAAY,OAAO,WAAW;EAAM,CAAC;EACjE,OAAO,MAAM,WAAW;CAC1B,GAAG;EAAC;EAAe;EAAW;EAAW;EAAmB;CAAY,CAAC;CAIzE,OAAO,UAA0D;EAC/D,gBAAgB;EAChB;EACA,OAAA,EAL+B,QAK3B;EACJ;EACA;EACA,WAAW,MACT,qBAAA,UAAA,EAAA,UAAA,CACG,kBAAkB,UAAU,CAAC,GAC7B,WAAW,oBAAC,mBAAD;GAAmB,iBAAiB;GAAuB;EAAY,CAAA,CACnF,EAAA,CAAA;EAEJ,OAAO;EACP,cAAc;GACZ,QAAQ,WAAW,KAAA,IAAY,WAAW,OAAO,WAAW,WAAW,SAAS,KAAA;GAGxE;EACV;EACA,KAAK;CACP,CAAC;AACH;AAEA,WAAW,cAAc"}