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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/dist/accordion/accordion-root.mjs +2 -1
  2. package/dist/accordion/accordion-root.mjs.map +1 -1
  3. package/dist/combobox/combobox-clear.mjs +2 -1
  4. package/dist/combobox/combobox-clear.mjs.map +1 -1
  5. package/dist/combobox/combobox-list.mjs +2 -1
  6. package/dist/combobox/combobox-list.mjs.map +1 -1
  7. package/dist/combobox/combobox-popup.d.mts.map +1 -1
  8. package/dist/combobox/combobox-popup.mjs +7 -2
  9. package/dist/combobox/combobox-popup.mjs.map +1 -1
  10. package/dist/combobox/combobox-root.mjs +1 -1
  11. package/dist/combobox/combobox-trigger.mjs +2 -1
  12. package/dist/combobox/combobox-trigger.mjs.map +1 -1
  13. package/dist/dialog/dialog-close.mjs +2 -1
  14. package/dist/dialog/dialog-close.mjs.map +1 -1
  15. package/dist/dialog/dialog-form.d.mts +14 -8
  16. package/dist/dialog/dialog-form.d.mts.map +1 -1
  17. package/dist/dialog/dialog-form.mjs +36 -5
  18. package/dist/dialog/dialog-form.mjs.map +1 -1
  19. package/dist/dialog/dialog-popup.d.mts.map +1 -1
  20. package/dist/dialog/dialog-popup.mjs +13 -5
  21. package/dist/dialog/dialog-popup.mjs.map +1 -1
  22. package/dist/dialog/dialog-root.mjs +1 -1
  23. package/dist/dialog/dialog-trigger.mjs +2 -1
  24. package/dist/dialog/dialog-trigger.mjs.map +1 -1
  25. package/dist/field/field-root.mjs +5 -3
  26. package/dist/field/field-root.mjs.map +1 -1
  27. package/dist/form/form-root.mjs +2 -1
  28. package/dist/form/form-root.mjs.map +1 -1
  29. package/dist/form/form-submit.mjs +2 -1
  30. package/dist/form/form-submit.mjs.map +1 -1
  31. package/dist/index.d.mts +27 -1
  32. package/dist/index.mjs +6 -1
  33. package/dist/internal/announce.mjs +53 -0
  34. package/dist/internal/announce.mjs.map +1 -0
  35. package/dist/internal/decimal.mjs +27 -0
  36. package/dist/internal/decimal.mjs.map +1 -0
  37. package/dist/internal/dev-warn.mjs +17 -0
  38. package/dist/internal/dev-warn.mjs.map +1 -0
  39. package/dist/internal/field-context.mjs.map +1 -1
  40. package/dist/internal/range-value.mjs +51 -0
  41. package/dist/internal/range-value.mjs.map +1 -0
  42. package/dist/internal/render-element.mjs +54 -0
  43. package/dist/internal/render-element.mjs.map +1 -0
  44. package/dist/internal/toolbar-context.mjs +17 -0
  45. package/dist/internal/toolbar-context.mjs.map +1 -0
  46. package/dist/internal/use-exit-animation.mjs +26 -6
  47. package/dist/internal/use-exit-animation.mjs.map +1 -1
  48. package/dist/internal/use-field-control.mjs +5 -3
  49. package/dist/internal/use-field-control.mjs.map +1 -1
  50. package/dist/internal/use-popover-popup.mjs +3 -2
  51. package/dist/internal/use-popover-popup.mjs.map +1 -1
  52. package/dist/internal/use-popover-trigger.mjs +1 -1
  53. package/dist/internal/use-render.mjs +11 -34
  54. package/dist/internal/use-render.mjs.map +1 -1
  55. package/dist/internal/use-roving-focus.mjs +301 -0
  56. package/dist/internal/use-roving-focus.mjs.map +1 -0
  57. package/dist/menu/menu-checkbox-item.mjs +2 -1
  58. package/dist/menu/menu-checkbox-item.mjs.map +1 -1
  59. package/dist/menu/menu-item.mjs +2 -1
  60. package/dist/menu/menu-item.mjs.map +1 -1
  61. package/dist/menu/menu-popup.d.mts.map +1 -1
  62. package/dist/menu/menu-popup.mjs +7 -2
  63. package/dist/menu/menu-popup.mjs.map +1 -1
  64. package/dist/menu/menu-radio-item.mjs +2 -1
  65. package/dist/menu/menu-radio-item.mjs.map +1 -1
  66. package/dist/menu/menu-root.mjs +1 -1
  67. package/dist/menu/menu-trigger.d.mts +2 -1
  68. package/dist/menu/menu-trigger.d.mts.map +1 -1
  69. package/dist/menu/menu-trigger.mjs +15 -4
  70. package/dist/menu/menu-trigger.mjs.map +1 -1
  71. package/dist/meter/index.d.mts +24 -0
  72. package/dist/meter/index.d.mts.map +1 -0
  73. package/dist/meter/index.mjs +23 -0
  74. package/dist/meter/index.mjs.map +1 -0
  75. package/dist/meter/meter-context.mjs +13 -0
  76. package/dist/meter/meter-context.mjs.map +1 -0
  77. package/dist/meter/meter-indicator.d.mts +19 -0
  78. package/dist/meter/meter-indicator.d.mts.map +1 -0
  79. package/dist/meter/meter-indicator.mjs +25 -0
  80. package/dist/meter/meter-indicator.mjs.map +1 -0
  81. package/dist/meter/meter-label.d.mts +19 -0
  82. package/dist/meter/meter-label.d.mts.map +1 -0
  83. package/dist/meter/meter-label.mjs +28 -0
  84. package/dist/meter/meter-label.mjs.map +1 -0
  85. package/dist/meter/meter-region.d.mts +4 -0
  86. package/dist/meter/meter-region.d.mts.map +1 -0
  87. package/dist/meter/meter-region.mjs +39 -0
  88. package/dist/meter/meter-region.mjs.map +1 -0
  89. package/dist/meter/meter-root.d.mts +71 -0
  90. package/dist/meter/meter-root.d.mts.map +1 -0
  91. package/dist/meter/meter-root.mjs +83 -0
  92. package/dist/meter/meter-root.mjs.map +1 -0
  93. package/dist/meter/meter-track.d.mts +19 -0
  94. package/dist/meter/meter-track.d.mts.map +1 -0
  95. package/dist/meter/meter-track.mjs +25 -0
  96. package/dist/meter/meter-track.mjs.map +1 -0
  97. package/dist/meter/meter-value.d.mts +20 -0
  98. package/dist/meter/meter-value.d.mts.map +1 -0
  99. package/dist/meter/meter-value.mjs +29 -0
  100. package/dist/meter/meter-value.mjs.map +1 -0
  101. package/dist/number-field/index.d.mts +26 -0
  102. package/dist/number-field/index.d.mts.map +1 -0
  103. package/dist/number-field/index.mjs +26 -0
  104. package/dist/number-field/index.mjs.map +1 -0
  105. package/dist/number-field/number-field-arithmetic.mjs +78 -0
  106. package/dist/number-field/number-field-arithmetic.mjs.map +1 -0
  107. package/dist/number-field/number-field-context.d.mts +2 -0
  108. package/dist/number-field/number-field-context.mjs +13 -0
  109. package/dist/number-field/number-field-context.mjs.map +1 -0
  110. package/dist/number-field/number-field-decrement.d.mts +16 -0
  111. package/dist/number-field/number-field-decrement.d.mts.map +1 -0
  112. package/dist/number-field/number-field-decrement.mjs +19 -0
  113. package/dist/number-field/number-field-decrement.mjs.map +1 -0
  114. package/dist/number-field/number-field-format.mjs +279 -0
  115. package/dist/number-field/number-field-format.mjs.map +1 -0
  116. package/dist/number-field/number-field-increment.d.mts +32 -0
  117. package/dist/number-field/number-field-increment.d.mts.map +1 -0
  118. package/dist/number-field/number-field-increment.mjs +68 -0
  119. package/dist/number-field/number-field-increment.mjs.map +1 -0
  120. package/dist/number-field/number-field-input.d.mts +29 -0
  121. package/dist/number-field/number-field-input.d.mts.map +1 -0
  122. package/dist/number-field/number-field-input.mjs +163 -0
  123. package/dist/number-field/number-field-input.mjs.map +1 -0
  124. package/dist/number-field/number-field-label.d.mts +23 -0
  125. package/dist/number-field/number-field-label.d.mts.map +1 -0
  126. package/dist/number-field/number-field-label.mjs +47 -0
  127. package/dist/number-field/number-field-label.mjs.map +1 -0
  128. package/dist/number-field/number-field-root.d.mts +108 -0
  129. package/dist/number-field/number-field-root.d.mts.map +1 -0
  130. package/dist/number-field/number-field-root.mjs +523 -0
  131. package/dist/number-field/number-field-root.mjs.map +1 -0
  132. package/dist/number-field/use-step-button.mjs +107 -0
  133. package/dist/number-field/use-step-button.mjs.map +1 -0
  134. package/dist/popover/popover-close.mjs +2 -1
  135. package/dist/popover/popover-close.mjs.map +1 -1
  136. package/dist/popover/popover-popup.d.mts.map +1 -1
  137. package/dist/popover/popover-popup.mjs +7 -2
  138. package/dist/popover/popover-popup.mjs.map +1 -1
  139. package/dist/popover/popover-root.mjs +1 -1
  140. package/dist/progress/index.d.mts +24 -0
  141. package/dist/progress/index.d.mts.map +1 -0
  142. package/dist/progress/index.mjs +24 -0
  143. package/dist/progress/index.mjs.map +1 -0
  144. package/dist/progress/progress-context.mjs +13 -0
  145. package/dist/progress/progress-context.mjs.map +1 -0
  146. package/dist/progress/progress-indicator.d.mts +19 -0
  147. package/dist/progress/progress-indicator.d.mts.map +1 -0
  148. package/dist/progress/progress-indicator.mjs +29 -0
  149. package/dist/progress/progress-indicator.mjs.map +1 -0
  150. package/dist/progress/progress-label.d.mts +19 -0
  151. package/dist/progress/progress-label.d.mts.map +1 -0
  152. package/dist/progress/progress-label.mjs +30 -0
  153. package/dist/progress/progress-label.mjs.map +1 -0
  154. package/dist/progress/progress-root.d.mts +68 -0
  155. package/dist/progress/progress-root.d.mts.map +1 -0
  156. package/dist/progress/progress-root.mjs +80 -0
  157. package/dist/progress/progress-root.mjs.map +1 -0
  158. package/dist/progress/progress-track.d.mts +19 -0
  159. package/dist/progress/progress-track.d.mts.map +1 -0
  160. package/dist/progress/progress-track.mjs +29 -0
  161. package/dist/progress/progress-track.mjs.map +1 -0
  162. package/dist/progress/progress-value.d.mts +20 -0
  163. package/dist/progress/progress-value.d.mts.map +1 -0
  164. package/dist/progress/progress-value.mjs +33 -0
  165. package/dist/progress/progress-value.mjs.map +1 -0
  166. package/dist/slider/slider-arithmetic.mjs +1 -17
  167. package/dist/slider/slider-arithmetic.mjs.map +1 -1
  168. package/dist/slider/slider-root.mjs +3 -2
  169. package/dist/slider/slider-root.mjs.map +1 -1
  170. package/dist/slider/slider-thumb.mjs +2 -1
  171. package/dist/slider/slider-thumb.mjs.map +1 -1
  172. package/dist/slider/slider-track.mjs +2 -1
  173. package/dist/slider/slider-track.mjs.map +1 -1
  174. package/dist/tabs/tabs-indicator.mjs +2 -1
  175. package/dist/tabs/tabs-indicator.mjs.map +1 -1
  176. package/dist/tabs/tabs-tab.mjs +2 -1
  177. package/dist/tabs/tabs-tab.mjs.map +1 -1
  178. package/dist/toast/toast-action.mjs +2 -1
  179. package/dist/toast/toast-action.mjs.map +1 -1
  180. package/dist/toast/toast-close.mjs +2 -1
  181. package/dist/toast/toast-close.mjs.map +1 -1
  182. package/dist/toast/toast-root.mjs +3 -2
  183. package/dist/toast/toast-root.mjs.map +1 -1
  184. package/dist/toast/toast-viewport.d.mts.map +1 -1
  185. package/dist/toast/toast-viewport.mjs +30 -7
  186. package/dist/toast/toast-viewport.mjs.map +1 -1
  187. package/dist/toast/toast.d.mts.map +1 -1
  188. package/dist/toast/toast.mjs +5 -1
  189. package/dist/toast/toast.mjs.map +1 -1
  190. package/dist/toggle/toggle.mjs +2 -1
  191. package/dist/toggle/toggle.mjs.map +1 -1
  192. package/dist/toggle-group/index.d.mts +16 -0
  193. package/dist/toggle-group/index.d.mts.map +1 -0
  194. package/dist/toggle-group/index.mjs +15 -0
  195. package/dist/toggle-group/index.mjs.map +1 -0
  196. package/dist/toggle-group/toggle-group-context.d.mts +4 -0
  197. package/dist/toggle-group/toggle-group-context.d.mts.map +1 -0
  198. package/dist/toggle-group/toggle-group-context.mjs +13 -0
  199. package/dist/toggle-group/toggle-group-context.mjs.map +1 -0
  200. package/dist/toggle-group/toggle-group-item.d.mts +47 -0
  201. package/dist/toggle-group/toggle-group-item.d.mts.map +1 -0
  202. package/dist/toggle-group/toggle-group-item.mjs +63 -0
  203. package/dist/toggle-group/toggle-group-item.mjs.map +1 -0
  204. package/dist/toggle-group/toggle-group-root.d.mts +95 -0
  205. package/dist/toggle-group/toggle-group-root.d.mts.map +1 -0
  206. package/dist/toggle-group/toggle-group-root.mjs +211 -0
  207. package/dist/toggle-group/toggle-group-root.mjs.map +1 -0
  208. package/dist/toolbar/index.d.mts +18 -0
  209. package/dist/toolbar/index.d.mts.map +1 -0
  210. package/dist/toolbar/index.mjs +18 -0
  211. package/dist/toolbar/index.mjs.map +1 -0
  212. package/dist/toolbar/toolbar-button.d.mts +42 -0
  213. package/dist/toolbar/toolbar-button.d.mts.map +1 -0
  214. package/dist/toolbar/toolbar-button.mjs +50 -0
  215. package/dist/toolbar/toolbar-button.mjs.map +1 -0
  216. package/dist/toolbar/toolbar-root.d.mts +38 -0
  217. package/dist/toolbar/toolbar-root.d.mts.map +1 -0
  218. package/dist/toolbar/toolbar-root.mjs +66 -0
  219. package/dist/toolbar/toolbar-root.mjs.map +1 -0
  220. package/dist/toolbar/toolbar-separator.d.mts +25 -0
  221. package/dist/toolbar/toolbar-separator.d.mts.map +1 -0
  222. package/dist/toolbar/toolbar-separator.mjs +31 -0
  223. package/dist/toolbar/toolbar-separator.mjs.map +1 -0
  224. package/dist/tooltip/tooltip-popup.mjs +2 -1
  225. package/dist/tooltip/tooltip-popup.mjs.map +1 -1
  226. package/dist/tooltip/tooltip-root.mjs +18 -2
  227. package/dist/tooltip/tooltip-root.mjs.map +1 -1
  228. package/dist/tooltip/tooltip-trigger.mjs +2 -1
  229. package/dist/tooltip/tooltip-trigger.mjs.map +1 -1
  230. package/package.json +6 -1
