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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/dist/accordion/accordion-root.mjs +2 -1
  2. package/dist/accordion/accordion-root.mjs.map +1 -1
  3. package/dist/combobox/combobox-clear.mjs +2 -1
  4. package/dist/combobox/combobox-clear.mjs.map +1 -1
  5. package/dist/combobox/combobox-list.mjs +2 -1
  6. package/dist/combobox/combobox-list.mjs.map +1 -1
  7. package/dist/combobox/combobox-popup.d.mts.map +1 -1
  8. package/dist/combobox/combobox-popup.mjs +7 -2
  9. package/dist/combobox/combobox-popup.mjs.map +1 -1
  10. package/dist/combobox/combobox-root.mjs +1 -1
  11. package/dist/combobox/combobox-trigger.mjs +2 -1
  12. package/dist/combobox/combobox-trigger.mjs.map +1 -1
  13. package/dist/dialog/dialog-close.mjs +2 -1
  14. package/dist/dialog/dialog-close.mjs.map +1 -1
  15. package/dist/dialog/dialog-form.d.mts +14 -8
  16. package/dist/dialog/dialog-form.d.mts.map +1 -1
  17. package/dist/dialog/dialog-form.mjs +36 -5
  18. package/dist/dialog/dialog-form.mjs.map +1 -1
  19. package/dist/dialog/dialog-popup.d.mts.map +1 -1
  20. package/dist/dialog/dialog-popup.mjs +13 -5
  21. package/dist/dialog/dialog-popup.mjs.map +1 -1
  22. package/dist/dialog/dialog-root.mjs +1 -1
  23. package/dist/dialog/dialog-trigger.mjs +2 -1
  24. package/dist/dialog/dialog-trigger.mjs.map +1 -1
  25. package/dist/field/field-root.mjs +5 -3
  26. package/dist/field/field-root.mjs.map +1 -1
  27. package/dist/form/form-root.mjs +2 -1
  28. package/dist/form/form-root.mjs.map +1 -1
  29. package/dist/form/form-submit.mjs +2 -1
  30. package/dist/form/form-submit.mjs.map +1 -1
  31. package/dist/index.d.mts +27 -1
  32. package/dist/index.mjs +6 -1
  33. package/dist/internal/announce.mjs +53 -0
  34. package/dist/internal/announce.mjs.map +1 -0
  35. package/dist/internal/decimal.mjs +27 -0
  36. package/dist/internal/decimal.mjs.map +1 -0
  37. package/dist/internal/dev-warn.mjs +17 -0
  38. package/dist/internal/dev-warn.mjs.map +1 -0
  39. package/dist/internal/field-context.mjs.map +1 -1
  40. package/dist/internal/range-value.mjs +51 -0
  41. package/dist/internal/range-value.mjs.map +1 -0
  42. package/dist/internal/render-element.mjs +54 -0
  43. package/dist/internal/render-element.mjs.map +1 -0
  44. package/dist/internal/toolbar-context.mjs +17 -0
  45. package/dist/internal/toolbar-context.mjs.map +1 -0
  46. package/dist/internal/use-exit-animation.mjs +26 -6
  47. package/dist/internal/use-exit-animation.mjs.map +1 -1
  48. package/dist/internal/use-field-control.mjs +5 -3
  49. package/dist/internal/use-field-control.mjs.map +1 -1
  50. package/dist/internal/use-popover-popup.mjs +3 -2
  51. package/dist/internal/use-popover-popup.mjs.map +1 -1
  52. package/dist/internal/use-popover-trigger.mjs +1 -1
  53. package/dist/internal/use-render.mjs +11 -34
  54. package/dist/internal/use-render.mjs.map +1 -1
  55. package/dist/internal/use-roving-focus.mjs +255 -0
  56. package/dist/internal/use-roving-focus.mjs.map +1 -0
  57. package/dist/menu/menu-checkbox-item.mjs +2 -1
  58. package/dist/menu/menu-checkbox-item.mjs.map +1 -1
  59. package/dist/menu/menu-item.mjs +2 -1
  60. package/dist/menu/menu-item.mjs.map +1 -1
  61. package/dist/menu/menu-popup.d.mts.map +1 -1
  62. package/dist/menu/menu-popup.mjs +7 -2
  63. package/dist/menu/menu-popup.mjs.map +1 -1
  64. package/dist/menu/menu-radio-item.mjs +2 -1
  65. package/dist/menu/menu-radio-item.mjs.map +1 -1
  66. package/dist/menu/menu-root.mjs +1 -1
  67. package/dist/menu/menu-trigger.d.mts +2 -1
  68. package/dist/menu/menu-trigger.d.mts.map +1 -1
  69. package/dist/menu/menu-trigger.mjs +15 -4
  70. package/dist/menu/menu-trigger.mjs.map +1 -1
  71. package/dist/meter/index.d.mts +24 -0
  72. package/dist/meter/index.d.mts.map +1 -0
  73. package/dist/meter/index.mjs +23 -0
  74. package/dist/meter/index.mjs.map +1 -0
  75. package/dist/meter/meter-context.mjs +13 -0
  76. package/dist/meter/meter-context.mjs.map +1 -0
  77. package/dist/meter/meter-indicator.d.mts +19 -0
  78. package/dist/meter/meter-indicator.d.mts.map +1 -0
  79. package/dist/meter/meter-indicator.mjs +25 -0
  80. package/dist/meter/meter-indicator.mjs.map +1 -0
  81. package/dist/meter/meter-label.d.mts +19 -0
  82. package/dist/meter/meter-label.d.mts.map +1 -0
  83. package/dist/meter/meter-label.mjs +28 -0
  84. package/dist/meter/meter-label.mjs.map +1 -0
  85. package/dist/meter/meter-region.d.mts +4 -0
  86. package/dist/meter/meter-region.d.mts.map +1 -0
  87. package/dist/meter/meter-region.mjs +39 -0
  88. package/dist/meter/meter-region.mjs.map +1 -0
  89. package/dist/meter/meter-root.d.mts +71 -0
  90. package/dist/meter/meter-root.d.mts.map +1 -0
  91. package/dist/meter/meter-root.mjs +83 -0
  92. package/dist/meter/meter-root.mjs.map +1 -0
  93. package/dist/meter/meter-track.d.mts +19 -0
  94. package/dist/meter/meter-track.d.mts.map +1 -0
  95. package/dist/meter/meter-track.mjs +25 -0
  96. package/dist/meter/meter-track.mjs.map +1 -0
  97. package/dist/meter/meter-value.d.mts +20 -0
  98. package/dist/meter/meter-value.d.mts.map +1 -0
  99. package/dist/meter/meter-value.mjs +29 -0
  100. package/dist/meter/meter-value.mjs.map +1 -0
  101. package/dist/number-field/index.d.mts +26 -0
  102. package/dist/number-field/index.d.mts.map +1 -0
  103. package/dist/number-field/index.mjs +26 -0
  104. package/dist/number-field/index.mjs.map +1 -0
  105. package/dist/number-field/number-field-arithmetic.mjs +78 -0
  106. package/dist/number-field/number-field-arithmetic.mjs.map +1 -0
  107. package/dist/number-field/number-field-context.d.mts +2 -0
  108. package/dist/number-field/number-field-context.mjs +13 -0
  109. package/dist/number-field/number-field-context.mjs.map +1 -0
  110. package/dist/number-field/number-field-decrement.d.mts +16 -0
  111. package/dist/number-field/number-field-decrement.d.mts.map +1 -0
  112. package/dist/number-field/number-field-decrement.mjs +19 -0
  113. package/dist/number-field/number-field-decrement.mjs.map +1 -0
  114. package/dist/number-field/number-field-format.mjs +279 -0
  115. package/dist/number-field/number-field-format.mjs.map +1 -0
  116. package/dist/number-field/number-field-increment.d.mts +32 -0
  117. package/dist/number-field/number-field-increment.d.mts.map +1 -0
  118. package/dist/number-field/number-field-increment.mjs +68 -0
  119. package/dist/number-field/number-field-increment.mjs.map +1 -0
  120. package/dist/number-field/number-field-input.d.mts +29 -0
  121. package/dist/number-field/number-field-input.d.mts.map +1 -0
  122. package/dist/number-field/number-field-input.mjs +163 -0
  123. package/dist/number-field/number-field-input.mjs.map +1 -0
  124. package/dist/number-field/number-field-label.d.mts +23 -0
  125. package/dist/number-field/number-field-label.d.mts.map +1 -0
  126. package/dist/number-field/number-field-label.mjs +47 -0
  127. package/dist/number-field/number-field-label.mjs.map +1 -0
  128. package/dist/number-field/number-field-root.d.mts +108 -0
  129. package/dist/number-field/number-field-root.d.mts.map +1 -0
  130. package/dist/number-field/number-field-root.mjs +523 -0
  131. package/dist/number-field/number-field-root.mjs.map +1 -0
  132. package/dist/number-field/use-step-button.mjs +107 -0
  133. package/dist/number-field/use-step-button.mjs.map +1 -0
  134. package/dist/popover/popover-close.mjs +2 -1
  135. package/dist/popover/popover-close.mjs.map +1 -1
  136. package/dist/popover/popover-popup.d.mts.map +1 -1
  137. package/dist/popover/popover-popup.mjs +7 -2
  138. package/dist/popover/popover-popup.mjs.map +1 -1
  139. package/dist/popover/popover-root.mjs +1 -1
  140. package/dist/progress/index.d.mts +24 -0
  141. package/dist/progress/index.d.mts.map +1 -0
  142. package/dist/progress/index.mjs +24 -0
  143. package/dist/progress/index.mjs.map +1 -0
  144. package/dist/progress/progress-context.mjs +13 -0
  145. package/dist/progress/progress-context.mjs.map +1 -0
  146. package/dist/progress/progress-indicator.d.mts +19 -0
  147. package/dist/progress/progress-indicator.d.mts.map +1 -0
  148. package/dist/progress/progress-indicator.mjs +29 -0
  149. package/dist/progress/progress-indicator.mjs.map +1 -0
  150. package/dist/progress/progress-label.d.mts +19 -0
  151. package/dist/progress/progress-label.d.mts.map +1 -0
  152. package/dist/progress/progress-label.mjs +30 -0
  153. package/dist/progress/progress-label.mjs.map +1 -0
  154. package/dist/progress/progress-root.d.mts +68 -0
  155. package/dist/progress/progress-root.d.mts.map +1 -0
  156. package/dist/progress/progress-root.mjs +80 -0
  157. package/dist/progress/progress-root.mjs.map +1 -0
  158. package/dist/progress/progress-track.d.mts +19 -0
  159. package/dist/progress/progress-track.d.mts.map +1 -0
  160. package/dist/progress/progress-track.mjs +29 -0
  161. package/dist/progress/progress-track.mjs.map +1 -0
  162. package/dist/progress/progress-value.d.mts +20 -0
  163. package/dist/progress/progress-value.d.mts.map +1 -0
  164. package/dist/progress/progress-value.mjs +33 -0
  165. package/dist/progress/progress-value.mjs.map +1 -0
  166. package/dist/slider/slider-arithmetic.mjs +1 -17
  167. package/dist/slider/slider-arithmetic.mjs.map +1 -1
  168. package/dist/slider/slider-root.mjs +3 -2
  169. package/dist/slider/slider-root.mjs.map +1 -1
  170. package/dist/slider/slider-thumb.mjs +2 -1
  171. package/dist/slider/slider-thumb.mjs.map +1 -1
  172. package/dist/slider/slider-track.mjs +2 -1
  173. package/dist/slider/slider-track.mjs.map +1 -1
  174. package/dist/tabs/tabs-indicator.mjs +2 -1
  175. package/dist/tabs/tabs-indicator.mjs.map +1 -1
  176. package/dist/tabs/tabs-tab.mjs +2 -1
  177. package/dist/tabs/tabs-tab.mjs.map +1 -1
  178. package/dist/toast/toast-action.mjs +2 -1
  179. package/dist/toast/toast-action.mjs.map +1 -1
  180. package/dist/toast/toast-close.mjs +2 -1
  181. package/dist/toast/toast-close.mjs.map +1 -1
  182. package/dist/toast/toast-root.mjs +3 -2
  183. package/dist/toast/toast-root.mjs.map +1 -1
  184. package/dist/toast/toast-viewport.d.mts.map +1 -1
  185. package/dist/toast/toast-viewport.mjs +30 -7
  186. package/dist/toast/toast-viewport.mjs.map +1 -1
  187. package/dist/toast/toast.d.mts.map +1 -1
  188. package/dist/toast/toast.mjs +5 -1
  189. package/dist/toast/toast.mjs.map +1 -1
  190. package/dist/toggle/toggle.mjs +2 -1
  191. package/dist/toggle/toggle.mjs.map +1 -1
  192. package/dist/toggle-group/index.d.mts +16 -0
  193. package/dist/toggle-group/index.d.mts.map +1 -0
  194. package/dist/toggle-group/index.mjs +15 -0
  195. package/dist/toggle-group/index.mjs.map +1 -0
  196. package/dist/toggle-group/toggle-group-context.d.mts +4 -0
  197. package/dist/toggle-group/toggle-group-context.d.mts.map +1 -0
  198. package/dist/toggle-group/toggle-group-context.mjs +13 -0
  199. package/dist/toggle-group/toggle-group-context.mjs.map +1 -0
  200. package/dist/toggle-group/toggle-group-item.d.mts +47 -0
  201. package/dist/toggle-group/toggle-group-item.d.mts.map +1 -0
  202. package/dist/toggle-group/toggle-group-item.mjs +63 -0
  203. package/dist/toggle-group/toggle-group-item.mjs.map +1 -0
  204. package/dist/toggle-group/toggle-group-root.d.mts +95 -0
  205. package/dist/toggle-group/toggle-group-root.d.mts.map +1 -0
  206. package/dist/toggle-group/toggle-group-root.mjs +211 -0
  207. package/dist/toggle-group/toggle-group-root.mjs.map +1 -0
  208. package/dist/toolbar/index.d.mts +18 -0
  209. package/dist/toolbar/index.d.mts.map +1 -0
  210. package/dist/toolbar/index.mjs +18 -0
  211. package/dist/toolbar/index.mjs.map +1 -0
  212. package/dist/toolbar/toolbar-button.d.mts +42 -0
  213. package/dist/toolbar/toolbar-button.d.mts.map +1 -0
  214. package/dist/toolbar/toolbar-button.mjs +50 -0
  215. package/dist/toolbar/toolbar-button.mjs.map +1 -0
  216. package/dist/toolbar/toolbar-root.d.mts +38 -0
  217. package/dist/toolbar/toolbar-root.d.mts.map +1 -0
  218. package/dist/toolbar/toolbar-root.mjs +66 -0
  219. package/dist/toolbar/toolbar-root.mjs.map +1 -0
  220. package/dist/toolbar/toolbar-separator.d.mts +25 -0
  221. package/dist/toolbar/toolbar-separator.d.mts.map +1 -0
  222. package/dist/toolbar/toolbar-separator.mjs +31 -0
  223. package/dist/toolbar/toolbar-separator.mjs.map +1 -0
  224. package/dist/tooltip/tooltip-popup.mjs +2 -1
  225. package/dist/tooltip/tooltip-popup.mjs.map +1 -1
  226. package/dist/tooltip/tooltip-root.mjs +16 -2
  227. package/dist/tooltip/tooltip-root.mjs.map +1 -1
  228. package/dist/tooltip/tooltip-trigger.mjs +2 -1
  229. package/dist/tooltip/tooltip-trigger.mjs.map +1 -1
  230. package/package.json +6 -1
@@ -1,11 +1,13 @@
1
1
  "use client";
2
2
  import { useTransitionBoundary } from "../internal/transition-boundary.mjs";
3
3
  import { dataAttr } from "../internal/data-attr.mjs";
4
- import { resolveStateValue, useRender } from "../internal/use-render.mjs";
4
+ import { resolveStateValue } from "../internal/render-element.mjs";
5
+ import { useRender } from "../internal/use-render.mjs";
6
+ import { ToolbarContext } from "../internal/toolbar-context.mjs";
5
7
  import { useDevWarning } from "../internal/use-dev-warning.mjs";
6
8
  import { useDialogContext } from "./dialog-context.mjs";
7
9
  import { useContentFocusProbe } from "../internal/content-focus-probe.mjs";
8
- import { exitAnimations, useExitAnimation } from "../internal/use-exit-animation.mjs";
10
+ import { useExitAnimation } from "../internal/use-exit-animation.mjs";
9
11
  import { useModalRegistration } from "../internal/use-modal-registration.mjs";
10
12
  import { Activity, useCallback, useEffect, useEffectEvent, useLayoutEffect, useRef, useState } from "react";
11
13
  import { jsx } from "react/jsx-runtime";