@@ -4,7 +4,8 @@ import { dataAttr } from "../internal/data-attr.mjs";
4
4
  import { transitionTypes } from "../internal/transition-types.mjs";
5
5
  import { useLatest } from "../internal/use-latest.mjs";
6
6
  import { useControllableAction } from "../internal/use-controllable-action.mjs";
7
- import { resolveStateValue, useRender } from "../internal/use-render.mjs";
7
+ import { resolveStateValue } from "../internal/render-element.mjs";
8
+ import { useRender } from "../internal/use-render.mjs";
8
9
  import { useDevWarning } from "../internal/use-dev-warning.mjs";
9
10
  import { AccordionRootContext } from "./accordion-context.mjs";
10
11
  import { useCallback, useEffectEvent, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
@@ -1 +1 @@
1
- {"version":3,"file":"accordion-root.mjs","names":[],"sources":["../../src/accordion/accordion-root.tsx"],"sourcesContent":["\"use client\";\nimport {\n useCallback,\n useEffectEvent,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, KeyboardEvent, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { transitionTypes } from \"../internal/transition-types.js\";\nimport type { ActionDetails, ChangeAction, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useLatest } from \"../internal/use-latest.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport { AccordionRootContext } from \"./accordion-context.js\";\nimport type {\n AccordionChangeReason,\n AccordionMode,\n AccordionRootContextValue,\n} from \"./accordion-context.js\";\n\nexport interface AccordionRootState {\n /** An action dispatched through this Accordion is in flight (library mode only). */\n readonly pending: boolean;\n /** The Root's `disabled` prop. */\n readonly disabled: boolean;\n}\n\n/** Details passed as the second argument of `valueChangeAction`. */\nexport interface AccordionValueChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user activated a Trigger. `\"platform\"`: the browser changed `open` itself\n * (find-in-page, a text fragment, a click before hydration); `event` is then the `toggle`\n * event, or `undefined` when the mismatch was found on mount.\n */\n readonly reason: AccordionChangeReason;\n /** The item whose open state changed. */\n readonly item: string;\n /** That item's new open state. */\n readonly open: boolean;\n}\n\ninterface AccordionRootBaseProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n // `defaultValue` is also a native HTMLAttributes member; the Accordion's own definition wins.\n \"onKeyDown\" | \"defaultValue\" | \"className\" | \"style\" | \"children\"\n> {\n /** Disables every item: triggers render `aria-disabled` and never dispatch. Default `false`. */\n disabled?: boolean;\n /**\n * Renders `interpolate-size: allow-keywords` on the root so `::details-content` can transition\n * `height` to `auto`. Chromium-only polish today; use the `grid-template-rows` form for a\n * cross-engine animation. Merged with `style`; the consumer's declaration wins.\n */\n experimental_interpolateSize?: boolean;\n className?: StateValue<string | undefined, AccordionRootState>;\n style?: StateValue<CSSProperties | undefined, AccordionRootState>;\n children?: StateValue<ReactNode, AccordionRootState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, AccordionRootState>;\n}\n\nexport interface AccordionRootMultipleProps extends AccordionRootBaseProps {\n /** Any number of items may be open. */\n exclusive?: false;\n /** Controlled open items. `undefined` means uncontrolled; `[]` means none open. */\n value?: string[];\n /** Initial open items in uncontrolled library mode. */\n defaultValue?: string[];\n /**\n * Called with the next open items on every change. Runs inside the Root's transition; may be\n * sync or async (also a Server Function). Rejections reach the nearest Error Boundary.\n *\n * May also be called with `details.reason === \"platform\"` when the browser opened an item\n * itself (find-in-page, a click before hydration); committed state is re-applied to the DOM\n * once the action settles.\n *\n * A `setState` placed after your own `await` is not a Transition update; wrap it in\n * `startTransition` (React limitation).\n */\n valueChangeAction?: ChangeAction<string[], AccordionValueChangeDetails>;\n}\n\nexport interface AccordionRootExclusiveProps extends AccordionRootBaseProps {\n /**\n * At most one item open. JS-free mode: renders the same `name` on every `<details>` (the\n * platform's exclusive group). Library mode: the value is a single item or `null`.\n */\n exclusive: true;\n /** Controlled open item. `undefined` means uncontrolled; `null` means none open. */\n value?: string | null;\n /** Initial open item in uncontrolled library mode. */\n defaultValue?: string | null;\n /** See {@link AccordionRootMultipleProps.valueChangeAction}. */\n valueChangeAction?: ChangeAction<string | null, AccordionValueChangeDetails>;\n}\n\n/**\n * Props of `Accordion.Root`, discriminated on `exclusive`.\n *\n * Without `value`, `defaultValue` and `valueChangeAction` the Accordion runs in **JS-free mode**:\n * the browser owns each `<details open>`, nothing runs on toggle, and `exclusive` renders `name`.\n * With any of them it runs in **library mode**: the summary click is intercepted, `open` is driven\n * by committed state inside a transition, and triggers show optimistic `data-open`.\n */\nexport type AccordionRootProps = AccordionRootMultipleProps | AccordionRootExclusiveProps;\n\ntype AccordionValue = string[] | string | null;\ntype OpenSet = ReadonlySet<string>;\n\nconst EMPTY: OpenSet = new Set();\n\nfunction toSet(value: AccordionValue): OpenSet {\n if (value === null) return EMPTY;\n return typeof value === \"string\" ? new Set([value]) : new Set(value);\n}\n\n/**\n * Converts the open set back to the public value. Multiple mode lists the items in registration\n * order (the order the Items mounted, which is document order for a list rendered top to bottom),\n * not in the order they were opened; unregistered values keep their insertion order at the end.\n */\nfunction fromSet(set: OpenSet, exclusive: boolean, order: readonly string[]): AccordionValue {\n if (exclusive) {\n const [first] = set;\n return first ?? null;\n }\n const index = new Map(order.map((v, i) => [v, i] as const));\n return [...set].sort((a, b) => (index.get(a) ?? Infinity) - (index.get(b) ?? Infinity));\n}\n\n/** The optimistic reducer of design 4.1: add/remove one item, or replace when exclusive. */\nfunction reduce(set: OpenSet, item: string, open: boolean, exclusive: boolean): OpenSet {\n if (!open) {\n if (!set.has(item)) return set;\n const next = new Set(set);\n next.delete(item);\n return next;\n }\n if (exclusive) return new Set([item]);\n return set.has(item) ? set : new Set(set).add(item);\n}\n\nfunction setsEqual(a: OpenSet, b: OpenSet): boolean {\n if (a.size !== b.size) return false;\n for (const v of a) if (!b.has(v)) return false;\n return true;\n}\n\nfunction byDocumentOrder<T extends Node>(a: T, b: T): number {\n if (a === b) return 0;\n return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;\n}\n\nconst NAV_KEYS = new Set([\"ArrowDown\", \"ArrowUp\", \"Home\", \"End\"]);\n\n/** Writes `open` to the DOM; it fires `toggle`, whose handler sees DOM === committed and returns. */\nfunction applyOpen(element: HTMLDetailsElement, open: boolean): void {\n if (element.open !== open) element.open = open;\n}\n\nfunction isEnabledTrigger(el: HTMLElement): boolean {\n return el.getAttribute(\"aria-disabled\") !== \"true\";\n}\n\n/**\n * Picks the trigger to focus for an arrow/Home/End key: `sorted` is every registered trigger in\n * document order; disabled ones are skipped; arrows wrap. Works when `current` is itself disabled\n * (disabled triggers stay focusable).\n */\nfunction nextTrigger(\n sorted: readonly HTMLElement[],\n current: HTMLElement,\n key: string,\n): HTMLElement | undefined {\n const enabled = sorted.filter(isEnabledTrigger);\n if (enabled.length === 0) return undefined;\n if (key === \"Home\") return enabled[0];\n if (key === \"End\") return enabled[enabled.length - 1];\n const position = sorted.indexOf(current);\n if (key === \"ArrowDown\") {\n return enabled.find((el) => sorted.indexOf(el) > position) ?? enabled[0];\n }\n return enabled.findLast((el) => sorted.indexOf(el) < position) ?? enabled[enabled.length - 1];\n}\n\n/**\n * The accordion container: a `div` that owns the open state, the transition, the context the\n * other parts read, and ArrowUp/ArrowDown/Home/End navigation between triggers.\n */\nexport function AccordionRoot(props: AccordionRootProps) {\n const {\n exclusive = false,\n value,\n defaultValue,\n valueChangeAction,\n disabled = false,\n experimental_interpolateSize = false,\n className,\n style,\n children,\n render,\n ...rest\n } = props;\n\n // Mode is decided once, from the first render's props (design 12).\n const isLibraryNow =\n value !== undefined || defaultValue !== undefined || valueChangeAction !== undefined;\n const [mode] = useState<AccordionMode>(isLibraryNow ? \"library\" : \"static\");\n useDevWarning(\n (mode === \"library\") !== isLibraryNow,\n \"Accordion.Root: the mode changed after mount (`value`, `defaultValue` and `valueChangeAction` \" +\n \"were all absent on one render and present on another). Switching between JS-free and \" +\n \"library mode is not supported.\",\n );\n\n // Both prop variants are narrowed by `exclusive` at the call site; internally one signature.\n const action = valueChangeAction as\n | ChangeAction<AccordionValue, AccordionValueChangeDetails>\n | undefined;\n\n // Keep the committed set's identity stable while the consumer's `value` has the same contents,\n // so `useOptimistic` only re-bases, and the context only changes, when the controlled value\n // really changed. Keyed on the contents, not the array: an inline `value={[\"a\"]}` is a new\n // array on every render. (Order-sensitive; a reordered array only costs one new set.)\n const controlledKey = value === undefined ? undefined : JSON.stringify(value);\n const controlledSet = useMemo(\n () =>\n controlledKey === undefined ? undefined : toSet(JSON.parse(controlledKey) as AccordionValue),\n [controlledKey],\n );\n\n const items = useRef(new Map<HTMLDetailsElement, string>());\n const triggers = useRef(new Set<HTMLElement>());\n const registeredValues = () => [...items.current.values()];\n\n const { committed, optimistic, pending, dispatch, latestInFlight } = useControllableAction<\n OpenSet,\n AccordionValueChangeDetails\n >({\n controlled: controlledSet,\n defaultValue: defaultValue === undefined ? undefined : toSet(defaultValue),\n initialValue: EMPTY,\n action:\n action && ((next, details) => action(fromSet(next, exclusive, registeredValues()), details)),\n transitionTypes: (_next, _prev, details) =>\n transitionTypes(\"accordion\", details.open ? \"expand\" : \"collapse\"),\n // One item per dispatch, re-based on the committed set, except the mount reconciliation\n // (reason \"platform\", no event), which folds every pre-hydration click into one `next`.\n reducer: (prev, next, details) =>\n details.reason === \"platform\" && details.event === undefined\n ? next\n : reduce(prev, details.item, details.open, exclusive),\n warn: { component: \"Accordion.Root\", prop: \"value\" },\n });\n\n const registerItem = useCallback((itemValue: string, element: HTMLDetailsElement) => {\n items.current.set(element, itemValue);\n return () => {\n items.current.delete(element);\n };\n }, []);\n\n const registerTrigger = useCallback((element: HTMLElement) => {\n triggers.current.add(element);\n return () => {\n triggers.current.delete(element);\n };\n }, []);\n\n const dispatchChange = useCallback(\n (\n next: OpenSet,\n item: string,\n open: boolean,\n reason: AccordionChangeReason,\n event: Event | undefined,\n ): void => {\n dispatch(next, createActionDetails(event, { reason, item, open }));\n },\n [dispatch],\n );\n\n // The two dispatchers below read the last committed optimistic set through `latest`, so they\n // keep one identity and the Items' contexts are reused across open-state changes (#48): a\n // handler only ever runs against the committed UI, so this is the set it closed over before.\n const latest = useLatest({ optimistic, exclusive });\n\n const dispatchFromTrigger = useCallback<AccordionRootContextValue[\"dispatch\"]>(\n (item, open, event) => {\n const { optimistic, exclusive } = latest.current;\n // `next` comes from the optimistic set: what the user sees and intends (design 2.2).\n dispatchChange(reduce(optimistic, item, open, exclusive), item, open, \"activate\", event);\n },\n [dispatchChange, latest],\n );\n\n const dispatchFromDom = useCallback<AccordionRootContextValue[\"dispatchFromDom\"]>(\n (item, open, event) => {\n const { optimistic, exclusive } = latest.current;\n // The browser flipped one item (design 2.3). `next` builds on what the user already\n // intends (the latest in-flight dispatch, else the optimistic set), never on the DOM: a\n // pending optimistic open would otherwise be lost to latest-wins. Exclusive mode therefore\n // lets the toggled item win when it opened (walkthrough 9.6).\n const base = latestInFlight()?.next ?? optimistic;\n const next = reduce(base, item, open, exclusive);\n if (setsEqual(next, base)) return;\n dispatchChange(next, item, open, \"platform\", event);\n },\n [dispatchChange, latestInFlight, latest],\n );\n\n const isLibrary = mode === \"library\";\n\n // Mount reconciliation (design 2.3): clicks before hydration left some `<details open>` out of\n // step with committed state. Every mismatch is folded into ONE dispatch with reason \"platform\"\n // and no event; disabled items are simply put back. Runs once per mount; the StrictMode re-run\n // is deduplicated against the dispatch just made.\n const reconcileOnMount = useEffectEvent(() => {\n const base = latestInFlight()?.next ?? optimistic;\n const mismatches: { item: string; open: boolean }[] = [];\n for (const [element, item] of items.current) {\n const want = committed.has(item);\n if (element.open === want) continue;\n if (element.hasAttribute(\"data-disabled\")) applyOpen(element, want);\n else mismatches.push({ item, open: element.open });\n }\n if (mismatches.length === 0) return;\n // Exclusive mode: the first opened item in registration order wins (the others are re-closed\n // by the re-apply effect); otherwise fold every flip onto the base.\n const opened = exclusive ? mismatches.find((m) => m.open) : undefined;\n const chosen = opened ?? mismatches[0]!;\n const next = opened\n ? new Set([opened.item])\n : mismatches.reduce((set, m) => reduce(set, m.item, m.open, exclusive), base);\n if (setsEqual(next, base)) return;\n dispatchChange(next, chosen.item, chosen.open, \"platform\", undefined);\n });\n useLayoutEffect(() => {\n if (isLibrary) reconcileOnMount();\n }, [isLibrary]);\n\n // Re-apply committed state to the DOM once nothing is in flight: closes what find-in-page\n // revealed and the parent refused, or what a rejected action left open. Writing `open` fires\n // `toggle`, whose handler sees DOM === committed and does nothing.\n useLayoutEffect(() => {\n if (!isLibrary || pending || latestInFlight() !== null) return;\n for (const [element, item] of items.current) applyOpen(element, committed.has(item));\n }, [isLibrary, pending, committed, latestInFlight]);\n\n function onKeyDown(event: KeyboardEvent<HTMLDivElement>): void {\n if (!NAV_KEYS.has(event.key)) return;\n const target = event.target;\n if (!(target instanceof HTMLElement) || !triggers.current.has(target)) return;\n const sorted = [...triggers.current].sort(byDocumentOrder);\n const next = nextTrigger(sorted, target, event.key);\n if (next === undefined) return;\n event.preventDefault();\n // A nested Root handles its own triggers; the outer one must not move focus again.\n event.stopPropagation();\n next.focus();\n }\n\n const id = useId();\n const name = !isLibrary && exclusive ? id : undefined;\n const rootPending = isLibrary && pending;\n const state: AccordionRootState = { pending: rootPending, disabled };\n const contextCommitted = isLibrary ? committed : EMPTY;\n const contextOptimistic = isLibrary ? optimistic : EMPTY;\n\n // Memoised (design 12, #48): the dispatchers are stable, so this changes only with the open\n // sets, pending and the props below.\n const context = useMemo<AccordionRootContextValue>(\n () => ({\n mode,\n exclusive,\n name,\n disabled,\n pending: rootPending,\n committed: contextCommitted,\n optimistic: contextOptimistic,\n dispatch: dispatchFromTrigger,\n dispatchFromDom,\n registerItem,\n registerTrigger,\n }),\n [\n mode,\n exclusive,\n name,\n disabled,\n rootPending,\n contextCommitted,\n contextOptimistic,\n dispatchFromTrigger,\n dispatchFromDom,\n registerItem,\n registerTrigger,\n ],\n );\n\n const resolvedStyle = resolveStateValue(style, state);\n const finalStyle: CSSProperties | undefined = experimental_interpolateSize\n ? { interpolateSize: \"allow-keywords\", ...resolvedStyle }\n : resolvedStyle;\n\n const element = useRender<ComponentPropsWithRef<\"div\">, AccordionRootState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n style: finalStyle,\n children,\n props: rest,\n // Library-controlled layer: wins over a `render` element's props (conventions 1.2).\n libraryProps: {\n \"data-pending\": dataAttr(state.pending),\n \"data-disabled\": dataAttr(disabled),\n onKeyDown,\n },\n });\n\n return <AccordionRootContext value={context}>{element}</AccordionRootContext>;\n}\n\nAccordionRoot.displayName = \"AccordionRoot\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"accordion-root.mjs","names":[],"sources":["../../src/accordion/accordion-root.tsx"],"sourcesContent":["\"use client\";\nimport {\n useCallback,\n useEffectEvent,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, KeyboardEvent, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { transitionTypes } from \"../internal/transition-types.js\";\nimport type { ActionDetails, ChangeAction, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useLatest } from \"../internal/use-latest.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport { AccordionRootContext } from \"./accordion-context.js\";\nimport type {\n AccordionChangeReason,\n AccordionMode,\n AccordionRootContextValue,\n} from \"./accordion-context.js\";\n\nexport interface AccordionRootState {\n /** An action dispatched through this Accordion is in flight (library mode only). */\n readonly pending: boolean;\n /** The Root's `disabled` prop. */\n readonly disabled: boolean;\n}\n\n/** Details passed as the second argument of `valueChangeAction`. */\nexport interface AccordionValueChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user activated a Trigger. `\"platform\"`: the browser changed `open` itself\n * (find-in-page, a text fragment, a click before hydration); `event` is then the `toggle`\n * event, or `undefined` when the mismatch was found on mount.\n */\n readonly reason: AccordionChangeReason;\n /** The item whose open state changed. */\n readonly item: string;\n /** That item's new open state. */\n readonly open: boolean;\n}\n\ninterface AccordionRootBaseProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n // `defaultValue` is also a native HTMLAttributes member; the Accordion's own definition wins.\n \"onKeyDown\" | \"defaultValue\" | \"className\" | \"style\" | \"children\"\n> {\n /** Disables every item: triggers render `aria-disabled` and never dispatch. Default `false`. */\n disabled?: boolean;\n /**\n * Renders `interpolate-size: allow-keywords` on the root so `::details-content` can transition\n * `height` to `auto`. Chromium-only polish today; use the `grid-template-rows` form for a\n * cross-engine animation. Merged with `style`; the consumer's declaration wins.\n */\n experimental_interpolateSize?: boolean;\n className?: StateValue<string | undefined, AccordionRootState>;\n style?: StateValue<CSSProperties | undefined, AccordionRootState>;\n children?: StateValue<ReactNode, AccordionRootState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, AccordionRootState>;\n}\n\nexport interface AccordionRootMultipleProps extends AccordionRootBaseProps {\n /** Any number of items may be open. */\n exclusive?: false;\n /** Controlled open items. `undefined` means uncontrolled; `[]` means none open. */\n value?: string[];\n /** Initial open items in uncontrolled library mode. */\n defaultValue?: string[];\n /**\n * Called with the next open items on every change. Runs inside the Root's transition; may be\n * sync or async (also a Server Function). Rejections reach the nearest Error Boundary.\n *\n * May also be called with `details.reason === \"platform\"` when the browser opened an item\n * itself (find-in-page, a click before hydration); committed state is re-applied to the DOM\n * once the action settles.\n *\n * A `setState` placed after your own `await` is not a Transition update; wrap it in\n * `startTransition` (React limitation).\n */\n valueChangeAction?: ChangeAction<string[], AccordionValueChangeDetails>;\n}\n\nexport interface AccordionRootExclusiveProps extends AccordionRootBaseProps {\n /**\n * At most one item open. JS-free mode: renders the same `name` on every `<details>` (the\n * platform's exclusive group). Library mode: the value is a single item or `null`.\n */\n exclusive: true;\n /** Controlled open item. `undefined` means uncontrolled; `null` means none open. */\n value?: string | null;\n /** Initial open item in uncontrolled library mode. */\n defaultValue?: string | null;\n /** See {@link AccordionRootMultipleProps.valueChangeAction}. */\n valueChangeAction?: ChangeAction<string | null, AccordionValueChangeDetails>;\n}\n\n/**\n * Props of `Accordion.Root`, discriminated on `exclusive`.\n *\n * Without `value`, `defaultValue` and `valueChangeAction` the Accordion runs in **JS-free mode**:\n * the browser owns each `<details open>`, nothing runs on toggle, and `exclusive` renders `name`.\n * With any of them it runs in **library mode**: the summary click is intercepted, `open` is driven\n * by committed state inside a transition, and triggers show optimistic `data-open`.\n */\nexport type AccordionRootProps = AccordionRootMultipleProps | AccordionRootExclusiveProps;\n\ntype AccordionValue = string[] | string | null;\ntype OpenSet = ReadonlySet<string>;\n\nconst EMPTY: OpenSet = new Set();\n\nfunction toSet(value: AccordionValue): OpenSet {\n if (value === null) return EMPTY;\n return typeof value === \"string\" ? new Set([value]) : new Set(value);\n}\n\n/**\n * Converts the open set back to the public value. Multiple mode lists the items in registration\n * order (the order the Items mounted, which is document order for a list rendered top to bottom),\n * not in the order they were opened; unregistered values keep their insertion order at the end.\n */\nfunction fromSet(set: OpenSet, exclusive: boolean, order: readonly string[]): AccordionValue {\n if (exclusive) {\n const [first] = set;\n return first ?? null;\n }\n const index = new Map(order.map((v, i) => [v, i] as const));\n return [...set].sort((a, b) => (index.get(a) ?? Infinity) - (index.get(b) ?? Infinity));\n}\n\n/** The optimistic reducer of design 4.1: add/remove one item, or replace when exclusive. */\nfunction reduce(set: OpenSet, item: string, open: boolean, exclusive: boolean): OpenSet {\n if (!open) {\n if (!set.has(item)) return set;\n const next = new Set(set);\n next.delete(item);\n return next;\n }\n if (exclusive) return new Set([item]);\n return set.has(item) ? set : new Set(set).add(item);\n}\n\nfunction setsEqual(a: OpenSet, b: OpenSet): boolean {\n if (a.size !== b.size) return false;\n for (const v of a) if (!b.has(v)) return false;\n return true;\n}\n\nfunction byDocumentOrder<T extends Node>(a: T, b: T): number {\n if (a === b) return 0;\n return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;\n}\n\nconst NAV_KEYS = new Set([\"ArrowDown\", \"ArrowUp\", \"Home\", \"End\"]);\n\n/** Writes `open` to the DOM; it fires `toggle`, whose handler sees DOM === committed and returns. */\nfunction applyOpen(element: HTMLDetailsElement, open: boolean): void {\n if (element.open !== open) element.open = open;\n}\n\nfunction isEnabledTrigger(el: HTMLElement): boolean {\n return el.getAttribute(\"aria-disabled\") !== \"true\";\n}\n\n/**\n * Picks the trigger to focus for an arrow/Home/End key: `sorted` is every registered trigger in\n * document order; disabled ones are skipped; arrows wrap. Works when `current` is itself disabled\n * (disabled triggers stay focusable).\n */\nfunction nextTrigger(\n sorted: readonly HTMLElement[],\n current: HTMLElement,\n key: string,\n): HTMLElement | undefined {\n const enabled = sorted.filter(isEnabledTrigger);\n if (enabled.length === 0) return undefined;\n if (key === \"Home\") return enabled[0];\n if (key === \"End\") return enabled[enabled.length - 1];\n const position = sorted.indexOf(current);\n if (key === \"ArrowDown\") {\n return enabled.find((el) => sorted.indexOf(el) > position) ?? enabled[0];\n }\n return enabled.findLast((el) => sorted.indexOf(el) < position) ?? enabled[enabled.length - 1];\n}\n\n/**\n * The accordion container: a `div` that owns the open state, the transition, the context the\n * other parts read, and ArrowUp/ArrowDown/Home/End navigation between triggers.\n */\nexport function AccordionRoot(props: AccordionRootProps) {\n const {\n exclusive = false,\n value,\n defaultValue,\n valueChangeAction,\n disabled = false,\n experimental_interpolateSize = false,\n className,\n style,\n children,\n render,\n ...rest\n } = props;\n\n // Mode is decided once, from the first render's props (design 12).\n const isLibraryNow =\n value !== undefined || defaultValue !== undefined || valueChangeAction !== undefined;\n const [mode] = useState<AccordionMode>(isLibraryNow ? \"library\" : \"static\");\n useDevWarning(\n (mode === \"library\") !== isLibraryNow,\n \"Accordion.Root: the mode changed after mount (`value`, `defaultValue` and `valueChangeAction` \" +\n \"were all absent on one render and present on another). Switching between JS-free and \" +\n \"library mode is not supported.\",\n );\n\n // Both prop variants are narrowed by `exclusive` at the call site; internally one signature.\n const action = valueChangeAction as\n | ChangeAction<AccordionValue, AccordionValueChangeDetails>\n | undefined;\n\n // Keep the committed set's identity stable while the consumer's `value` has the same contents,\n // so `useOptimistic` only re-bases, and the context only changes, when the controlled value\n // really changed. Keyed on the contents, not the array: an inline `value={[\"a\"]}` is a new\n // array on every render. (Order-sensitive; a reordered array only costs one new set.)\n const controlledKey = value === undefined ? undefined : JSON.stringify(value);\n const controlledSet = useMemo(\n () =>\n controlledKey === undefined ? undefined : toSet(JSON.parse(controlledKey) as AccordionValue),\n [controlledKey],\n );\n\n const items = useRef(new Map<HTMLDetailsElement, string>());\n const triggers = useRef(new Set<HTMLElement>());\n const registeredValues = () => [...items.current.values()];\n\n const { committed, optimistic, pending, dispatch, latestInFlight } = useControllableAction<\n OpenSet,\n AccordionValueChangeDetails\n >({\n controlled: controlledSet,\n defaultValue: defaultValue === undefined ? undefined : toSet(defaultValue),\n initialValue: EMPTY,\n action:\n action && ((next, details) => action(fromSet(next, exclusive, registeredValues()), details)),\n transitionTypes: (_next, _prev, details) =>\n transitionTypes(\"accordion\", details.open ? \"expand\" : \"collapse\"),\n // One item per dispatch, re-based on the committed set, except the mount reconciliation\n // (reason \"platform\", no event), which folds every pre-hydration click into one `next`.\n reducer: (prev, next, details) =>\n details.reason === \"platform\" && details.event === undefined\n ? next\n : reduce(prev, details.item, details.open, exclusive),\n warn: { component: \"Accordion.Root\", prop: \"value\" },\n });\n\n const registerItem = useCallback((itemValue: string, element: HTMLDetailsElement) => {\n items.current.set(element, itemValue);\n return () => {\n items.current.delete(element);\n };\n }, []);\n\n const registerTrigger = useCallback((element: HTMLElement) => {\n triggers.current.add(element);\n return () => {\n triggers.current.delete(element);\n };\n }, []);\n\n const dispatchChange = useCallback(\n (\n next: OpenSet,\n item: string,\n open: boolean,\n reason: AccordionChangeReason,\n event: Event | undefined,\n ): void => {\n dispatch(next, createActionDetails(event, { reason, item, open }));\n },\n [dispatch],\n );\n\n // The two dispatchers below read the last committed optimistic set through `latest`, so they\n // keep one identity and the Items' contexts are reused across open-state changes (#48): a\n // handler only ever runs against the committed UI, so this is the set it closed over before.\n const latest = useLatest({ optimistic, exclusive });\n\n const dispatchFromTrigger = useCallback<AccordionRootContextValue[\"dispatch\"]>(\n (item, open, event) => {\n const { optimistic, exclusive } = latest.current;\n // `next` comes from the optimistic set: what the user sees and intends (design 2.2).\n dispatchChange(reduce(optimistic, item, open, exclusive), item, open, \"activate\", event);\n },\n [dispatchChange, latest],\n );\n\n const dispatchFromDom = useCallback<AccordionRootContextValue[\"dispatchFromDom\"]>(\n (item, open, event) => {\n const { optimistic, exclusive } = latest.current;\n // The browser flipped one item (design 2.3). `next` builds on what the user already\n // intends (the latest in-flight dispatch, else the optimistic set), never on the DOM: a\n // pending optimistic open would otherwise be lost to latest-wins. Exclusive mode therefore\n // lets the toggled item win when it opened (walkthrough 9.6).\n const base = latestInFlight()?.next ?? optimistic;\n const next = reduce(base, item, open, exclusive);\n if (setsEqual(next, base)) return;\n dispatchChange(next, item, open, \"platform\", event);\n },\n [dispatchChange, latestInFlight, latest],\n );\n\n const isLibrary = mode === \"library\";\n\n // Mount reconciliation (design 2.3): clicks before hydration left some `<details open>` out of\n // step with committed state. Every mismatch is folded into ONE dispatch with reason \"platform\"\n // and no event; disabled items are simply put back. Runs once per mount; the StrictMode re-run\n // is deduplicated against the dispatch just made.\n const reconcileOnMount = useEffectEvent(() => {\n const base = latestInFlight()?.next ?? optimistic;\n const mismatches: { item: string; open: boolean }[] = [];\n for (const [element, item] of items.current) {\n const want = committed.has(item);\n if (element.open === want) continue;\n if (element.hasAttribute(\"data-disabled\")) applyOpen(element, want);\n else mismatches.push({ item, open: element.open });\n }\n if (mismatches.length === 0) return;\n // Exclusive mode: the first opened item in registration order wins (the others are re-closed\n // by the re-apply effect); otherwise fold every flip onto the base.\n const opened = exclusive ? mismatches.find((m) => m.open) : undefined;\n const chosen = opened ?? mismatches[0]!;\n const next = opened\n ? new Set([opened.item])\n : mismatches.reduce((set, m) => reduce(set, m.item, m.open, exclusive), base);\n if (setsEqual(next, base)) return;\n dispatchChange(next, chosen.item, chosen.open, \"platform\", undefined);\n });\n useLayoutEffect(() => {\n if (isLibrary) reconcileOnMount();\n }, [isLibrary]);\n\n // Re-apply committed state to the DOM once nothing is in flight: closes what find-in-page\n // revealed and the parent refused, or what a rejected action left open. Writing `open` fires\n // `toggle`, whose handler sees DOM === committed and does nothing.\n useLayoutEffect(() => {\n if (!isLibrary || pending || latestInFlight() !== null) return;\n for (const [element, item] of items.current) applyOpen(element, committed.has(item));\n }, [isLibrary, pending, committed, latestInFlight]);\n\n function onKeyDown(event: KeyboardEvent<HTMLDivElement>): void {\n if (!NAV_KEYS.has(event.key)) return;\n const target = event.target;\n if (!(target instanceof HTMLElement) || !triggers.current.has(target)) return;\n const sorted = [...triggers.current].sort(byDocumentOrder);\n const next = nextTrigger(sorted, target, event.key);\n if (next === undefined) return;\n event.preventDefault();\n // A nested Root handles its own triggers; the outer one must not move focus again.\n event.stopPropagation();\n next.focus();\n }\n\n const id = useId();\n const name = !isLibrary && exclusive ? id : undefined;\n const rootPending = isLibrary && pending;\n const state: AccordionRootState = { pending: rootPending, disabled };\n const contextCommitted = isLibrary ? committed : EMPTY;\n const contextOptimistic = isLibrary ? optimistic : EMPTY;\n\n // Memoised (design 12, #48): the dispatchers are stable, so this changes only with the open\n // sets, pending and the props below.\n const context = useMemo<AccordionRootContextValue>(\n () => ({\n mode,\n exclusive,\n name,\n disabled,\n pending: rootPending,\n committed: contextCommitted,\n optimistic: contextOptimistic,\n dispatch: dispatchFromTrigger,\n dispatchFromDom,\n registerItem,\n registerTrigger,\n }),\n [\n mode,\n exclusive,\n name,\n disabled,\n rootPending,\n contextCommitted,\n contextOptimistic,\n dispatchFromTrigger,\n dispatchFromDom,\n registerItem,\n registerTrigger,\n ],\n );\n\n const resolvedStyle = resolveStateValue(style, state);\n const finalStyle: CSSProperties | undefined = experimental_interpolateSize\n ? { interpolateSize: \"allow-keywords\", ...resolvedStyle }\n : resolvedStyle;\n\n const element = useRender<ComponentPropsWithRef<\"div\">, AccordionRootState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n style: finalStyle,\n children,\n props: rest,\n // Library-controlled layer: wins over a `render` element's props (conventions 1.2).\n libraryProps: {\n \"data-pending\": dataAttr(state.pending),\n \"data-disabled\": dataAttr(disabled),\n onKeyDown,\n },\n });\n\n return <AccordionRootContext value={context}>{element}</AccordionRootContext>;\n}\n\nAccordionRoot.displayName = \"AccordionRoot\";\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { dataAttr } from "../internal/data-attr.mjs";
3
- import { rendersButton, useRender } from "../internal/use-render.mjs";
3
+ import { rendersButton } from "../internal/render-element.mjs";
4
+ import { useRender } from "../internal/use-render.mjs";
4
5
  import { useComboboxContext } from "./combobox-context.mjs";
5
6
  import { useNameWarning } from "./combobox-trigger.mjs";
6
7
  //#region src/combobox/combobox-clear.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"combobox-clear.mjs","names":[],"sources":["../../src/combobox/combobox-clear.tsx"],"sourcesContent":["\"use client\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n MouseEvent,\n PointerEvent,\n ReactNode,\n} from \"react\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { useComboboxContext } from \"./combobox-context.js\";\nimport { useNameWarning } from \"./combobox-trigger.js\";\n\nexport interface ComboboxClearState {\n /** Optimistic selection. */\n readonly value: string | null;\n readonly inputValue: string;\n readonly disabled: boolean;\n}\n\nexport interface ComboboxClearProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n | \"onClick\"\n | \"onPointerDown\"\n | \"onMouseDown\"\n | \"tabIndex\"\n | \"disabled\"\n | \"className\"\n | \"style\"\n | \"children\"\n> {\n /** Disables this button; the Root's `disabled` does too. */\n disabled?: boolean;\n className?: StateValue<string | undefined, ComboboxClearState>;\n style?: StateValue<CSSProperties | undefined, ComboboxClearState>;\n children?: StateValue<ReactNode, ComboboxClearState>;\n /** Should render a `<button>`. */\n render?: RenderProp<ComponentPropsWithRef<\"button\">, ComboboxClearState>;\n}\n\n/**\n * The optional button that clears the combobox: `<button type=\"button\" tabindex=\"-1\">`. A click\n * empties the text (urgent) and dispatches `valueChangeAction(null)` unless nothing is selected;\n * the input keeps focus. Always rendered: hide it with CSS (`:placeholder-shown`) or `state`.\n * A click while the listbox is open closes it (the platform's light dismiss: the button is\n * outside the popup).\n *\n * Owned keys: `type` (on a `<button>`), `tabIndex`, `disabled`, `onClick`, `onPointerDown`,\n * `onMouseDown`, `data-disabled`.\n */\nexport function ComboboxClear({\n disabled: ownDisabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: ComboboxClearProps) {\n const ctx = useComboboxContext(\"Combobox.Clear\");\n const disabled = ownDisabled || ctx.disabled;\n const ref = useNameWarning(\"Combobox.Clear\");\n const state: ComboboxClearState = { value: ctx.value, inputValue: ctx.inputValue, disabled };\n\n const libraryProps: ComponentPropsWithRef<\"button\"> & DataAttributes = {\n ...(rendersButton(render) && { type: \"button\" as const }),\n tabIndex: -1,\n disabled,\n onPointerDown: (event: PointerEvent<HTMLButtonElement>) => {\n event.preventDefault();\n },\n onMouseDown: (event: MouseEvent<HTMLButtonElement>) => {\n event.preventDefault();\n },\n onClick: (event: MouseEvent<HTMLButtonElement>) => {\n if (disabled) return;\n ctx.clear(event.nativeEvent);\n },\n \"data-disabled\": dataAttr(disabled),\n };\n\n return useRender<ComponentPropsWithRef<\"button\">, ComboboxClearState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps,\n ref,\n });\n}\n\nComboboxClear.displayName = \"ComboboxClear\";\n"],"mappings":";;;;;;;;;;;;;;;;AAoDA,SAAgB,cAAc,EAC5B,UAAU,cAAc,OACxB,WACA,OACA,UACA,QACA,GAAG,QACkB;CACrB,MAAM,MAAM,mBAAmB,gBAAgB;CAC/C,MAAM,WAAW,eAAe,IAAI;CACpC,MAAM,MAAM,eAAe,gBAAgB;CAC3C,MAAM,QAA4B;EAAE,OAAO,IAAI;EAAO,YAAY,IAAI;EAAY;CAAS;CAE3F,MAAM,eAAiE;EACrE,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;EACvD,UAAU;EACV;EACA,gBAAgB,UAA2C;GACzD,MAAM,eAAe;EACvB;EACA,cAAc,UAAyC;GACrD,MAAM,eAAe;EACvB;EACA,UAAU,UAAyC;GACjD,IAAI,UAAU;GACd,IAAI,MAAM,MAAM,WAAW;EAC7B;EACA,iBAAiB,SAAS,QAAQ;CACpC;CAEA,OAAO,UAA+D;EACpE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;EACA;CACF,CAAC;AACH;AAEA,cAAc,cAAc"}
1
+ {"version":3,"file":"combobox-clear.mjs","names":[],"sources":["../../src/combobox/combobox-clear.tsx"],"sourcesContent":["\"use client\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n MouseEvent,\n PointerEvent,\n ReactNode,\n} from \"react\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { useComboboxContext } from \"./combobox-context.js\";\nimport { useNameWarning } from \"./combobox-trigger.js\";\n\nexport interface ComboboxClearState {\n /** Optimistic selection. */\n readonly value: string | null;\n readonly inputValue: string;\n readonly disabled: boolean;\n}\n\nexport interface ComboboxClearProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n | \"onClick\"\n | \"onPointerDown\"\n | \"onMouseDown\"\n | \"tabIndex\"\n | \"disabled\"\n | \"className\"\n | \"style\"\n | \"children\"\n> {\n /** Disables this button; the Root's `disabled` does too. */\n disabled?: boolean;\n className?: StateValue<string | undefined, ComboboxClearState>;\n style?: StateValue<CSSProperties | undefined, ComboboxClearState>;\n children?: StateValue<ReactNode, ComboboxClearState>;\n /** Should render a `<button>`. */\n render?: RenderProp<ComponentPropsWithRef<\"button\">, ComboboxClearState>;\n}\n\n/**\n * The optional button that clears the combobox: `<button type=\"button\" tabindex=\"-1\">`. A click\n * empties the text (urgent) and dispatches `valueChangeAction(null)` unless nothing is selected;\n * the input keeps focus. Always rendered: hide it with CSS (`:placeholder-shown`) or `state`.\n * A click while the listbox is open closes it (the platform's light dismiss: the button is\n * outside the popup).\n *\n * Owned keys: `type` (on a `<button>`), `tabIndex`, `disabled`, `onClick`, `onPointerDown`,\n * `onMouseDown`, `data-disabled`.\n */\nexport function ComboboxClear({\n disabled: ownDisabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: ComboboxClearProps) {\n const ctx = useComboboxContext(\"Combobox.Clear\");\n const disabled = ownDisabled || ctx.disabled;\n const ref = useNameWarning(\"Combobox.Clear\");\n const state: ComboboxClearState = { value: ctx.value, inputValue: ctx.inputValue, disabled };\n\n const libraryProps: ComponentPropsWithRef<\"button\"> & DataAttributes = {\n ...(rendersButton(render) && { type: \"button\" as const }),\n tabIndex: -1,\n disabled,\n onPointerDown: (event: PointerEvent<HTMLButtonElement>) => {\n event.preventDefault();\n },\n onMouseDown: (event: MouseEvent<HTMLButtonElement>) => {\n event.preventDefault();\n },\n onClick: (event: MouseEvent<HTMLButtonElement>) => {\n if (disabled) return;\n ctx.clear(event.nativeEvent);\n },\n \"data-disabled\": dataAttr(disabled),\n };\n\n return useRender<ComponentPropsWithRef<\"button\">, ComboboxClearState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps,\n ref,\n });\n}\n\nComboboxClear.displayName = \"ComboboxClear\";\n"],"mappings":";;;;;;;;;;;;;;;;;AAoDA,SAAgB,cAAc,EAC5B,UAAU,cAAc,OACxB,WACA,OACA,UACA,QACA,GAAG,QACkB;CACrB,MAAM,MAAM,mBAAmB,gBAAgB;CAC/C,MAAM,WAAW,eAAe,IAAI;CACpC,MAAM,MAAM,eAAe,gBAAgB;CAC3C,MAAM,QAA4B;EAAE,OAAO,IAAI;EAAO,YAAY,IAAI;EAAY;CAAS;CAE3F,MAAM,eAAiE;EACrE,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;EACvD,UAAU;EACV;EACA,gBAAgB,UAA2C;GACzD,MAAM,eAAe;EACvB;EACA,cAAc,UAAyC;GACrD,MAAM,eAAe;EACvB;EACA,UAAU,UAAyC;GACjD,IAAI,UAAU;GACd,IAAI,MAAM,MAAM,WAAW;EAC7B;EACA,iBAAiB,SAAS,QAAQ;CACpC;CAEA,OAAO,UAA+D;EACpE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;EACA;CACF,CAAC;AACH;AAEA,cAAc,cAAc"}
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { dataAttr } from "../internal/data-attr.mjs";
3
- import { resolveStateValue, useRender } from "../internal/use-render.mjs";
3
+ import { resolveStateValue } from "../internal/render-element.mjs";
4
+ import { useRender } from "../internal/use-render.mjs";
4
5
  import { useDevWarning } from "../internal/use-dev-warning.mjs";
5
6
  import { ComboboxListContext, ComboboxPopupContext, useComboboxContext } from "./combobox-context.mjs";
6
7
  import { use, useCallback, useRef } from "react";
@@ -1 +1 @@
1
- {"version":3,"file":"combobox-list.mjs","names":[],"sources":["../../src/combobox/combobox-list.tsx"],"sourcesContent":["\"use client\";\nimport { use, useCallback, useRef } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport {\n ComboboxListContext,\n ComboboxPopupContext,\n useComboboxContext,\n} from \"./combobox-context.js\";\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nexport interface ComboboxListState {\n /** The deferred text the results are for: render the Options for it. */\n readonly query: string;\n /** The results shown are for an older text (`aria-busy`). */\n readonly stale: boolean;\n /** No option is mounted and the results are settled. */\n readonly empty: boolean;\n}\n\nexport interface ComboboxListProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"id\" | \"role\" | \"aria-busy\" | \"className\" | \"style\" | \"children\"\n> {\n className?: StateValue<string | undefined, ComboboxListState>;\n style?: StateValue<CSSProperties | undefined, ComboboxListState>;\n /**\n * Usually a function of `{ query }` rendering the Options for it, under your own `<Suspense>`\n * for an async search. The previous results stay visible while the next ones load.\n */\n children?: StateValue<ReactNode, ComboboxListState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, ComboboxListState>;\n}\n\n/**\n * The listbox: `<div role=\"listbox\" aria-labelledby={label}>` (only while a `Combobox.Label` is\n * mounted), the element the input's `aria-controls` names. Its children are the results for the\n * deferred `query` (the library filters nothing, design 6.1); while they are for an older text it\n * carries `aria-busy=\"true\"` and `data-stale`. Contains only Options and Groups; a `Status`\n * rendered as a Suspense fallback inside it marks the results as loading (`aria-busy=\"true\"`).\n *\n * Owned keys (conventions 1.2): `id`, `role`, `aria-labelledby` (token-merged), `aria-busy`,\n * `data-stale`, `data-empty`.\n */\nexport function ComboboxList({ className, style, children, render, ...rest }: ComboboxListProps) {\n const ctx = useComboboxContext(\"Combobox.List\");\n const inPopup = use(ComboboxPopupContext);\n useDevWarning(\n !inPopup,\n \"Combobox.List: rendered outside a Combobox.Popup. The listbox must be inside the Popup, \" +\n \"which is the element the library shows and hides.\",\n );\n const { listRef } = ctx;\n const warnedName = useRef(false);\n\n const ref = useCallback(\n (element: HTMLElement) => {\n listRef.current = element;\n if (isDev && !warnedName.current) {\n // A mounted Label registers in a layout effect whose re-render is flushed before this\n // microtask, so `aria-labelledby` is in the DOM by then if it ever will be.\n queueMicrotask(() => {\n if (warnedName.current || !element.isConnected) return;\n if (element.hasAttribute(\"aria-labelledby\") || element.hasAttribute(\"aria-label\")) return;\n warnedName.current = true;\n console.error(\n \"Combobox.List: the listbox has no accessible name. Render a Combobox.Label, or give \" +\n \"the List an `aria-label` / `aria-labelledby`.\",\n );\n });\n }\n return () => {\n if (listRef.current === element) listRef.current = null;\n };\n },\n [listRef],\n );\n\n const state: ComboboxListState = { query: ctx.query, stale: ctx.stale, empty: ctx.empty };\n\n const libraryProps: ComponentPropsWithRef<\"div\"> & DataAttributes = {\n id: ctx.ids.list,\n role: \"listbox\",\n ...(ctx.hasLabel\n ? { \"aria-labelledby\": ctx.ids.label }\n : ctx.fieldLabelId !== undefined && { \"aria-labelledby\": ctx.fieldLabelId }),\n \"aria-busy\": ctx.stale || ctx.loading ? true : undefined,\n \"data-stale\": dataAttr(ctx.stale),\n \"data-empty\": dataAttr(ctx.empty),\n };\n\n return useRender<ComponentPropsWithRef<\"div\">, ComboboxListState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n style,\n children: (s) => (\n <ComboboxListContext value={true}>{resolveStateValue(children, s)}</ComboboxListContext>\n ),\n props: rest,\n libraryProps,\n ref,\n });\n}\n\nComboboxList.displayName = \"ComboboxList\";\n"],"mappings":";;;;;;;;AAcA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;;;;;;AAmCvC,SAAgB,aAAa,EAAE,WAAW,OAAO,UAAU,QAAQ,GAAG,QAA2B;CAC/F,MAAM,MAAM,mBAAmB,eAAe;CAC9C,MAAM,UAAU,IAAI,oBAAoB;CACxC,cACE,CAAC,SACD,2IAEF;CACA,MAAM,EAAE,YAAY;CACpB,MAAM,aAAa,OAAO,KAAK;CAE/B,MAAM,MAAM,aACT,YAAyB;EACxB,QAAQ,UAAU;EAClB,IAAI,SAAS,CAAC,WAAW,SAGvB,qBAAqB;GACnB,IAAI,WAAW,WAAW,CAAC,QAAQ,aAAa;GAChD,IAAI,QAAQ,aAAa,iBAAiB,KAAK,QAAQ,aAAa,YAAY,GAAG;GACnF,WAAW,UAAU;GACrB,QAAQ,MACN,mIAEF;EACF,CAAC;EAEH,aAAa;GACX,IAAI,QAAQ,YAAY,SAAS,QAAQ,UAAU;EACrD;CACF,GACA,CAAC,OAAO,CACV;CAEA,MAAM,QAA2B;EAAE,OAAO,IAAI;EAAO,OAAO,IAAI;EAAO,OAAO,IAAI;CAAM;CAExF,MAAM,eAA8D;EAClE,IAAI,IAAI,IAAI;EACZ,MAAM;EACN,GAAI,IAAI,WACJ,EAAE,mBAAmB,IAAI,IAAI,MAAM,IACnC,IAAI,iBAAiB,KAAA,KAAa,EAAE,mBAAmB,IAAI,aAAa;EAC5E,aAAa,IAAI,SAAS,IAAI,UAAU,OAAO,KAAA;EAC/C,cAAc,SAAS,IAAI,KAAK;EAChC,cAAc,SAAS,IAAI,KAAK;CAClC;CAEA,OAAO,UAA2D;EAChE,gBAAgB;EAChB;EACA;EACA;EACA;EACA,WAAW,MACT,oBAAC,qBAAD;GAAqB,OAAO;GAAO,UAAA,kBAAkB,UAAU,CAAC;EAAuB,CAAA;EAEzF,OAAO;EACP;EACA;CACF,CAAC;AACH;AAEA,aAAa,cAAc"}
1
+ {"version":3,"file":"combobox-list.mjs","names":[],"sources":["../../src/combobox/combobox-list.tsx"],"sourcesContent":["\"use client\";\nimport { use, useCallback, useRef } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport {\n ComboboxListContext,\n ComboboxPopupContext,\n useComboboxContext,\n} from \"./combobox-context.js\";\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nexport interface ComboboxListState {\n /** The deferred text the results are for: render the Options for it. */\n readonly query: string;\n /** The results shown are for an older text (`aria-busy`). */\n readonly stale: boolean;\n /** No option is mounted and the results are settled. */\n readonly empty: boolean;\n}\n\nexport interface ComboboxListProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"id\" | \"role\" | \"aria-busy\" | \"className\" | \"style\" | \"children\"\n> {\n className?: StateValue<string | undefined, ComboboxListState>;\n style?: StateValue<CSSProperties | undefined, ComboboxListState>;\n /**\n * Usually a function of `{ query }` rendering the Options for it, under your own `<Suspense>`\n * for an async search. The previous results stay visible while the next ones load.\n */\n children?: StateValue<ReactNode, ComboboxListState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, ComboboxListState>;\n}\n\n/**\n * The listbox: `<div role=\"listbox\" aria-labelledby={label}>` (only while a `Combobox.Label` is\n * mounted), the element the input's `aria-controls` names. Its children are the results for the\n * deferred `query` (the library filters nothing, design 6.1); while they are for an older text it\n * carries `aria-busy=\"true\"` and `data-stale`. Contains only Options and Groups; a `Status`\n * rendered as a Suspense fallback inside it marks the results as loading (`aria-busy=\"true\"`).\n *\n * Owned keys (conventions 1.2): `id`, `role`, `aria-labelledby` (token-merged), `aria-busy`,\n * `data-stale`, `data-empty`.\n */\nexport function ComboboxList({ className, style, children, render, ...rest }: ComboboxListProps) {\n const ctx = useComboboxContext(\"Combobox.List\");\n const inPopup = use(ComboboxPopupContext);\n useDevWarning(\n !inPopup,\n \"Combobox.List: rendered outside a Combobox.Popup. The listbox must be inside the Popup, \" +\n \"which is the element the library shows and hides.\",\n );\n const { listRef } = ctx;\n const warnedName = useRef(false);\n\n const ref = useCallback(\n (element: HTMLElement) => {\n listRef.current = element;\n if (isDev && !warnedName.current) {\n // A mounted Label registers in a layout effect whose re-render is flushed before this\n // microtask, so `aria-labelledby` is in the DOM by then if it ever will be.\n queueMicrotask(() => {\n if (warnedName.current || !element.isConnected) return;\n if (element.hasAttribute(\"aria-labelledby\") || element.hasAttribute(\"aria-label\")) return;\n warnedName.current = true;\n console.error(\n \"Combobox.List: the listbox has no accessible name. Render a Combobox.Label, or give \" +\n \"the List an `aria-label` / `aria-labelledby`.\",\n );\n });\n }\n return () => {\n if (listRef.current === element) listRef.current = null;\n };\n },\n [listRef],\n );\n\n const state: ComboboxListState = { query: ctx.query, stale: ctx.stale, empty: ctx.empty };\n\n const libraryProps: ComponentPropsWithRef<\"div\"> & DataAttributes = {\n id: ctx.ids.list,\n role: \"listbox\",\n ...(ctx.hasLabel\n ? { \"aria-labelledby\": ctx.ids.label }\n : ctx.fieldLabelId !== undefined && { \"aria-labelledby\": ctx.fieldLabelId }),\n \"aria-busy\": ctx.stale || ctx.loading ? true : undefined,\n \"data-stale\": dataAttr(ctx.stale),\n \"data-empty\": dataAttr(ctx.empty),\n };\n\n return useRender<ComponentPropsWithRef<\"div\">, ComboboxListState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n style,\n children: (s) => (\n <ComboboxListContext value={true}>{resolveStateValue(children, s)}</ComboboxListContext>\n ),\n props: rest,\n libraryProps,\n ref,\n });\n}\n\nComboboxList.displayName = \"ComboboxList\";\n"],"mappings":";;;;;;;;;AAcA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;;;;;;AAmCvC,SAAgB,aAAa,EAAE,WAAW,OAAO,UAAU,QAAQ,GAAG,QAA2B;CAC/F,MAAM,MAAM,mBAAmB,eAAe;CAC9C,MAAM,UAAU,IAAI,oBAAoB;CACxC,cACE,CAAC,SACD,2IAEF;CACA,MAAM,EAAE,YAAY;CACpB,MAAM,aAAa,OAAO,KAAK;CAE/B,MAAM,MAAM,aACT,YAAyB;EACxB,QAAQ,UAAU;EAClB,IAAI,SAAS,CAAC,WAAW,SAGvB,qBAAqB;GACnB,IAAI,WAAW,WAAW,CAAC,QAAQ,aAAa;GAChD,IAAI,QAAQ,aAAa,iBAAiB,KAAK,QAAQ,aAAa,YAAY,GAAG;GACnF,WAAW,UAAU;GACrB,QAAQ,MACN,mIAEF;EACF,CAAC;EAEH,aAAa;GACX,IAAI,QAAQ,YAAY,SAAS,QAAQ,UAAU;EACrD;CACF,GACA,CAAC,OAAO,CACV;CAEA,MAAM,QAA2B;EAAE,OAAO,IAAI;EAAO,OAAO,IAAI;EAAO,OAAO,IAAI;CAAM;CAExF,MAAM,eAA8D;EAClE,IAAI,IAAI,IAAI;EACZ,MAAM;EACN,GAAI,IAAI,WACJ,EAAE,mBAAmB,IAAI,IAAI,MAAM,IACnC,IAAI,iBAAiB,KAAA,KAAa,EAAE,mBAAmB,IAAI,aAAa;EAC5E,aAAa,IAAI,SAAS,IAAI,UAAU,OAAO,KAAA;EAC/C,cAAc,SAAS,IAAI,KAAK;EAChC,cAAc,SAAS,IAAI,KAAK;CAClC;CAEA,OAAO,UAA2D;EAChE,gBAAgB;EAChB;EACA;EACA;EACA;EACA,WAAW,MACT,oBAAC,qBAAD;GAAqB,OAAO;GAAO,UAAA,kBAAkB,UAAU,CAAC;EAAuB,CAAA;EAEzF,OAAO;EACP;EACA;CACF,CAAC;AACH;AAEA,aAAa,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"combobox-popup.d.mts","names":[],"sources":["../../src/combobox/combobox-popup.tsx"],"mappings":";;;iBAmBiB;;WAEN;;WAEA;;WAEA;;iBAGM,2BAA2B,KAC1C;;;;;EAeA;;;;;;;;EAQA,aAAa;EACb,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,8BAA8B;;;;;;;;;;;;;;wBAmBpC,gBACd,aACA,YACA,WACA,OACA,UACA,WACG,QACF,qBAAkB;yBARL"}
1
+ {"version":3,"file":"combobox-popup.d.mts","names":[],"sources":["../../src/combobox/combobox-popup.tsx"],"mappings":";;;iBAoBiB;;WAEN;;WAEA;;WAEA;;iBAGM,2BAA2B,KAC1C;;;;;EAeA;;;;;;;;EAQA,aAAa;EACb,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,8BAA8B;;;;;;;;;;;;;;wBAmBpC,gBACd,aACA,YACA,WACA,OACA,UACA,WACG,QACF,qBAAkB;yBARL"}
@@ -2,7 +2,9 @@
2
2
  import { useTransitionBoundary } from "../internal/transition-boundary.mjs";
3
3
  import { dataAttr } from "../internal/data-attr.mjs";
4
4
  import { mergeRefs } from "../internal/merge-props.mjs";
5
- import { resolveStateValue, useRender } from "../internal/use-render.mjs";
5
+ import { resolveStateValue } from "../internal/render-element.mjs";
6
+ import { useRender } from "../internal/use-render.mjs";
7
+ import { ToolbarContext } from "../internal/toolbar-context.mjs";
6
8
  import { usePopoverPopup } from "../internal/use-popover-popup.mjs";
7
9
  import { ComboboxPopupContext, useComboboxContext } from "./combobox-context.mjs";
8
10
  import { Activity, useCallback, useMemo } from "react";
@@ -58,7 +60,10 @@ function ComboboxPopup({ keepMounted, transition, className, style, children, re
58
60
  else node = visible ? wrapContent(resolveStateValue(children, s)) : null;
59
61
  return /* @__PURE__ */ jsx(ComboboxPopupContext, {
60
62
  value: true,
61
- children: node
63
+ children: /* @__PURE__ */ jsx(ToolbarContext, {
64
+ value: null,
65
+ children: node
66
+ })
62
67
  });
63
68
  };
64
69
  const element = useRender({
@@ -1 +1 @@
1
- {"version":3,"file":"combobox-popup.mjs","names":[],"sources":["../../src/combobox/combobox-popup.tsx"],"sourcesContent":["\"use client\";\nimport { Activity, useCallback, useMemo } from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n MouseEvent,\n PointerEvent,\n ReactNode,\n ViewTransitionClass,\n} from \"react\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { mergeRefs } from \"../internal/merge-props.js\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { usePopoverPopup } from \"../internal/use-popover-popup.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { ComboboxPopupContext, useComboboxContext } from \"./combobox-context.js\";\n\nexport interface ComboboxPopupState {\n /** Committed open state. Read `:popover-open` for the DOM truth. */\n readonly open: boolean;\n /** The results shown are for an older text. */\n readonly stale: boolean;\n /** The deferred text the results are for. */\n readonly query: string;\n}\n\nexport interface ComboboxPopupProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n | \"id\"\n | \"popover\"\n | \"onToggle\"\n | \"onBeforeToggle\"\n | \"onPointerDown\"\n | \"onMouseDown\"\n | \"className\"\n | \"style\"\n | \"children\"\n> {\n /**\n * `false` (default): the children are not rendered while the listbox is closed. `true`: they\n * stay mounted inside `<Activity mode=\"hidden\">`.\n */\n keepMounted?: boolean;\n /**\n * Forwarded to the `<ViewTransition default>` around the popover element itself. Animates\n * library-driven opens and closes (typing, the Trigger, an accepted option) in every engine;\n * dismissals are the browser's (CSS only). **A deferred results render also activates the\n * boundary** (React: updates from `useDeferredValue` animate), so use a type map with\n * `default: \"none\"` unless every result change should cross-fade. Static.\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, ComboboxPopupState>;\n style?: StateValue<CSSProperties | undefined, ComboboxPopupState>;\n children?: StateValue<ReactNode, ComboboxPopupState>;\n /** Must render a `<div>`: a `<dialog>` would pull focus out of the input. */\n render?: RenderProp<ComponentPropsWithRef<\"div\">, ComboboxPopupState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nconst preventDefault = (event: { preventDefault(): void }) => event.preventDefault();\n\n/**\n * The listbox's container: `<div popover=\"auto\">`, opened by the library with\n * `showPopover({ source: input })` and placed below (or, flipped, above) the input with CSS\n * Anchor Positioning, at least as wide as the input. Focus never enters it: a press anywhere in\n * it is `preventDefault()`ed so the input keeps focus (design 7.1). Holds the `List` and, outside\n * it, `Empty` and `Status`. Style it with `:popover-open`; it emits `data-stale` but no\n * `data-open`.\n *\n * Owned keys (conventions 1.2): `id`, `popover`, `onPointerDown`, `onMouseDown`, `data-stale`.\n * Inline `position-area`, `position-try-fallbacks` and `min-width` sit under the consumer's\n * `style` (`style={{ positionTryFallbacks: \"none\" }}` removes the flip).\n */\nexport function ComboboxPopup({\n keepMounted,\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: ComboboxPopupProps) {\n const ctx = useComboboxContext(\"Combobox.Popup\");\n const { popover } = ctx;\n\n const { setElement, visible, wrapContent } = usePopoverPopup(popover, {\n part: \"Combobox.Popup\",\n initialFocus: undefined,\n // Focus stays in the input: nothing inside the popup is ever focused.\n resolveInitialFocus: () => null,\n keepMounted,\n transition,\n });\n\n const devRef = useCallback((element: HTMLElement) => {\n if (isDev && element.tagName !== \"DIV\") {\n console.error(\n `Combobox.Popup: rendered a <${element.tagName.toLowerCase()}>. Render a <div>: a ` +\n \"<dialog popover> runs the dialog focusing steps and takes focus out of the input.\",\n );\n }\n return () => {};\n }, []);\n const ref = useMemo(() => mergeRefs(setElement, devRef), [setElement, devRef]);\n\n const state: ComboboxPopupState = {\n open: popover.committed,\n stale: ctx.stale,\n query: ctx.query,\n };\n\n const libraryProps: ComponentPropsWithRef<\"div\"> & DataAttributes = {\n id: popover.ids.popup,\n popover: \"auto\",\n onPointerDown: (event: PointerEvent<HTMLDivElement>) => preventDefault(event),\n onMouseDown: (event: MouseEvent<HTMLDivElement>) => preventDefault(event),\n \"data-stale\": dataAttr(ctx.stale),\n };\n\n const content = (s: ComboboxPopupState): ReactNode => {\n let node: ReactNode;\n if (keepMounted === true) {\n node = (\n <Activity mode={visible ? \"visible\" : \"hidden\"}>\n {wrapContent(resolveStateValue(children, s))}\n </Activity>\n );\n } else {\n node = visible ? wrapContent(resolveStateValue(children, s)) : null;\n }\n return <ComboboxPopupContext value={true}>{node}</ComboboxPopupContext>;\n };\n\n const element = useRender<ComponentPropsWithRef<\"div\">, ComboboxPopupState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n // Logical keywords: right under `direction: rtl` with no JS (design 7.2). The consumer wins.\n style: (s) => ({\n positionArea: \"block-end span-inline-end\",\n positionTryFallbacks: \"flip-block\",\n minWidth: \"anchor-size(width)\",\n ...resolveStateValue(style, s),\n }),\n children: content,\n props: rest,\n libraryProps,\n ref,\n });\n\n return useTransitionBoundary(\"Combobox.Popup\", transition, element);\n}\n\nComboboxPopup.displayName = \"ComboboxPopup\";\n"],"mappings":";;;;;;;;;;AA4DA,MAAM,QAAQ,QAAQ,IAAI,aAAa;AAEvC,MAAM,kBAAkB,UAAsC,MAAM,eAAe;;;;;;;;;;;;;AAcnF,SAAgB,cAAc,EAC5B,aACA,YACA,WACA,OACA,UACA,QACA,GAAG,QACkB;CACrB,MAAM,MAAM,mBAAmB,gBAAgB;CAC/C,MAAM,EAAE,YAAY;CAEpB,MAAM,EAAE,YAAY,SAAS,gBAAgB,gBAAgB,SAAS;EACpE,MAAM;EACN,cAAc,KAAA;EAEd,2BAA2B;EAC3B;EACA;CACF,CAAC;CAED,MAAM,SAAS,aAAa,YAAyB;EACnD,IAAI,SAAS,QAAQ,YAAY,OAC/B,QAAQ,MACN,+BAA+B,QAAQ,QAAQ,YAAY,EAAE,uGAE/D;EAEF,aAAa,CAAC;CAChB,GAAG,CAAC,CAAC;CACL,MAAM,MAAM,cAAc,UAAU,YAAY,MAAM,GAAG,CAAC,YAAY,MAAM,CAAC;CAE7E,MAAM,QAA4B;EAChC,MAAM,QAAQ;EACd,OAAO,IAAI;EACX,OAAO,IAAI;CACb;CAEA,MAAM,eAA8D;EAClE,IAAI,QAAQ,IAAI;EAChB,SAAS;EACT,gBAAgB,UAAwC,eAAe,KAAK;EAC5E,cAAc,UAAsC,eAAe,KAAK;EACxE,cAAc,SAAS,IAAI,KAAK;CAClC;CAEA,MAAM,WAAW,MAAqC;EACpD,IAAI;EACJ,IAAI,gBAAgB,MAClB,OACE,oBAAC,UAAD;GAAU,MAAM,UAAU,YAAY;GACnC,UAAA,YAAY,kBAAkB,UAAU,CAAC,CAAC;EACnC,CAAA;OAGZ,OAAO,UAAU,YAAY,kBAAkB,UAAU,CAAC,CAAC,IAAI;EAEjE,OAAO,oBAAC,sBAAD;GAAsB,OAAO;GAAO,UAAA;EAA2B,CAAA;CACxE;CAEA,MAAM,UAAU,UAA4D;EAC1E,gBAAgB;EAChB;EACA;EACA;EAEA,QAAQ,OAAO;GACb,cAAc;GACd,sBAAsB;GACtB,UAAU;GACV,GAAG,kBAAkB,OAAO,CAAC;EAC/B;EACA,UAAU;EACV,OAAO;EACP;EACA;CACF,CAAC;CAED,OAAO,sBAAsB,kBAAkB,YAAY,OAAO;AACpE;AAEA,cAAc,cAAc"}
1
+ {"version":3,"file":"combobox-popup.mjs","names":[],"sources":["../../src/combobox/combobox-popup.tsx"],"sourcesContent":["\"use client\";\nimport { Activity, useCallback, useMemo } from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n MouseEvent,\n PointerEvent,\n ReactNode,\n ViewTransitionClass,\n} from \"react\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { mergeRefs } from \"../internal/merge-props.js\";\nimport { ToolbarContext } from \"../internal/toolbar-context.js\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { usePopoverPopup } from \"../internal/use-popover-popup.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { ComboboxPopupContext, useComboboxContext } from \"./combobox-context.js\";\n\nexport interface ComboboxPopupState {\n /** Committed open state. Read `:popover-open` for the DOM truth. */\n readonly open: boolean;\n /** The results shown are for an older text. */\n readonly stale: boolean;\n /** The deferred text the results are for. */\n readonly query: string;\n}\n\nexport interface ComboboxPopupProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n | \"id\"\n | \"popover\"\n | \"onToggle\"\n | \"onBeforeToggle\"\n | \"onPointerDown\"\n | \"onMouseDown\"\n | \"className\"\n | \"style\"\n | \"children\"\n> {\n /**\n * `false` (default): the children are not rendered while the listbox is closed. `true`: they\n * stay mounted inside `<Activity mode=\"hidden\">`.\n */\n keepMounted?: boolean;\n /**\n * Forwarded to the `<ViewTransition default>` around the popover element itself. Animates\n * library-driven opens and closes (typing, the Trigger, an accepted option) in every engine;\n * dismissals are the browser's (CSS only). **A deferred results render also activates the\n * boundary** (React: updates from `useDeferredValue` animate), so use a type map with\n * `default: \"none\"` unless every result change should cross-fade. Static.\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, ComboboxPopupState>;\n style?: StateValue<CSSProperties | undefined, ComboboxPopupState>;\n children?: StateValue<ReactNode, ComboboxPopupState>;\n /** Must render a `<div>`: a `<dialog>` would pull focus out of the input. */\n render?: RenderProp<ComponentPropsWithRef<\"div\">, ComboboxPopupState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nconst preventDefault = (event: { preventDefault(): void }) => event.preventDefault();\n\n/**\n * The listbox's container: `<div popover=\"auto\">`, opened by the library with\n * `showPopover({ source: input })` and placed below (or, flipped, above) the input with CSS\n * Anchor Positioning, at least as wide as the input. Focus never enters it: a press anywhere in\n * it is `preventDefault()`ed so the input keeps focus (design 7.1). Holds the `List` and, outside\n * it, `Empty` and `Status`. Style it with `:popover-open`; it emits `data-stale` but no\n * `data-open`.\n *\n * Owned keys (conventions 1.2): `id`, `popover`, `onPointerDown`, `onMouseDown`, `data-stale`.\n * Inline `position-area`, `position-try-fallbacks` and `min-width` sit under the consumer's\n * `style` (`style={{ positionTryFallbacks: \"none\" }}` removes the flip).\n */\nexport function ComboboxPopup({\n keepMounted,\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: ComboboxPopupProps) {\n const ctx = useComboboxContext(\"Combobox.Popup\");\n const { popover } = ctx;\n\n const { setElement, visible, wrapContent } = usePopoverPopup(popover, {\n part: \"Combobox.Popup\",\n initialFocus: undefined,\n // Focus stays in the input: nothing inside the popup is ever focused.\n resolveInitialFocus: () => null,\n keepMounted,\n transition,\n });\n\n const devRef = useCallback((element: HTMLElement) => {\n if (isDev && element.tagName !== \"DIV\") {\n console.error(\n `Combobox.Popup: rendered a <${element.tagName.toLowerCase()}>. Render a <div>: a ` +\n \"<dialog popover> runs the dialog focusing steps and takes focus out of the input.\",\n );\n }\n return () => {};\n }, []);\n const ref = useMemo(() => mergeRefs(setElement, devRef), [setElement, devRef]);\n\n const state: ComboboxPopupState = {\n open: popover.committed,\n stale: ctx.stale,\n query: ctx.query,\n };\n\n const libraryProps: ComponentPropsWithRef<\"div\"> & DataAttributes = {\n id: popover.ids.popup,\n popover: \"auto\",\n onPointerDown: (event: PointerEvent<HTMLDivElement>) => preventDefault(event),\n onMouseDown: (event: MouseEvent<HTMLDivElement>) => preventDefault(event),\n \"data-stale\": dataAttr(ctx.stale),\n };\n\n const content = (s: ComboboxPopupState): ReactNode => {\n let node: ReactNode;\n if (keepMounted === true) {\n node = (\n <Activity mode={visible ? \"visible\" : \"hidden\"}>\n {wrapContent(resolveStateValue(children, s))}\n </Activity>\n );\n } else {\n node = visible ? wrapContent(resolveStateValue(children, s)) : null;\n }\n // Not part of an enclosing Toolbar (the engine excludes `[popover]`).\n return (\n <ComboboxPopupContext value={true}>\n <ToolbarContext value={null}>{node}</ToolbarContext>\n </ComboboxPopupContext>\n );\n };\n\n const element = useRender<ComponentPropsWithRef<\"div\">, ComboboxPopupState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n // Logical keywords: right under `direction: rtl` with no JS (design 7.2). The consumer wins.\n style: (s) => ({\n positionArea: \"block-end span-inline-end\",\n positionTryFallbacks: \"flip-block\",\n minWidth: \"anchor-size(width)\",\n ...resolveStateValue(style, s),\n }),\n children: content,\n props: rest,\n libraryProps,\n ref,\n });\n\n return useTransitionBoundary(\"Combobox.Popup\", transition, element);\n}\n\nComboboxPopup.displayName = \"ComboboxPopup\";\n"],"mappings":";;;;;;;;;;;;AA6DA,MAAM,QAAQ,QAAQ,IAAI,aAAa;AAEvC,MAAM,kBAAkB,UAAsC,MAAM,eAAe;;;;;;;;;;;;;AAcnF,SAAgB,cAAc,EAC5B,aACA,YACA,WACA,OACA,UACA,QACA,GAAG,QACkB;CACrB,MAAM,MAAM,mBAAmB,gBAAgB;CAC/C,MAAM,EAAE,YAAY;CAEpB,MAAM,EAAE,YAAY,SAAS,gBAAgB,gBAAgB,SAAS;EACpE,MAAM;EACN,cAAc,KAAA;EAEd,2BAA2B;EAC3B;EACA;CACF,CAAC;CAED,MAAM,SAAS,aAAa,YAAyB;EACnD,IAAI,SAAS,QAAQ,YAAY,OAC/B,QAAQ,MACN,+BAA+B,QAAQ,QAAQ,YAAY,EAAE,uGAE/D;EAEF,aAAa,CAAC;CAChB,GAAG,CAAC,CAAC;CACL,MAAM,MAAM,cAAc,UAAU,YAAY,MAAM,GAAG,CAAC,YAAY,MAAM,CAAC;CAE7E,MAAM,QAA4B;EAChC,MAAM,QAAQ;EACd,OAAO,IAAI;EACX,OAAO,IAAI;CACb;CAEA,MAAM,eAA8D;EAClE,IAAI,QAAQ,IAAI;EAChB,SAAS;EACT,gBAAgB,UAAwC,eAAe,KAAK;EAC5E,cAAc,UAAsC,eAAe,KAAK;EACxE,cAAc,SAAS,IAAI,KAAK;CAClC;CAEA,MAAM,WAAW,MAAqC;EACpD,IAAI;EACJ,IAAI,gBAAgB,MAClB,OACE,oBAAC,UAAD;GAAU,MAAM,UAAU,YAAY;GACnC,UAAA,YAAY,kBAAkB,UAAU,CAAC,CAAC;EACnC,CAAA;OAGZ,OAAO,UAAU,YAAY,kBAAkB,UAAU,CAAC,CAAC,IAAI;EAGjE,OACE,oBAAC,sBAAD;GAAsB,OAAO;GAC3B,UAAA,oBAAC,gBAAD;IAAgB,OAAO;IAAO,UAAA;GAAqB,CAAA;EAC/B,CAAA;CAE1B;CAEA,MAAM,UAAU,UAA4D;EAC1E,gBAAgB;EAChB;EACA;EACA;EAEA,QAAQ,OAAO;GACb,cAAc;GACd,sBAAsB;GACtB,UAAU;GACV,GAAG,kBAAkB,OAAO,CAAC;EAC/B;EACA,UAAU;EACV,OAAO;EACP;EACA;CACF,CAAC;CAED,OAAO,sBAAsB,kBAAkB,YAAY,OAAO;AACpE;AAEA,cAAc,cAAc"}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { createActionDetails } from "../internal/action-details.mjs";
3
3
  import { useControllableAction } from "../internal/use-controllable-action.mjs";
4
- import { resolveStateValue } from "../internal/use-render.mjs";
4
+ import { resolveStateValue } from "../internal/render-element.mjs";
5
5
  import { useDevWarning } from "../internal/use-dev-warning.mjs";
6
6
  import { FieldContext } from "../internal/field-context.mjs";
7
7
  import { useFieldValueChange } from "../internal/use-field-control.mjs";
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { dataAttr } from "../internal/data-attr.mjs";
3
- import { rendersButton, useRender } from "../internal/use-render.mjs";
3
+ import { rendersButton } from "../internal/render-element.mjs";
4
+ import { useRender } from "../internal/use-render.mjs";
4
5
  import { useComboboxContext } from "./combobox-context.mjs";
5
6
  import { useCallback, useRef } from "react";
6
7
  //#region src/combobox/combobox-trigger.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"combobox-trigger.mjs","names":[],"sources":["../../src/combobox/combobox-trigger.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useRef } from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n MouseEvent,\n PointerEvent,\n ReactNode,\n} from \"react\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { useComboboxContext } from \"./combobox-context.js\";\n\nexport interface ComboboxTriggerState {\n /** Optimistic open state. */\n readonly open: boolean;\n /** The open (or close) transition is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface ComboboxTriggerProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n | \"onClick\"\n | \"onPointerDown\"\n | \"onMouseDown\"\n | \"onPointerCancel\"\n | \"tabIndex\"\n | \"aria-expanded\"\n | \"aria-controls\"\n | \"disabled\"\n | \"className\"\n | \"style\"\n | \"children\"\n> {\n /** Disables this button; the Root's `disabled` does too. */\n disabled?: boolean;\n className?: StateValue<string | undefined, ComboboxTriggerState>;\n style?: StateValue<CSSProperties | undefined, ComboboxTriggerState>;\n /** Rendered from optimistic state; must not suspend. */\n children?: StateValue<ReactNode, ComboboxTriggerState>;\n /** Should render a `<button>`. */\n render?: RenderProp<ComponentPropsWithRef<\"button\">, ComboboxTriggerState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * Development check (design 9): an icon-only Trigger needs a name from the consumer. Checked a\n * microtask after the element attaches, once per instance.\n */\nexport function useNameWarning(part: string): (element: HTMLElement) => () => void {\n const warned = useRef(false);\n return useCallback(\n (element: HTMLElement) => {\n if (!isDev || warned.current) return () => {};\n queueMicrotask(() => {\n if (warned.current || !element.isConnected) return;\n if (element.hasAttribute(\"aria-label\") || element.hasAttribute(\"aria-labelledby\")) return;\n if ((element.textContent ?? \"\").trim() !== \"\" || element.hasAttribute(\"title\")) return;\n warned.current = true;\n console.error(`${part}: the button has no accessible name. Give it an \\`aria-label\\`.`);\n });\n return () => {};\n },\n [part],\n );\n}\n\n/**\n * The optional button that toggles the listbox: `<button type=\"button\" tabindex=\"-1\"\n * aria-expanded aria-controls>` (the APG keeps it out of the Tab sequence; the input is the one\n * stop). A press does not take focus from the input, and a click focuses the input. No\n * `commandfor`: the listbox is anchored to the input, never to this button (design 1).\n *\n * Owned keys: `type` (on a `<button>`), `tabIndex`, `disabled`, `aria-expanded`,\n * `aria-controls` (token-merged), `onClick`, `onPointerDown`, `onPointerCancel`, `onMouseDown`,\n * `data-open`, `data-pending`, `data-disabled`.\n */\nexport function ComboboxTrigger({\n disabled: ownDisabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: ComboboxTriggerProps) {\n const ctx = useComboboxContext(\"Combobox.Trigger\");\n const { popover } = ctx;\n const disabled = ownDisabled || ctx.disabled;\n // What the user saw at the press: a press outside the popup light-dismisses it before the\n // click arrives, so the toggle is decided against the state at `pointerdown`.\n const openAtPress = useRef<boolean | null>(null);\n const ref = useNameWarning(\"Combobox.Trigger\");\n\n const state: ComboboxTriggerState = {\n open: popover.optimistic,\n pending: popover.pending,\n disabled,\n };\n\n const libraryProps: ComponentPropsWithRef<\"button\"> & DataAttributes = {\n ...(rendersButton(render) && { type: \"button\" as const }),\n tabIndex: -1,\n disabled,\n \"aria-expanded\": popover.optimistic,\n \"aria-controls\": ctx.ids.list,\n onPointerDown: (event: PointerEvent<HTMLButtonElement>) => {\n openAtPress.current = popover.intended();\n event.preventDefault();\n },\n onPointerCancel: () => {\n // The press never becomes a click.\n openAtPress.current = null;\n },\n onMouseDown: (event: MouseEvent<HTMLButtonElement>) => {\n event.preventDefault();\n },\n onClick: (event: MouseEvent<HTMLButtonElement>) => {\n // `detail === 0`: a click without a press (assistive technology, `element.click()`); a\n // snapshot left by a press that never clicked (dragged off) does not belong to it.\n const pressed = event.detail === 0 ? null : openAtPress.current;\n const wasOpen = pressed ?? popover.intended();\n openAtPress.current = null;\n if (disabled) return;\n const native = event.nativeEvent;\n if (wasOpen) popover.requestClose(native, \"activate\");\n else popover.requestOpen(native, \"activate\");\n ctx.inputRef.current?.focus();\n },\n \"data-open\": dataAttr(popover.optimistic),\n \"data-pending\": dataAttr(popover.pending),\n \"data-disabled\": dataAttr(disabled),\n };\n\n return useRender<ComponentPropsWithRef<\"button\">, ComboboxTriggerState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps,\n ref,\n });\n}\n\nComboboxTrigger.displayName = \"ComboboxTrigger\";\n"],"mappings":";;;;;;AA+CA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;AAMvC,SAAgB,eAAe,MAAoD;CACjF,MAAM,SAAS,OAAO,KAAK;CAC3B,OAAO,aACJ,YAAyB;EACxB,IAAI,CAAC,SAAS,OAAO,SAAS,aAAa,CAAC;EAC5C,qBAAqB;GACnB,IAAI,OAAO,WAAW,CAAC,QAAQ,aAAa;GAC5C,IAAI,QAAQ,aAAa,YAAY,KAAK,QAAQ,aAAa,iBAAiB,GAAG;GACnF,KAAK,QAAQ,eAAe,GAAA,CAAI,KAAK,MAAM,MAAM,QAAQ,aAAa,OAAO,GAAG;GAChF,OAAO,UAAU;GACjB,QAAQ,MAAM,GAAG,KAAK,gEAAgE;EACxF,CAAC;EACD,aAAa,CAAC;CAChB,GACA,CAAC,IAAI,CACP;AACF;;;;;;;;;;;AAYA,SAAgB,gBAAgB,EAC9B,UAAU,cAAc,OACxB,WACA,OACA,UACA,QACA,GAAG,QACoB;CACvB,MAAM,MAAM,mBAAmB,kBAAkB;CACjD,MAAM,EAAE,YAAY;CACpB,MAAM,WAAW,eAAe,IAAI;CAGpC,MAAM,cAAc,OAAuB,IAAI;CAC/C,MAAM,MAAM,eAAe,kBAAkB;CAE7C,MAAM,QAA8B;EAClC,MAAM,QAAQ;EACd,SAAS,QAAQ;EACjB;CACF;CAEA,MAAM,eAAiE;EACrE,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;EACvD,UAAU;EACV;EACA,iBAAiB,QAAQ;EACzB,iBAAiB,IAAI,IAAI;EACzB,gBAAgB,UAA2C;GACzD,YAAY,UAAU,QAAQ,SAAS;GACvC,MAAM,eAAe;EACvB;EACA,uBAAuB;GAErB,YAAY,UAAU;EACxB;EACA,cAAc,UAAyC;GACrD,MAAM,eAAe;EACvB;EACA,UAAU,UAAyC;GAIjD,MAAM,WADU,MAAM,WAAW,IAAI,OAAO,YAAY,YAC7B,QAAQ,SAAS;GAC5C,YAAY,UAAU;GACtB,IAAI,UAAU;GACd,MAAM,SAAS,MAAM;GACrB,IAAI,SAAS,QAAQ,aAAa,QAAQ,UAAU;QAC/C,QAAQ,YAAY,QAAQ,UAAU;GAC3C,IAAI,SAAS,SAAS,MAAM;EAC9B;EACA,aAAa,SAAS,QAAQ,UAAU;EACxC,gBAAgB,SAAS,QAAQ,OAAO;EACxC,iBAAiB,SAAS,QAAQ;CACpC;CAEA,OAAO,UAAiE;EACtE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;EACA;CACF,CAAC;AACH;AAEA,gBAAgB,cAAc"}
1
+ {"version":3,"file":"combobox-trigger.mjs","names":[],"sources":["../../src/combobox/combobox-trigger.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useRef } from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n MouseEvent,\n PointerEvent,\n ReactNode,\n} from \"react\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { useComboboxContext } from \"./combobox-context.js\";\n\nexport interface ComboboxTriggerState {\n /** Optimistic open state. */\n readonly open: boolean;\n /** The open (or close) transition is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface ComboboxTriggerProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n | \"onClick\"\n | \"onPointerDown\"\n | \"onMouseDown\"\n | \"onPointerCancel\"\n | \"tabIndex\"\n | \"aria-expanded\"\n | \"aria-controls\"\n | \"disabled\"\n | \"className\"\n | \"style\"\n | \"children\"\n> {\n /** Disables this button; the Root's `disabled` does too. */\n disabled?: boolean;\n className?: StateValue<string | undefined, ComboboxTriggerState>;\n style?: StateValue<CSSProperties | undefined, ComboboxTriggerState>;\n /** Rendered from optimistic state; must not suspend. */\n children?: StateValue<ReactNode, ComboboxTriggerState>;\n /** Should render a `<button>`. */\n render?: RenderProp<ComponentPropsWithRef<\"button\">, ComboboxTriggerState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * Development check (design 9): an icon-only Trigger needs a name from the consumer. Checked a\n * microtask after the element attaches, once per instance.\n */\nexport function useNameWarning(part: string): (element: HTMLElement) => () => void {\n const warned = useRef(false);\n return useCallback(\n (element: HTMLElement) => {\n if (!isDev || warned.current) return () => {};\n queueMicrotask(() => {\n if (warned.current || !element.isConnected) return;\n if (element.hasAttribute(\"aria-label\") || element.hasAttribute(\"aria-labelledby\")) return;\n if ((element.textContent ?? \"\").trim() !== \"\" || element.hasAttribute(\"title\")) return;\n warned.current = true;\n console.error(`${part}: the button has no accessible name. Give it an \\`aria-label\\`.`);\n });\n return () => {};\n },\n [part],\n );\n}\n\n/**\n * The optional button that toggles the listbox: `<button type=\"button\" tabindex=\"-1\"\n * aria-expanded aria-controls>` (the APG keeps it out of the Tab sequence; the input is the one\n * stop). A press does not take focus from the input, and a click focuses the input. No\n * `commandfor`: the listbox is anchored to the input, never to this button (design 1).\n *\n * Owned keys: `type` (on a `<button>`), `tabIndex`, `disabled`, `aria-expanded`,\n * `aria-controls` (token-merged), `onClick`, `onPointerDown`, `onPointerCancel`, `onMouseDown`,\n * `data-open`, `data-pending`, `data-disabled`.\n */\nexport function ComboboxTrigger({\n disabled: ownDisabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: ComboboxTriggerProps) {\n const ctx = useComboboxContext(\"Combobox.Trigger\");\n const { popover } = ctx;\n const disabled = ownDisabled || ctx.disabled;\n // What the user saw at the press: a press outside the popup light-dismisses it before the\n // click arrives, so the toggle is decided against the state at `pointerdown`.\n const openAtPress = useRef<boolean | null>(null);\n const ref = useNameWarning(\"Combobox.Trigger\");\n\n const state: ComboboxTriggerState = {\n open: popover.optimistic,\n pending: popover.pending,\n disabled,\n };\n\n const libraryProps: ComponentPropsWithRef<\"button\"> & DataAttributes = {\n ...(rendersButton(render) && { type: \"button\" as const }),\n tabIndex: -1,\n disabled,\n \"aria-expanded\": popover.optimistic,\n \"aria-controls\": ctx.ids.list,\n onPointerDown: (event: PointerEvent<HTMLButtonElement>) => {\n openAtPress.current = popover.intended();\n event.preventDefault();\n },\n onPointerCancel: () => {\n // The press never becomes a click.\n openAtPress.current = null;\n },\n onMouseDown: (event: MouseEvent<HTMLButtonElement>) => {\n event.preventDefault();\n },\n onClick: (event: MouseEvent<HTMLButtonElement>) => {\n // `detail === 0`: a click without a press (assistive technology, `element.click()`); a\n // snapshot left by a press that never clicked (dragged off) does not belong to it.\n const pressed = event.detail === 0 ? null : openAtPress.current;\n const wasOpen = pressed ?? popover.intended();\n openAtPress.current = null;\n if (disabled) return;\n const native = event.nativeEvent;\n if (wasOpen) popover.requestClose(native, \"activate\");\n else popover.requestOpen(native, \"activate\");\n ctx.inputRef.current?.focus();\n },\n \"data-open\": dataAttr(popover.optimistic),\n \"data-pending\": dataAttr(popover.pending),\n \"data-disabled\": dataAttr(disabled),\n };\n\n return useRender<ComponentPropsWithRef<\"button\">, ComboboxTriggerState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps,\n ref,\n });\n}\n\nComboboxTrigger.displayName = \"ComboboxTrigger\";\n"],"mappings":";;;;;;;AA+CA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;AAMvC,SAAgB,eAAe,MAAoD;CACjF,MAAM,SAAS,OAAO,KAAK;CAC3B,OAAO,aACJ,YAAyB;EACxB,IAAI,CAAC,SAAS,OAAO,SAAS,aAAa,CAAC;EAC5C,qBAAqB;GACnB,IAAI,OAAO,WAAW,CAAC,QAAQ,aAAa;GAC5C,IAAI,QAAQ,aAAa,YAAY,KAAK,QAAQ,aAAa,iBAAiB,GAAG;GACnF,KAAK,QAAQ,eAAe,GAAA,CAAI,KAAK,MAAM,MAAM,QAAQ,aAAa,OAAO,GAAG;GAChF,OAAO,UAAU;GACjB,QAAQ,MAAM,GAAG,KAAK,gEAAgE;EACxF,CAAC;EACD,aAAa,CAAC;CAChB,GACA,CAAC,IAAI,CACP;AACF;;;;;;;;;;;AAYA,SAAgB,gBAAgB,EAC9B,UAAU,cAAc,OACxB,WACA,OACA,UACA,QACA,GAAG,QACoB;CACvB,MAAM,MAAM,mBAAmB,kBAAkB;CACjD,MAAM,EAAE,YAAY;CACpB,MAAM,WAAW,eAAe,IAAI;CAGpC,MAAM,cAAc,OAAuB,IAAI;CAC/C,MAAM,MAAM,eAAe,kBAAkB;CAE7C,MAAM,QAA8B;EAClC,MAAM,QAAQ;EACd,SAAS,QAAQ;EACjB;CACF;CAEA,MAAM,eAAiE;EACrE,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;EACvD,UAAU;EACV;EACA,iBAAiB,QAAQ;EACzB,iBAAiB,IAAI,IAAI;EACzB,gBAAgB,UAA2C;GACzD,YAAY,UAAU,QAAQ,SAAS;GACvC,MAAM,eAAe;EACvB;EACA,uBAAuB;GAErB,YAAY,UAAU;EACxB;EACA,cAAc,UAAyC;GACrD,MAAM,eAAe;EACvB;EACA,UAAU,UAAyC;GAIjD,MAAM,WADU,MAAM,WAAW,IAAI,OAAO,YAAY,YAC7B,QAAQ,SAAS;GAC5C,YAAY,UAAU;GACtB,IAAI,UAAU;GACd,MAAM,SAAS,MAAM;GACrB,IAAI,SAAS,QAAQ,aAAa,QAAQ,UAAU;QAC/C,QAAQ,YAAY,QAAQ,UAAU;GAC3C,IAAI,SAAS,SAAS,MAAM;EAC9B;EACA,aAAa,SAAS,QAAQ,UAAU;EACxC,gBAAgB,SAAS,QAAQ,OAAO;EACxC,iBAAiB,SAAS,QAAQ;CACpC;CAEA,OAAO,UAAiE;EACtE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;EACA;CACF,CAAC;AACH;AAEA,gBAAgB,cAAc"}
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  import { createActionDetails } from "../internal/action-details.mjs";
3
3
  import { dataAttr } from "../internal/data-attr.mjs";
4
- import { rendersButton, useRender } from "../internal/use-render.mjs";
4
+ import { rendersButton } from "../internal/render-element.mjs";
5
+ import { useRender } from "../internal/use-render.mjs";
5
6
  import { useActionTrigger } from "../internal/use-action-trigger.mjs";
6
7
  import { useDialogContext } from "./dialog-context.mjs";
7
8
  import { useCallback } from "react";
@@ -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 (`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
+ {"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"}
@@ -20,14 +20,18 @@ export interface DialogFormProps extends Omit<ComponentPropsWithRef<"form">, "me
20
20
  action?: string | ((formData: FormData) => unknown);
21
21
  /**
22
22
  * With a function `action`: close the dialog once the action's transition settles without
23
- * throwing. Default `true`.
23
+ * throwing and, when this form is rendered as a `Form.Root`
24
+ * (`render={<Form.Root errors={state.errors} />}`), with no field error: a settle whose
25
+ * `errors` has a non-empty entry (hidden stale ones included, the `autoReset` rule) keeps
26
+ * the dialog open and the Form focuses the first errored field. Default `true`.
24
27
  *
25
- * Any result that does not throw counts as success, including a `useActionState`-style result
26
- * that reports validation errors: with the default the dialog closes and those errors are never
27
- * seen. Throw to keep the dialog open, or set `closeOnSettle={false}` and close it yourself:
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.)
28
+ * Without a `Form.Root` the library never sees a result, so any settle that does not throw
29
+ * counts, including a `useActionState` result that reports errors: render the form as a
30
+ * `Form.Root` to route them, throw to keep the dialog open, or set `closeOnSettle={false}`
31
+ * and close it yourself (a `Dialog.Close`, a submit button in a separate `method="dialog"`
32
+ * form, or a controlled `open`). A `formMethod="dialog"` submitter inside this form cannot
33
+ * close it: React prevents every submit of a form with a function `action` and runs the
34
+ * action again.
31
35
  *
32
36
  * The dialog is not closed either when it was closed (Escape, a Close part) while the action
33
37
  * ran, also if the user re-opened it meanwhile.
@@ -49,7 +53,9 @@ export interface DialogFormProps extends Omit<ComponentPropsWithRef<"form">, "me
49
53
  * in library mode the Popup cancels the submission and closes through the Root). With a
50
54
  * function `action`: `<form action={action}>` (React overrides the method) and a status watcher
51
55
  * that closes the dialog once the action has settled, with the submitter's `value`, unless
52
- * `closeOnSettle` is `false` or the dialog was closed while the action ran.
56
+ * `closeOnSettle` is `false`, the dialog was closed while the action ran, or the form is rendered
57
+ * as a `Form.Root` and the result has a field error (`render={<Form.Root errors={...} />}`, the
58
+ * recommended composition for validation).
53
59
  *
54
60
  * `DialogFormState.pending` is this form's own status; it is not emitted as `data-pending` and
55
61
  * is not folded into any control's `pending` (conventions 2.7). Submit buttons read
@@ -1 +1 @@
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
+ {"version":3,"file":"dialog-form.d.mts","names":[],"sources":["../../src/dialog/dialog-form.tsx"],"mappings":";;;iBAWiB;;WAEN;;iBAGM,wBAAwB,KACvC;;;;;;;;;;;;EAcA,oBAAoB,UAAU;;;;;;;;;;;;;;;;;;;EAmB9B;;;;;EAKA,SAAS;EACT,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,+BAA+B;;;;;;;;;;;;;;;;;;wBAmBrC,aACd,QACA,eACA,QACA,WACA,OACA,UACA,WACG,QACF,kBAAe;yBATF"}
@@ -1,8 +1,12 @@
1
1
  "use client";
2
- import { resolveStateValue, useRender } from "../internal/use-render.mjs";
2
+ import { resolveStateValue } from "../internal/render-element.mjs";
3
+ import { useRender } from "../internal/use-render.mjs";
4
+ import { useDevWarning } from "../internal/use-dev-warning.mjs";
5
+ import { FormContext } from "../internal/field-context.mjs";
3
6
  import { useDialogContext } from "./dialog-context.mjs";
4
7
  import { FormStatusWatcher } from "../internal/form-status-watcher.mjs";
5
- import { useCallback, useRef, useState } from "react";
8
+ import { hasAnyError } from "../internal/validity.mjs";
9
+ import { use, useCallback, useRef, useState } from "react";
6
10
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
11
  //#region src/dialog/dialog-form.tsx
8
12
  /**
@@ -11,7 +15,9 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
15
  * in library mode the Popup cancels the submission and closes through the Root). With a
12
16
  * function `action`: `<form action={action}>` (React overrides the method) and a status watcher
13
17
  * that closes the dialog once the action has settled, with the submitter's `value`, unless
14
- * `closeOnSettle` is `false` or the dialog was closed while the action ran.
18
+ * `closeOnSettle` is `false`, the dialog was closed while the action ran, or the form is rendered
19
+ * as a `Form.Root` and the result has a field error (`render={<Form.Root errors={...} />}`, the
20
+ * recommended composition for validation).
15
21
  *
16
22
  * `DialogFormState.pending` is this form's own status; it is not emitted as `data-pending` and
17
23
  * is not folded into any control's `pending` (conventions 2.7). Submit buttons read
@@ -48,11 +54,12 @@ function DialogForm({ action, closeOnSettle = true, method, className, style, ch
48
54
  dialog?.removeEventListener("close", onClose);
49
55
  };
50
56
  }, []);
51
- const onSettled = useCallback(() => {
57
+ const onSettled = useCallback((clean) => {
52
58
  const dialog = dialogRef.current;
53
59
  const submitted = submission.current;
54
60
  submission.current = null;
55
61
  if (!closeOnSettle || closedSinceSubmit.current) return;
62
+ if (!clean) return;
56
63
  if (dialog === null || !dialog.open) return;
57
64
  const returnValue = submitted?.returnValue ?? "";
58
65
  if (isLibrary) {
@@ -77,7 +84,9 @@ function DialogForm({ action, closeOnSettle = true, method, className, style, ch
77
84
  state: { pending },
78
85
  className,
79
86
  style,
80
- children: (s) => /* @__PURE__ */ jsxs(Fragment, { children: [resolveStateValue(children, s), watches && /* @__PURE__ */ jsx(FormStatusWatcher, {
87
+ children: (s) => /* @__PURE__ */ jsxs(Fragment, { children: [resolveStateValue(children, s), /* @__PURE__ */ jsx(DialogFormSettle, {
88
+ watches,
89
+ missingAction: action === void 0,
81
90
  onPendingChange: setPending,
82
91
  onSettled
83
92
  })] }),
@@ -90,6 +99,28 @@ function DialogForm({ action, closeOnSettle = true, method, className, style, ch
90
99
  });
91
100
  }
92
101
  DialogForm.displayName = "DialogForm";
102
+ /**
103
+ * Rendered among the form's children, so it reads the `FormContext` exactly when the form is a
104
+ * `Form.Root` (either orientation of the `render` composition): the settle close then waits for
105
+ * a result with no field error (`hasAnyError`, the `autoReset` predicate, hidden stale entries
106
+ * included).
107
+ */
108
+ function DialogFormSettle({ watches, missingAction, onPendingChange, onSettled }) {
109
+ const form = use(FormContext);
110
+ useDevWarning(form !== null && missingAction, "`Dialog.Form` rendered as a `Form.Root` needs a function `action`; without one the form navigates instead of closing the dialog. Render a plain `<Dialog.Form>` for a `method=\"dialog\"` form.");
111
+ const isForm = form !== null;
112
+ const errors = form?.errors;
113
+ const settled = useCallback(() => onSettled(!isForm || !hasAnyError(errors)), [
114
+ isForm,
115
+ errors,
116
+ onSettled
117
+ ]);
118
+ if (!watches) return null;
119
+ return /* @__PURE__ */ jsx(FormStatusWatcher, {
120
+ onPendingChange,
121
+ onSettled: settled
122
+ });
123
+ }
93
124
  //#endregion
94
125
  export { DialogForm };
95
126
 
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"dialog-form.mjs","names":[],"sources":["../../src/dialog/dialog-form.tsx"],"sourcesContent":["\"use client\";\nimport { use, useCallback, useRef, useState } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { FormContext } from \"../internal/field-context.js\";\nimport { FormStatusWatcher } from \"../internal/form-status-watcher.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport { hasAnyError } from \"../internal/validity.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 and, when this form is rendered as a `Form.Root`\n * (`render={<Form.Root errors={state.errors} />}`), with no field error: a settle whose\n * `errors` has a non-empty entry (hidden stale ones included, the `autoReset` rule) keeps\n * the dialog open and the Form focuses the first errored field. Default `true`.\n *\n * Without a `Form.Root` the library never sees a result, so any settle that does not throw\n * counts, including a `useActionState` result that reports errors: render the form as a\n * `Form.Root` to route them, throw to keep the dialog open, or set `closeOnSettle={false}`\n * and close it yourself (a `Dialog.Close`, a submit button in a separate `method=\"dialog\"`\n * form, or a controlled `open`). A `formMethod=\"dialog\"` submitter inside this form cannot\n * close it: React prevents every submit of a form with a function `action` and runs the\n * 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`, the dialog was closed while the action ran, or the form is rendered\n * as a `Form.Root` and the result has a field error (`render={<Form.Root errors={...} />}`, the\n * recommended composition for validation).\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 // `clean`: no field error in the settle's result (always `true` without a `Form.Root`).\n const onSettled = useCallback(\n (clean: boolean) => {\n const dialog = dialogRef.current;\n const submitted = submission.current;\n submission.current = null;\n if (!closeOnSettle || closedSinceSubmit.current) return;\n // A result with field errors keeps the dialog open; the Form.Root focuses the first errored\n // field (design/components/dialog.md, \"Dialog.Form and Form.Root\").\n if (!clean) 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 },\n [closeOnSettle, dialogRef, isLibrary, recordCloseIntent, requestClose],\n );\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 <DialogFormSettle\n watches={watches}\n missingAction={action === undefined}\n onPendingChange={setPending}\n onSettled={onSettled}\n />\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\n/**\n * Rendered among the form's children, so it reads the `FormContext` exactly when the form is a\n * `Form.Root` (either orientation of the `render` composition): the settle close then waits for\n * a result with no field error (`hasAnyError`, the `autoReset` predicate, hidden stale entries\n * included).\n */\nfunction DialogFormSettle({\n watches,\n missingAction,\n onPendingChange,\n onSettled,\n}: {\n readonly watches: boolean;\n readonly missingAction: boolean;\n readonly onPendingChange: (pending: boolean) => void;\n readonly onSettled: (clean: boolean) => void;\n}) {\n const form = use(FormContext);\n useDevWarning(\n form !== null && missingAction,\n \"`Dialog.Form` rendered as a `Form.Root` needs a function `action`; without one the form \" +\n \"navigates instead of closing the dialog. Render a plain `<Dialog.Form>` for a \" +\n '`method=\"dialog\"` form.',\n );\n const isForm = form !== null;\n const errors = form?.errors;\n const settled = useCallback(\n () => onSettled(!isForm || !hasAnyError(errors)),\n [isForm, errors, onSettled],\n );\n if (!watches) return null;\n return <FormStatusWatcher onPendingChange={onPendingChange} onSettled={settled} />;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA8EA,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;CAGL,MAAM,YAAY,aACf,UAAmB;EAClB,MAAM,SAAS,UAAU;EACzB,MAAM,YAAY,WAAW;EAC7B,WAAW,UAAU;EACrB,IAAI,CAAC,iBAAiB,kBAAkB,SAAS;EAGjD,IAAI,CAAC,OAAO;EACZ,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,GACA;EAAC;EAAe;EAAW;EAAW;EAAmB;CAAY,CACvE;CAIA,OAAO,UAA0D;EAC/D,gBAAgB;EAChB;EACA,OAAA,EAL+B,QAK3B;EACJ;EACA;EACA,WAAW,MACT,qBAAA,UAAA,EAAA,UAAA,CACG,kBAAkB,UAAU,CAAC,GAC9B,oBAAC,kBAAD;GACW;GACT,eAAe,WAAW,KAAA;GAC1B,iBAAiB;GACN;EACZ,CAAA,CACD,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;;;;;;;AAQzB,SAAS,iBAAiB,EACxB,SACA,eACA,iBACA,aAMC;CACD,MAAM,OAAO,IAAI,WAAW;CAC5B,cACE,SAAS,QAAQ,eACjB,iMAGF;CACA,MAAM,SAAS,SAAS;CACxB,MAAM,SAAS,MAAM;CACrB,MAAM,UAAU,kBACR,UAAU,CAAC,UAAU,CAAC,YAAY,MAAM,CAAC,GAC/C;EAAC;EAAQ;EAAQ;CAAS,CAC5B;CACA,IAAI,CAAC,SAAS,OAAO;CACrB,OAAO,oBAAC,mBAAD;EAAoC;EAAiB,WAAW;CAAU,CAAA;AACnF"}
@@ -1 +1 @@
1
- {"version":3,"file":"dialog-popup.d.mts","names":[],"sources":["../../src/dialog/dialog-popup.tsx"],"mappings":";;;iBA4BiB;;WAEN;WACA;;iBAGM,yBAAyB,KACxC;;;;;;;;;;;EAaA;;;;;;;;;;EAUA,eAAe,UAAU;;;;;;;EAOzB;;;;;;;EAOA;;;;;;;;;;;;;EAaA,aAAa;EACb,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,iCAAiC;;;;;;;;;;;;;;;;;;;;wBAiGvC,cACd,aACA,cACA,cACA,uBACA,YACA,WACA,OACA,UACA,WACG,QACF,mBAAgB;yBAXH"}
1
+ {"version":3,"file":"dialog-popup.d.mts","names":[],"sources":["../../src/dialog/dialog-popup.tsx"],"mappings":";;;iBA6BiB;;WAEN;WACA;;iBAGM,yBAAyB,KACxC;;;;;;;;;;;EAaA;;;;;;;;;;EAUA,eAAe,UAAU;;;;;;;EAOzB;;;;;;;EAOA;;;;;;;;;;;;;EAaA,aAAa;EACb,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,iCAAiC;;;;;;;;;;;;;;;;;;;;wBAiGvC,cACd,aACA,cACA,cACA,uBACA,YACA,WACA,OACA,UACA,WACG,QACF,mBAAgB;yBAXH"}