@@ -241,7 +243,7 @@ function DialogPopup({ keepMounted, initialFocus, lightDismiss = false, experime
241
243
  libraryClose.current = false;
242
244
  if (!isLibrary) return;
243
245
  if (!echo || transition === void 0) {
244
- const animations = exitAnimations(element);
246
+ const animations = exit.exitAnimations(element);
245
247
  if (animations.length > 0) exit.holdUntil(animations);
246
248
  else exit.release();
247
249
  }
@@ -255,9 +257,11 @@ function DialogPopup({ keepMounted, initialFocus, lightDismiss = false, experime
255
257
  };
256
258
  });
257
259
  const onBeforeToggle = useEffectEvent((event) => {
258
- if (event.target !== dialogRef.current) return;
260
+ const element = dialogRef.current;
261
+ if (element === null || event.target !== element) return;
259
262
  const newState = event.newState;
260
263
  awaitingClose.current = newState === "closed";
264
+ if (newState === "closed") exit.recordBaseline(element);
261
265
  if (newState === "open") {
262
266
  dismissedWhilePending.current = null;
263
267
  ctx.nextOpenGeneration();
@@ -395,7 +399,11 @@ function DialogPopup({ keepMounted, initialFocus, lightDismiss = false, experime
395
399
  modal
396
400
  };
397
401
  const visible = ctx.committed || exit.held;
398
- const content = (s) => {
402
+ const content = (s) => /* @__PURE__ */ jsx(ToolbarContext, {
403
+ value: null,
404
+ children: contentOf(s)
405
+ });
406
+ const contentOf = (s) => {
399
407
  if (!isLibrary) return resolveStateValue(children, s);
400
408
  if (keepMounted === true) return /* @__PURE__ */ jsx(Activity, {
401
409
  mode: visible ? "visible" : "hidden",
@@ -1 +1 @@
1
- {"version":3,"file":"dialog-popup.mjs","names":[],"sources":["../../src/dialog/dialog-popup.tsx"],"sourcesContent":["\"use client\";\nimport {\n Activity,\n useCallback,\n useEffect,\n useEffectEvent,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n ReactNode,\n RefObject,\n ViewTransitionClass,\n} from \"react\";\nimport { useContentFocusProbe } from \"../internal/content-focus-probe.js\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { exitAnimations, useExitAnimation } from \"../internal/use-exit-animation.js\";\nimport { useModalRegistration } from \"../internal/use-modal-registration.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogPopupState {\n /** Committed open state (library mode); `false` in JS-free mode. Read `:open` for the DOM truth. */\n readonly open: boolean;\n readonly modal: boolean;\n}\n\nexport interface DialogPopupProps extends Omit<\n ComponentPropsWithRef<\"dialog\">,\n \"id\" | \"open\" | \"closedby\" | \"onToggle\" | \"onBeforeToggle\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Library mode: what happens to the children while the dialog is closed.\n * - `false` (default): not rendered. Mounting them is part of the opening transition, so\n * content that suspends holds the open until it is ready. Unmount waits for the dialog's\n * exit animations.\n * - `true`: rendered inside `<Activity mode=\"hidden\">`: DOM and state preserved, effects\n * stopped, pre-rendered at low priority. Not in the server HTML.\n *\n * Ignored in JS-free mode (always mounted).\n */\n keepMounted?: boolean;\n /**\n * Element to focus when the dialog opens: a ref, or a selector resolved inside the dialog.\n * The library writes `autofocus` on it right before `showModal()` / `show()` and the platform\n * focuses it. Default: the platform's choice (first focusable descendant; the dialog itself if\n * none). Use `tabIndex={-1}` on a heading or wrapper for long or destructive dialogs (APG).\n *\n * In JS-free mode the browser opens the dialog itself, so the attribute is written after\n * hydration and kept while mounted; before hydration only `autoFocus` in the markup works.\n */\n initialFocus?: RefObject<HTMLElement | null> | string;\n /**\n * Close on a click outside the dialog (on the backdrop for modal dialogs). Default `false`.\n * Baseline: JS `pointerdown` + `click` listeners, which close only when both the press and the\n * click land outside (a drag that starts inside, such as selecting text, never closes); with\n * `experimental_closedBy`, native `closedby=\"any\"`, which behaves the same.\n */\n lightDismiss?: boolean;\n /**\n * Experimental (Chrome 134+, Firefox 141+; Safari 27.0 ignores it). Renders `closedby`\n * (`\"any\"` when `lightDismiss`, else `\"closerequest\"`) and skips the JS dismiss listeners where\n * `HTMLDialogElement.prototype.closedBy` exists. Elsewhere the JS baseline stays active.\n * Default `false`.\n */\n experimental_closedBy?: boolean;\n /**\n * Library mode only. Forwarded to the `<ViewTransition default>` around the dialog. Animates\n * the opening commit, content changes while open, and every library-driven close (a Close\n * part, a Form submit or settled action, a foreign close command, a controlled\n * `open={false}`), in all engines; dismissals (Escape, a click outside) are the browser's and\n * animate with CSS only. Ignored (dev warning) in JS-free mode.\n *\n * Static: without `transition` the part renders no `<ViewTransition>`; changing it between\n * `undefined` and a value remounts the element. Any value, `\"none\"` included, makes a mount or\n * `<Activity>` reveal of this part inside a transition start a document view transition: the\n * page cross-fades for about 250 ms and the next transition commit waits for it (issue #35).\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, DialogPopupState>;\n style?: StateValue<CSSProperties | undefined, DialogPopupState>;\n children?: StateValue<ReactNode, DialogPopupState>;\n /** Must render a `<dialog>`. */\n render?: RenderProp<ComponentPropsWithRef<\"dialog\">, DialogPopupState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/** Resolves `initialFocus` against the dialog (a ref, or a selector inside the dialog). */\nfunction resolveInitialFocus(\n dialog: HTMLDialogElement,\n initialFocus: RefObject<HTMLElement | null> | string,\n): HTMLElement | null {\n if (typeof initialFocus === \"string\") return dialog.querySelector<HTMLElement>(initialFocus);\n return initialFocus.current;\n}\n\n/** The nearest `<dialog>` around a node (the dialog itself counts). */\nfunction closestDialog(target: EventTarget | null): Element | null {\n return target instanceof Element ? target.closest(\"dialog\") : null;\n}\n\n/**\n * An open popover that takes Escape before the dialog: `auto` or `hint` (they have a close\n * watcher). A `manual` popover, or `hint` where the engine maps it to `manual`, does not.\n */\nfunction isOpenLightPopover(element: Element): boolean {\n if (!element.hasAttribute(\"popover\")) return false;\n // The IDL attribute reflects the state the engine applies; jsdom has none.\n const reflected: unknown = (element as HTMLElement).popover;\n const state =\n typeof reflected === \"string\" ? reflected : element.getAttribute(\"popover\")!.toLowerCase();\n if (state === \"manual\") return false;\n try {\n return element.matches(\":popover-open\");\n } catch {\n return false;\n }\n}\n\n/**\n * Whether Escape belongs to a layer inside `dialog` that sits above it (design 9: \"closes the\n * innermost one only\"): the target is inside an open nested `<dialog>` or popover, or an open\n * `auto` / `hint` popover inside the dialog would take the close request (close watchers are\n * per document, not per focus).\n */\nfunction escapeBelongsToInnerLayer(dialog: HTMLDialogElement, target: EventTarget | null): boolean {\n for (\n let node = target instanceof Element ? target : null;\n node !== null && node !== dialog;\n node = node.parentElement\n ) {\n if (node instanceof HTMLDialogElement && node.open) return true;\n if (isOpenLightPopover(node)) return true;\n }\n for (const element of dialog.querySelectorAll(\"[popover]\")) {\n if (isOpenLightPopover(element)) return true;\n }\n return false;\n}\n\n/** Whether a pointer event on the dialog itself lies outside its border box (the `::backdrop`). */\nfunction onBackdrop(dialog: HTMLDialogElement, event: Event): boolean {\n if (event.target !== dialog) return false;\n const { clientX, clientY } = event as MouseEvent;\n const rect = dialog.getBoundingClientRect();\n return !(\n rect.top <= clientY &&\n clientY <= rect.bottom &&\n rect.left <= clientX &&\n clientX <= rect.right\n );\n}\n\n/** The submitter's or invoker's `value` (`\"\"` without one, or where the engine gives no source). */\nfunction valueOf(element: unknown): string {\n if (element instanceof HTMLButtonElement || element instanceof HTMLInputElement) {\n return element.value;\n }\n return \"\";\n}\n\n/**\n * The dialog itself: a `<dialog>` with the id the Trigger's `commandfor` names, wired to Title\n * and Description through `aria-labelledby` / `aria-describedby`. Never renders `open`: modal\n * opening and closing go through `showModal()` / `close()` only. Style it with `:open` and\n * `:modal`; it emits `data-modal` but no `data-open`.\n *\n * In library mode the children render from committed state, so content that suspends keeps the\n * dialog closed (and the Trigger pending) until it is ready; `showModal()` runs in the layout\n * effect of the commit that mounted them. A close a part asks for (Close, a `method=\"dialog\"`\n * submit, a Form's settle close, a foreign close command) is cancelled and goes through the\n * Root: `openChangeAction(false)` is awaited and `close(returnValue)` runs in the layout effect\n * of the commit that ends it, inside the view transition if `transition` is set. A dismissal\n * (Escape, a click outside) is the browser's: the native `close` event becomes\n * `openChangeAction(false, { reason: \"dismiss\", returnValue })`.\n *\n * Owned keys (conventions 1.2): `id`, `open`, `closedby`, `aria-labelledby` and\n * `aria-describedby` (token-merged), `data-modal`, `suppressHydrationWarning`.\n */\nexport function DialogPopup({\n keepMounted,\n initialFocus,\n lightDismiss = false,\n experimental_closedBy = false,\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogPopupProps) {\n const ctx = useDialogContext(\"Dialog.Popup\");\n const isLibrary = ctx.mode === \"library\";\n const { modal, ids, dialogRef, triggerRef } = ctx;\n const [dialog, setDialog] = useState<HTMLDialogElement | null>(null);\n // Set by the synchronous `beforetoggle` of a close, cleared by the `close` event a task later:\n // the dialog is closed but its notification is not dispatched yet, so the apply effect must\n // not re-open it from a commit that lands in between.\n const awaitingClose = useRef(false);\n // Set right before the library's own `close()` (the apply effect), consumed by the `close`\n // event it causes: the echo is recognised by its source, not by what is intended (review L4).\n const libraryClose = useRef(false);\n // A dismissal (Escape, a click outside) that closed the dialog while a part-initiated close\n // was in flight (review M1). Its notification was skipped because `false` was intended; if that\n // close then settles with committed `true` (a controlled parent vetoed it), the apply effect\n // sends the dismissal instead of re-opening. Valid only for the open generation it was\n // recorded in: a re-open (Trigger, foreign invoker) supersedes it.\n const dismissedWhilePending = useRef<{\n readonly event: Event;\n readonly returnValue: string;\n readonly generation: number;\n } | null>(null);\n const warnedFocus = useRef(false);\n\n // --- `held`: children stay mounted while the close animates (design 4.4) -----------------\n\n const exit = useExitAnimation();\n\n // A library-driven close commits `false` first and closes in the layout effect, so without\n // `transition` the children are held through the close commit (\"previous value in state\").\n // With `transition` the close commit unmounts them inside the view transition, whose old\n // snapshot carries the content out. (A dismissal's notification commit sets it too; the apply\n // effect's release rule clears that one.)\n const [prevCommitted, setPrevCommitted] = useState(ctx.committed);\n if (prevCommitted !== ctx.committed) {\n setPrevCommitted(ctx.committed);\n if (isLibrary && prevCommitted && transition === undefined) exit.holdDuringRender();\n }\n\n // Whether the content unmounted while holding focus since the dialog was last shown: a close\n // with `transition` removes the focused Close part before `close()` runs (design 6.2 rule 2).\n const contentProbe = useContentFocusProbe(dialogRef);\n const { wrapContent } = contentProbe;\n\n // Toast's Viewport moves its region into the topmost open modal (design/components/toast.md 4.3).\n useModalRegistration(dialog);\n\n useDevWarning(\n !isLibrary && transition !== undefined,\n \"Dialog.Popup: `transition` has no effect in JS-free mode; the browser opens and closes the \" +\n \"dialog outside any React transition. Animate it with CSS (`@starting-style`).\",\n );\n useDevWarning(\n !isLibrary && keepMounted === false,\n \"Dialog.Popup: `keepMounted={false}` cannot be honoured in JS-free mode; the content is \" +\n \"always mounted so that the server HTML is complete.\",\n );\n\n const libraryRef = useCallback(\n (element: HTMLDialogElement) => {\n if (isDev && element.tagName !== \"DIALOG\") {\n console.error(\n `Dialog.Popup: expected to render a <dialog> element but got <${element.tagName.toLowerCase()}>. ` +\n \"Top layer, backdrop, focus and Escape all come from a real <dialog>.\",\n );\n }\n dialogRef.current = element;\n setDialog(element);\n return () => {\n if (dialogRef.current === element) dialogRef.current = null;\n setDialog(null);\n };\n },\n [dialogRef],\n );\n\n // --- Opening -----------------------------------------------------------------------------\n\n const openDialog = (element: HTMLDialogElement): void => {\n let target: HTMLElement | null = null;\n if (initialFocus !== undefined) {\n target = resolveInitialFocus(element, initialFocus);\n if (target === null || !element.contains(target)) {\n if (isDev && !warnedFocus.current) {\n warnedFocus.current = true;\n console.error(\n target === null\n ? \"Dialog.Popup: `initialFocus` resolved to no element; the platform picks the focus target.\"\n : \"Dialog.Popup: `initialFocus` is outside the dialog; the platform picks the focus target.\",\n );\n }\n target = null;\n }\n }\n // The platform's dialog focusing steps honour `autofocus`; React drops `autoFocus` on\n // client-rendered content (react/react#23301). Never `.focus()` (design 6.1).\n const added = target !== null && !target.hasAttribute(\"autofocus\");\n if (added) target!.setAttribute(\"autofocus\", \"\");\n try {\n if (modal) element.showModal();\n else element.show();\n } finally {\n if (added) target!.removeAttribute(\"autofocus\");\n }\n };\n\n // Mount reconciliation (design 2.4): a click before hydration opened the dialog natively.\n // Runs before the apply effect; the dispatch makes the apply effect wait for it.\n const reconcileOnMount = useEffectEvent(() => {\n const element = dialogRef.current;\n if (element === null || !element.open) return;\n ctx.requestOpen(undefined, \"platform\");\n });\n useLayoutEffect(() => {\n if (isLibrary) reconcileOnMount();\n }, [isLibrary]);\n\n /**\n * Design 6.2 rule 2, after a close whose commit unmounted the focused content: when focus is\n * now on `<body>` or still inside the closed dialog, focus the Trigger (if connected), else\n * blur what holds focus. A modal dialog never needs it (the platform restores focus on\n * `close()` unconditionally); a non-modal one restores only when focus is inside.\n */\n const returnFocus = (element: HTMLDialogElement): void => {\n const doc = element.ownerDocument;\n const active = doc.activeElement;\n if (active !== null && active !== doc.body && !element.contains(active)) return;\n const trigger = triggerRef.current;\n if (trigger !== null && trigger.isConnected) trigger.focus();\n else if (active instanceof HTMLElement && active !== doc.body) active.blur();\n };\n\n // The apply effect (design 2.5): after every commit that re-renders the Popup, once nothing\n // is in flight, the DOM follows committed state. The Root's context is memoised (#48), so an\n // unrelated Root render does not re-render the Popup and does not run this; every path that\n // makes the DOM diverge from state goes through a dispatch, whose `pending` flips re-render the\n // Popup (review of #48, finding 4). Do not rely on an unrelated render to re-apply state.\n // Opens on the user's dispatch, `defaultOpen`, a controlled parent, the re-open of a refused\n // dismissal; closes after a part-initiated close settled, a controlled close, or a refused\n // pre-hydration open. Also returns focus after a close whose commit unmounted the focused\n // content, and releases a `held` no exit animation is going to release (4.4).\n useLayoutEffect(() => {\n if (!isLibrary) return;\n const element = dialogRef.current;\n if (element === null || !element.isConnected) return;\n if (!ctx.pending && ctx.latestInFlight() === null) {\n const dismissed = dismissedWhilePending.current;\n if (ctx.committed && !element.open) {\n // Closed, but its `close` event has not arrived yet: wait for it.\n if (!awaitingClose.current) {\n dismissedWhilePending.current = null;\n if (dismissed !== null && dismissed.generation === ctx.openGeneration()) {\n // The user dismissed the dialog while a part's close was in flight, and that close\n // was vetoed: the dismissal stands and is notified now (review M1), instead of the\n // dialog coming back. A parent that keeps `open` again gets it re-opened, as for any\n // refused dismissal.\n ctx.notifyClosed(dismissed.event, \"dismiss\", dismissed.returnValue);\n } else {\n openDialog(element);\n }\n }\n } else if (!ctx.committed && element.open) {\n // The library's own close: the `close` listener recognises it by `libraryClose` and\n // dispatches nothing. `close()`, not `requestClose()`: the veto point was\n // `openChangeAction`, not `cancel`. The part's `returnValue` (design 2.3), `\"\"` for a\n // controlled close.\n ctx.recordCloseIntent(undefined);\n libraryClose.current = true;\n element.close(ctx.takeCloseReturnValue());\n // The close commit may have unmounted the focused content before `close()`: a\n // non-modal dialog restores focus only from inside, so do it now.\n if (contentProbe.hadFocus()) {\n contentProbe.forget();\n returnFocus(element);\n }\n }\n }\n // The in-flight close settled as a close: the recorded dismissal is moot.\n if (!ctx.committed && dismissedWhilePending.current !== null && ctx.latestInFlight() === null) {\n dismissedWhilePending.current = null;\n }\n // Content that unmounted while the dialog stays open (not a close): forget it.\n if (ctx.committed && element.open) contentProbe.forget();\n // A hold the close commit set (or a dismissal's notification commit) that no exit animation\n // is going to release: the dialog is closed and its `close` event has been handled.\n if (exit.held && !ctx.committed && !exit.waiting() && !awaitingClose.current && !element.open) {\n exit.release();\n }\n });\n\n // JS-free mode: there is no \"right before showModal()\" (the browser runs the command), so the\n // `autofocus` attribute stays on the target while it is mounted.\n useEffect(() => {\n if (isLibrary || initialFocus === undefined || dialog === null) return;\n const target = resolveInitialFocus(dialog, initialFocus);\n if (target === null || !dialog.contains(target) || target.hasAttribute(\"autofocus\")) return;\n target.setAttribute(\"autofocus\", \"\");\n return () => target.removeAttribute(\"autofocus\");\n }, [isLibrary, initialFocus, dialog]);\n\n // --- Native listeners (conventions 7.4) ---------------------------------------------------\n\n const onCommand = useEffectEvent((event: Event) => {\n if (event.target !== dialogRef.current || event.defaultPrevented) return;\n const command = String((event as Event & { command?: string }).command ?? \"\").toLowerCase();\n if (command === \"show-modal\") {\n // A foreign invoker (design 2.2): opening goes through the library in library mode.\n if (!isLibrary) return;\n event.preventDefault();\n ctx.requestOpen(event, \"activate\");\n } else if (command === \"request-close\" || command === \"close\") {\n if (isLibrary) {\n // A foreign close invoker (design 2.3) goes through the library like a foreign\n // `show-modal`: same markup as Dialog.Close, same behaviour. `source` is the invoker\n // (`\"\"` where an engine gives none).\n event.preventDefault();\n const source = (event as Event & { source?: unknown }).source;\n ctx.requestClose(event, \"activate\", valueOf(source));\n } else {\n ctx.recordCloseIntent({ reason: \"activate\", event });\n }\n }\n });\n\n // On the dialog's root node (the document, or a shadow root: `submit` is not composed), bubble\n // phase: after React's root listener, so a consumer's `onSubmit` has run (design 8.1).\n const onSubmit = useEffectEvent((event: Event) => {\n const element = dialogRef.current;\n const form = event.target;\n if (element === null || !(form instanceof HTMLFormElement)) return;\n if (form.closest(\"dialog\") !== element) return;\n const submitter = (event as SubmitEvent).submitter;\n const formMethod = submitter?.getAttribute(\"formmethod\");\n const method = formMethod != null ? formMethod.toLowerCase() : form.method;\n if (method !== \"dialog\") return;\n if (!isLibrary) {\n ctx.recordCloseIntent({ reason: \"activate\", event });\n return;\n }\n // The consumer blocked the submission: the form did not submit, the dialog stays open.\n if (event.defaultPrevented) return;\n // The consumer opted out with `stopPropagation()`: the browser closes the dialog, notified\n // as a dismissal. Checked explicitly because React's root can be this same node (FUNSTACK\n // Static hydrates the document), where `stopPropagation()` does not stop this listener.\n if (event.cancelBubble) return;\n // Cancel the native close; the library closes once `openChangeAction(false)` settled.\n event.preventDefault();\n ctx.requestClose(event, \"activate\", valueOf(submitter));\n });\n\n const onClose = useEffectEvent((event: Event) => {\n const element = dialogRef.current;\n if (element === null || event.target !== element) return;\n awaitingClose.current = false;\n\n // Focus return is the platform's; it leaves focus inside the hidden dialog only when the\n // opener is gone (design 6.2).\n const doc = element.ownerDocument;\n const active = doc.activeElement;\n if (active !== null && active !== doc.body && element.contains(active)) {\n const trigger = triggerRef.current;\n if (trigger !== null && trigger.isConnected) trigger.focus();\n else if (active instanceof HTMLElement) active.blur();\n }\n\n const intent = ctx.takeCloseIntent();\n const echo = libraryClose.current;\n libraryClose.current = false;\n if (!isLibrary) return;\n\n // The library's own `close()` needs no notification, and with `transition` no hold either:\n // the close commit's view transition carried the content out (4.4).\n if (!echo || transition === undefined) {\n const animations = exitAnimations(element);\n if (animations.length > 0) {\n // Before the notification, so its commit keeps the children (urgent, internal).\n exit.holdUntil(animations);\n } else {\n // Nothing animates (Firefox, or no CSS exit): release a hold now; the commit is\n // invisible, the dialog is already `display: none`.\n exit.release();\n }\n }\n\n if (echo) return;\n // Closed by the browser: a returnValue a pending part close recorded will never be used.\n ctx.takeCloseReturnValue();\n if (ctx.intended()) {\n ctx.notifyClosed(intent?.event ?? event, intent?.reason ?? \"dismiss\", element.returnValue);\n } else {\n // A part's close is in flight (walkthrough 11.13): nothing more to notify unless that close\n // is vetoed (the apply effect).\n dismissedWhilePending.current = {\n event: intent?.event ?? event,\n returnValue: element.returnValue,\n generation: ctx.openGeneration(),\n };\n }\n });\n\n const onBeforeToggle = useEffectEvent((event: Event) => {\n if (event.target !== dialogRef.current) return;\n const newState = (event as ToggleEvent).newState;\n awaitingClose.current = newState === \"closed\";\n // Every native open counts as a new opening (design 3.4, as Popover 14.1): foreign,\n // pre-hydration and JS-free opens too, so a Close action from an earlier opening skips its\n // close. Counted here, synchronously inside `showModal()` / `show()`, not at the queued\n // `toggle`: an action settling between the open and that task would otherwise still see the\n // old generation and close the new open.\n if (newState === \"open\") {\n dismissedWhilePending.current = null;\n ctx.nextOpenGeneration();\n contentProbe.forget();\n }\n });\n\n useEffect(() => {\n if (dialog === null) return;\n const doc = dialog.ownerDocument;\n // The document, or the shadow root the dialog lives in (`submit` does not cross it).\n const root = dialog.getRootNode();\n // Feature detection only with the flag on, inside an effect (conventions 8).\n const nativeClosedBy = experimental_closedBy && \"closedBy\" in dialog;\n\n const listeners: [EventTarget, string, EventListener, boolean][] = [\n [dialog, \"command\", onCommand, false],\n // Bubble phase on the root node: after React's root listener (the consumer's `onSubmit`).\n [root, \"submit\", onSubmit, false],\n [dialog, \"close\", onClose, false],\n [dialog, \"beforetoggle\", onBeforeToggle, false],\n ];\n\n if (!modal && !nativeClosedBy) {\n // Non-modal dialogs get no close watcher Escape from the platform (design 2.6).\n listeners.push([\n dialog,\n \"keydown\",\n (event) => {\n const keyboardEvent = event as KeyboardEvent;\n if (keyboardEvent.key !== \"Escape\" || keyboardEvent.defaultPrevented) return;\n if (!dialog.open || closestDialog(keyboardEvent.target) !== dialog) return;\n // A popover or nested dialog inside takes this Escape (its close watcher); leave the\n // event alone so that it can.\n if (escapeBelongsToInnerLayer(dialog, keyboardEvent.target)) return;\n keyboardEvent.preventDefault();\n dialog.requestClose();\n },\n false,\n ]);\n }\n\n // Light dismiss needs both the press and the click outside, like native `closedby=\"any\"`: a\n // drag that starts inside (selecting text in an input) and ends on the backdrop or outside\n // fires `click` on the common ancestor and must not close (it would lose a half-filled form).\n let pressedOutside = false;\n\n const isOutside = (target: EventTarget | null): boolean => {\n if (!(target instanceof Node)) return true;\n if (dialog.contains(target)) return false;\n // The Trigger re-opening a non-modal dialog is not a click outside.\n return !(triggerRef.current?.contains(target) ?? false);\n };\n let outsideAttached = false;\n const onOutsidePointerDown = (event: Event) => {\n pressedOutside = dialog.open && isOutside(event.target);\n };\n const onOutsideClick = (event: Event) => {\n const pressed = pressedOutside;\n pressedOutside = false;\n if (!dialog.open || !pressed || !isOutside(event.target)) return;\n dialog.requestClose();\n };\n const syncOutside = () => {\n const want = dialog.open;\n if (want === outsideAttached) return;\n outsideAttached = want;\n pressedOutside = false;\n if (want) {\n doc.addEventListener(\"pointerdown\", onOutsidePointerDown, true);\n doc.addEventListener(\"click\", onOutsideClick, true);\n } else {\n doc.removeEventListener(\"pointerdown\", onOutsidePointerDown, true);\n doc.removeEventListener(\"click\", onOutsideClick, true);\n }\n };\n\n if (lightDismiss && !nativeClosedBy) {\n if (modal) {\n // A press or click on the ::backdrop targets the dialog itself, outside its border box.\n listeners.push(\n [\n dialog,\n \"pointerdown\",\n (event) => {\n pressedOutside = dialog.open && onBackdrop(dialog, event);\n },\n false,\n ],\n [\n dialog,\n \"click\",\n (event) => {\n const pressed = pressedOutside;\n pressedOutside = false;\n if (!dialog.open || !pressed || !onBackdrop(dialog, event)) return;\n dialog.requestClose();\n },\n false,\n ],\n );\n } else {\n // A document listener only while open, following the native `toggle` (the React\n // `onToggle` prop never fires on a hydrated <dialog>); reads `dialog.open`, not\n // `newState` (coalescing).\n listeners.push([dialog, \"toggle\", syncOutside, false]);\n syncOutside();\n }\n }\n\n for (const [target, type, listener, capture] of listeners) {\n target.addEventListener(type, listener, capture);\n }\n return () => {\n for (const [target, type, listener, capture] of listeners) {\n target.removeEventListener(type, listener, capture);\n }\n if (outsideAttached) {\n doc.removeEventListener(\"pointerdown\", onOutsidePointerDown, true);\n doc.removeEventListener(\"click\", onOutsideClick, true);\n }\n };\n }, [dialog, modal, lightDismiss, experimental_closedBy, triggerRef]);\n\n // Development check: the dialog has an accessible name when it is (or can be) shown.\n const shown = !isLibrary || ctx.committed;\n useEffect(() => {\n if (!isDev || dialog === null || !shown) return;\n if (dialog.hasAttribute(\"aria-label\")) return;\n const labelledBy = (dialog.getAttribute(\"aria-labelledby\") ?? \"\").split(/\\s+/);\n const doc = dialog.ownerDocument;\n if (labelledBy.some((id) => id !== \"\" && doc.getElementById(id) !== null)) return;\n console.error(\n \"Dialog.Popup: the dialog has no accessible name. Render a Dialog.Title, or give the \" +\n \"Popup an `aria-label`.\",\n );\n }, [dialog, shown]);\n\n const state: DialogPopupState = { open: ctx.committed, modal };\n const visible = ctx.committed || exit.held;\n\n const content = (s: DialogPopupState): ReactNode => {\n if (!isLibrary) return resolveStateValue(children, s);\n if (keepMounted === true) {\n // The mode changes with committed state, inside the Root's transition (conventions 5.5);\n // it stays visible while the close animates (a hidden Activity is `display: none`).\n return (\n <Activity mode={visible ? \"visible\" : \"hidden\"}>\n {wrapContent(resolveStateValue(children, s))}\n </Activity>\n );\n }\n return visible ? wrapContent(resolveStateValue(children, s)) : null;\n };\n\n const libraryProps: ComponentPropsWithRef<\"dialog\"> & DataAttributes = {\n id: ids.dialog,\n // Never rendered: removing `open` by hand hides a modal dialog without `close` and leaves\n // `:modal` true (research). Owned so a `render` element cannot add it.\n open: undefined,\n closedby: experimental_closedBy ? (lightDismiss ? \"any\" : \"closerequest\") : undefined,\n \"aria-labelledby\": ids.title,\n \"aria-describedby\": ids.description,\n \"data-modal\": dataAttr(modal),\n // A pre-hydration open leaves an `open` attribute the server did not render (design 2.4).\n suppressHydrationWarning: true,\n };\n\n const element = useRender<ComponentPropsWithRef<\"dialog\">, DialogPopupState>({\n defaultElement: \"dialog\",\n render,\n state,\n className,\n style,\n children: content,\n props: rest,\n libraryProps,\n ref: libraryRef,\n });\n\n return useTransitionBoundary(\"Dialog.Popup\", isLibrary ? transition : undefined, element);\n}\n\nDialogPopup.displayName = \"DialogPopup\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"dialog-popup.mjs","names":[],"sources":["../../src/dialog/dialog-popup.tsx"],"sourcesContent":["\"use client\";\nimport {\n Activity,\n useCallback,\n useEffect,\n useEffectEvent,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n ReactNode,\n RefObject,\n ViewTransitionClass,\n} from \"react\";\nimport { useContentFocusProbe } from \"../internal/content-focus-probe.js\";\nimport { ToolbarContext } from \"../internal/toolbar-context.js\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useExitAnimation } from \"../internal/use-exit-animation.js\";\nimport { useModalRegistration } from \"../internal/use-modal-registration.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogPopupState {\n /** Committed open state (library mode); `false` in JS-free mode. Read `:open` for the DOM truth. */\n readonly open: boolean;\n readonly modal: boolean;\n}\n\nexport interface DialogPopupProps extends Omit<\n ComponentPropsWithRef<\"dialog\">,\n \"id\" | \"open\" | \"closedby\" | \"onToggle\" | \"onBeforeToggle\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Library mode: what happens to the children while the dialog is closed.\n * - `false` (default): not rendered. Mounting them is part of the opening transition, so\n * content that suspends holds the open until it is ready. Unmount waits for the dialog's\n * exit animations.\n * - `true`: rendered inside `<Activity mode=\"hidden\">`: DOM and state preserved, effects\n * stopped, pre-rendered at low priority. Not in the server HTML.\n *\n * Ignored in JS-free mode (always mounted).\n */\n keepMounted?: boolean;\n /**\n * Element to focus when the dialog opens: a ref, or a selector resolved inside the dialog.\n * The library writes `autofocus` on it right before `showModal()` / `show()` and the platform\n * focuses it. Default: the platform's choice (first focusable descendant; the dialog itself if\n * none). Use `tabIndex={-1}` on a heading or wrapper for long or destructive dialogs (APG).\n *\n * In JS-free mode the browser opens the dialog itself, so the attribute is written after\n * hydration and kept while mounted; before hydration only `autoFocus` in the markup works.\n */\n initialFocus?: RefObject<HTMLElement | null> | string;\n /**\n * Close on a click outside the dialog (on the backdrop for modal dialogs). Default `false`.\n * Baseline: JS `pointerdown` + `click` listeners, which close only when both the press and the\n * click land outside (a drag that starts inside, such as selecting text, never closes); with\n * `experimental_closedBy`, native `closedby=\"any\"`, which behaves the same.\n */\n lightDismiss?: boolean;\n /**\n * Experimental (Chrome 134+, Firefox 141+; Safari 27.0 ignores it). Renders `closedby`\n * (`\"any\"` when `lightDismiss`, else `\"closerequest\"`) and skips the JS dismiss listeners where\n * `HTMLDialogElement.prototype.closedBy` exists. Elsewhere the JS baseline stays active.\n * Default `false`.\n */\n experimental_closedBy?: boolean;\n /**\n * Library mode only. Forwarded to the `<ViewTransition default>` around the dialog. Animates\n * the opening commit, content changes while open, and every library-driven close (a Close\n * part, a Form submit or settled action, a foreign close command, a controlled\n * `open={false}`), in all engines; dismissals (Escape, a click outside) are the browser's and\n * animate with CSS only. Ignored (dev warning) in JS-free mode.\n *\n * Static: without `transition` the part renders no `<ViewTransition>`; changing it between\n * `undefined` and a value remounts the element. Any value, `\"none\"` included, makes a mount or\n * `<Activity>` reveal of this part inside a transition start a document view transition: the\n * page cross-fades for about 250 ms and the next transition commit waits for it (issue #35).\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, DialogPopupState>;\n style?: StateValue<CSSProperties | undefined, DialogPopupState>;\n children?: StateValue<ReactNode, DialogPopupState>;\n /** Must render a `<dialog>`. */\n render?: RenderProp<ComponentPropsWithRef<\"dialog\">, DialogPopupState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/** Resolves `initialFocus` against the dialog (a ref, or a selector inside the dialog). */\nfunction resolveInitialFocus(\n dialog: HTMLDialogElement,\n initialFocus: RefObject<HTMLElement | null> | string,\n): HTMLElement | null {\n if (typeof initialFocus === \"string\") return dialog.querySelector<HTMLElement>(initialFocus);\n return initialFocus.current;\n}\n\n/** The nearest `<dialog>` around a node (the dialog itself counts). */\nfunction closestDialog(target: EventTarget | null): Element | null {\n return target instanceof Element ? target.closest(\"dialog\") : null;\n}\n\n/**\n * An open popover that takes Escape before the dialog: `auto` or `hint` (they have a close\n * watcher). A `manual` popover, or `hint` where the engine maps it to `manual`, does not.\n */\nfunction isOpenLightPopover(element: Element): boolean {\n if (!element.hasAttribute(\"popover\")) return false;\n // The IDL attribute reflects the state the engine applies; jsdom has none.\n const reflected: unknown = (element as HTMLElement).popover;\n const state =\n typeof reflected === \"string\" ? reflected : element.getAttribute(\"popover\")!.toLowerCase();\n if (state === \"manual\") return false;\n try {\n return element.matches(\":popover-open\");\n } catch {\n return false;\n }\n}\n\n/**\n * Whether Escape belongs to a layer inside `dialog` that sits above it (design 9: \"closes the\n * innermost one only\"): the target is inside an open nested `<dialog>` or popover, or an open\n * `auto` / `hint` popover inside the dialog would take the close request (close watchers are\n * per document, not per focus).\n */\nfunction escapeBelongsToInnerLayer(dialog: HTMLDialogElement, target: EventTarget | null): boolean {\n for (\n let node = target instanceof Element ? target : null;\n node !== null && node !== dialog;\n node = node.parentElement\n ) {\n if (node instanceof HTMLDialogElement && node.open) return true;\n if (isOpenLightPopover(node)) return true;\n }\n for (const element of dialog.querySelectorAll(\"[popover]\")) {\n if (isOpenLightPopover(element)) return true;\n }\n return false;\n}\n\n/** Whether a pointer event on the dialog itself lies outside its border box (the `::backdrop`). */\nfunction onBackdrop(dialog: HTMLDialogElement, event: Event): boolean {\n if (event.target !== dialog) return false;\n const { clientX, clientY } = event as MouseEvent;\n const rect = dialog.getBoundingClientRect();\n return !(\n rect.top <= clientY &&\n clientY <= rect.bottom &&\n rect.left <= clientX &&\n clientX <= rect.right\n );\n}\n\n/** The submitter's or invoker's `value` (`\"\"` without one, or where the engine gives no source). */\nfunction valueOf(element: unknown): string {\n if (element instanceof HTMLButtonElement || element instanceof HTMLInputElement) {\n return element.value;\n }\n return \"\";\n}\n\n/**\n * The dialog itself: a `<dialog>` with the id the Trigger's `commandfor` names, wired to Title\n * and Description through `aria-labelledby` / `aria-describedby`. Never renders `open`: modal\n * opening and closing go through `showModal()` / `close()` only. Style it with `:open` and\n * `:modal`; it emits `data-modal` but no `data-open`.\n *\n * In library mode the children render from committed state, so content that suspends keeps the\n * dialog closed (and the Trigger pending) until it is ready; `showModal()` runs in the layout\n * effect of the commit that mounted them. A close a part asks for (Close, a `method=\"dialog\"`\n * submit, a Form's settle close, a foreign close command) is cancelled and goes through the\n * Root: `openChangeAction(false)` is awaited and `close(returnValue)` runs in the layout effect\n * of the commit that ends it, inside the view transition if `transition` is set. A dismissal\n * (Escape, a click outside) is the browser's: the native `close` event becomes\n * `openChangeAction(false, { reason: \"dismiss\", returnValue })`.\n *\n * Owned keys (conventions 1.2): `id`, `open`, `closedby`, `aria-labelledby` and\n * `aria-describedby` (token-merged), `data-modal`, `suppressHydrationWarning`.\n */\nexport function DialogPopup({\n keepMounted,\n initialFocus,\n lightDismiss = false,\n experimental_closedBy = false,\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogPopupProps) {\n const ctx = useDialogContext(\"Dialog.Popup\");\n const isLibrary = ctx.mode === \"library\";\n const { modal, ids, dialogRef, triggerRef } = ctx;\n const [dialog, setDialog] = useState<HTMLDialogElement | null>(null);\n // Set by the synchronous `beforetoggle` of a close, cleared by the `close` event a task later:\n // the dialog is closed but its notification is not dispatched yet, so the apply effect must\n // not re-open it from a commit that lands in between.\n const awaitingClose = useRef(false);\n // Set right before the library's own `close()` (the apply effect), consumed by the `close`\n // event it causes: the echo is recognised by its source, not by what is intended (review L4).\n const libraryClose = useRef(false);\n // A dismissal (Escape, a click outside) that closed the dialog while a part-initiated close\n // was in flight (review M1). Its notification was skipped because `false` was intended; if that\n // close then settles with committed `true` (a controlled parent vetoed it), the apply effect\n // sends the dismissal instead of re-opening. Valid only for the open generation it was\n // recorded in: a re-open (Trigger, foreign invoker) supersedes it.\n const dismissedWhilePending = useRef<{\n readonly event: Event;\n readonly returnValue: string;\n readonly generation: number;\n } | null>(null);\n const warnedFocus = useRef(false);\n\n // --- `held`: children stay mounted while the close animates (design 4.4) -----------------\n\n const exit = useExitAnimation();\n\n // A library-driven close commits `false` first and closes in the layout effect, so without\n // `transition` the children are held through the close commit (\"previous value in state\").\n // With `transition` the close commit unmounts them inside the view transition, whose old\n // snapshot carries the content out. (A dismissal's notification commit sets it too; the apply\n // effect's release rule clears that one.)\n const [prevCommitted, setPrevCommitted] = useState(ctx.committed);\n if (prevCommitted !== ctx.committed) {\n setPrevCommitted(ctx.committed);\n if (isLibrary && prevCommitted && transition === undefined) exit.holdDuringRender();\n }\n\n // Whether the content unmounted while holding focus since the dialog was last shown: a close\n // with `transition` removes the focused Close part before `close()` runs (design 6.2 rule 2).\n const contentProbe = useContentFocusProbe(dialogRef);\n const { wrapContent } = contentProbe;\n\n // Toast's Viewport moves its region into the topmost open modal (design/components/toast.md 4.3).\n useModalRegistration(dialog);\n\n useDevWarning(\n !isLibrary && transition !== undefined,\n \"Dialog.Popup: `transition` has no effect in JS-free mode; the browser opens and closes the \" +\n \"dialog outside any React transition. Animate it with CSS (`@starting-style`).\",\n );\n useDevWarning(\n !isLibrary && keepMounted === false,\n \"Dialog.Popup: `keepMounted={false}` cannot be honoured in JS-free mode; the content is \" +\n \"always mounted so that the server HTML is complete.\",\n );\n\n const libraryRef = useCallback(\n (element: HTMLDialogElement) => {\n if (isDev && element.tagName !== \"DIALOG\") {\n console.error(\n `Dialog.Popup: expected to render a <dialog> element but got <${element.tagName.toLowerCase()}>. ` +\n \"Top layer, backdrop, focus and Escape all come from a real <dialog>.\",\n );\n }\n dialogRef.current = element;\n setDialog(element);\n return () => {\n if (dialogRef.current === element) dialogRef.current = null;\n setDialog(null);\n };\n },\n [dialogRef],\n );\n\n // --- Opening -----------------------------------------------------------------------------\n\n const openDialog = (element: HTMLDialogElement): void => {\n let target: HTMLElement | null = null;\n if (initialFocus !== undefined) {\n target = resolveInitialFocus(element, initialFocus);\n if (target === null || !element.contains(target)) {\n if (isDev && !warnedFocus.current) {\n warnedFocus.current = true;\n console.error(\n target === null\n ? \"Dialog.Popup: `initialFocus` resolved to no element; the platform picks the focus target.\"\n : \"Dialog.Popup: `initialFocus` is outside the dialog; the platform picks the focus target.\",\n );\n }\n target = null;\n }\n }\n // The platform's dialog focusing steps honour `autofocus`; React drops `autoFocus` on\n // client-rendered content (react/react#23301). Never `.focus()` (design 6.1).\n const added = target !== null && !target.hasAttribute(\"autofocus\");\n if (added) target!.setAttribute(\"autofocus\", \"\");\n try {\n if (modal) element.showModal();\n else element.show();\n } finally {\n if (added) target!.removeAttribute(\"autofocus\");\n }\n };\n\n // Mount reconciliation (design 2.4): a click before hydration opened the dialog natively.\n // Runs before the apply effect; the dispatch makes the apply effect wait for it.\n const reconcileOnMount = useEffectEvent(() => {\n const element = dialogRef.current;\n if (element === null || !element.open) return;\n ctx.requestOpen(undefined, \"platform\");\n });\n useLayoutEffect(() => {\n if (isLibrary) reconcileOnMount();\n }, [isLibrary]);\n\n /**\n * Design 6.2 rule 2, after a close whose commit unmounted the focused content: when focus is\n * now on `<body>` or still inside the closed dialog, focus the Trigger (if connected), else\n * blur what holds focus. A modal dialog never needs it (the platform restores focus on\n * `close()` unconditionally); a non-modal one restores only when focus is inside.\n */\n const returnFocus = (element: HTMLDialogElement): void => {\n const doc = element.ownerDocument;\n const active = doc.activeElement;\n if (active !== null && active !== doc.body && !element.contains(active)) return;\n const trigger = triggerRef.current;\n if (trigger !== null && trigger.isConnected) trigger.focus();\n else if (active instanceof HTMLElement && active !== doc.body) active.blur();\n };\n\n // The apply effect (design 2.5): after every commit that re-renders the Popup, once nothing\n // is in flight, the DOM follows committed state. The Root's context is memoised (#48), so an\n // unrelated Root render does not re-render the Popup and does not run this; every path that\n // makes the DOM diverge from state goes through a dispatch, whose `pending` flips re-render the\n // Popup (review of #48, finding 4). Do not rely on an unrelated render to re-apply state.\n // Opens on the user's dispatch, `defaultOpen`, a controlled parent, the re-open of a refused\n // dismissal; closes after a part-initiated close settled, a controlled close, or a refused\n // pre-hydration open. Also returns focus after a close whose commit unmounted the focused\n // content, and releases a `held` no exit animation is going to release (4.4).\n useLayoutEffect(() => {\n if (!isLibrary) return;\n const element = dialogRef.current;\n if (element === null || !element.isConnected) return;\n if (!ctx.pending && ctx.latestInFlight() === null) {\n const dismissed = dismissedWhilePending.current;\n if (ctx.committed && !element.open) {\n // Closed, but its `close` event has not arrived yet: wait for it.\n if (!awaitingClose.current) {\n dismissedWhilePending.current = null;\n if (dismissed !== null && dismissed.generation === ctx.openGeneration()) {\n // The user dismissed the dialog while a part's close was in flight, and that close\n // was vetoed: the dismissal stands and is notified now (review M1), instead of the\n // dialog coming back. A parent that keeps `open` again gets it re-opened, as for any\n // refused dismissal.\n ctx.notifyClosed(dismissed.event, \"dismiss\", dismissed.returnValue);\n } else {\n openDialog(element);\n }\n }\n } else if (!ctx.committed && element.open) {\n // The library's own close: the `close` listener recognises it by `libraryClose` and\n // dispatches nothing. `close()`, not `requestClose()`: the veto point was\n // `openChangeAction`, not `cancel`. The part's `returnValue` (design 2.3), `\"\"` for a\n // controlled close.\n ctx.recordCloseIntent(undefined);\n libraryClose.current = true;\n element.close(ctx.takeCloseReturnValue());\n // The close commit may have unmounted the focused content before `close()`: a\n // non-modal dialog restores focus only from inside, so do it now.\n if (contentProbe.hadFocus()) {\n contentProbe.forget();\n returnFocus(element);\n }\n }\n }\n // The in-flight close settled as a close: the recorded dismissal is moot.\n if (!ctx.committed && dismissedWhilePending.current !== null && ctx.latestInFlight() === null) {\n dismissedWhilePending.current = null;\n }\n // Content that unmounted while the dialog stays open (not a close): forget it.\n if (ctx.committed && element.open) contentProbe.forget();\n // A hold the close commit set (or a dismissal's notification commit) that no exit animation\n // is going to release: the dialog is closed and its `close` event has been handled.\n if (exit.held && !ctx.committed && !exit.waiting() && !awaitingClose.current && !element.open) {\n exit.release();\n }\n });\n\n // JS-free mode: there is no \"right before showModal()\" (the browser runs the command), so the\n // `autofocus` attribute stays on the target while it is mounted.\n useEffect(() => {\n if (isLibrary || initialFocus === undefined || dialog === null) return;\n const target = resolveInitialFocus(dialog, initialFocus);\n if (target === null || !dialog.contains(target) || target.hasAttribute(\"autofocus\")) return;\n target.setAttribute(\"autofocus\", \"\");\n return () => target.removeAttribute(\"autofocus\");\n }, [isLibrary, initialFocus, dialog]);\n\n // --- Native listeners (conventions 7.4) ---------------------------------------------------\n\n const onCommand = useEffectEvent((event: Event) => {\n if (event.target !== dialogRef.current || event.defaultPrevented) return;\n const command = String((event as Event & { command?: string }).command ?? \"\").toLowerCase();\n if (command === \"show-modal\") {\n // A foreign invoker (design 2.2): opening goes through the library in library mode.\n if (!isLibrary) return;\n event.preventDefault();\n ctx.requestOpen(event, \"activate\");\n } else if (command === \"request-close\" || command === \"close\") {\n if (isLibrary) {\n // A foreign close invoker (design 2.3) goes through the library like a foreign\n // `show-modal`: same markup as Dialog.Close, same behaviour. `source` is the invoker\n // (`\"\"` where an engine gives none).\n event.preventDefault();\n const source = (event as Event & { source?: unknown }).source;\n ctx.requestClose(event, \"activate\", valueOf(source));\n } else {\n ctx.recordCloseIntent({ reason: \"activate\", event });\n }\n }\n });\n\n // On the dialog's root node (the document, or a shadow root: `submit` is not composed), bubble\n // phase: after React's root listener, so a consumer's `onSubmit` has run (design 8.1).\n const onSubmit = useEffectEvent((event: Event) => {\n const element = dialogRef.current;\n const form = event.target;\n if (element === null || !(form instanceof HTMLFormElement)) return;\n if (form.closest(\"dialog\") !== element) return;\n const submitter = (event as SubmitEvent).submitter;\n const formMethod = submitter?.getAttribute(\"formmethod\");\n const method = formMethod != null ? formMethod.toLowerCase() : form.method;\n if (method !== \"dialog\") return;\n if (!isLibrary) {\n ctx.recordCloseIntent({ reason: \"activate\", event });\n return;\n }\n // The consumer blocked the submission: the form did not submit, the dialog stays open.\n if (event.defaultPrevented) return;\n // The consumer opted out with `stopPropagation()`: the browser closes the dialog, notified\n // as a dismissal. Checked explicitly because React's root can be this same node (FUNSTACK\n // Static hydrates the document), where `stopPropagation()` does not stop this listener.\n if (event.cancelBubble) return;\n // Cancel the native close; the library closes once `openChangeAction(false)` settled.\n event.preventDefault();\n ctx.requestClose(event, \"activate\", valueOf(submitter));\n });\n\n const onClose = useEffectEvent((event: Event) => {\n const element = dialogRef.current;\n if (element === null || event.target !== element) return;\n awaitingClose.current = false;\n\n // Focus return is the platform's; it leaves focus inside the hidden dialog only when the\n // opener is gone (design 6.2).\n const doc = element.ownerDocument;\n const active = doc.activeElement;\n if (active !== null && active !== doc.body && element.contains(active)) {\n const trigger = triggerRef.current;\n if (trigger !== null && trigger.isConnected) trigger.focus();\n else if (active instanceof HTMLElement) active.blur();\n }\n\n const intent = ctx.takeCloseIntent();\n const echo = libraryClose.current;\n libraryClose.current = false;\n if (!isLibrary) return;\n\n // The library's own `close()` needs no notification, and with `transition` no hold either:\n // the close commit's view transition carried the content out (4.4).\n if (!echo || transition === undefined) {\n const animations = exit.exitAnimations(element);\n if (animations.length > 0) {\n // Before the notification, so its commit keeps the children (urgent, internal).\n exit.holdUntil(animations);\n } else {\n // Nothing animates (Firefox, or no CSS exit): release a hold now; the commit is\n // invisible, the dialog is already `display: none`.\n exit.release();\n }\n }\n\n if (echo) return;\n // Closed by the browser: a returnValue a pending part close recorded will never be used.\n ctx.takeCloseReturnValue();\n if (ctx.intended()) {\n ctx.notifyClosed(intent?.event ?? event, intent?.reason ?? \"dismiss\", element.returnValue);\n } else {\n // A part's close is in flight (walkthrough 11.13): nothing more to notify unless that close\n // is vetoed (the apply effect).\n dismissedWhilePending.current = {\n event: intent?.event ?? event,\n returnValue: element.returnValue,\n generation: ctx.openGeneration(),\n };\n }\n });\n\n const onBeforeToggle = useEffectEvent((event: Event) => {\n const element = dialogRef.current;\n if (element === null || event.target !== element) return;\n const newState = (event as ToggleEvent).newState;\n awaitingClose.current = newState === \"closed\";\n // What already runs inside is not the exit (#64).\n if (newState === \"closed\") exit.recordBaseline(element);\n // Every native open counts as a new opening (design 3.4, as Popover 14.1): foreign,\n // pre-hydration and JS-free opens too, so a Close action from an earlier opening skips its\n // close. Counted here, synchronously inside `showModal()` / `show()`, not at the queued\n // `toggle`: an action settling between the open and that task would otherwise still see the\n // old generation and close the new open.\n if (newState === \"open\") {\n dismissedWhilePending.current = null;\n ctx.nextOpenGeneration();\n contentProbe.forget();\n }\n });\n\n useEffect(() => {\n if (dialog === null) return;\n const doc = dialog.ownerDocument;\n // The document, or the shadow root the dialog lives in (`submit` does not cross it).\n const root = dialog.getRootNode();\n // Feature detection only with the flag on, inside an effect (conventions 8).\n const nativeClosedBy = experimental_closedBy && \"closedBy\" in dialog;\n\n const listeners: [EventTarget, string, EventListener, boolean][] = [\n [dialog, \"command\", onCommand, false],\n // Bubble phase on the root node: after React's root listener (the consumer's `onSubmit`).\n [root, \"submit\", onSubmit, false],\n [dialog, \"close\", onClose, false],\n [dialog, \"beforetoggle\", onBeforeToggle, false],\n ];\n\n if (!modal && !nativeClosedBy) {\n // Non-modal dialogs get no close watcher Escape from the platform (design 2.6).\n listeners.push([\n dialog,\n \"keydown\",\n (event) => {\n const keyboardEvent = event as KeyboardEvent;\n if (keyboardEvent.key !== \"Escape\" || keyboardEvent.defaultPrevented) return;\n if (!dialog.open || closestDialog(keyboardEvent.target) !== dialog) return;\n // A popover or nested dialog inside takes this Escape (its close watcher); leave the\n // event alone so that it can.\n if (escapeBelongsToInnerLayer(dialog, keyboardEvent.target)) return;\n keyboardEvent.preventDefault();\n dialog.requestClose();\n },\n false,\n ]);\n }\n\n // Light dismiss needs both the press and the click outside, like native `closedby=\"any\"`: a\n // drag that starts inside (selecting text in an input) and ends on the backdrop or outside\n // fires `click` on the common ancestor and must not close (it would lose a half-filled form).\n let pressedOutside = false;\n\n const isOutside = (target: EventTarget | null): boolean => {\n if (!(target instanceof Node)) return true;\n if (dialog.contains(target)) return false;\n // The Trigger re-opening a non-modal dialog is not a click outside.\n return !(triggerRef.current?.contains(target) ?? false);\n };\n let outsideAttached = false;\n const onOutsidePointerDown = (event: Event) => {\n pressedOutside = dialog.open && isOutside(event.target);\n };\n const onOutsideClick = (event: Event) => {\n const pressed = pressedOutside;\n pressedOutside = false;\n if (!dialog.open || !pressed || !isOutside(event.target)) return;\n dialog.requestClose();\n };\n const syncOutside = () => {\n const want = dialog.open;\n if (want === outsideAttached) return;\n outsideAttached = want;\n pressedOutside = false;\n if (want) {\n doc.addEventListener(\"pointerdown\", onOutsidePointerDown, true);\n doc.addEventListener(\"click\", onOutsideClick, true);\n } else {\n doc.removeEventListener(\"pointerdown\", onOutsidePointerDown, true);\n doc.removeEventListener(\"click\", onOutsideClick, true);\n }\n };\n\n if (lightDismiss && !nativeClosedBy) {\n if (modal) {\n // A press or click on the ::backdrop targets the dialog itself, outside its border box.\n listeners.push(\n [\n dialog,\n \"pointerdown\",\n (event) => {\n pressedOutside = dialog.open && onBackdrop(dialog, event);\n },\n false,\n ],\n [\n dialog,\n \"click\",\n (event) => {\n const pressed = pressedOutside;\n pressedOutside = false;\n if (!dialog.open || !pressed || !onBackdrop(dialog, event)) return;\n dialog.requestClose();\n },\n false,\n ],\n );\n } else {\n // A document listener only while open, following the native `toggle` (the React\n // `onToggle` prop never fires on a hydrated <dialog>); reads `dialog.open`, not\n // `newState` (coalescing).\n listeners.push([dialog, \"toggle\", syncOutside, false]);\n syncOutside();\n }\n }\n\n for (const [target, type, listener, capture] of listeners) {\n target.addEventListener(type, listener, capture);\n }\n return () => {\n for (const [target, type, listener, capture] of listeners) {\n target.removeEventListener(type, listener, capture);\n }\n if (outsideAttached) {\n doc.removeEventListener(\"pointerdown\", onOutsidePointerDown, true);\n doc.removeEventListener(\"click\", onOutsideClick, true);\n }\n };\n }, [dialog, modal, lightDismiss, experimental_closedBy, triggerRef]);\n\n // Development check: the dialog has an accessible name when it is (or can be) shown.\n const shown = !isLibrary || ctx.committed;\n useEffect(() => {\n if (!isDev || dialog === null || !shown) return;\n if (dialog.hasAttribute(\"aria-label\")) return;\n const labelledBy = (dialog.getAttribute(\"aria-labelledby\") ?? \"\").split(/\\s+/);\n const doc = dialog.ownerDocument;\n if (labelledBy.some((id) => id !== \"\" && doc.getElementById(id) !== null)) return;\n console.error(\n \"Dialog.Popup: the dialog has no accessible name. Render a Dialog.Title, or give the \" +\n \"Popup an `aria-label`.\",\n );\n }, [dialog, shown]);\n\n const state: DialogPopupState = { open: ctx.committed, modal };\n const visible = ctx.committed || exit.held;\n\n // Not part of an enclosing Toolbar (the engine excludes `dialog`): parts inside the dialog must\n // not see the Toolbar either.\n const content = (s: DialogPopupState): ReactNode => (\n <ToolbarContext value={null}>{contentOf(s)}</ToolbarContext>\n );\n const contentOf = (s: DialogPopupState): ReactNode => {\n if (!isLibrary) return resolveStateValue(children, s);\n if (keepMounted === true) {\n // The mode changes with committed state, inside the Root's transition (conventions 5.5);\n // it stays visible while the close animates (a hidden Activity is `display: none`).\n return (\n <Activity mode={visible ? \"visible\" : \"hidden\"}>\n {wrapContent(resolveStateValue(children, s))}\n </Activity>\n );\n }\n return visible ? wrapContent(resolveStateValue(children, s)) : null;\n };\n\n const libraryProps: ComponentPropsWithRef<\"dialog\"> & DataAttributes = {\n id: ids.dialog,\n // Never rendered: removing `open` by hand hides a modal dialog without `close` and leaves\n // `:modal` true (research). Owned so a `render` element cannot add it.\n open: undefined,\n closedby: experimental_closedBy ? (lightDismiss ? \"any\" : \"closerequest\") : undefined,\n \"aria-labelledby\": ids.title,\n \"aria-describedby\": ids.description,\n \"data-modal\": dataAttr(modal),\n // A pre-hydration open leaves an `open` attribute the server did not render (design 2.4).\n suppressHydrationWarning: true,\n };\n\n const element = useRender<ComponentPropsWithRef<\"dialog\">, DialogPopupState>({\n defaultElement: \"dialog\",\n render,\n state,\n className,\n style,\n children: content,\n props: rest,\n libraryProps,\n ref: libraryRef,\n });\n\n return useTransitionBoundary(\"Dialog.Popup\", isLibrary ? transition : undefined, element);\n}\n\nDialogPopup.displayName = \"DialogPopup\";\n"],"mappings":"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"}
@@ -3,7 +3,7 @@ import { createActionDetails } from "../internal/action-details.mjs";
3
3
  import { transitionTypes } from "../internal/transition-types.mjs";
4
4
  import { useLatest } from "../internal/use-latest.mjs";
5
5
  import { useControllableAction } from "../internal/use-controllable-action.mjs";
6
- import { resolveStateValue } from "../internal/use-render.mjs";
6
+ import { resolveStateValue } from "../internal/render-element.mjs";
7
7
  import { useDevWarning } from "../internal/use-dev-warning.mjs";
8
8
  import { DialogContext } from "./dialog-context.mjs";
9
9
  import { useCallback, useId, useInsertionEffect, useMemo, useRef, useState } from "react";
@@ -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 { useDialogContext } from "./dialog-context.mjs";
5
6
  import { useCallback, useEffect } from "react";
6
7
  //#region src/dialog/dialog-trigger.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"dialog-trigger.mjs","names":[],"sources":["../../src/dialog/dialog-trigger.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useEffect } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, MouseEvent, ReactNode } 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 { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogTriggerState {\n /** Optimistic open state (library mode); `false` in JS-free mode. */\n readonly open: boolean;\n /**\n * The Root's transition is pending: an open in flight, a close a part asked for in flight\n * (`openChangeAction(false)` awaited while the dialog is still open), a close notification in\n * flight, or a re-open requested while a close notification is still settling.\n */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface DialogTriggerProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"className\" | \"style\" | \"children\"\n> {\n /** Native `disabled`: a disabled button is not a valid invoker and never dispatches. */\n disabled?: boolean;\n className?: StateValue<string | undefined, DialogTriggerState>;\n style?: StateValue<CSSProperties | undefined, DialogTriggerState>;\n /** Rendered from optimistic state; must not suspend. */\n children?: StateValue<ReactNode, DialogTriggerState>;\n /** Should render a `<button>`; another element works only in library mode (click handler). */\n render?: RenderProp<ComponentPropsWithRef<\"button\">, DialogTriggerState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * The opener: `<button type=\"button\" commandfor command=\"show-modal\">`. In JS-free mode the\n * browser runs the command. In library mode the click is cancelled (so the command does not run)\n * and dispatched through the Root's transition; the button shows optimistic `data-open` and\n * `data-pending` until the dialog has opened. Non-modal dialogs have no command and always use a\n * click handler (`show()`).\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `commandfor`,\n * `command`, `onClick`, `data-open`, `data-pending`, `data-disabled`.\n */\nexport function DialogTrigger({\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogTriggerProps) {\n const ctx = useDialogContext(\"Dialog.Trigger\");\n const isLibrary = ctx.mode === \"library\";\n const { modal, ids, triggerRef, dialogRef } = ctx;\n\n const state: DialogTriggerState = {\n open: ctx.optimistic,\n // Every Root dispatch is an open, a part-initiated close or a dismissal notification, and\n // all of them belong to the Trigger (design 4.2). Not `optimistic !== committed`: a re-open\n // while a close notification is still settling has both `true` (the close never committed)\n // and is pending all the same.\n pending: ctx.pending,\n disabled,\n };\n\n const libraryRef = useCallback(\n (element: HTMLElement) => {\n if (isDev && !isLibrary && modal && element.tagName !== \"BUTTON\") {\n console.error(\n `Dialog.Trigger: rendered a <${element.tagName.toLowerCase()}> in JS-free mode. Only a ` +\n '<button> runs `command=\"show-modal\"`; this trigger will not open the dialog. Render ' +\n \"a <button>, or put the Dialog in library mode (`defaultOpen={false}`).\",\n );\n }\n triggerRef.current = element;\n return () => {\n if (triggerRef.current === element) triggerRef.current = null;\n };\n },\n [isLibrary, modal, triggerRef],\n );\n\n // A Trigger without a Popup opens nothing (design 14, dev warnings).\n useEffect(() => {\n if (!isDev) return;\n if (document.getElementById(ids.dialog) === null) {\n console.error(\n \"Dialog.Trigger: no Dialog.Popup is rendered for this Dialog.Root; the trigger opens nothing.\",\n );\n }\n }, [ids.dialog]);\n\n // Library mode (both modal and non-modal) and non-modal JS-free mode need a click handler;\n // modal JS-free mode is the browser's command.\n const installClick = isLibrary || !modal;\n const onClick = (event: MouseEvent<HTMLElement>) => {\n if (isLibrary) {\n // Cancelling the click cancels the command (it runs as the button's activation\n // behaviour, only if the click was not cancelled).\n event.preventDefault();\n if (disabled) return;\n ctx.requestOpen(event.nativeEvent, \"activate\");\n return;\n }\n if (disabled) return;\n const dialog = dialogRef.current;\n if (dialog !== null && !dialog.open && dialog.isConnected) dialog.show();\n };\n\n const libraryProps: ComponentPropsWithRef<\"button\"> & DataAttributes = {\n // `type` only applies to a <button>; on any other element the key is not owned.\n ...(rendersButton(render) && { type: \"button\" as const }),\n // Owned: a render element must not re-enable a disabled Trigger (in JS-free mode the\n // browser would run its command).\n disabled,\n commandfor: modal ? ids.dialog : undefined,\n command: modal ? \"show-modal\" : undefined,\n // Only owned when installed, so a render element keeps its own `onClick` in modal JS-free\n // mode (Accordion's rule).\n ...(installClick && { onClick }),\n \"data-open\": dataAttr(state.open),\n \"data-pending\": dataAttr(state.pending),\n \"data-disabled\": dataAttr(isLibrary && disabled),\n };\n\n return useRender<ComponentPropsWithRef<\"button\">, DialogTriggerState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps,\n ref: libraryRef,\n });\n}\n\nDialogTrigger.displayName = \"DialogTrigger\";\n"],"mappings":";;;;;;AAmCA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;;;;;;AAYvC,SAAgB,cAAc,EAC5B,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACkB;CACrB,MAAM,MAAM,iBAAiB,gBAAgB;CAC7C,MAAM,YAAY,IAAI,SAAS;CAC/B,MAAM,EAAE,OAAO,KAAK,YAAY,cAAc;CAE9C,MAAM,QAA4B;EAChC,MAAM,IAAI;EAKV,SAAS,IAAI;EACb;CACF;CAEA,MAAM,aAAa,aAChB,YAAyB;EACxB,IAAI,SAAS,CAAC,aAAa,SAAS,QAAQ,YAAY,UACtD,QAAQ,MACN,+BAA+B,QAAQ,QAAQ,YAAY,EAAE,yLAG/D;EAEF,WAAW,UAAU;EACrB,aAAa;GACX,IAAI,WAAW,YAAY,SAAS,WAAW,UAAU;EAC3D;CACF,GACA;EAAC;EAAW;EAAO;CAAU,CAC/B;CAGA,gBAAgB;EACd,IAAI,CAAC,OAAO;EACZ,IAAI,SAAS,eAAe,IAAI,MAAM,MAAM,MAC1C,QAAQ,MACN,8FACF;CAEJ,GAAG,CAAC,IAAI,MAAM,CAAC;CAIf,MAAM,eAAe,aAAa,CAAC;CACnC,MAAM,WAAW,UAAmC;EAClD,IAAI,WAAW;GAGb,MAAM,eAAe;GACrB,IAAI,UAAU;GACd,IAAI,YAAY,MAAM,aAAa,UAAU;GAC7C;EACF;EACA,IAAI,UAAU;EACd,MAAM,SAAS,UAAU;EACzB,IAAI,WAAW,QAAQ,CAAC,OAAO,QAAQ,OAAO,aAAa,OAAO,KAAK;CACzE;CAEA,MAAM,eAAiE;EAErE,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;EAGvD;EACA,YAAY,QAAQ,IAAI,SAAS,KAAA;EACjC,SAAS,QAAQ,eAAe,KAAA;EAGhC,GAAI,gBAAgB,EAAE,QAAQ;EAC9B,aAAa,SAAS,MAAM,IAAI;EAChC,gBAAgB,SAAS,MAAM,OAAO;EACtC,iBAAiB,SAAS,aAAa,QAAQ;CACjD;CAEA,OAAO,UAA+D;EACpE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;EACA,KAAK;CACP,CAAC;AACH;AAEA,cAAc,cAAc"}
1
+ {"version":3,"file":"dialog-trigger.mjs","names":[],"sources":["../../src/dialog/dialog-trigger.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useEffect } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, MouseEvent, ReactNode } 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 { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogTriggerState {\n /** Optimistic open state (library mode); `false` in JS-free mode. */\n readonly open: boolean;\n /**\n * The Root's transition is pending: an open in flight, a close a part asked for in flight\n * (`openChangeAction(false)` awaited while the dialog is still open), a close notification in\n * flight, or a re-open requested while a close notification is still settling.\n */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface DialogTriggerProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"className\" | \"style\" | \"children\"\n> {\n /** Native `disabled`: a disabled button is not a valid invoker and never dispatches. */\n disabled?: boolean;\n className?: StateValue<string | undefined, DialogTriggerState>;\n style?: StateValue<CSSProperties | undefined, DialogTriggerState>;\n /** Rendered from optimistic state; must not suspend. */\n children?: StateValue<ReactNode, DialogTriggerState>;\n /** Should render a `<button>`; another element works only in library mode (click handler). */\n render?: RenderProp<ComponentPropsWithRef<\"button\">, DialogTriggerState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * The opener: `<button type=\"button\" commandfor command=\"show-modal\">`. In JS-free mode the\n * browser runs the command. In library mode the click is cancelled (so the command does not run)\n * and dispatched through the Root's transition; the button shows optimistic `data-open` and\n * `data-pending` until the dialog has opened. Non-modal dialogs have no command and always use a\n * click handler (`show()`).\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `commandfor`,\n * `command`, `onClick`, `data-open`, `data-pending`, `data-disabled`.\n */\nexport function DialogTrigger({\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogTriggerProps) {\n const ctx = useDialogContext(\"Dialog.Trigger\");\n const isLibrary = ctx.mode === \"library\";\n const { modal, ids, triggerRef, dialogRef } = ctx;\n\n const state: DialogTriggerState = {\n open: ctx.optimistic,\n // Every Root dispatch is an open, a part-initiated close or a dismissal notification, and\n // all of them belong to the Trigger (design 4.2). Not `optimistic !== committed`: a re-open\n // while a close notification is still settling has both `true` (the close never committed)\n // and is pending all the same.\n pending: ctx.pending,\n disabled,\n };\n\n const libraryRef = useCallback(\n (element: HTMLElement) => {\n if (isDev && !isLibrary && modal && element.tagName !== \"BUTTON\") {\n console.error(\n `Dialog.Trigger: rendered a <${element.tagName.toLowerCase()}> in JS-free mode. Only a ` +\n '<button> runs `command=\"show-modal\"`; this trigger will not open the dialog. Render ' +\n \"a <button>, or put the Dialog in library mode (`defaultOpen={false}`).\",\n );\n }\n triggerRef.current = element;\n return () => {\n if (triggerRef.current === element) triggerRef.current = null;\n };\n },\n [isLibrary, modal, triggerRef],\n );\n\n // A Trigger without a Popup opens nothing (design 14, dev warnings).\n useEffect(() => {\n if (!isDev) return;\n if (document.getElementById(ids.dialog) === null) {\n console.error(\n \"Dialog.Trigger: no Dialog.Popup is rendered for this Dialog.Root; the trigger opens nothing.\",\n );\n }\n }, [ids.dialog]);\n\n // Library mode (both modal and non-modal) and non-modal JS-free mode need a click handler;\n // modal JS-free mode is the browser's command.\n const installClick = isLibrary || !modal;\n const onClick = (event: MouseEvent<HTMLElement>) => {\n if (isLibrary) {\n // Cancelling the click cancels the command (it runs as the button's activation\n // behaviour, only if the click was not cancelled).\n event.preventDefault();\n if (disabled) return;\n ctx.requestOpen(event.nativeEvent, \"activate\");\n return;\n }\n if (disabled) return;\n const dialog = dialogRef.current;\n if (dialog !== null && !dialog.open && dialog.isConnected) dialog.show();\n };\n\n const libraryProps: ComponentPropsWithRef<\"button\"> & DataAttributes = {\n // `type` only applies to a <button>; on any other element the key is not owned.\n ...(rendersButton(render) && { type: \"button\" as const }),\n // Owned: a render element must not re-enable a disabled Trigger (in JS-free mode the\n // browser would run its command).\n disabled,\n commandfor: modal ? ids.dialog : undefined,\n command: modal ? \"show-modal\" : undefined,\n // Only owned when installed, so a render element keeps its own `onClick` in modal JS-free\n // mode (Accordion's rule).\n ...(installClick && { onClick }),\n \"data-open\": dataAttr(state.open),\n \"data-pending\": dataAttr(state.pending),\n \"data-disabled\": dataAttr(isLibrary && disabled),\n };\n\n return useRender<ComponentPropsWithRef<\"button\">, DialogTriggerState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps,\n ref: libraryRef,\n });\n}\n\nDialogTrigger.displayName = \"DialogTrigger\";\n"],"mappings":";;;;;;;AAmCA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;;;;;;AAYvC,SAAgB,cAAc,EAC5B,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACkB;CACrB,MAAM,MAAM,iBAAiB,gBAAgB;CAC7C,MAAM,YAAY,IAAI,SAAS;CAC/B,MAAM,EAAE,OAAO,KAAK,YAAY,cAAc;CAE9C,MAAM,QAA4B;EAChC,MAAM,IAAI;EAKV,SAAS,IAAI;EACb;CACF;CAEA,MAAM,aAAa,aAChB,YAAyB;EACxB,IAAI,SAAS,CAAC,aAAa,SAAS,QAAQ,YAAY,UACtD,QAAQ,MACN,+BAA+B,QAAQ,QAAQ,YAAY,EAAE,yLAG/D;EAEF,WAAW,UAAU;EACrB,aAAa;GACX,IAAI,WAAW,YAAY,SAAS,WAAW,UAAU;EAC3D;CACF,GACA;EAAC;EAAW;EAAO;CAAU,CAC/B;CAGA,gBAAgB;EACd,IAAI,CAAC,OAAO;EACZ,IAAI,SAAS,eAAe,IAAI,MAAM,MAAM,MAC1C,QAAQ,MACN,8FACF;CAEJ,GAAG,CAAC,IAAI,MAAM,CAAC;CAIf,MAAM,eAAe,aAAa,CAAC;CACnC,MAAM,WAAW,UAAmC;EAClD,IAAI,WAAW;GAGb,MAAM,eAAe;GACrB,IAAI,UAAU;GACd,IAAI,YAAY,MAAM,aAAa,UAAU;GAC7C;EACF;EACA,IAAI,UAAU;EACd,MAAM,SAAS,UAAU;EACzB,IAAI,WAAW,QAAQ,CAAC,OAAO,QAAQ,OAAO,aAAa,OAAO,KAAK;CACzE;CAEA,MAAM,eAAiE;EAErE,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;EAGvD;EACA,YAAY,QAAQ,IAAI,SAAS,KAAA;EACjC,SAAS,QAAQ,eAAe,KAAA;EAGhC,GAAI,gBAAgB,EAAE,QAAQ;EAC9B,aAAa,SAAS,MAAM,IAAI;EAChC,gBAAgB,SAAS,MAAM,OAAO;EACtC,iBAAiB,SAAS,aAAa,QAAQ;CACjD;CAEA,OAAO,UAA+D;EACpE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;EACA,KAAK;CACP,CAAC;AACH;AAEA,cAAc,cAAc"}
@@ -118,7 +118,8 @@ function FieldRoot({ name, disabled = false, required = false, validate, classNa
118
118
  const refresh = useCallback((options = {}) => {
119
119
  const registration = controlRef.current;
120
120
  if (registration === null) return NONE;
121
- const el = validityElementOf(registration.element);
121
+ const surface = validityElementOf(registration.element);
122
+ const el = registration.validityElement?.() ?? surface;
122
123
  if (el === null) return NONE;
123
124
  const { name, validate, serverMessages, serverShown, formPending, form } = latest.current;
124
125
  const owner = el.form;
@@ -129,8 +130,9 @@ function FieldRoot({ name, disabled = false, required = false, validate, classNa
129
130
  if (slotElement.current !== null && slotElement.current !== el) slotElement.current.setCustomValidity("");
130
131
  slotElement.current = el;
131
132
  el.setCustomValidity(showServer ? serverMessages.join("\n") : custom.join("\n"));
133
+ if (surface !== null && surface !== el && surface.validationMessage !== el.validationMessage) surface.setCustomValidity(el.validationMessage);
132
134
  if (options.reset === true) attempted.current = false;
133
- const userInvalid = supportsUserInvalid() ? el.matches(":user-invalid") : attempted.current && !el.validity.valid;
135
+ const userInvalid = supportsUserInvalid() ? (surface ?? el).matches(":user-invalid") : attempted.current && !el.validity.valid;
134
136
  const validity = snapshotValidity(el.validity);
135
137
  const messages = validity.valid ? NONE : validity.customError && !showServer && custom.length > 0 ? custom : el.validationMessage === "" ? NONE : [el.validationMessage];
136
138
  const next = {
@@ -242,7 +244,7 @@ function FieldRoot({ name, disabled = false, required = false, validate, classNa
242
244
  }, [refresh, latest]);
243
245
  useEffect(() => {
244
246
  if (!isDev || controlRef.current !== null) return;
245
- console.error("Field.Root: no form control registered. Render a Field.Control or a FUNSTACK form control (Checkbox, Switch, RadioGroup, CheckboxGroup, Select, Combobox) inside it.");
247
+ console.error("Field.Root: no form control registered. Render a Field.Control or a FUNSTACK form control (Checkbox, Switch, RadioGroup, CheckboxGroup, Select, Combobox, Slider, NumberField) inside it.");
246
248
  }, []);
247
249
  const invalid = mirror.userInvalid || serverShown;
248
250
  const messages = serverShown ? serverMessages : mirror.userInvalid ? mirror.messages : NONE;
@@ -1 +1 @@
1
- {"version":3,"file":"field-root.mjs","names":[],"sources":["../../src/field/field-root.tsx"],"sourcesContent":["\"use client\";\nimport {\n use,\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode, RefObject } from \"react\";\nimport { useFormStatus } from \"react-dom\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { FieldContext, FormContext } from \"../internal/field-context.js\";\nimport type {\n FieldContextValue,\n FieldControlRegistration,\n FieldState,\n FormFieldRegistration,\n} from \"../internal/field-context.js\";\nimport { collectInvalid } from \"../internal/focus-first-invalid.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useFormOwnerEvent } from \"../internal/use-form-owner-event.js\";\nimport { useLatest } from \"../internal/use-latest.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport {\n arraysEqual,\n focusTargetOf,\n normaliseErrorValue,\n snapshotValidity,\n supportsUserInvalid,\n VALID,\n validityElementOf,\n validityEquals,\n} from \"../internal/validity.js\";\nimport type { FieldErrorValue, FieldValidity, ValidatableElement } from \"../internal/validity.js\";\n\nexport type { FieldState } from \"../internal/field-context.js\";\nexport type { FieldValidity } from \"../internal/validity.js\";\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * A custom constraint: receives the field's submitted value (`formData.get(name)` as a string,\n * `\"\"` when absent) and the whole form's `FormData`. Return a message (or several) for an error,\n * `null` / `undefined` / `\"\"` for none.\n */\nexport type FieldValidate = (value: string, formData: FormData) => FieldErrorValue;\n\nexport interface FieldRootProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"className\" | \"style\" | \"children\"\n> {\n /** Form field name: the control's default `name`, the key in `Form.Root errors`, and the entry `validate` reads. */\n name?: string;\n /** Disables the control (native `disabled`) and marks every part `data-disabled`. */\n disabled?: boolean;\n /** Makes the control `required` (native) and marks every part `data-required`. */\n required?: boolean;\n /**\n * A custom constraint, written into the control with `setCustomValidity`. Sync. Receives the\n * field's submitted value (`formData.get(name)` as a string, `\"\"` when absent) and the whole\n * form's `FormData`, so a cross-field rule can read the other field. Runs at mount, on every\n * `input` / `change` in the form (and a FUNSTACK control's value change), on blur, after a\n * reset, on a submission attempt, and in every commit in which this prop changed identity, so\n * a rule that reads outside state (a list that loads, a sibling value in React state) updates\n * on render. An inline function therefore re-runs on every render of the Field; wrap it in\n * `useCallback` to re-run only when its inputs change. Return a string\n * or strings for an error, `null` / `undefined` / `\"\"` for none. Shown when the platform's\n * `:user-invalid` heuristic says so, like a native constraint. Not serialisable: a Field with\n * `validate` is rendered from a client component.\n */\n validate?: FieldValidate;\n className?: StateValue<string | undefined, FieldState>;\n style?: StateValue<CSSProperties | undefined, FieldState>;\n children?: StateValue<ReactNode, FieldState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, FieldState>;\n}\n\n/** The urgent mirror of browser-owned validity (design 2.4). */\ninterface Mirror {\n readonly userInvalid: boolean;\n readonly validity: FieldValidity;\n readonly messages: readonly string[];\n}\n\nconst NONE: readonly string[] = Object.freeze([]);\nconst INITIAL_MIRROR: Mirror = { userInvalid: false, validity: VALID, messages: NONE };\n\nfunction mirrorEquals(a: Mirror, b: Mirror): boolean {\n return (\n a.userInvalid === b.userInvalid &&\n validityEquals(a.validity, b.validity) &&\n arraysEqual(a.messages, b.messages)\n );\n}\n\n/** `FormData` entries as comparable strings; a `File` counts as unchanged (design 3.2 item 2). */\nfunction entryStrings(entries: readonly FormDataEntryValue[]): string[] {\n return entries.map((entry) => (typeof entry === \"string\" ? entry : \"\\u0000file\"));\n}\n\ninterface RefreshOptions {\n /** The user edited the control: the server message is dropped from the slot at once. */\n readonly dismiss?: boolean;\n /** After a form reset: the user-validity flag is clear again. */\n readonly reset?: boolean;\n}\n\n/**\n * The field: a `div` that owns the ids, the urgent validity mirror, the custom-validity slot\n * (`validate` and the server error), the control registration and the native listeners\n * (design/components/field-form.md 2, 14). `data-pending` is the enclosing form's submission\n * (`useFormStatus`), the one part that folds the form channel into its own state, because a\n * Field has no action of its own.\n *\n * Owned keys (conventions 1.2): `data-invalid`, `data-disabled`, `data-required`, `data-pending`.\n */\nexport function FieldRoot({\n name,\n disabled = false,\n required = false,\n validate,\n className,\n style,\n children,\n render,\n ...rest\n}: FieldRootProps) {\n const parent = use(FieldContext);\n const form = use(FormContext);\n useDevWarning(\n parent !== null,\n \"Field.Root: nested inside another Field.Root. A control registers with the nearest Field \" +\n \"only; give each control its own, sibling Field.\",\n );\n useDevWarning(\n validate !== undefined && name === undefined,\n \"Field.Root: `validate` without `name`. `validate` receives `formData.get(name)`, which is \" +\n 'always \"\" without a name.',\n );\n\n const id = useId();\n const ids = useMemo(\n () => ({\n control: `${id}-control`,\n label: `${id}-label`,\n description: `${id}-description`,\n error: `${id}-error`,\n }),\n [id],\n );\n\n const { pending: formPending } = useFormStatus();\n\n // --- Registration -------------------------------------------------------------------------\n\n const controlRef = useRef<FieldControlRegistration | null>(null);\n const [control, setControl] = useState<FieldControlRegistration | null>(null);\n const warnedTwoControls = useRef(false);\n const registerControl = useCallback((registration: FieldControlRegistration) => {\n const current = controlRef.current;\n if (current !== null && current.element !== registration.element) {\n if (isDev && !warnedTwoControls.current) {\n warnedTwoControls.current = true;\n console.error(\n \"Field.Root: two form controls registered in one Field. The first one is the \" +\n \"Field's control; give each control its own Field.Root.\",\n );\n }\n return () => {};\n }\n controlRef.current = registration;\n setControl(registration);\n return () => {\n if (controlRef.current !== registration) return;\n controlRef.current = null;\n setControl(null);\n };\n }, []);\n\n const errorCount = useRef(0);\n const registerError = useCallback(() => {\n errorCount.current += 1;\n if (isDev && errorCount.current === 2) {\n console.error(\n \"Field.Error: two Field.Error parts in one Field.Root. Render one; its function \" +\n \"`children` receives `validity` for per-constraint wording.\",\n );\n }\n return () => {\n errorCount.current -= 1;\n };\n }, []);\n\n // --- Server errors: shown, dismissed, re-armed, suppressed (design 3.4) -------------------\n\n const errors = form?.errors;\n const serverMessages = useMemo(\n () => normaliseErrorValue(name === undefined ? undefined : errors?.[name]),\n [errors, name],\n );\n const serverKey = serverMessages.join(\"\\n\");\n /** The user edited the control since the last settled submission. */\n const [dismissed, setDismissed] = useState(false);\n /**\n * The errors shown now answer a value the user had already changed during their flight\n * (intercepted path). Fixed from one settle to the next, so a hidden stale error stays hidden\n * while the next submission is in flight (the `errors` prop still holds that result).\n */\n const [suppressed, setSuppressed] = useState(false);\n /** The user changed the value during the flight in progress: becomes `suppressed` at its settle. */\n const [suppressNext, setSuppressNext] = useState(false);\n // The settle edge of the enclosing form, seen in the render that commits the result (the\n // action's result and `useFormStatus`'s NotPending land together), so the next submission's\n // errors are shown in that same commit, before the Form's settle effect focuses them.\n const [previousFormPending, setPreviousFormPending] = useState(formPending);\n if (previousFormPending !== formPending) {\n setPreviousFormPending(formPending);\n if (formPending) {\n setSuppressNext(false);\n } else {\n setDismissed(false);\n setSuppressed(suppressNext);\n setSuppressNext(false);\n }\n }\n const serverShown = serverMessages.length > 0 && !dismissed && !suppressed;\n\n // --- The mirror -----------------------------------------------------------------------------\n\n const [mirror, setMirror] = useState<Mirror>(INITIAL_MIRROR);\n /** The last mirror written: `refresh` compares against it, so an unchanged read renders nothing. */\n const mirrorRef = useRef<Mirror>(INITIAL_MIRROR);\n /** jsdom fallback for `:user-invalid` (design 2.2): `invalid` fired, cleared by a reset. */\n const attempted = useRef(false);\n /** The element the slot was last written on (a group's first member can change). */\n const slotElement = useRef<ValidatableElement | null>(null);\n\n const latest = useLatest({ name, validate, serverMessages, serverShown, formPending, form });\n\n /** Re-reads the slot and the mirror; returns the messages the Field shows for the control. */\n const refresh = useCallback(\n (options: RefreshOptions = {}): readonly string[] => {\n const registration = controlRef.current;\n if (registration === null) return NONE;\n const el = validityElementOf(registration.element);\n if (el === null) return NONE;\n const { name, validate, serverMessages, serverShown, formPending, form } = latest.current;\n\n const owner = el.form;\n const formData = owner === null ? new FormData() : new FormData(owner);\n const entry = name === undefined ? null : formData.get(name);\n const value = typeof entry === \"string\" ? entry : \"\";\n const custom = validate === undefined ? NONE : normaliseErrorValue(validate(value, formData));\n\n // The slot (design 2.1): the server message while shown, else `validate`'s.\n const showServer = serverShown && options.dismiss !== true;\n if (slotElement.current !== null && slotElement.current !== el) {\n slotElement.current.setCustomValidity(\"\");\n }\n slotElement.current = el;\n el.setCustomValidity(showServer ? serverMessages.join(\"\\n\") : custom.join(\"\\n\"));\n\n if (options.reset === true) attempted.current = false;\n const userInvalid = supportsUserInvalid()\n ? el.matches(\":user-invalid\")\n : attempted.current && !el.validity.valid;\n const validity = snapshotValidity(el.validity);\n const messages = validity.valid\n ? NONE\n : validity.customError && !showServer && custom.length > 0\n ? custom\n : el.validationMessage === \"\"\n ? NONE\n : [el.validationMessage];\n const next: Mirror = { userInvalid, validity, messages };\n // Only a change schedules a render (a `focusout` with nothing new causes none).\n if (!mirrorEquals(mirrorRef.current, next)) {\n mirrorRef.current = next;\n setMirror(next);\n }\n\n // Stale-value suppression (design 3.2 item 2): while the Form's own submission is in\n // flight, an error is about the value it sent; compare with what the field holds now.\n const submitted = formPending && name !== undefined ? (form?.submittedData() ?? null) : null;\n if (submitted !== null && name !== undefined) {\n setSuppressNext(\n !arraysEqual(entryStrings(formData.getAll(name)), entryStrings(submitted.getAll(name))),\n );\n }\n return showServer ? serverMessages : messages;\n },\n [latest],\n );\n\n // The slot follows the server error and `validate` (and is written once the control is known).\n // Keyed on `validate`'s identity (design 2.1): a rule reading outside state updates in the\n // commit that changed it. An inline function re-runs on every render (documented: `useCallback`\n // avoids it); the form's own values are covered by the listeners below.\n useLayoutEffect(() => {\n refresh();\n }, [refresh, control, serverShown, serverKey, validate]);\n\n // Clear the slot when the control goes away (it may be reused outside the Field).\n useLayoutEffect(\n () => () => {\n slotElement.current?.setCustomValidity(\"\");\n slotElement.current = null;\n },\n [],\n );\n\n // The control's own events (capture, so a group's members are seen on its fieldset).\n const element = control?.element ?? null;\n useLayoutEffect(() => {\n if (element === null) return;\n const onEdit = () => {\n setDismissed(true);\n refresh({ dismiss: true });\n };\n const onBlur = () => refresh();\n const onInvalid = (event: Event) => {\n const target = event.target as ValidatableElement;\n // A submission attempt sets user validity before validating; `checkValidity()` and\n // `reportValidity()` do not (design 2.5). The fallback cannot tell them apart.\n const attempt = supportsUserInvalid() ? target.matches(\":user-invalid\") : true;\n if (!supportsUserInvalid()) attempted.current = true;\n if (attempt && errorCount.current > 0) {\n event.preventDefault();\n const messages = refresh();\n collectInvalid(focusTargetOf(element), messages.join(\" \"));\n return;\n }\n refresh();\n };\n element.addEventListener(\"input\", onEdit, true);\n element.addEventListener(\"change\", onEdit, true);\n element.addEventListener(\"focusout\", onBlur, true);\n element.addEventListener(\"invalid\", onInvalid, true);\n return () => {\n element.removeEventListener(\"input\", onEdit, true);\n element.removeEventListener(\"change\", onEdit, true);\n element.removeEventListener(\"focusout\", onBlur, true);\n element.removeEventListener(\"invalid\", onInvalid, true);\n };\n }, [element, refresh]);\n\n // The form owner's events: another field's edit re-runs `validate` (cross-field rules); a reset\n // clears the user-validity flag, re-read when the controls hold their reset values.\n const ownerRef = useMemo<RefObject<Element | null>>(\n () => ({\n get current() {\n return controlRef.current?.element ?? null;\n },\n set current(_ignored) {},\n }),\n [],\n );\n const onOwnerEdit = (event: Event) => {\n const target = event.target;\n if (target instanceof Node && controlRef.current?.element.contains(target) === true) return;\n refresh();\n };\n useFormOwnerEvent(ownerRef, \"input\", onOwnerEdit);\n useFormOwnerEvent(ownerRef, \"change\", onOwnerEdit);\n // A new submission sends the current values: nothing differs from it yet (a queued second\n // submit arrives while the status stays pending, so the pending edge alone would miss it).\n useFormOwnerEvent(ownerRef, \"submit\", () => {\n setSuppressNext(false);\n });\n useFormOwnerEvent(ownerRef, \"reset\", (event) => {\n queueMicrotask(() => {\n if (event.defaultPrevented) return;\n refresh({ reset: true });\n });\n });\n\n // Registration with the Form, for the first-error focus after a settle (design 3.5) and the\n // refresh of the other Fields on a change no DOM event carried.\n const registerField = form?.registerField;\n const formRegistration = useRef<FormFieldRegistration | null>(null);\n useLayoutEffect(() => {\n if (registerField === undefined) return;\n const registration: FormFieldRegistration = {\n getFocusTarget: () => focusTargetOf(controlRef.current?.element ?? null),\n showsServerError: () => latest.current.serverShown,\n serverMessages: () => latest.current.serverMessages,\n refresh: () => {\n refresh();\n },\n };\n formRegistration.current = registration;\n const unregister = registerField(registration);\n return () => {\n unregister();\n if (formRegistration.current === registration) formRegistration.current = null;\n };\n }, [registerField, latest, refresh]);\n\n const notifyChange = useCallback(\n (edit: boolean) => {\n if (edit) setDismissed(true);\n refresh(edit ? { dismiss: true } : {});\n latest.current.form?.refreshFields(formRegistration.current);\n },\n [refresh, latest],\n );\n\n // Development: a Field around something that is not a form control (Toggle, Menu, ...).\n useEffect(() => {\n if (!isDev || controlRef.current !== null) return;\n console.error(\n \"Field.Root: no form control registered. Render a Field.Control or a FUNSTACK form \" +\n \"control (Checkbox, Switch, RadioGroup, CheckboxGroup, Select, Combobox) inside it.\",\n );\n }, []);\n\n const invalid = mirror.userInvalid || serverShown;\n const messages = serverShown ? serverMessages : mirror.userInvalid ? mirror.messages : NONE;\n const validity = mirror.validity;\n const stateDisabled = disabled || (control?.disabled ?? false);\n const stateRequired = required || (control?.required ?? false);\n // Memoised: the context value (and so every part) changes only with the state.\n const memoState = useMemo<FieldState>(\n () => ({\n invalid,\n messages,\n validity,\n disabled: stateDisabled,\n required: stateRequired,\n pending: formPending,\n }),\n [invalid, messages, validity, stateDisabled, stateRequired, formPending],\n );\n\n const labelable = control?.labelable ?? true;\n const context = useMemo<FieldContextValue>(\n () => ({\n ids,\n name,\n disabled,\n required,\n state: memoState,\n labelable,\n registerControl,\n registerError,\n notifyChange,\n }),\n [\n ids,\n name,\n disabled,\n required,\n memoState,\n labelable,\n registerControl,\n registerError,\n notifyChange,\n ],\n );\n\n const element$ = useRender<ComponentPropsWithRef<\"div\">, FieldState>({\n defaultElement: \"div\",\n render,\n state: memoState,\n className,\n style,\n children,\n props: rest,\n libraryProps: fieldDataAttributes(memoState),\n });\n\n return <FieldContext value={context}>{element$}</FieldContext>;\n}\n\nFieldRoot.displayName = \"FieldRoot\";\n\n/** The four state attributes every Field part renders (design 6.1). */\nexport function fieldDataAttributes(state: FieldState) {\n return {\n \"data-invalid\": dataAttr(state.invalid),\n \"data-disabled\": dataAttr(state.disabled),\n \"data-required\": dataAttr(state.required),\n \"data-pending\": dataAttr(state.pending),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"field-root.mjs","names":[],"sources":["../../src/field/field-root.tsx"],"sourcesContent":["\"use client\";\nimport {\n use,\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode, RefObject } from \"react\";\nimport { useFormStatus } from \"react-dom\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { FieldContext, FormContext } from \"../internal/field-context.js\";\nimport type {\n FieldContextValue,\n FieldControlRegistration,\n FieldState,\n FormFieldRegistration,\n} from \"../internal/field-context.js\";\nimport { collectInvalid } from \"../internal/focus-first-invalid.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useFormOwnerEvent } from \"../internal/use-form-owner-event.js\";\nimport { useLatest } from \"../internal/use-latest.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport {\n arraysEqual,\n focusTargetOf,\n normaliseErrorValue,\n snapshotValidity,\n supportsUserInvalid,\n VALID,\n validityElementOf,\n validityEquals,\n} from \"../internal/validity.js\";\nimport type { FieldErrorValue, FieldValidity, ValidatableElement } from \"../internal/validity.js\";\n\nexport type { FieldState } from \"../internal/field-context.js\";\nexport type { FieldValidity } from \"../internal/validity.js\";\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * A custom constraint: receives the field's submitted value (`formData.get(name)` as a string,\n * `\"\"` when absent) and the whole form's `FormData`. Return a message (or several) for an error,\n * `null` / `undefined` / `\"\"` for none.\n */\nexport type FieldValidate = (value: string, formData: FormData) => FieldErrorValue;\n\nexport interface FieldRootProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"className\" | \"style\" | \"children\"\n> {\n /** Form field name: the control's default `name`, the key in `Form.Root errors`, and the entry `validate` reads. */\n name?: string;\n /** Disables the control (native `disabled`) and marks every part `data-disabled`. */\n disabled?: boolean;\n /** Makes the control `required` (native) and marks every part `data-required`. */\n required?: boolean;\n /**\n * A custom constraint, written into the control with `setCustomValidity`. Sync. Receives the\n * field's submitted value (`formData.get(name)` as a string, `\"\"` when absent) and the whole\n * form's `FormData`, so a cross-field rule can read the other field. Runs at mount, on every\n * `input` / `change` in the form (and a FUNSTACK control's value change), on blur, after a\n * reset, on a submission attempt, and in every commit in which this prop changed identity, so\n * a rule that reads outside state (a list that loads, a sibling value in React state) updates\n * on render. An inline function therefore re-runs on every render of the Field; wrap it in\n * `useCallback` to re-run only when its inputs change. Return a string\n * or strings for an error, `null` / `undefined` / `\"\"` for none. Shown when the platform's\n * `:user-invalid` heuristic says so, like a native constraint. Not serialisable: a Field with\n * `validate` is rendered from a client component.\n */\n validate?: FieldValidate;\n className?: StateValue<string | undefined, FieldState>;\n style?: StateValue<CSSProperties | undefined, FieldState>;\n children?: StateValue<ReactNode, FieldState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, FieldState>;\n}\n\n/** The urgent mirror of browser-owned validity (design 2.4). */\ninterface Mirror {\n readonly userInvalid: boolean;\n readonly validity: FieldValidity;\n readonly messages: readonly string[];\n}\n\nconst NONE: readonly string[] = Object.freeze([]);\nconst INITIAL_MIRROR: Mirror = { userInvalid: false, validity: VALID, messages: NONE };\n\nfunction mirrorEquals(a: Mirror, b: Mirror): boolean {\n return (\n a.userInvalid === b.userInvalid &&\n validityEquals(a.validity, b.validity) &&\n arraysEqual(a.messages, b.messages)\n );\n}\n\n/** `FormData` entries as comparable strings; a `File` counts as unchanged (design 3.2 item 2). */\nfunction entryStrings(entries: readonly FormDataEntryValue[]): string[] {\n return entries.map((entry) => (typeof entry === \"string\" ? entry : \"\\u0000file\"));\n}\n\ninterface RefreshOptions {\n /** The user edited the control: the server message is dropped from the slot at once. */\n readonly dismiss?: boolean;\n /** After a form reset: the user-validity flag is clear again. */\n readonly reset?: boolean;\n}\n\n/**\n * The field: a `div` that owns the ids, the urgent validity mirror, the custom-validity slot\n * (`validate` and the server error), the control registration and the native listeners\n * (design/components/field-form.md 2, 14). `data-pending` is the enclosing form's submission\n * (`useFormStatus`), the one part that folds the form channel into its own state, because a\n * Field has no action of its own.\n *\n * Owned keys (conventions 1.2): `data-invalid`, `data-disabled`, `data-required`, `data-pending`.\n */\nexport function FieldRoot({\n name,\n disabled = false,\n required = false,\n validate,\n className,\n style,\n children,\n render,\n ...rest\n}: FieldRootProps) {\n const parent = use(FieldContext);\n const form = use(FormContext);\n useDevWarning(\n parent !== null,\n \"Field.Root: nested inside another Field.Root. A control registers with the nearest Field \" +\n \"only; give each control its own, sibling Field.\",\n );\n useDevWarning(\n validate !== undefined && name === undefined,\n \"Field.Root: `validate` without `name`. `validate` receives `formData.get(name)`, which is \" +\n 'always \"\" without a name.',\n );\n\n const id = useId();\n const ids = useMemo(\n () => ({\n control: `${id}-control`,\n label: `${id}-label`,\n description: `${id}-description`,\n error: `${id}-error`,\n }),\n [id],\n );\n\n const { pending: formPending } = useFormStatus();\n\n // --- Registration -------------------------------------------------------------------------\n\n const controlRef = useRef<FieldControlRegistration | null>(null);\n const [control, setControl] = useState<FieldControlRegistration | null>(null);\n const warnedTwoControls = useRef(false);\n const registerControl = useCallback((registration: FieldControlRegistration) => {\n const current = controlRef.current;\n if (current !== null && current.element !== registration.element) {\n if (isDev && !warnedTwoControls.current) {\n warnedTwoControls.current = true;\n console.error(\n \"Field.Root: two form controls registered in one Field. The first one is the \" +\n \"Field's control; give each control its own Field.Root.\",\n );\n }\n return () => {};\n }\n controlRef.current = registration;\n setControl(registration);\n return () => {\n if (controlRef.current !== registration) return;\n controlRef.current = null;\n setControl(null);\n };\n }, []);\n\n const errorCount = useRef(0);\n const registerError = useCallback(() => {\n errorCount.current += 1;\n if (isDev && errorCount.current === 2) {\n console.error(\n \"Field.Error: two Field.Error parts in one Field.Root. Render one; its function \" +\n \"`children` receives `validity` for per-constraint wording.\",\n );\n }\n return () => {\n errorCount.current -= 1;\n };\n }, []);\n\n // --- Server errors: shown, dismissed, re-armed, suppressed (design 3.4) -------------------\n\n const errors = form?.errors;\n const serverMessages = useMemo(\n () => normaliseErrorValue(name === undefined ? undefined : errors?.[name]),\n [errors, name],\n );\n const serverKey = serverMessages.join(\"\\n\");\n /** The user edited the control since the last settled submission. */\n const [dismissed, setDismissed] = useState(false);\n /**\n * The errors shown now answer a value the user had already changed during their flight\n * (intercepted path). Fixed from one settle to the next, so a hidden stale error stays hidden\n * while the next submission is in flight (the `errors` prop still holds that result).\n */\n const [suppressed, setSuppressed] = useState(false);\n /** The user changed the value during the flight in progress: becomes `suppressed` at its settle. */\n const [suppressNext, setSuppressNext] = useState(false);\n // The settle edge of the enclosing form, seen in the render that commits the result (the\n // action's result and `useFormStatus`'s NotPending land together), so the next submission's\n // errors are shown in that same commit, before the Form's settle effect focuses them.\n const [previousFormPending, setPreviousFormPending] = useState(formPending);\n if (previousFormPending !== formPending) {\n setPreviousFormPending(formPending);\n if (formPending) {\n setSuppressNext(false);\n } else {\n setDismissed(false);\n setSuppressed(suppressNext);\n setSuppressNext(false);\n }\n }\n const serverShown = serverMessages.length > 0 && !dismissed && !suppressed;\n\n // --- The mirror -----------------------------------------------------------------------------\n\n const [mirror, setMirror] = useState<Mirror>(INITIAL_MIRROR);\n /** The last mirror written: `refresh` compares against it, so an unchanged read renders nothing. */\n const mirrorRef = useRef<Mirror>(INITIAL_MIRROR);\n /** jsdom fallback for `:user-invalid` (design 2.2): `invalid` fired, cleared by a reset. */\n const attempted = useRef(false);\n /** The element the slot was last written on (a group's first member can change). */\n const slotElement = useRef<ValidatableElement | null>(null);\n\n const latest = useLatest({ name, validate, serverMessages, serverShown, formPending, form });\n\n /** Re-reads the slot and the mirror; returns the messages the Field shows for the control. */\n const refresh = useCallback(\n (options: RefreshOptions = {}): readonly string[] => {\n const registration = controlRef.current;\n if (registration === null) return NONE;\n // The element whose validity is real (field-form.md amendment `validityElement`) and the\n // element whose `:user-invalid` the user's interaction sets; the same for every control but\n // NumberField.\n const surface = validityElementOf(registration.element);\n const el = registration.validityElement?.() ?? surface;\n if (el === null) return NONE;\n const { name, validate, serverMessages, serverShown, formPending, form } = latest.current;\n\n const owner = el.form;\n const formData = owner === null ? new FormData() : new FormData(owner);\n const entry = name === undefined ? null : formData.get(name);\n const value = typeof entry === \"string\" ? entry : \"\";\n const custom = validate === undefined ? NONE : normaliseErrorValue(validate(value, formData));\n\n // The slot (design 2.1): the server message while shown, else `validate`'s.\n const showServer = serverShown && options.dismiss !== true;\n if (slotElement.current !== null && slotElement.current !== el) {\n slotElement.current.setCustomValidity(\"\");\n }\n slotElement.current = el;\n el.setCustomValidity(showServer ? serverMessages.join(\"\\n\") : custom.join(\"\\n\"));\n // Mirror onto the surface so its `:user-invalid` read below sees the new state (the control\n // writes the same derived value after each of its own writes).\n if (\n surface !== null &&\n surface !== el &&\n surface.validationMessage !== el.validationMessage\n ) {\n surface.setCustomValidity(el.validationMessage);\n }\n\n if (options.reset === true) attempted.current = false;\n const userInvalid = supportsUserInvalid()\n ? (surface ?? el).matches(\":user-invalid\")\n : attempted.current && !el.validity.valid;\n const validity = snapshotValidity(el.validity);\n const messages = validity.valid\n ? NONE\n : validity.customError && !showServer && custom.length > 0\n ? custom\n : el.validationMessage === \"\"\n ? NONE\n : [el.validationMessage];\n const next: Mirror = { userInvalid, validity, messages };\n // Only a change schedules a render (a `focusout` with nothing new causes none).\n if (!mirrorEquals(mirrorRef.current, next)) {\n mirrorRef.current = next;\n setMirror(next);\n }\n\n // Stale-value suppression (design 3.2 item 2): while the Form's own submission is in\n // flight, an error is about the value it sent; compare with what the field holds now.\n const submitted = formPending && name !== undefined ? (form?.submittedData() ?? null) : null;\n if (submitted !== null && name !== undefined) {\n setSuppressNext(\n !arraysEqual(entryStrings(formData.getAll(name)), entryStrings(submitted.getAll(name))),\n );\n }\n return showServer ? serverMessages : messages;\n },\n [latest],\n );\n\n // The slot follows the server error and `validate` (and is written once the control is known).\n // Keyed on `validate`'s identity (design 2.1): a rule reading outside state updates in the\n // commit that changed it. An inline function re-runs on every render (documented: `useCallback`\n // avoids it); the form's own values are covered by the listeners below.\n useLayoutEffect(() => {\n refresh();\n }, [refresh, control, serverShown, serverKey, validate]);\n\n // Clear the slot when the control goes away (it may be reused outside the Field).\n useLayoutEffect(\n () => () => {\n slotElement.current?.setCustomValidity(\"\");\n slotElement.current = null;\n },\n [],\n );\n\n // The control's own events (capture, so a group's members are seen on its fieldset).\n const element = control?.element ?? null;\n useLayoutEffect(() => {\n if (element === null) return;\n const onEdit = () => {\n setDismissed(true);\n refresh({ dismiss: true });\n };\n const onBlur = () => refresh();\n const onInvalid = (event: Event) => {\n const target = event.target as ValidatableElement;\n // A submission attempt sets user validity before validating; `checkValidity()` and\n // `reportValidity()` do not (design 2.5). The fallback cannot tell them apart.\n const attempt = supportsUserInvalid() ? target.matches(\":user-invalid\") : true;\n if (!supportsUserInvalid()) attempted.current = true;\n if (attempt && errorCount.current > 0) {\n event.preventDefault();\n const messages = refresh();\n collectInvalid(focusTargetOf(element), messages.join(\" \"));\n return;\n }\n refresh();\n };\n element.addEventListener(\"input\", onEdit, true);\n element.addEventListener(\"change\", onEdit, true);\n element.addEventListener(\"focusout\", onBlur, true);\n element.addEventListener(\"invalid\", onInvalid, true);\n return () => {\n element.removeEventListener(\"input\", onEdit, true);\n element.removeEventListener(\"change\", onEdit, true);\n element.removeEventListener(\"focusout\", onBlur, true);\n element.removeEventListener(\"invalid\", onInvalid, true);\n };\n }, [element, refresh]);\n\n // The form owner's events: another field's edit re-runs `validate` (cross-field rules); a reset\n // clears the user-validity flag, re-read when the controls hold their reset values.\n const ownerRef = useMemo<RefObject<Element | null>>(\n () => ({\n get current() {\n return controlRef.current?.element ?? null;\n },\n set current(_ignored) {},\n }),\n [],\n );\n const onOwnerEdit = (event: Event) => {\n const target = event.target;\n if (target instanceof Node && controlRef.current?.element.contains(target) === true) return;\n refresh();\n };\n useFormOwnerEvent(ownerRef, \"input\", onOwnerEdit);\n useFormOwnerEvent(ownerRef, \"change\", onOwnerEdit);\n // A new submission sends the current values: nothing differs from it yet (a queued second\n // submit arrives while the status stays pending, so the pending edge alone would miss it).\n useFormOwnerEvent(ownerRef, \"submit\", () => {\n setSuppressNext(false);\n });\n useFormOwnerEvent(ownerRef, \"reset\", (event) => {\n queueMicrotask(() => {\n if (event.defaultPrevented) return;\n refresh({ reset: true });\n });\n });\n\n // Registration with the Form, for the first-error focus after a settle (design 3.5) and the\n // refresh of the other Fields on a change no DOM event carried.\n const registerField = form?.registerField;\n const formRegistration = useRef<FormFieldRegistration | null>(null);\n useLayoutEffect(() => {\n if (registerField === undefined) return;\n const registration: FormFieldRegistration = {\n getFocusTarget: () => focusTargetOf(controlRef.current?.element ?? null),\n showsServerError: () => latest.current.serverShown,\n serverMessages: () => latest.current.serverMessages,\n refresh: () => {\n refresh();\n },\n };\n formRegistration.current = registration;\n const unregister = registerField(registration);\n return () => {\n unregister();\n if (formRegistration.current === registration) formRegistration.current = null;\n };\n }, [registerField, latest, refresh]);\n\n const notifyChange = useCallback(\n (edit: boolean) => {\n if (edit) setDismissed(true);\n refresh(edit ? { dismiss: true } : {});\n latest.current.form?.refreshFields(formRegistration.current);\n },\n [refresh, latest],\n );\n\n // Development: a Field around something that is not a form control (Toggle, Menu, ...).\n useEffect(() => {\n if (!isDev || controlRef.current !== null) return;\n console.error(\n \"Field.Root: no form control registered. Render a Field.Control or a FUNSTACK form \" +\n \"control (Checkbox, Switch, RadioGroup, CheckboxGroup, Select, Combobox, Slider, \" +\n \"NumberField) inside it.\",\n );\n }, []);\n\n const invalid = mirror.userInvalid || serverShown;\n const messages = serverShown ? serverMessages : mirror.userInvalid ? mirror.messages : NONE;\n const validity = mirror.validity;\n const stateDisabled = disabled || (control?.disabled ?? false);\n const stateRequired = required || (control?.required ?? false);\n // Memoised: the context value (and so every part) changes only with the state.\n const memoState = useMemo<FieldState>(\n () => ({\n invalid,\n messages,\n validity,\n disabled: stateDisabled,\n required: stateRequired,\n pending: formPending,\n }),\n [invalid, messages, validity, stateDisabled, stateRequired, formPending],\n );\n\n const labelable = control?.labelable ?? true;\n const context = useMemo<FieldContextValue>(\n () => ({\n ids,\n name,\n disabled,\n required,\n state: memoState,\n labelable,\n registerControl,\n registerError,\n notifyChange,\n }),\n [\n ids,\n name,\n disabled,\n required,\n memoState,\n labelable,\n registerControl,\n registerError,\n notifyChange,\n ],\n );\n\n const element$ = useRender<ComponentPropsWithRef<\"div\">, FieldState>({\n defaultElement: \"div\",\n render,\n state: memoState,\n className,\n style,\n children,\n props: rest,\n libraryProps: fieldDataAttributes(memoState),\n });\n\n return <FieldContext value={context}>{element$}</FieldContext>;\n}\n\nFieldRoot.displayName = \"FieldRoot\";\n\n/** The four state attributes every Field part renders (design 6.1). */\nexport function fieldDataAttributes(state: FieldState) {\n return {\n \"data-invalid\": dataAttr(state.invalid),\n \"data-disabled\": dataAttr(state.disabled),\n \"data-required\": dataAttr(state.required),\n \"data-pending\": dataAttr(state.pending),\n };\n}\n"],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  import { dataAttr } from "../internal/data-attr.mjs";
3
3
  import { useLatest } from "../internal/use-latest.mjs";
4
- import { resolveStateValue, useRender } from "../internal/use-render.mjs";
4
+ import { resolveStateValue } from "../internal/render-element.mjs";
5
+ import { useRender } from "../internal/use-render.mjs";
5
6
  import { FormContext } from "../internal/field-context.mjs";
6
7
  import { FormStatusWatcher } from "../internal/form-status-watcher.mjs";
7
8
  import { hasAnyError, sortByDocumentOrder } from "../internal/validity.mjs";
@@ -1 +1 @@
1
- {"version":3,"file":"form-root.mjs","names":[],"sources":["../../src/form/form-root.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useMemo, useRef, useState, useTransition } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, FormEvent, ReactNode } from \"react\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { FormContext } from \"../internal/field-context.js\";\nimport type { FormContextValue, FormFieldRegistration } from \"../internal/field-context.js\";\nimport { announce } from \"../internal/focus-first-invalid.js\";\nimport { FormStatusWatcher } from \"../internal/form-status-watcher.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useLatest } from \"../internal/use-latest.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport { hasAnyError, sortByDocumentOrder } from \"../internal/validity.js\";\nimport type { FormErrors } from \"../internal/validity.js\";\n\nexport type { FieldErrorValue, FormErrors } from \"../internal/validity.js\";\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nexport interface FormRootState {\n /** A submission is in flight: the Form's own transition, or the form's status (`useFormStatus`), including the Suspense wait of the result. */\n readonly pending: boolean;\n}\n\nexport interface FormRootProps extends Omit<\n ComponentPropsWithRef<\"form\">,\n \"onSubmit\" | \"method\" | \"action\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * React form action (`(formData) => unknown`: a Server Function, a `useActionState` dispatch,\n * or a client function), rendered on the form unwrapped so React's progressive enhancement is\n * kept, and run by the Form inside its own transition after hydration (no automatic reset; see\n * `autoReset`). A string is a navigating form. A submitter with its own `formAction` is left to\n * React (automatic reset applies to it).\n */\n action?: string | ((formData: FormData) => unknown);\n /**\n * Field errors keyed by field `name` (usually `state.errors` from `useActionState`). Each\n * `Field.Root` with that name shows its entry, writes it into the control's custom validity,\n * and clears it on the user's next edit; the next settled submission shows the new entries.\n * Errors present at mount are shown without moving focus.\n */\n errors?: FormErrors;\n /**\n * Reset the form (`form.reset()`) when a submission the Form ran settles with no non-empty\n * entry in `errors`. Default `true`. A submission that reports errors never resets, so the\n * user's input, `:user-invalid` and custom validity stay. `false`: never reset.\n */\n autoReset?: boolean;\n /** Only with a string `action`. Owned otherwise (React's own with a function action). */\n method?: ComponentPropsWithRef<\"form\">[\"method\"];\n className?: StateValue<string | undefined, FormRootState>;\n style?: StateValue<CSSProperties | undefined, FormRootState>;\n children?: StateValue<ReactNode, FormRootState>;\n /** Must render a `<form>`. */\n render?: RenderProp<ComponentPropsWithRef<\"form\">, FormRootState>;\n}\n\ninterface Submission {\n /** The Form ran the action itself (`onSubmit` + its own transition). */\n readonly intercepted: boolean;\n readonly formData: FormData | null;\n}\n\n/**\n * A `<form>` that runs its function `action` itself after hydration (design/components/field-form.md\n * 3): `onSubmit` cancels the native submission, so react-dom neither runs the action nor schedules\n * its automatic reset, and the Form awaits `action(formData)` inside its own transition, which\n * still starts the form's status (`useFormStatus`). The action stays rendered unwrapped on the\n * element, so a Server Function posts before hydration.\n *\n * When a submission settles, the Form focuses the first Field that shows a server error (from\n * `errors`), or, when `errors` has no entry and `autoReset` is on, resets the form.\n *\n * Owned keys (conventions 1.2): `action`, `method` (except with a string `action`), `onSubmit`,\n * `data-pending`.\n */\nexport function FormRoot({\n action,\n errors,\n autoReset = true,\n method,\n className,\n style,\n children,\n render,\n ...rest\n}: FormRootProps) {\n const [isPending, startTransition] = useTransition();\n const [statusPending, setStatusPending] = useState(false);\n const pending = isPending || statusPending;\n\n const formRef = useRef<HTMLFormElement | null>(null);\n const submission = useRef<Submission | null>(null);\n const fields = useRef(new Set<FormFieldRegistration>());\n const warnedElement = useRef(false);\n const latest = useLatest({ errors, autoReset });\n const isFunction = typeof action === \"function\";\n\n // A native capture listener sees every submission, React's path included (a submitter with its\n // own `formAction`), before React's `onSubmit` runs and marks it intercepted.\n const libraryRef = useCallback((form: HTMLFormElement) => {\n formRef.current = form;\n if (isDev && !warnedElement.current && form.tagName !== \"FORM\") {\n warnedElement.current = true;\n console.error(\n `Form.Root: expected to render a <form> element but got <${form.tagName.toLowerCase()}>.`,\n );\n }\n const onSubmit = (event: Event) => {\n if (event.target !== form) return;\n submission.current = { intercepted: false, formData: null };\n };\n form.addEventListener(\"submit\", onSubmit, true);\n return () => {\n form.removeEventListener(\"submit\", onSubmit, true);\n if (formRef.current === form) formRef.current = null;\n };\n }, []);\n\n const onSubmit = (event: FormEvent<HTMLFormElement>) => {\n if (typeof action !== \"function\") return; // a navigating form: the browser's\n // A `render` element's own `onSubmit` (merged, it runs first) vetoed the submission\n // (conventions: a `preventDefault()` in the consumer's handler cancels the library's work).\n if (event.defaultPrevented) {\n submission.current = null;\n return;\n }\n const native = event.nativeEvent as SubmitEvent;\n const submitter = native.submitter;\n // A per-button action: React's own path (and its automatic reset).\n if (submitter?.hasAttribute(\"formaction\") === true) return;\n // react-dom now skips its action run and its reset, and still starts the form's status.\n event.preventDefault();\n const formData = new FormData(event.currentTarget, submitter);\n submission.current = { intercepted: true, formData };\n startTransition(async () => {\n await action(formData);\n });\n };\n\n const registerField = useCallback((registration: FormFieldRegistration) => {\n fields.current.add(registration);\n return () => {\n fields.current.delete(registration);\n };\n }, []);\n\n // The settle (design 3.2): the form's status went `true → false`. The Fields rendered the\n // result's errors in that commit; this effect runs after it.\n const onSettled = useCallback(() => {\n const settled = submission.current;\n submission.current = null;\n const form = formRef.current;\n if (form === null) return;\n const shown = [...fields.current]\n .filter((field) => field.showsServerError())\n .map((field) => ({ field, target: field.getFocusTarget() }))\n .filter(\n (entry): entry is { field: FormFieldRegistration; target: HTMLElement } =>\n entry.target !== null && entry.target.isConnected,\n );\n if (shown.length > 0) {\n const first = sortByDocumentOrder(shown, (entry) => entry.target)[0]!;\n // Focus already there (the user pressed Enter in that field): `focus()` does nothing and\n // nothing reads the new description, so the message is announced instead (design 5.3).\n if (first.target.ownerDocument.activeElement === first.target) {\n announce(first.target, first.field.serverMessages().join(\" \"));\n } else {\n first.target.focus();\n }\n return;\n }\n const { errors, autoReset } = latest.current;\n if (autoReset && settled?.intercepted === true && !hasAnyError(errors)) form.reset();\n }, [latest]);\n\n const submittedData = useCallback(() => {\n const current = submission.current;\n return current?.intercepted === true ? current.formData : null;\n }, []);\n\n const refreshFields = useCallback((source: FormFieldRegistration | null) => {\n for (const field of fields.current) if (field !== source) field.refresh();\n }, []);\n\n const context = useMemo<FormContextValue>(\n () => ({ errors, registerField, submittedData, refreshFields }),\n [errors, registerField, submittedData, refreshFields],\n );\n\n const state: FormRootState = { pending };\n\n const element = useRender<ComponentPropsWithRef<\"form\">, FormRootState>({\n defaultElement: \"form\",\n render,\n state,\n className,\n style,\n children: (s) => (\n <>\n {resolveStateValue(children, s)}\n {isFunction && (\n <FormStatusWatcher onPendingChange={setStatusPending} onSettled={onSettled} />\n )}\n </>\n ),\n props: rest,\n libraryProps: {\n method: 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 onSubmit: isFunction ? onSubmit : undefined,\n \"data-pending\": dataAttr(pending),\n },\n ref: libraryRef,\n });\n\n return <FormContext value={context}>{element}</FormContext>;\n}\n\nFormRoot.displayName = \"FormRoot\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"form-root.mjs","names":[],"sources":["../../src/form/form-root.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useMemo, useRef, useState, useTransition } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, FormEvent, ReactNode } from \"react\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { FormContext } from \"../internal/field-context.js\";\nimport type { FormContextValue, FormFieldRegistration } from \"../internal/field-context.js\";\nimport { announce } from \"../internal/focus-first-invalid.js\";\nimport { FormStatusWatcher } from \"../internal/form-status-watcher.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useLatest } from \"../internal/use-latest.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport { hasAnyError, sortByDocumentOrder } from \"../internal/validity.js\";\nimport type { FormErrors } from \"../internal/validity.js\";\n\nexport type { FieldErrorValue, FormErrors } from \"../internal/validity.js\";\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nexport interface FormRootState {\n /** A submission is in flight: the Form's own transition, or the form's status (`useFormStatus`), including the Suspense wait of the result. */\n readonly pending: boolean;\n}\n\nexport interface FormRootProps extends Omit<\n ComponentPropsWithRef<\"form\">,\n \"onSubmit\" | \"method\" | \"action\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * React form action (`(formData) => unknown`: a Server Function, a `useActionState` dispatch,\n * or a client function), rendered on the form unwrapped so React's progressive enhancement is\n * kept, and run by the Form inside its own transition after hydration (no automatic reset; see\n * `autoReset`). A string is a navigating form. A submitter with its own `formAction` is left to\n * React (automatic reset applies to it).\n */\n action?: string | ((formData: FormData) => unknown);\n /**\n * Field errors keyed by field `name` (usually `state.errors` from `useActionState`). Each\n * `Field.Root` with that name shows its entry, writes it into the control's custom validity,\n * and clears it on the user's next edit; the next settled submission shows the new entries.\n * Errors present at mount are shown without moving focus.\n */\n errors?: FormErrors;\n /**\n * Reset the form (`form.reset()`) when a submission the Form ran settles with no non-empty\n * entry in `errors`. Default `true`. A submission that reports errors never resets, so the\n * user's input, `:user-invalid` and custom validity stay. `false`: never reset.\n */\n autoReset?: boolean;\n /** Only with a string `action`. Owned otherwise (React's own with a function action). */\n method?: ComponentPropsWithRef<\"form\">[\"method\"];\n className?: StateValue<string | undefined, FormRootState>;\n style?: StateValue<CSSProperties | undefined, FormRootState>;\n children?: StateValue<ReactNode, FormRootState>;\n /** Must render a `<form>`. */\n render?: RenderProp<ComponentPropsWithRef<\"form\">, FormRootState>;\n}\n\ninterface Submission {\n /** The Form ran the action itself (`onSubmit` + its own transition). */\n readonly intercepted: boolean;\n readonly formData: FormData | null;\n}\n\n/**\n * A `<form>` that runs its function `action` itself after hydration (design/components/field-form.md\n * 3): `onSubmit` cancels the native submission, so react-dom neither runs the action nor schedules\n * its automatic reset, and the Form awaits `action(formData)` inside its own transition, which\n * still starts the form's status (`useFormStatus`). The action stays rendered unwrapped on the\n * element, so a Server Function posts before hydration.\n *\n * When a submission settles, the Form focuses the first Field that shows a server error (from\n * `errors`), or, when `errors` has no entry and `autoReset` is on, resets the form.\n *\n * Owned keys (conventions 1.2): `action`, `method` (except with a string `action`), `onSubmit`,\n * `data-pending`.\n */\nexport function FormRoot({\n action,\n errors,\n autoReset = true,\n method,\n className,\n style,\n children,\n render,\n ...rest\n}: FormRootProps) {\n const [isPending, startTransition] = useTransition();\n const [statusPending, setStatusPending] = useState(false);\n const pending = isPending || statusPending;\n\n const formRef = useRef<HTMLFormElement | null>(null);\n const submission = useRef<Submission | null>(null);\n const fields = useRef(new Set<FormFieldRegistration>());\n const warnedElement = useRef(false);\n const latest = useLatest({ errors, autoReset });\n const isFunction = typeof action === \"function\";\n\n // A native capture listener sees every submission, React's path included (a submitter with its\n // own `formAction`), before React's `onSubmit` runs and marks it intercepted.\n const libraryRef = useCallback((form: HTMLFormElement) => {\n formRef.current = form;\n if (isDev && !warnedElement.current && form.tagName !== \"FORM\") {\n warnedElement.current = true;\n console.error(\n `Form.Root: expected to render a <form> element but got <${form.tagName.toLowerCase()}>.`,\n );\n }\n const onSubmit = (event: Event) => {\n if (event.target !== form) return;\n submission.current = { intercepted: false, formData: null };\n };\n form.addEventListener(\"submit\", onSubmit, true);\n return () => {\n form.removeEventListener(\"submit\", onSubmit, true);\n if (formRef.current === form) formRef.current = null;\n };\n }, []);\n\n const onSubmit = (event: FormEvent<HTMLFormElement>) => {\n if (typeof action !== \"function\") return; // a navigating form: the browser's\n // A `render` element's own `onSubmit` (merged, it runs first) vetoed the submission\n // (conventions: a `preventDefault()` in the consumer's handler cancels the library's work).\n if (event.defaultPrevented) {\n submission.current = null;\n return;\n }\n const native = event.nativeEvent as SubmitEvent;\n const submitter = native.submitter;\n // A per-button action: React's own path (and its automatic reset).\n if (submitter?.hasAttribute(\"formaction\") === true) return;\n // react-dom now skips its action run and its reset, and still starts the form's status.\n event.preventDefault();\n const formData = new FormData(event.currentTarget, submitter);\n submission.current = { intercepted: true, formData };\n startTransition(async () => {\n await action(formData);\n });\n };\n\n const registerField = useCallback((registration: FormFieldRegistration) => {\n fields.current.add(registration);\n return () => {\n fields.current.delete(registration);\n };\n }, []);\n\n // The settle (design 3.2): the form's status went `true → false`. The Fields rendered the\n // result's errors in that commit; this effect runs after it.\n const onSettled = useCallback(() => {\n const settled = submission.current;\n submission.current = null;\n const form = formRef.current;\n if (form === null) return;\n const shown = [...fields.current]\n .filter((field) => field.showsServerError())\n .map((field) => ({ field, target: field.getFocusTarget() }))\n .filter(\n (entry): entry is { field: FormFieldRegistration; target: HTMLElement } =>\n entry.target !== null && entry.target.isConnected,\n );\n if (shown.length > 0) {\n const first = sortByDocumentOrder(shown, (entry) => entry.target)[0]!;\n // Focus already there (the user pressed Enter in that field): `focus()` does nothing and\n // nothing reads the new description, so the message is announced instead (design 5.3).\n if (first.target.ownerDocument.activeElement === first.target) {\n announce(first.target, first.field.serverMessages().join(\" \"));\n } else {\n first.target.focus();\n }\n return;\n }\n const { errors, autoReset } = latest.current;\n if (autoReset && settled?.intercepted === true && !hasAnyError(errors)) form.reset();\n }, [latest]);\n\n const submittedData = useCallback(() => {\n const current = submission.current;\n return current?.intercepted === true ? current.formData : null;\n }, []);\n\n const refreshFields = useCallback((source: FormFieldRegistration | null) => {\n for (const field of fields.current) if (field !== source) field.refresh();\n }, []);\n\n const context = useMemo<FormContextValue>(\n () => ({ errors, registerField, submittedData, refreshFields }),\n [errors, registerField, submittedData, refreshFields],\n );\n\n const state: FormRootState = { pending };\n\n const element = useRender<ComponentPropsWithRef<\"form\">, FormRootState>({\n defaultElement: \"form\",\n render,\n state,\n className,\n style,\n children: (s) => (\n <>\n {resolveStateValue(children, s)}\n {isFunction && (\n <FormStatusWatcher onPendingChange={setStatusPending} onSettled={onSettled} />\n )}\n </>\n ),\n props: rest,\n libraryProps: {\n method: 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 onSubmit: isFunction ? onSubmit : undefined,\n \"data-pending\": dataAttr(pending),\n },\n ref: libraryRef,\n });\n\n return <FormContext value={context}>{element}</FormContext>;\n}\n\nFormRoot.displayName = \"FormRoot\";\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 { useCallback, useRef } from "react";
5
6
  import { useFormStatus } from "react-dom";
6
7
  //#region src/form/form-submit.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"form-submit.mjs","names":[],"sources":["../../src/form/form-submit.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useRef } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { useFormStatus } from \"react-dom\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nexport interface FormSubmitState {\n /** The enclosing form's submission is in flight (`useFormStatus().pending`). */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface FormSubmitProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"type\" | \"className\" | \"style\" | \"children\"\n> {\n /** Native `disabled`; owned. Never set by the library (conventions 3.6). */\n disabled?: boolean;\n className?: StateValue<string | undefined, FormSubmitState>;\n style?: StateValue<CSSProperties | undefined, FormSubmitState>;\n children?: StateValue<ReactNode, FormSubmitState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, FormSubmitState>;\n}\n\n/**\n * The form's submit button: `<button type=\"submit\">` with `data-pending` while the enclosing\n * form's submission is in flight (`useFormStatus`). Works in any React-rendered form\n * (`Form.Root`, `Dialog.Form`, a plain `<form action>`). Never disabled while pending: block\n * repeat clicks with CSS on `[data-pending]` if you need to.\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>`), `disabled`, `data-pending`,\n * `data-disabled`.\n */\nexport function FormSubmit({\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: FormSubmitProps) {\n const { pending } = useFormStatus();\n const warned = useRef(false);\n const ref = useCallback((el: HTMLElement) => {\n const submitter =\n el.tagName === \"BUTTON\" ||\n (el.tagName === \"INPUT\" && [\"submit\", \"image\"].includes((el as HTMLInputElement).type));\n if (isDev && !warned.current && !submitter) {\n warned.current = true;\n console.error(\n `Form.Submit: expected to render a <button> (or <input type=\"submit\">) but got ` +\n `<${el.tagName.toLowerCase()}>. Only a submit button submits the form natively.`,\n );\n }\n // A cleanup, so the merged ref never calls this with `null`.\n return () => {};\n }, []);\n const state: FormSubmitState = { pending, disabled };\n return useRender<ComponentPropsWithRef<\"button\">, FormSubmitState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n ...(rendersButton(render) && { type: \"submit\" as const }),\n disabled,\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n },\n ref,\n });\n}\n\nFormSubmit.displayName = \"FormSubmit\";\n"],"mappings":";;;;;;AAQA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;;;;;AA6BvC,SAAgB,WAAW,EACzB,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACe;CAClB,MAAM,EAAE,YAAY,cAAc;CAClC,MAAM,SAAS,OAAO,KAAK;CAC3B,MAAM,MAAM,aAAa,OAAoB;EAC3C,MAAM,YACJ,GAAG,YAAY,YACd,GAAG,YAAY,WAAW,CAAC,UAAU,OAAO,CAAC,CAAC,SAAU,GAAwB,IAAI;EACvF,IAAI,SAAS,CAAC,OAAO,WAAW,CAAC,WAAW;GAC1C,OAAO,UAAU;GACjB,QAAQ,MACN,kFACM,GAAG,QAAQ,YAAY,EAAE,mDACjC;EACF;EAEA,aAAa,CAAC;CAChB,GAAG,CAAC,CAAC;CAEL,OAAO,UAA4D;EACjE,gBAAgB;EAChB;EACA,OAAA;GAJ+B;GAAS;EAIpC;EACJ;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;GACvD;GACA,gBAAgB,SAAS,OAAO;GAChC,iBAAiB,SAAS,QAAQ;EACpC;EACA;CACF,CAAC;AACH;AAEA,WAAW,cAAc"}
1
+ {"version":3,"file":"form-submit.mjs","names":[],"sources":["../../src/form/form-submit.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useRef } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { useFormStatus } from \"react-dom\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nexport interface FormSubmitState {\n /** The enclosing form's submission is in flight (`useFormStatus().pending`). */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface FormSubmitProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"type\" | \"className\" | \"style\" | \"children\"\n> {\n /** Native `disabled`; owned. Never set by the library (conventions 3.6). */\n disabled?: boolean;\n className?: StateValue<string | undefined, FormSubmitState>;\n style?: StateValue<CSSProperties | undefined, FormSubmitState>;\n children?: StateValue<ReactNode, FormSubmitState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, FormSubmitState>;\n}\n\n/**\n * The form's submit button: `<button type=\"submit\">` with `data-pending` while the enclosing\n * form's submission is in flight (`useFormStatus`). Works in any React-rendered form\n * (`Form.Root`, `Dialog.Form`, a plain `<form action>`). Never disabled while pending: block\n * repeat clicks with CSS on `[data-pending]` if you need to.\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>`), `disabled`, `data-pending`,\n * `data-disabled`.\n */\nexport function FormSubmit({\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: FormSubmitProps) {\n const { pending } = useFormStatus();\n const warned = useRef(false);\n const ref = useCallback((el: HTMLElement) => {\n const submitter =\n el.tagName === \"BUTTON\" ||\n (el.tagName === \"INPUT\" && [\"submit\", \"image\"].includes((el as HTMLInputElement).type));\n if (isDev && !warned.current && !submitter) {\n warned.current = true;\n console.error(\n `Form.Submit: expected to render a <button> (or <input type=\"submit\">) but got ` +\n `<${el.tagName.toLowerCase()}>. Only a submit button submits the form natively.`,\n );\n }\n // A cleanup, so the merged ref never calls this with `null`.\n return () => {};\n }, []);\n const state: FormSubmitState = { pending, disabled };\n return useRender<ComponentPropsWithRef<\"button\">, FormSubmitState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n ...(rendersButton(render) && { type: \"submit\" as const }),\n disabled,\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n },\n ref,\n });\n}\n\nFormSubmit.displayName = \"FormSubmit\";\n"],"mappings":";;;;;;;AAQA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;;;;;AA6BvC,SAAgB,WAAW,EACzB,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACe;CAClB,MAAM,EAAE,YAAY,cAAc;CAClC,MAAM,SAAS,OAAO,KAAK;CAC3B,MAAM,MAAM,aAAa,OAAoB;EAC3C,MAAM,YACJ,GAAG,YAAY,YACd,GAAG,YAAY,WAAW,CAAC,UAAU,OAAO,CAAC,CAAC,SAAU,GAAwB,IAAI;EACvF,IAAI,SAAS,CAAC,OAAO,WAAW,CAAC,WAAW;GAC1C,OAAO,UAAU;GACjB,QAAQ,MACN,kFACM,GAAG,QAAQ,YAAY,EAAE,mDACjC;EACF;EAEA,aAAa,CAAC;CAChB,GAAG,CAAC,CAAC;CAEL,OAAO,UAA4D;EACjE,gBAAgB;EAChB;EACA,OAAA;GAJ+B;GAAS;EAIpC;EACJ;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;GACvD;GACA,gBAAgB,SAAS,OAAO;GAChC,iBAAiB,SAAS,QAAQ;EACpC;EACA;CACF,CAAC;AACH;AAEA,WAAW,cAAc"}