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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/dist/accordion/accordion-context.mjs.map +1 -1
  2. package/dist/accordion/accordion-item.mjs +18 -4
  3. package/dist/accordion/accordion-item.mjs.map +1 -1
  4. package/dist/accordion/accordion-panel.mjs +2 -3
  5. package/dist/accordion/accordion-panel.mjs.map +1 -1
  6. package/dist/accordion/accordion-root.d.mts.map +1 -1
  7. package/dist/accordion/accordion-root.mjs +32 -14
  8. package/dist/accordion/accordion-root.mjs.map +1 -1
  9. package/dist/accordion/accordion-trigger.mjs +4 -5
  10. package/dist/accordion/accordion-trigger.mjs.map +1 -1
  11. package/dist/checkbox/checkbox.d.mts +5 -3
  12. package/dist/checkbox/checkbox.d.mts.map +1 -1
  13. package/dist/checkbox/checkbox.mjs +25 -9
  14. package/dist/checkbox/checkbox.mjs.map +1 -1
  15. package/dist/checkbox-group/checkbox-group-context.mjs.map +1 -1
  16. package/dist/checkbox-group/checkbox-group-item.d.mts +2 -1
  17. package/dist/checkbox-group/checkbox-group-item.d.mts.map +1 -1
  18. package/dist/checkbox-group/checkbox-group-item.mjs +3 -1
  19. package/dist/checkbox-group/checkbox-group-item.mjs.map +1 -1
  20. package/dist/checkbox-group/checkbox-group-root.d.mts +5 -2
  21. package/dist/checkbox-group/checkbox-group-root.d.mts.map +1 -1
  22. package/dist/checkbox-group/checkbox-group-root.mjs +24 -5
  23. package/dist/checkbox-group/checkbox-group-root.mjs.map +1 -1
  24. package/dist/combobox/combobox-clear.d.mts +34 -0
  25. package/dist/combobox/combobox-clear.d.mts.map +1 -0
  26. package/dist/combobox/combobox-clear.mjs +58 -0
  27. package/dist/combobox/combobox-clear.mjs.map +1 -0
  28. package/dist/combobox/combobox-context.mjs +23 -0
  29. package/dist/combobox/combobox-context.mjs.map +1 -0
  30. package/dist/combobox/combobox-group.d.mts +72 -0
  31. package/dist/combobox/combobox-group.d.mts.map +1 -0
  32. package/dist/combobox/combobox-group.mjs +104 -0
  33. package/dist/combobox/combobox-group.mjs.map +1 -0
  34. package/dist/combobox/combobox-input.d.mts +42 -0
  35. package/dist/combobox/combobox-input.d.mts.map +1 -0
  36. package/dist/combobox/combobox-input.mjs +102 -0
  37. package/dist/combobox/combobox-input.mjs.map +1 -0
  38. package/dist/combobox/combobox-label.d.mts +22 -0
  39. package/dist/combobox/combobox-label.d.mts.map +1 -0
  40. package/dist/combobox/combobox-label.mjs +32 -0
  41. package/dist/combobox/combobox-label.mjs.map +1 -0
  42. package/dist/combobox/combobox-list.d.mts +37 -0
  43. package/dist/combobox/combobox-list.d.mts.map +1 -0
  44. package/dist/combobox/combobox-list.mjs +70 -0
  45. package/dist/combobox/combobox-list.mjs.map +1 -0
  46. package/dist/combobox/combobox-option.d.mts +45 -0
  47. package/dist/combobox/combobox-option.d.mts.map +1 -0
  48. package/dist/combobox/combobox-option.mjs +88 -0
  49. package/dist/combobox/combobox-option.mjs.map +1 -0
  50. package/dist/combobox/combobox-popup.d.mts +49 -0
  51. package/dist/combobox/combobox-popup.d.mts.map +1 -0
  52. package/dist/combobox/combobox-popup.mjs +86 -0
  53. package/dist/combobox/combobox-popup.mjs.map +1 -0
  54. package/dist/combobox/combobox-root.d.mts +135 -0
  55. package/dist/combobox/combobox-root.d.mts.map +1 -0
  56. package/dist/combobox/combobox-root.mjs +523 -0
  57. package/dist/combobox/combobox-root.mjs.map +1 -0
  58. package/dist/combobox/combobox-trigger.d.mts +36 -0
  59. package/dist/combobox/combobox-trigger.d.mts.map +1 -0
  60. package/dist/combobox/combobox-trigger.mjs +92 -0
  61. package/dist/combobox/combobox-trigger.mjs.map +1 -0
  62. package/dist/combobox/index.d.mts +36 -0
  63. package/dist/combobox/index.d.mts.map +1 -0
  64. package/dist/combobox/index.mjs +36 -0
  65. package/dist/combobox/index.mjs.map +1 -0
  66. package/dist/dialog/dialog-close.d.mts +17 -10
  67. package/dist/dialog/dialog-close.d.mts.map +1 -1
  68. package/dist/dialog/dialog-close.mjs +31 -20
  69. package/dist/dialog/dialog-close.mjs.map +1 -1
  70. package/dist/dialog/dialog-context.mjs.map +1 -1
  71. package/dist/dialog/dialog-form.d.mts +12 -5
  72. package/dist/dialog/dialog-form.d.mts.map +1 -1
  73. package/dist/dialog/dialog-form.mjs +16 -28
  74. package/dist/dialog/dialog-form.mjs.map +1 -1
  75. package/dist/dialog/dialog-popup.d.mts +10 -4
  76. package/dist/dialog/dialog-popup.d.mts.map +1 -1
  77. package/dist/dialog/dialog-popup.mjs +106 -41
  78. package/dist/dialog/dialog-popup.mjs.map +1 -1
  79. package/dist/dialog/dialog-root.d.mts +14 -8
  80. package/dist/dialog/dialog-root.d.mts.map +1 -1
  81. package/dist/dialog/dialog-root.mjs +66 -11
  82. package/dist/dialog/dialog-root.mjs.map +1 -1
  83. package/dist/dialog/dialog-trigger.d.mts +3 -2
  84. package/dist/dialog/dialog-trigger.d.mts.map +1 -1
  85. package/dist/dialog/dialog-trigger.mjs.map +1 -1
  86. package/dist/dialog/index.d.mts +4 -2
  87. package/dist/dialog/index.d.mts.map +1 -1
  88. package/dist/dialog/index.mjs +4 -2
  89. package/dist/dialog/index.mjs.map +1 -1
  90. package/dist/field/field-control.d.mts +33 -0
  91. package/dist/field/field-control.d.mts.map +1 -0
  92. package/dist/field/field-control.mjs +68 -0
  93. package/dist/field/field-control.mjs.map +1 -0
  94. package/dist/field/field-description.d.mts +24 -0
  95. package/dist/field/field-description.d.mts.map +1 -0
  96. package/dist/field/field-description.mjs +34 -0
  97. package/dist/field/field-description.mjs.map +1 -0
  98. package/dist/field/field-error.d.mts +30 -0
  99. package/dist/field/field-error.d.mts.map +1 -0
  100. package/dist/field/field-error.mjs +42 -0
  101. package/dist/field/field-error.mjs.map +1 -0
  102. package/dist/field/field-label.d.mts +25 -0
  103. package/dist/field/field-label.d.mts.map +1 -0
  104. package/dist/field/field-label.mjs +46 -0
  105. package/dist/field/field-label.mjs.map +1 -0
  106. package/dist/field/field-root.d.mts +53 -0
  107. package/dist/field/field-root.d.mts.map +1 -0
  108. package/dist/field/field-root.mjs +317 -0
  109. package/dist/field/field-root.mjs.map +1 -0
  110. package/dist/field/index.d.mts +18 -0
  111. package/dist/field/index.d.mts.map +1 -0
  112. package/dist/field/index.mjs +17 -0
  113. package/dist/field/index.mjs.map +1 -0
  114. package/dist/form/form-root.d.mts +58 -0
  115. package/dist/form/form-root.d.mts.map +1 -0
  116. package/dist/form/form-root.mjs +146 -0
  117. package/dist/form/form-root.mjs.map +1 -0
  118. package/dist/form/form-submit.d.mts +31 -0
  119. package/dist/form/form-submit.d.mts.map +1 -0
  120. package/dist/form/form-submit.mjs +52 -0
  121. package/dist/form/form-submit.mjs.map +1 -0
  122. package/dist/form/index.d.mts +11 -0
  123. package/dist/form/index.d.mts.map +1 -0
  124. package/dist/form/index.mjs +11 -0
  125. package/dist/form/index.mjs.map +1 -0
  126. package/dist/index.d.mts +46 -2
  127. package/dist/index.mjs +8 -1
  128. package/dist/internal/content-focus-probe.mjs +42 -0
  129. package/dist/internal/content-focus-probe.mjs.map +1 -0
  130. package/dist/internal/field-context.d.mts +19 -0
  131. package/dist/internal/field-context.d.mts.map +1 -0
  132. package/dist/internal/field-context.mjs +14 -0
  133. package/dist/internal/field-context.mjs.map +1 -0
  134. package/dist/internal/focus-first-invalid.mjs +67 -0
  135. package/dist/internal/focus-first-invalid.mjs.map +1 -0
  136. package/dist/internal/form-status-watcher.mjs +31 -0
  137. package/dist/internal/form-status-watcher.mjs.map +1 -0
  138. package/dist/internal/modal-registry.mjs +39 -0
  139. package/dist/internal/modal-registry.mjs.map +1 -0
  140. package/dist/internal/platform-attributes.d.mts +12 -1
  141. package/dist/internal/platform-attributes.d.mts.map +1 -1
  142. package/dist/internal/types.d.mts +11 -1
  143. package/dist/internal/types.d.mts.map +1 -1
  144. package/dist/internal/use-controllable-action.mjs +35 -13
  145. package/dist/internal/use-controllable-action.mjs.map +1 -1
  146. package/dist/internal/use-field-control.mjs +107 -0
  147. package/dist/internal/use-field-control.mjs.map +1 -0
  148. package/dist/internal/use-form-owner-event.mjs +44 -0
  149. package/dist/internal/use-form-owner-event.mjs.map +1 -0
  150. package/dist/internal/use-form-reset.mjs +12 -31
  151. package/dist/internal/use-form-reset.mjs.map +1 -1
  152. package/dist/internal/use-hydrated-mount.mjs +21 -0
  153. package/dist/internal/use-hydrated-mount.mjs.map +1 -0
  154. package/dist/internal/use-latest.mjs +32 -0
  155. package/dist/internal/use-latest.mjs.map +1 -0
  156. package/dist/internal/use-modal-registration.mjs +47 -0
  157. package/dist/internal/use-modal-registration.mjs.map +1 -0
  158. package/dist/internal/use-native-group-reconciliation.mjs +6 -5
  159. package/dist/internal/use-native-group-reconciliation.mjs.map +1 -1
  160. package/dist/internal/use-native-select-value.mjs +184 -0
  161. package/dist/internal/use-native-select-value.mjs.map +1 -0
  162. package/dist/internal/use-popover-popup.mjs +8 -22
  163. package/dist/internal/use-popover-popup.mjs.map +1 -1
  164. package/dist/internal/use-popover-root.mjs +58 -23
  165. package/dist/internal/use-popover-root.mjs.map +1 -1
  166. package/dist/internal/validity.d.mts +24 -0
  167. package/dist/internal/validity.d.mts.map +1 -0
  168. package/dist/internal/validity.mjs +108 -0
  169. package/dist/internal/validity.mjs.map +1 -0
  170. package/dist/menu/menu-group.d.mts.map +1 -1
  171. package/dist/menu/menu-group.mjs +3 -2
  172. package/dist/menu/menu-group.mjs.map +1 -1
  173. package/dist/menu/menu-radio-group.mjs +9 -4
  174. package/dist/menu/menu-radio-group.mjs.map +1 -1
  175. package/dist/menu/menu-root.mjs.map +1 -1
  176. package/dist/radio-group/radio-group-root.d.mts +4 -2
  177. package/dist/radio-group/radio-group-root.d.mts.map +1 -1
  178. package/dist/radio-group/radio-group-root.mjs +34 -8
  179. package/dist/radio-group/radio-group-root.mjs.map +1 -1
  180. package/dist/select/index.d.mts +30 -0
  181. package/dist/select/index.d.mts.map +1 -0
  182. package/dist/select/index.mjs +30 -0
  183. package/dist/select/index.mjs.map +1 -0
  184. package/dist/select/select-context.mjs +13 -0
  185. package/dist/select/select-context.mjs.map +1 -0
  186. package/dist/select/select-dom.mjs +44 -0
  187. package/dist/select/select-dom.mjs.map +1 -0
  188. package/dist/select/select-group-label.d.mts +22 -0
  189. package/dist/select/select-group-label.d.mts.map +1 -0
  190. package/dist/select/select-group-label.mjs +39 -0
  191. package/dist/select/select-group-label.mjs.map +1 -0
  192. package/dist/select/select-group.d.mts +28 -0
  193. package/dist/select/select-group.d.mts.map +1 -0
  194. package/dist/select/select-group.mjs +47 -0
  195. package/dist/select/select-group.mjs.map +1 -0
  196. package/dist/select/select-option.d.mts +42 -0
  197. package/dist/select/select-option.d.mts.map +1 -0
  198. package/dist/select/select-option.mjs +65 -0
  199. package/dist/select/select-option.mjs.map +1 -0
  200. package/dist/select/select-root.d.mts +68 -0
  201. package/dist/select/select-root.d.mts.map +1 -0
  202. package/dist/select/select-root.mjs +114 -0
  203. package/dist/select/select-root.mjs.map +1 -0
  204. package/dist/select/select-separator.d.mts +20 -0
  205. package/dist/select/select-separator.d.mts.map +1 -0
  206. package/dist/select/select-separator.mjs +30 -0
  207. package/dist/select/select-separator.mjs.map +1 -0
  208. package/dist/select/select-trigger.d.mts +29 -0
  209. package/dist/select/select-trigger.d.mts.map +1 -0
  210. package/dist/select/select-trigger.mjs +48 -0
  211. package/dist/select/select-trigger.mjs.map +1 -0
  212. package/dist/select/select-value.d.mts +31 -0
  213. package/dist/select/select-value.d.mts.map +1 -0
  214. package/dist/select/select-value.mjs +101 -0
  215. package/dist/select/select-value.mjs.map +1 -0
  216. package/dist/slider/index.d.mts +32 -0
  217. package/dist/slider/index.d.mts.map +1 -0
  218. package/dist/slider/index.mjs +27 -0
  219. package/dist/slider/index.mjs.map +1 -0
  220. package/dist/slider/slider-arithmetic.mjs +120 -0
  221. package/dist/slider/slider-arithmetic.mjs.map +1 -0
  222. package/dist/slider/slider-context.mjs +13 -0
  223. package/dist/slider/slider-context.mjs.map +1 -0
  224. package/dist/slider/slider-indicator.d.mts +22 -0
  225. package/dist/slider/slider-indicator.d.mts.map +1 -0
  226. package/dist/slider/slider-indicator.mjs +36 -0
  227. package/dist/slider/slider-indicator.mjs.map +1 -0
  228. package/dist/slider/slider-label.d.mts +23 -0
  229. package/dist/slider/slider-label.d.mts.map +1 -0
  230. package/dist/slider/slider-label.mjs +44 -0
  231. package/dist/slider/slider-label.mjs.map +1 -0
  232. package/dist/slider/slider-root.d.mts +111 -0
  233. package/dist/slider/slider-root.d.mts.map +1 -0
  234. package/dist/slider/slider-root.mjs +582 -0
  235. package/dist/slider/slider-root.mjs.map +1 -0
  236. package/dist/slider/slider-thumb.d.mts +58 -0
  237. package/dist/slider/slider-thumb.d.mts.map +1 -0
  238. package/dist/slider/slider-thumb.mjs +145 -0
  239. package/dist/slider/slider-thumb.mjs.map +1 -0
  240. package/dist/slider/slider-track.d.mts +29 -0
  241. package/dist/slider/slider-track.d.mts.map +1 -0
  242. package/dist/slider/slider-track.mjs +54 -0
  243. package/dist/slider/slider-track.mjs.map +1 -0
  244. package/dist/slider/slider-value.d.mts +34 -0
  245. package/dist/slider/slider-value.d.mts.map +1 -0
  246. package/dist/slider/slider-value.mjs +38 -0
  247. package/dist/slider/slider-value.mjs.map +1 -0
  248. package/dist/switch/switch.d.mts +5 -3
  249. package/dist/switch/switch.d.mts.map +1 -1
  250. package/dist/switch/switch.mjs +24 -8
  251. package/dist/switch/switch.mjs.map +1 -1
  252. package/dist/tabs/tabs-root.mjs +13 -3
  253. package/dist/tabs/tabs-root.mjs.map +1 -1
  254. package/dist/toast/index.d.mts +25 -0
  255. package/dist/toast/index.d.mts.map +1 -0
  256. package/dist/toast/index.mjs +26 -0
  257. package/dist/toast/index.mjs.map +1 -0
  258. package/dist/toast/toast-action.d.mts +39 -0
  259. package/dist/toast/toast-action.d.mts.map +1 -0
  260. package/dist/toast/toast-action.mjs +64 -0
  261. package/dist/toast/toast-action.mjs.map +1 -0
  262. package/dist/toast/toast-close.d.mts +25 -0
  263. package/dist/toast/toast-close.d.mts.map +1 -0
  264. package/dist/toast/toast-close.mjs +46 -0
  265. package/dist/toast/toast-close.mjs.map +1 -0
  266. package/dist/toast/toast-context.mjs +19 -0
  267. package/dist/toast/toast-context.mjs.map +1 -0
  268. package/dist/toast/toast-description.d.mts +24 -0
  269. package/dist/toast/toast-description.d.mts.map +1 -0
  270. package/dist/toast/toast-description.mjs +35 -0
  271. package/dist/toast/toast-description.mjs.map +1 -0
  272. package/dist/toast/toast-root.d.mts +43 -0
  273. package/dist/toast/toast-root.d.mts.map +1 -0
  274. package/dist/toast/toast-root.mjs +204 -0
  275. package/dist/toast/toast-root.mjs.map +1 -0
  276. package/dist/toast/toast-title.d.mts +24 -0
  277. package/dist/toast/toast-title.d.mts.map +1 -0
  278. package/dist/toast/toast-title.mjs +33 -0
  279. package/dist/toast/toast-title.mjs.map +1 -0
  280. package/dist/toast/toast-viewport.d.mts +52 -0
  281. package/dist/toast/toast-viewport.d.mts.map +1 -0
  282. package/dist/toast/toast-viewport.mjs +621 -0
  283. package/dist/toast/toast-viewport.mjs.map +1 -0
  284. package/dist/toast/toast.d.mts +119 -0
  285. package/dist/toast/toast.d.mts.map +1 -0
  286. package/dist/toast/toast.mjs +153 -0
  287. package/dist/toast/toast.mjs.map +1 -0
  288. package/package.json +7 -1
@@ -0,0 +1,24 @@
1
+ //#region src/internal/validity.d.ts
2
+ /** A field error: a message, several messages, or nothing (`""`, `[]`, `null`, `undefined`). */
3
+ export type FieldErrorValue = string | readonly string[] | null | undefined;
4
+ /** Field errors keyed by field `name` (usually `state.errors` from `useActionState`). */
5
+ export type FormErrors = Readonly<Record<string, FieldErrorValue>>;
6
+ /**
7
+ * A plain, serialisable snapshot of the control's `ValidityState` (all `false` and
8
+ * `valid: true` on the server and before the first read).
9
+ */
10
+ export interface FieldValidity {
11
+ readonly valueMissing: boolean;
12
+ readonly typeMismatch: boolean;
13
+ readonly patternMismatch: boolean;
14
+ readonly tooLong: boolean;
15
+ readonly tooShort: boolean;
16
+ readonly rangeUnderflow: boolean;
17
+ readonly rangeOverflow: boolean;
18
+ readonly stepMismatch: boolean;
19
+ readonly badInput: boolean;
20
+ readonly customError: boolean;
21
+ readonly valid: boolean;
22
+ }
23
+ //#endregion
24
+ //# sourceMappingURL=validity.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validity.d.mts","names":[],"sources":["../../src/internal/validity.ts"],"mappings":";;YAIY;;YAGA,aAAa,SAAS,eAAe;;;;;iBAMhC;WACN;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA"}
@@ -0,0 +1,108 @@
1
+ //#region src/internal/validity.ts
2
+ const VALIDITY_KEYS = [
3
+ "valueMissing",
4
+ "typeMismatch",
5
+ "patternMismatch",
6
+ "tooLong",
7
+ "tooShort",
8
+ "rangeUnderflow",
9
+ "rangeOverflow",
10
+ "stepMismatch",
11
+ "badInput",
12
+ "customError",
13
+ "valid"
14
+ ];
15
+ const VALID = Object.freeze({
16
+ valueMissing: false,
17
+ typeMismatch: false,
18
+ patternMismatch: false,
19
+ tooLong: false,
20
+ tooShort: false,
21
+ rangeUnderflow: false,
22
+ rangeOverflow: false,
23
+ stepMismatch: false,
24
+ badInput: false,
25
+ customError: false,
26
+ valid: true
27
+ });
28
+ function snapshotValidity(validity) {
29
+ const result = {};
30
+ for (const key of VALIDITY_KEYS) result[key] = validity[key];
31
+ return result;
32
+ }
33
+ function validityEquals(a, b) {
34
+ return VALIDITY_KEYS.every((key) => a[key] === b[key]);
35
+ }
36
+ function arraysEqual(a, b) {
37
+ return a.length === b.length && a.every((v, i) => v === b[i]);
38
+ }
39
+ const NONE = Object.freeze([]);
40
+ /** `""`, `[]`, `null` and `undefined` mean no error; empty strings inside an array are dropped. */
41
+ function normaliseErrorValue(value) {
42
+ if (value === null || value === void 0 || value === "") return NONE;
43
+ if (typeof value === "string") return [value];
44
+ const list = value.filter((v) => typeof v === "string" && v !== "");
45
+ return list.length === 0 ? NONE : list;
46
+ }
47
+ /** Some entry of `errors` is a non-empty error. */
48
+ function hasAnyError(errors) {
49
+ if (errors === void 0) return false;
50
+ return Object.values(errors).some((v) => normaliseErrorValue(v).length > 0);
51
+ }
52
+ let userInvalidSupport;
53
+ /**
54
+ * Whether the environment implements `:user-invalid` (every engine the library supports does).
55
+ * Decided once per module, so the read is not on a hot path.
56
+ *
57
+ * Detected with `CSS.supports("selector(:user-invalid)")` rather than by `matches()` throwing:
58
+ * jsdom 30's selector engine knows the name and silently answers `false` (it does not throw), and
59
+ * its `CSS.supports` reports `false`, so the fallback engages there and nowhere else
60
+ * (memo/developer/field-form.md).
61
+ */
62
+ function supportsUserInvalid() {
63
+ if (userInvalidSupport === void 0) try {
64
+ userInvalidSupport = typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("selector(:user-invalid)");
65
+ } catch {
66
+ userInvalidSupport = false;
67
+ }
68
+ return userInvalidSupport;
69
+ }
70
+ function isValidatable(el) {
71
+ return el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement;
72
+ }
73
+ /**
74
+ * Where `validity`, `validationMessage`, `:user-invalid` and `setCustomValidity` are read and
75
+ * written (design 7.1): the element itself when it is a form control, else the first named
76
+ * `willValidate` member of a `<fieldset>` (a group's first enabled item, not a CheckboxGroup
77
+ * Parent, which has no name), else the first `input` / `select` / `textarea` inside.
78
+ */
79
+ function validityElementOf(el) {
80
+ if (el === null) return null;
81
+ if (isValidatable(el)) return el;
82
+ const controls = (el instanceof HTMLFieldSetElement ? [...el.elements] : [...el.querySelectorAll("input, select, textarea")]).filter(isValidatable);
83
+ return controls.find((c) => c.willValidate && c.name !== "") ?? controls.find((c) => c.willValidate) ?? controls[0] ?? null;
84
+ }
85
+ /**
86
+ * What the Field focuses (design 2.5): the control itself, or for a group the checked radio if
87
+ * any, else the first enabled named member (a group's first item, not a CheckboxGroup Parent,
88
+ * which has no name), else the first enabled member.
89
+ */
90
+ function focusTargetOf(el) {
91
+ if (el === null) return null;
92
+ if (isValidatable(el)) return el;
93
+ const members = (el instanceof HTMLFieldSetElement ? [...el.elements] : [...el.querySelectorAll("input, select, textarea, button")]).filter((m) => m instanceof HTMLElement && !m.disabled && m.type !== "hidden");
94
+ return members.find((m) => m instanceof HTMLInputElement && m.type === "radio" && m.checked) ?? members.find((m) => isValidatable(m) && m.name !== "") ?? members.find(isValidatable) ?? members[0] ?? null;
95
+ }
96
+ /** Sorts elements into document order. */
97
+ function sortByDocumentOrder(items, elementOf) {
98
+ return [...items].sort((a, b) => {
99
+ const position = elementOf(a).compareDocumentPosition(elementOf(b));
100
+ if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
101
+ if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;
102
+ return 0;
103
+ });
104
+ }
105
+ //#endregion
106
+ export { VALID, arraysEqual, focusTargetOf, hasAnyError, normaliseErrorValue, snapshotValidity, sortByDocumentOrder, supportsUserInvalid, validityElementOf, validityEquals };
107
+
108
+ //# sourceMappingURL=validity.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validity.mjs","names":[],"sources":["../../src/internal/validity.ts"],"sourcesContent":["// Directive-free helpers for Field and Form (design/components/field-form.md 2, 14): the validity\n// snapshot, error-value normalisation, the `:user-invalid` read and the element resolution.\n\n/** A field error: a message, several messages, or nothing (`\"\"`, `[]`, `null`, `undefined`). */\nexport type FieldErrorValue = string | readonly string[] | null | undefined;\n\n/** Field errors keyed by field `name` (usually `state.errors` from `useActionState`). */\nexport type FormErrors = Readonly<Record<string, FieldErrorValue>>;\n\n/**\n * A plain, serialisable snapshot of the control's `ValidityState` (all `false` and\n * `valid: true` on the server and before the first read).\n */\nexport interface FieldValidity {\n readonly valueMissing: boolean;\n readonly typeMismatch: boolean;\n readonly patternMismatch: boolean;\n readonly tooLong: boolean;\n readonly tooShort: boolean;\n readonly rangeUnderflow: boolean;\n readonly rangeOverflow: boolean;\n readonly stepMismatch: boolean;\n readonly badInput: boolean;\n readonly customError: boolean;\n readonly valid: boolean;\n}\n\nconst VALIDITY_KEYS = [\n \"valueMissing\",\n \"typeMismatch\",\n \"patternMismatch\",\n \"tooLong\",\n \"tooShort\",\n \"rangeUnderflow\",\n \"rangeOverflow\",\n \"stepMismatch\",\n \"badInput\",\n \"customError\",\n \"valid\",\n] as const satisfies readonly (keyof FieldValidity)[];\n\nexport const VALID: FieldValidity = Object.freeze({\n valueMissing: false,\n typeMismatch: false,\n patternMismatch: false,\n tooLong: false,\n tooShort: false,\n rangeUnderflow: false,\n rangeOverflow: false,\n stepMismatch: false,\n badInput: false,\n customError: false,\n valid: true,\n});\n\nexport function snapshotValidity(validity: ValidityState): FieldValidity {\n const result = {} as Record<keyof FieldValidity, boolean>;\n for (const key of VALIDITY_KEYS) result[key] = validity[key];\n return result;\n}\n\nexport function validityEquals(a: FieldValidity, b: FieldValidity): boolean {\n return VALIDITY_KEYS.every((key) => a[key] === b[key]);\n}\n\nexport function arraysEqual(a: readonly string[], b: readonly string[]): boolean {\n return a.length === b.length && a.every((v, i) => v === b[i]);\n}\n\nconst NONE: readonly string[] = Object.freeze([]);\n\n/** `\"\"`, `[]`, `null` and `undefined` mean no error; empty strings inside an array are dropped. */\nexport function normaliseErrorValue(value: FieldErrorValue): readonly string[] {\n if (value === null || value === undefined || value === \"\") return NONE;\n if (typeof value === \"string\") return [value];\n const list = value.filter((v) => typeof v === \"string\" && v !== \"\");\n return list.length === 0 ? NONE : list;\n}\n\n/** Some entry of `errors` is a non-empty error. */\nexport function hasAnyError(errors: FormErrors | undefined): boolean {\n if (errors === undefined) return false;\n return Object.values(errors).some((v) => normaliseErrorValue(v).length > 0);\n}\n\nlet userInvalidSupport: boolean | undefined;\n\n/**\n * Whether the environment implements `:user-invalid` (every engine the library supports does).\n * Decided once per module, so the read is not on a hot path.\n *\n * Detected with `CSS.supports(\"selector(:user-invalid)\")` rather than by `matches()` throwing:\n * jsdom 30's selector engine knows the name and silently answers `false` (it does not throw), and\n * its `CSS.supports` reports `false`, so the fallback engages there and nowhere else\n * (memo/developer/field-form.md).\n */\nexport function supportsUserInvalid(): boolean {\n if (userInvalidSupport === undefined) {\n try {\n userInvalidSupport =\n typeof CSS !== \"undefined\" &&\n typeof CSS.supports === \"function\" &&\n CSS.supports(\"selector(:user-invalid)\");\n } catch {\n userInvalidSupport = false;\n }\n }\n return userInvalidSupport;\n}\n\n/** An element that takes part in constraint validation. */\nexport type ValidatableElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;\n\nfunction isValidatable(el: Element): el is ValidatableElement {\n return (\n el instanceof HTMLInputElement ||\n el instanceof HTMLSelectElement ||\n el instanceof HTMLTextAreaElement\n );\n}\n\n/**\n * Where `validity`, `validationMessage`, `:user-invalid` and `setCustomValidity` are read and\n * written (design 7.1): the element itself when it is a form control, else the first named\n * `willValidate` member of a `<fieldset>` (a group's first enabled item, not a CheckboxGroup\n * Parent, which has no name), else the first `input` / `select` / `textarea` inside.\n */\nexport function validityElementOf(el: Element | null): ValidatableElement | null {\n if (el === null) return null;\n if (isValidatable(el)) return el;\n const members =\n el instanceof HTMLFieldSetElement\n ? [...el.elements]\n : [...el.querySelectorAll(\"input, select, textarea\")];\n const controls = members.filter(isValidatable);\n return (\n controls.find((c) => c.willValidate && c.name !== \"\") ??\n controls.find((c) => c.willValidate) ??\n controls[0] ??\n null\n );\n}\n\n/**\n * What the Field focuses (design 2.5): the control itself, or for a group the checked radio if\n * any, else the first enabled named member (a group's first item, not a CheckboxGroup Parent,\n * which has no name), else the first enabled member.\n */\nexport function focusTargetOf(el: Element | null): HTMLElement | null {\n if (el === null) return null;\n if (isValidatable(el)) return el;\n const members = (\n el instanceof HTMLFieldSetElement\n ? [...el.elements]\n : [...el.querySelectorAll(\"input, select, textarea, button\")]\n ).filter(\n (m): m is HTMLElement =>\n m instanceof HTMLElement &&\n !(m as HTMLInputElement).disabled &&\n (m as HTMLInputElement).type !== \"hidden\",\n );\n const checkedRadio = members.find(\n (m) => m instanceof HTMLInputElement && m.type === \"radio\" && m.checked,\n );\n return (\n checkedRadio ??\n members.find((m) => isValidatable(m) && m.name !== \"\") ??\n members.find(isValidatable) ??\n members[0] ??\n null\n );\n}\n\n/** Sorts elements into document order. */\nexport function sortByDocumentOrder<T>(items: readonly T[], elementOf: (item: T) => Node): T[] {\n return [...items].sort((a, b) => {\n const position = elementOf(a).compareDocumentPosition(elementOf(b));\n if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;\n if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;\n return 0;\n });\n}\n"],"mappings":";AA2BA,MAAM,gBAAgB;CACpB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,MAAa,QAAuB,OAAO,OAAO;CAChD,cAAc;CACd,cAAc;CACd,iBAAiB;CACjB,SAAS;CACT,UAAU;CACV,gBAAgB;CAChB,eAAe;CACf,cAAc;CACd,UAAU;CACV,aAAa;CACb,OAAO;AACT,CAAC;AAED,SAAgB,iBAAiB,UAAwC;CACvE,MAAM,SAAS,CAAC;CAChB,KAAK,MAAM,OAAO,eAAe,OAAO,OAAO,SAAS;CACxD,OAAO;AACT;AAEA,SAAgB,eAAe,GAAkB,GAA2B;CAC1E,OAAO,cAAc,OAAO,QAAQ,EAAE,SAAS,EAAE,IAAI;AACvD;AAEA,SAAgB,YAAY,GAAsB,GAA+B;CAC/E,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,MAAM,EAAE,EAAE;AAC9D;AAEA,MAAM,OAA0B,OAAO,OAAO,CAAC,CAAC;;AAGhD,SAAgB,oBAAoB,OAA2C;CAC7E,IAAI,UAAU,QAAQ,UAAU,KAAA,KAAa,UAAU,IAAI,OAAO;CAClE,IAAI,OAAO,UAAU,UAAU,OAAO,CAAC,KAAK;CAC5C,MAAM,OAAO,MAAM,QAAQ,MAAM,OAAO,MAAM,YAAY,MAAM,EAAE;CAClE,OAAO,KAAK,WAAW,IAAI,OAAO;AACpC;;AAGA,SAAgB,YAAY,QAAyC;CACnE,IAAI,WAAW,KAAA,GAAW,OAAO;CACjC,OAAO,OAAO,OAAO,MAAM,CAAC,CAAC,MAAM,MAAM,oBAAoB,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5E;AAEA,IAAI;;;;;;;;;;AAWJ,SAAgB,sBAA+B;CAC7C,IAAI,uBAAuB,KAAA,GACzB,IAAI;EACF,qBACE,OAAO,QAAQ,eACf,OAAO,IAAI,aAAa,cACxB,IAAI,SAAS,yBAAyB;CAC1C,QAAQ;EACN,qBAAqB;CACvB;CAEF,OAAO;AACT;AAKA,SAAS,cAAc,IAAuC;CAC5D,OACE,cAAc,oBACd,cAAc,qBACd,cAAc;AAElB;;;;;;;AAQA,SAAgB,kBAAkB,IAA+C;CAC/E,IAAI,OAAO,MAAM,OAAO;CACxB,IAAI,cAAc,EAAE,GAAG,OAAO;CAK9B,MAAM,YAHJ,cAAc,sBACV,CAAC,GAAG,GAAG,QAAQ,IACf,CAAC,GAAG,GAAG,iBAAiB,yBAAyB,CAAC,EAAA,CAC/B,OAAO,aAAa;CAC7C,OACE,SAAS,MAAM,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,KACpD,SAAS,MAAM,MAAM,EAAE,YAAY,KACnC,SAAS,MACT;AAEJ;;;;;;AAOA,SAAgB,cAAc,IAAwC;CACpE,IAAI,OAAO,MAAM,OAAO;CACxB,IAAI,cAAc,EAAE,GAAG,OAAO;CAC9B,MAAM,WACJ,cAAc,sBACV,CAAC,GAAG,GAAG,QAAQ,IACf,CAAC,GAAG,GAAG,iBAAiB,iCAAiC,CAAC,EAAA,CAC9D,QACC,MACC,aAAa,eACb,CAAE,EAAuB,YACxB,EAAuB,SAAS,QACrC;CAIA,OAHqB,QAAQ,MAC1B,MAAM,aAAa,oBAAoB,EAAE,SAAS,WAAW,EAAE,OAGrD,KACX,QAAQ,MAAM,MAAM,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,KACrD,QAAQ,KAAK,aAAa,KAC1B,QAAQ,MACR;AAEJ;;AAGA,SAAgB,oBAAuB,OAAqB,WAAmC;CAC7F,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,GAAG,MAAM;EAC/B,MAAM,WAAW,UAAU,CAAC,CAAC,CAAC,wBAAwB,UAAU,CAAC,CAAC;EAClE,IAAI,WAAW,KAAK,6BAA6B,OAAO;EACxD,IAAI,WAAW,KAAK,6BAA6B,OAAO;EACxD,OAAO;CACT,CAAC;AACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"menu-group.d.mts","names":[],"sources":["../../src/menu/menu-group.tsx"],"mappings":";;;;YASY,iBAAiB;YACjB,sBAAsB;YACtB,qBAAqB;iBAIhB,uBAAuB,KACtC;EAGA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,8BAA8B;;;;;;wBAOpC,YAAY,WAAW,OAAO,UAAU,WAAW,QAAQ,iCAAc,IAAA;yBAAzE;;;iBAiBC,4BAA4B,KAC3C;EAGA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,8BAA8B;;;;;;wBAOpC,iBACd,WACA,OACA,UACA,WACG,QACF,sBAAmB;yBANN;;;iBA0BC,2BAA2B,KAC1C;EAGA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;;EAE9C,SAAS,WAAW,6BAA6B;;;wBAInC,gBAAgB,WAAW,OAAO,WAAW,QAAQ,qBAAkB;yBAAvE"}
1
+ {"version":3,"file":"menu-group.d.mts","names":[],"sources":["../../src/menu/menu-group.tsx"],"mappings":";;;;YASY,iBAAiB;YACjB,sBAAsB;YACtB,qBAAqB;iBAIhB,uBAAuB,KACtC;EAGA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,8BAA8B;;;;;;wBAOpC,YAAY,WAAW,OAAO,UAAU,WAAW,QAAQ,iCAAc,IAAA;yBAAzE;;;iBAkBC,4BAA4B,KAC3C;EAGA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,8BAA8B;;;;;;wBAOpC,iBACd,WACA,OACA,UACA,WACG,QACF,sBAAmB;yBANN;;;iBA0BC,2BAA2B,KAC1C;EAGA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;;EAE9C,SAAS,WAAW,6BAA6B;;;wBAInC,gBAAgB,WAAW,OAAO,WAAW,QAAQ,qBAAkB;yBAAvE"}
@@ -2,7 +2,7 @@
2
2
  import { useRender } from "../internal/use-render.mjs";
3
3
  import { useDevWarning } from "../internal/use-dev-warning.mjs";
4
4
  import { MenuGroupContext } from "./menu-context.mjs";
5
- import { use, useId } from "react";
5
+ import { use, useId, useMemo } from "react";
6
6
  import { jsx } from "react/jsx-runtime";
7
7
  //#region src/menu/menu-group.tsx
8
8
  const EMPTY = {};
@@ -25,8 +25,9 @@ function MenuGroup({ className, style, children, render, ...rest }) {
25
25
  "aria-labelledby": labelId
26
26
  }
27
27
  });
28
+ const context = useMemo(() => ({ labelId }), [labelId]);
28
29
  return /* @__PURE__ */ jsx(MenuGroupContext, {
29
- value: { labelId },
30
+ value: context,
30
31
  children: element
31
32
  });
32
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"menu-group.mjs","names":[],"sources":["../../src/menu/menu-group.tsx"],"sourcesContent":["\"use client\";\nimport { use, useId } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { MenuGroupContext } from \"./menu-context.js\";\n\n/** `Menu.Group`, `Menu.GroupLabel` and `Menu.Separator` have no state. */\nexport type MenuGroupState = Record<string, never>;\nexport type MenuGroupLabelState = Record<string, never>;\nexport type MenuSeparatorState = Record<string, never>;\n\nconst EMPTY: Record<string, never> = {};\n\nexport interface MenuGroupProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"role\" | \"className\" | \"style\" | \"children\"\n> {\n className?: StateValue<string | undefined, MenuGroupState>;\n style?: StateValue<CSSProperties | undefined, MenuGroupState>;\n children?: StateValue<ReactNode, MenuGroupState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, MenuGroupState>;\n}\n\n/**\n * A labelled set of items: `<div role=\"group\" aria-labelledby>` named by the `Menu.GroupLabel`\n * inside it. Owned keys: `role`, `aria-labelledby` (token-merged).\n */\nexport function MenuGroup({ className, style, children, render, ...rest }: MenuGroupProps) {\n const labelId = `${useId()}-label`;\n const element = useRender<ComponentPropsWithRef<\"div\">, MenuGroupState>({\n defaultElement: \"div\",\n render,\n state: EMPTY,\n className,\n style,\n children,\n props: rest,\n libraryProps: { role: \"group\", \"aria-labelledby\": labelId },\n });\n return <MenuGroupContext value={{ labelId }}>{element}</MenuGroupContext>;\n}\n\nMenuGroup.displayName = \"MenuGroup\";\n\nexport interface MenuGroupLabelProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"id\" | \"className\" | \"style\" | \"children\"\n> {\n className?: StateValue<string | undefined, MenuGroupLabelState>;\n style?: StateValue<CSSProperties | undefined, MenuGroupLabelState>;\n children?: StateValue<ReactNode, MenuGroupLabelState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, MenuGroupLabelState>;\n}\n\n/**\n * The name of the enclosing `Menu.Group` or `Menu.RadioGroup`: a `<div>` with the id the group's\n * `aria-labelledby` references. Owned key: `id`.\n */\nexport function MenuGroupLabel({\n className,\n style,\n children,\n render,\n ...rest\n}: MenuGroupLabelProps) {\n const group = use(MenuGroupContext);\n useDevWarning(\n group === null,\n \"Menu.GroupLabel: rendered outside a Menu.Group or Menu.RadioGroup; it names nothing.\",\n );\n return useRender<ComponentPropsWithRef<\"div\">, MenuGroupLabelState>({\n defaultElement: \"div\",\n render,\n state: EMPTY,\n className,\n style,\n children,\n props: rest,\n libraryProps: group === null ? {} : { id: group.labelId },\n });\n}\n\nMenuGroupLabel.displayName = \"MenuGroupLabel\";\n\nexport interface MenuSeparatorProps extends Omit<\n ComponentPropsWithRef<\"hr\">,\n \"className\" | \"style\" | \"children\"\n> {\n className?: StateValue<string | undefined, MenuSeparatorState>;\n style?: StateValue<CSSProperties | undefined, MenuSeparatorState>;\n /** Another element must carry `role=\"separator\"` itself. */\n render?: RenderProp<ComponentPropsWithRef<\"hr\">, MenuSeparatorState>;\n}\n\n/** A divider between items: `<hr>` (implicit `role=\"separator\"`). Owns no keys. */\nexport function MenuSeparator({ className, style, render, ...rest }: MenuSeparatorProps) {\n return useRender<ComponentPropsWithRef<\"hr\">, MenuSeparatorState>({\n defaultElement: \"hr\",\n render,\n state: EMPTY,\n className,\n style,\n children: undefined,\n props: rest,\n });\n}\n\nMenuSeparator.displayName = \"MenuSeparator\";\n"],"mappings":";;;;;;;AAaA,MAAM,QAA+B,CAAC;;;;;AAgBtC,SAAgB,UAAU,EAAE,WAAW,OAAO,UAAU,QAAQ,GAAG,QAAwB;CACzF,MAAM,UAAU,GAAG,MAAM,EAAE;CAC3B,MAAM,UAAU,UAAwD;EACtE,gBAAgB;EAChB;EACA,OAAO;EACP;EACA;EACA;EACA,OAAO;EACP,cAAc;GAAE,MAAM;GAAS,mBAAmB;EAAQ;CAC5D,CAAC;CACD,OAAO,oBAAC,kBAAD;EAAkB,OAAO,EAAE,QAAQ;EAAI,UAAA;CAA0B,CAAA;AAC1E;AAEA,UAAU,cAAc;;;;;AAgBxB,SAAgB,eAAe,EAC7B,WACA,OACA,UACA,QACA,GAAG,QACmB;CACtB,MAAM,QAAQ,IAAI,gBAAgB;CAClC,cACE,UAAU,MACV,sFACF;CACA,OAAO,UAA6D;EAClE,gBAAgB;EAChB;EACA,OAAO;EACP;EACA;EACA;EACA,OAAO;EACP,cAAc,UAAU,OAAO,CAAC,IAAI,EAAE,IAAI,MAAM,QAAQ;CAC1D,CAAC;AACH;AAEA,eAAe,cAAc;;AAa7B,SAAgB,cAAc,EAAE,WAAW,OAAO,QAAQ,GAAG,QAA4B;CACvF,OAAO,UAA2D;EAChE,gBAAgB;EAChB;EACA,OAAO;EACP;EACA;EACA,UAAU,KAAA;EACV,OAAO;CACT,CAAC;AACH;AAEA,cAAc,cAAc"}
1
+ {"version":3,"file":"menu-group.mjs","names":[],"sources":["../../src/menu/menu-group.tsx"],"sourcesContent":["\"use client\";\nimport { use, useId, useMemo } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { MenuGroupContext } from \"./menu-context.js\";\n\n/** `Menu.Group`, `Menu.GroupLabel` and `Menu.Separator` have no state. */\nexport type MenuGroupState = Record<string, never>;\nexport type MenuGroupLabelState = Record<string, never>;\nexport type MenuSeparatorState = Record<string, never>;\n\nconst EMPTY: Record<string, never> = {};\n\nexport interface MenuGroupProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"role\" | \"className\" | \"style\" | \"children\"\n> {\n className?: StateValue<string | undefined, MenuGroupState>;\n style?: StateValue<CSSProperties | undefined, MenuGroupState>;\n children?: StateValue<ReactNode, MenuGroupState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, MenuGroupState>;\n}\n\n/**\n * A labelled set of items: `<div role=\"group\" aria-labelledby>` named by the `Menu.GroupLabel`\n * inside it. Owned keys: `role`, `aria-labelledby` (token-merged).\n */\nexport function MenuGroup({ className, style, children, render, ...rest }: MenuGroupProps) {\n const labelId = `${useId()}-label`;\n const element = useRender<ComponentPropsWithRef<\"div\">, MenuGroupState>({\n defaultElement: \"div\",\n render,\n state: EMPTY,\n className,\n style,\n children,\n props: rest,\n libraryProps: { role: \"group\", \"aria-labelledby\": labelId },\n });\n const context = useMemo(() => ({ labelId }), [labelId]);\n return <MenuGroupContext value={context}>{element}</MenuGroupContext>;\n}\n\nMenuGroup.displayName = \"MenuGroup\";\n\nexport interface MenuGroupLabelProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"id\" | \"className\" | \"style\" | \"children\"\n> {\n className?: StateValue<string | undefined, MenuGroupLabelState>;\n style?: StateValue<CSSProperties | undefined, MenuGroupLabelState>;\n children?: StateValue<ReactNode, MenuGroupLabelState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, MenuGroupLabelState>;\n}\n\n/**\n * The name of the enclosing `Menu.Group` or `Menu.RadioGroup`: a `<div>` with the id the group's\n * `aria-labelledby` references. Owned key: `id`.\n */\nexport function MenuGroupLabel({\n className,\n style,\n children,\n render,\n ...rest\n}: MenuGroupLabelProps) {\n const group = use(MenuGroupContext);\n useDevWarning(\n group === null,\n \"Menu.GroupLabel: rendered outside a Menu.Group or Menu.RadioGroup; it names nothing.\",\n );\n return useRender<ComponentPropsWithRef<\"div\">, MenuGroupLabelState>({\n defaultElement: \"div\",\n render,\n state: EMPTY,\n className,\n style,\n children,\n props: rest,\n libraryProps: group === null ? {} : { id: group.labelId },\n });\n}\n\nMenuGroupLabel.displayName = \"MenuGroupLabel\";\n\nexport interface MenuSeparatorProps extends Omit<\n ComponentPropsWithRef<\"hr\">,\n \"className\" | \"style\" | \"children\"\n> {\n className?: StateValue<string | undefined, MenuSeparatorState>;\n style?: StateValue<CSSProperties | undefined, MenuSeparatorState>;\n /** Another element must carry `role=\"separator\"` itself. */\n render?: RenderProp<ComponentPropsWithRef<\"hr\">, MenuSeparatorState>;\n}\n\n/** A divider between items: `<hr>` (implicit `role=\"separator\"`). Owns no keys. */\nexport function MenuSeparator({ className, style, render, ...rest }: MenuSeparatorProps) {\n return useRender<ComponentPropsWithRef<\"hr\">, MenuSeparatorState>({\n defaultElement: \"hr\",\n render,\n state: EMPTY,\n className,\n style,\n children: undefined,\n props: rest,\n });\n}\n\nMenuSeparator.displayName = \"MenuSeparator\";\n"],"mappings":";;;;;;;AAaA,MAAM,QAA+B,CAAC;;;;;AAgBtC,SAAgB,UAAU,EAAE,WAAW,OAAO,UAAU,QAAQ,GAAG,QAAwB;CACzF,MAAM,UAAU,GAAG,MAAM,EAAE;CAC3B,MAAM,UAAU,UAAwD;EACtE,gBAAgB;EAChB;EACA,OAAO;EACP;EACA;EACA;EACA,OAAO;EACP,cAAc;GAAE,MAAM;GAAS,mBAAmB;EAAQ;CAC5D,CAAC;CACD,MAAM,UAAU,eAAe,EAAE,QAAQ,IAAI,CAAC,OAAO,CAAC;CACtD,OAAO,oBAAC,kBAAD;EAAkB,OAAO;EAAU,UAAA;CAA0B,CAAA;AACtE;AAEA,UAAU,cAAc;;;;;AAgBxB,SAAgB,eAAe,EAC7B,WACA,OACA,UACA,QACA,GAAG,QACmB;CACtB,MAAM,QAAQ,IAAI,gBAAgB;CAClC,cACE,UAAU,MACV,sFACF;CACA,OAAO,UAA6D;EAClE,gBAAgB;EAChB;EACA,OAAO;EACP;EACA;EACA;EACA,OAAO;EACP,cAAc,UAAU,OAAO,CAAC,IAAI,EAAE,IAAI,MAAM,QAAQ;CAC1D,CAAC;AACH;AAEA,eAAe,cAAc;;AAa7B,SAAgB,cAAc,EAAE,WAAW,OAAO,QAAQ,GAAG,QAA4B;CACvF,OAAO,UAA2D;EAChE,gBAAgB;EAChB;EACA,OAAO;EACP;EACA;EACA,UAAU,KAAA;EACV,OAAO;CACT,CAAC;AACH;AAEA,cAAc,cAAc"}
@@ -5,7 +5,7 @@ import { transitionTypes } from "../internal/transition-types.mjs";
5
5
  import { useControllableAction } from "../internal/use-controllable-action.mjs";
6
6
  import { useRender } from "../internal/use-render.mjs";
7
7
  import { MenuGroupContext, MenuRadioGroupContext } from "./menu-context.mjs";
8
- import { useId, useRef } from "react";
8
+ import { useId, useMemo, useRef } from "react";
9
9
  import { jsx } from "react/jsx-runtime";
10
10
  //#region src/menu/menu-radio-group.tsx
11
11
  /**
@@ -31,7 +31,7 @@ function MenuRadioGroup({ value, defaultValue, valueChangeAction, className, sty
31
31
  prop: "value"
32
32
  }
33
33
  });
34
- const context = {
34
+ const context = useMemo(() => ({
35
35
  value: optimistic,
36
36
  pending,
37
37
  select: (next, event, then) => {
@@ -39,7 +39,12 @@ function MenuRadioGroup({ value, defaultValue, valueChangeAction, className, sty
39
39
  if (then !== void 0) after.current.set(details, then);
40
40
  dispatch(next, details);
41
41
  }
42
- };
42
+ }), [
43
+ optimistic,
44
+ pending,
45
+ dispatch
46
+ ]);
47
+ const groupContext = useMemo(() => ({ labelId }), [labelId]);
43
48
  const element = useRender({
44
49
  defaultElement: "div",
45
50
  render,
@@ -58,7 +63,7 @@ function MenuRadioGroup({ value, defaultValue, valueChangeAction, className, sty
58
63
  }
59
64
  });
60
65
  return /* @__PURE__ */ jsx(MenuGroupContext, {
61
- value: { labelId },
66
+ value: groupContext,
62
67
  children: /* @__PURE__ */ jsx(MenuRadioGroupContext, {
63
68
  value: context,
64
69
  children: element
@@ -1 +1 @@
1
- {"version":3,"file":"menu-radio-group.mjs","names":[],"sources":["../../src/menu/menu-radio-group.tsx"],"sourcesContent":["\"use client\";\nimport { useId, useRef } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { transitionTypes } from \"../internal/transition-types.js\";\nimport type { ActionDetails, ChangeAction, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { MenuGroupContext, MenuRadioGroupContext } from \"./menu-context.js\";\nimport type { MenuRadioGroupContextValue } from \"./menu-context.js\";\n\nexport interface MenuRadioGroupState {\n /** Optimistic value; `null` when no item is selected. */\n readonly value: string | null;\n /** `valueChangeAction` is in flight. */\n readonly pending: boolean;\n}\n\nexport interface MenuRadioGroupProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"role\" | \"defaultValue\" | \"className\" | \"style\" | \"children\"\n> {\n /** Controlled value; `null` selects nothing, `undefined` means uncontrolled. */\n value?: string | null;\n /** Initial value when uncontrolled. Default `null`. */\n defaultValue?: string | null;\n /**\n * Receives the chosen item's `value`, awaited inside the group's transition; the items'\n * `aria-checked` follow at once and roll back on rejection. Re-activating the selected item\n * dispatches nothing. May be a Server Function.\n */\n valueChangeAction?: ChangeAction<string>;\n className?: StateValue<string | undefined, MenuRadioGroupState>;\n style?: StateValue<CSSProperties | undefined, MenuRadioGroupState>;\n children?: StateValue<ReactNode, MenuRadioGroupState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, MenuRadioGroupState>;\n}\n\n/**\n * The state owner of a set of `Menu.RadioItem`s: `<div role=\"group\" aria-labelledby>` (named by\n * a `Menu.GroupLabel` inside it). Emits `data-pending` while `valueChangeAction` runs.\n *\n * Owned keys (conventions 1.2): `role`, `aria-labelledby` (token-merged), `data-pending`.\n */\nexport function MenuRadioGroup({\n value,\n defaultValue,\n valueChangeAction,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuRadioGroupProps) {\n const labelId = `${useId()}-label`;\n // The work to run after a dispatch's action resolved (an item's `closeOnActivate`), keyed by\n // the dispatch's details object.\n const after = useRef(new WeakMap<object, () => void>());\n\n const { optimistic, pending, dispatch } = useControllableAction<string | null>({\n controlled: value,\n defaultValue,\n initialValue: null,\n action: async (next, details) => {\n if (next !== null) await valueChangeAction?.(next, details);\n after.current.get(details)?.();\n },\n transitionTypes: () => transitionTypes(\"menu\", \"select\"),\n warn: { component: \"Menu.RadioGroup\", prop: \"value\" },\n });\n\n const context: MenuRadioGroupContextValue = {\n value: optimistic,\n pending,\n select: (next, event, then) => {\n const details = createActionDetails(event, {}) as ActionDetails;\n if (then !== undefined) after.current.set(details, then);\n dispatch(next, details);\n },\n };\n\n const state: MenuRadioGroupState = { value: optimistic, pending };\n\n const element = useRender<ComponentPropsWithRef<\"div\">, MenuRadioGroupState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n role: \"group\",\n \"aria-labelledby\": labelId,\n \"data-pending\": dataAttr(pending),\n },\n });\n\n return (\n <MenuGroupContext value={{ labelId }}>\n <MenuRadioGroupContext value={context}>{element}</MenuRadioGroupContext>\n </MenuGroupContext>\n );\n}\n\nMenuRadioGroup.displayName = \"MenuRadioGroup\";\n"],"mappings":";;;;;;;;;;;;;;;;AA6CA,SAAgB,eAAe,EAC7B,OACA,cACA,mBACA,WACA,OACA,UACA,QACA,GAAG,QACmB;CACtB,MAAM,UAAU,GAAG,MAAM,EAAE;CAG3B,MAAM,QAAQ,uBAAO,IAAI,QAA4B,CAAC;CAEtD,MAAM,EAAE,YAAY,SAAS,aAAa,sBAAqC;EAC7E,YAAY;EACZ;EACA,cAAc;EACd,QAAQ,OAAO,MAAM,YAAY;GAC/B,IAAI,SAAS,MAAM,MAAM,oBAAoB,MAAM,OAAO;GAC1D,MAAM,QAAQ,IAAI,OAAO,CAAC,GAAG;EAC/B;EACA,uBAAuB,gBAAgB,QAAQ,QAAQ;EACvD,MAAM;GAAE,WAAW;GAAmB,MAAM;EAAQ;CACtD,CAAC;CAED,MAAM,UAAsC;EAC1C,OAAO;EACP;EACA,SAAS,MAAM,OAAO,SAAS;GAC7B,MAAM,UAAU,oBAAoB,OAAO,CAAC,CAAC;GAC7C,IAAI,SAAS,KAAA,GAAW,MAAM,QAAQ,IAAI,SAAS,IAAI;GACvD,SAAS,MAAM,OAAO;EACxB;CACF;CAIA,MAAM,UAAU,UAA6D;EAC3E,gBAAgB;EAChB;EACA,OAAA;GALmC,OAAO;GAAY;EAKlD;EACJ;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,MAAM;GACN,mBAAmB;GACnB,gBAAgB,SAAS,OAAO;EAClC;CACF,CAAC;CAED,OACE,oBAAC,kBAAD;EAAkB,OAAO,EAAE,QAAQ;EACjC,UAAA,oBAAC,uBAAD;GAAuB,OAAO;GAAU,UAAA;EAA+B,CAAA;CACvD,CAAA;AAEtB;AAEA,eAAe,cAAc"}
1
+ {"version":3,"file":"menu-radio-group.mjs","names":[],"sources":["../../src/menu/menu-radio-group.tsx"],"sourcesContent":["\"use client\";\nimport { useId, useMemo, useRef } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { transitionTypes } from \"../internal/transition-types.js\";\nimport type { ActionDetails, ChangeAction, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { MenuGroupContext, MenuRadioGroupContext } from \"./menu-context.js\";\nimport type { MenuRadioGroupContextValue } from \"./menu-context.js\";\n\nexport interface MenuRadioGroupState {\n /** Optimistic value; `null` when no item is selected. */\n readonly value: string | null;\n /** `valueChangeAction` is in flight. */\n readonly pending: boolean;\n}\n\nexport interface MenuRadioGroupProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"role\" | \"defaultValue\" | \"className\" | \"style\" | \"children\"\n> {\n /** Controlled value; `null` selects nothing, `undefined` means uncontrolled. */\n value?: string | null;\n /** Initial value when uncontrolled. Default `null`. */\n defaultValue?: string | null;\n /**\n * Receives the chosen item's `value`, awaited inside the group's transition; the items'\n * `aria-checked` follow at once and roll back on rejection. Re-activating the selected item\n * dispatches nothing. May be a Server Function.\n */\n valueChangeAction?: ChangeAction<string>;\n className?: StateValue<string | undefined, MenuRadioGroupState>;\n style?: StateValue<CSSProperties | undefined, MenuRadioGroupState>;\n children?: StateValue<ReactNode, MenuRadioGroupState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, MenuRadioGroupState>;\n}\n\n/**\n * The state owner of a set of `Menu.RadioItem`s: `<div role=\"group\" aria-labelledby>` (named by\n * a `Menu.GroupLabel` inside it). Emits `data-pending` while `valueChangeAction` runs.\n *\n * Owned keys (conventions 1.2): `role`, `aria-labelledby` (token-merged), `data-pending`.\n */\nexport function MenuRadioGroup({\n value,\n defaultValue,\n valueChangeAction,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuRadioGroupProps) {\n const labelId = `${useId()}-label`;\n // The work to run after a dispatch's action resolved (an item's `closeOnActivate`), keyed by\n // the dispatch's details object.\n const after = useRef(new WeakMap<object, () => void>());\n\n const { optimistic, pending, dispatch } = useControllableAction<string | null>({\n controlled: value,\n defaultValue,\n initialValue: null,\n action: async (next, details) => {\n if (next !== null) await valueChangeAction?.(next, details);\n after.current.get(details)?.();\n },\n transitionTypes: () => transitionTypes(\"menu\", \"select\"),\n warn: { component: \"Menu.RadioGroup\", prop: \"value\" },\n });\n\n // Memoised (#48): `dispatch` is stable.\n const context = useMemo<MenuRadioGroupContextValue>(\n () => ({\n value: optimistic,\n pending,\n select: (next, event, then) => {\n const details = createActionDetails(event, {}) as ActionDetails;\n if (then !== undefined) after.current.set(details, then);\n dispatch(next, details);\n },\n }),\n [optimistic, pending, dispatch],\n );\n const groupContext = useMemo(() => ({ labelId }), [labelId]);\n\n const state: MenuRadioGroupState = { value: optimistic, pending };\n\n const element = useRender<ComponentPropsWithRef<\"div\">, MenuRadioGroupState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n role: \"group\",\n \"aria-labelledby\": labelId,\n \"data-pending\": dataAttr(pending),\n },\n });\n\n return (\n <MenuGroupContext value={groupContext}>\n <MenuRadioGroupContext value={context}>{element}</MenuRadioGroupContext>\n </MenuGroupContext>\n );\n}\n\nMenuRadioGroup.displayName = \"MenuRadioGroup\";\n"],"mappings":";;;;;;;;;;;;;;;;AA6CA,SAAgB,eAAe,EAC7B,OACA,cACA,mBACA,WACA,OACA,UACA,QACA,GAAG,QACmB;CACtB,MAAM,UAAU,GAAG,MAAM,EAAE;CAG3B,MAAM,QAAQ,uBAAO,IAAI,QAA4B,CAAC;CAEtD,MAAM,EAAE,YAAY,SAAS,aAAa,sBAAqC;EAC7E,YAAY;EACZ;EACA,cAAc;EACd,QAAQ,OAAO,MAAM,YAAY;GAC/B,IAAI,SAAS,MAAM,MAAM,oBAAoB,MAAM,OAAO;GAC1D,MAAM,QAAQ,IAAI,OAAO,CAAC,GAAG;EAC/B;EACA,uBAAuB,gBAAgB,QAAQ,QAAQ;EACvD,MAAM;GAAE,WAAW;GAAmB,MAAM;EAAQ;CACtD,CAAC;CAGD,MAAM,UAAU,eACP;EACL,OAAO;EACP;EACA,SAAS,MAAM,OAAO,SAAS;GAC7B,MAAM,UAAU,oBAAoB,OAAO,CAAC,CAAC;GAC7C,IAAI,SAAS,KAAA,GAAW,MAAM,QAAQ,IAAI,SAAS,IAAI;GACvD,SAAS,MAAM,OAAO;EACxB;CACF,IACA;EAAC;EAAY;EAAS;CAAQ,CAChC;CACA,MAAM,eAAe,eAAe,EAAE,QAAQ,IAAI,CAAC,OAAO,CAAC;CAI3D,MAAM,UAAU,UAA6D;EAC3E,gBAAgB;EAChB;EACA,OAAA;GALmC,OAAO;GAAY;EAKlD;EACJ;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,MAAM;GACN,mBAAmB;GACnB,gBAAgB,SAAS,OAAO;EAClC;CACF,CAAC;CAED,OACE,oBAAC,kBAAD;EAAkB,OAAO;EACvB,UAAA,oBAAC,uBAAD;GAAuB,OAAO;GAAU,UAAA;EAA+B,CAAA;CACvD,CAAA;AAEtB;AAEA,eAAe,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"menu-root.mjs","names":[],"sources":["../../src/menu/menu-root.tsx"],"sourcesContent":["\"use client\";\nimport { use, useCallback, useMemo, useRef } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { ActionDetails, ChangeAction, ChangeReason, StateValue } from \"../internal/types.js\";\nimport { useDelayedPair } from \"../internal/use-delayed-pair.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { usePopoverRoot } from \"../internal/use-popover-root.js\";\nimport { resolveStateValue } from \"../internal/use-render.js\";\nimport { MenuContext, MenuListContext } from \"./menu-context.js\";\nimport type { MenuContextValue, MenuOpenFocus } from \"./menu-context.js\";\n\n/** Details passed as the second argument of `Menu.Root`'s `openChangeAction`. */\nexport interface MenuOpenChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user acted on a part: the Trigger or SubmenuTrigger (click, Enter / Space,\n * arrow keys), an item's activation (the close after its action), ArrowLeft in a submenu, or a\n * foreign invoker.\n * `\"dismiss\"`: Escape, a click outside, Tab or focus leaving the menu, the pointer leaving a\n * hover-opened submenu, the parent menu closing (a submenu when the whole stack closes), script.\n * `\"platform\"`: the menu was found open on mount (opened before hydration); `event` is\n * `undefined`.\n */\n readonly reason: ChangeReason;\n}\n\nexport interface MenuRootState {\n /** Optimistic open state. Content rendered from it must not suspend. */\n readonly open: boolean;\n /** An open or close of this Root is in flight (its `openChangeAction`). */\n readonly pending: boolean;\n /** This Root is a submenu (rendered inside a `Menu.Popup`). */\n readonly submenu: boolean;\n}\n\nexport interface MenuRootProps {\n /** Controlled open state. `undefined` means uncontrolled. */\n open?: boolean;\n /** Initial open state when uncontrolled. Opens after hydration. */\n defaultOpen?: boolean;\n /**\n * Awaited for `true` (the menu opens when it resolves and the items have rendered) and for the\n * closes the user asks a part for (Trigger toggle, an item's activation, ArrowLeft in a\n * submenu); a notification for dismissals (Escape, a click outside, Tab, the parent menu\n * closing). May be a Server Function. A `setState` placed after your own `await` needs\n * `startTransition` (React limitation).\n */\n openChangeAction?: ChangeAction<boolean, MenuOpenChangeDetails>;\n /** Arrow keys wrap from the last item to the first and back. Default `true`. */\n loop?: boolean;\n /** Submenu only: milliseconds from hovering the SubmenuTrigger to the open. Default 100. */\n delay?: number;\n /**\n * Submenu only: milliseconds from the pointer leaving the SubmenuTrigger and the submenu to the\n * close. Default 150.\n */\n closeDelay?: number;\n children?: StateValue<ReactNode, MenuRootState>;\n}\n\n/**\n * Owns the ids, the open state and the context of one menu level. Renders no DOM. A `Menu.Root`\n * rendered inside a `Menu.Popup` is a **submenu**: its Trigger is a `Menu.SubmenuTrigger` (an\n * item of the parent menu) and its Popup opens beside that item.\n *\n * A menu is always in library mode (design/components/menu.md 2.1): opening and the closes a\n * part asks for are transitions (pending Trigger, items render from committed state,\n * `showPopover()` / `hidePopover()` once the change has committed); dismissals are the\n * browser's and `openChangeAction(false)` is a notification.\n */\nconst DEFAULT_FOCUS: MenuOpenFocus = { cause: \"first\", pointer: false };\n\nexport function MenuRoot({\n open,\n defaultOpen,\n openChangeAction,\n loop = true,\n delay,\n closeDelay,\n children,\n}: MenuRootProps) {\n const parent = use(MenuContext);\n const list = use(MenuListContext);\n // A Root inside a Popup's content is a submenu (design 1); the enclosing menu is the Popup's.\n const parentMenu = list !== null ? list.menu : null;\n const submenu = parentMenu !== null;\n\n useDevWarning(\n !submenu && (delay !== undefined || closeDelay !== undefined),\n \"Menu.Root: `delay` and `closeDelay` only apply to a submenu (a Menu.Root inside a \" +\n \"Menu.Popup); a top-level menu does not open on hover.\",\n );\n useDevWarning(\n parent !== null && !submenu,\n \"Menu.Root: rendered inside another Menu.Root but outside its Menu.Popup. A submenu's Root \" +\n \"goes inside the parent's Menu.Popup.\",\n );\n\n const timers = useDelayedPair();\n // The cause recorded by the part about to dispatch an open, and the cause of the open being\n // shown (design 14, \"Open cause\").\n const nextCause = useRef<MenuOpenFocus | null>(null);\n const openCause = useRef<MenuOpenFocus | null>(null);\n\n const popover = usePopoverRoot<MenuOpenChangeDetails>({\n component: \"menu\",\n part: \"Menu.Root\",\n mode: \"library\",\n open,\n defaultOpen,\n openChangeAction,\n onDispatch: (next) => {\n // Any dispatch cancels hover intent (walkthrough 9).\n timers.cancel();\n if (next) openCause.current = nextCause.current ?? DEFAULT_FOCUS;\n nextCause.current = null;\n },\n });\n\n const setOpenCause = useCallback((cause: MenuOpenFocus[\"cause\"], pointer = false) => {\n nextCause.current = { cause, pointer };\n }, []);\n const takeOpenCause = useCallback(() => {\n const cause = openCause.current ?? DEFAULT_FOCUS;\n openCause.current = null;\n return cause;\n }, []);\n\n const resolvedDelay = delay ?? 100;\n const resolvedCloseDelay = closeDelay ?? 150;\n // Note: `popover` is a new object on every render (the shared `usePopoverRoot` is not memoised:\n // its `dispatch` closes over the render's state), so today this recomputes on every render of\n // the Root and the parts re-render with it, as Popover's do. The other members are stable; the\n // memo starts to hit without further change once that hook returns a stable value.\n const value = useMemo<MenuContextValue>(() => {\n const self: MenuContextValue = {\n popover,\n submenu,\n // The top-level menu: this one, or the parent's root (design 2.2).\n get root() {\n return parentMenu?.root ?? self;\n },\n parent: parentMenu,\n loop,\n delay: resolvedDelay,\n closeDelay: resolvedCloseDelay,\n triggerId: `${popover.ids.popup}-trigger`,\n setOpenCause,\n takeOpenCause,\n timers,\n };\n return self;\n }, [\n popover,\n submenu,\n parentMenu,\n loop,\n resolvedDelay,\n resolvedCloseDelay,\n setOpenCause,\n takeOpenCause,\n timers,\n ]);\n\n const state: MenuRootState = {\n open: popover.optimistic,\n pending: popover.pending,\n submenu,\n };\n\n return <MenuContext value={value}>{resolveStateValue(children, state)}</MenuContext>;\n}\n\nMenuRoot.displayName = \"MenuRoot\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAqEA,MAAM,gBAA+B;CAAE,OAAO;CAAS,SAAS;AAAM;AAEtE,SAAgB,SAAS,EACvB,MACA,aACA,kBACA,OAAO,MACP,OACA,YACA,YACgB;CAChB,MAAM,SAAS,IAAI,WAAW;CAC9B,MAAM,OAAO,IAAI,eAAe;CAEhC,MAAM,aAAa,SAAS,OAAO,KAAK,OAAO;CAC/C,MAAM,UAAU,eAAe;CAE/B,cACE,CAAC,YAAY,UAAU,KAAA,KAAa,eAAe,KAAA,IACnD,yIAEF;CACA,cACE,WAAW,QAAQ,CAAC,SACpB,gIAEF;CAEA,MAAM,SAAS,eAAe;CAG9B,MAAM,YAAY,OAA6B,IAAI;CACnD,MAAM,YAAY,OAA6B,IAAI;CAEnD,MAAM,UAAU,eAAsC;EACpD,WAAW;EACX,MAAM;EACN,MAAM;EACN;EACA;EACA;EACA,aAAa,SAAS;GAEpB,OAAO,OAAO;GACd,IAAI,MAAM,UAAU,UAAU,UAAU,WAAW;GACnD,UAAU,UAAU;EACtB;CACF,CAAC;CAED,MAAM,eAAe,aAAa,OAA+B,UAAU,UAAU;EACnF,UAAU,UAAU;GAAE;GAAO;EAAQ;CACvC,GAAG,CAAC,CAAC;CACL,MAAM,gBAAgB,kBAAkB;EACtC,MAAM,QAAQ,UAAU,WAAW;EACnC,UAAU,UAAU;EACpB,OAAO;CACT,GAAG,CAAC,CAAC;CAEL,MAAM,gBAAgB,SAAS;CAC/B,MAAM,qBAAqB,cAAc;CAKzC,MAAM,QAAQ,cAAgC;EAC5C,MAAM,OAAyB;GAC7B;GACA;GAEA,IAAI,OAAO;IACT,OAAO,YAAY,QAAQ;GAC7B;GACA,QAAQ;GACR;GACA,OAAO;GACP,YAAY;GACZ,WAAW,GAAG,QAAQ,IAAI,MAAM;GAChC;GACA;GACA;EACF;EACA,OAAO;CACT,GAAG;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CAED,MAAM,QAAuB;EAC3B,MAAM,QAAQ;EACd,SAAS,QAAQ;EACjB;CACF;CAEA,OAAO,oBAAC,aAAD;EAAoB;EAAQ,UAAA,kBAAkB,UAAU,KAAK;CAAe,CAAA;AACrF;AAEA,SAAS,cAAc"}
1
+ {"version":3,"file":"menu-root.mjs","names":[],"sources":["../../src/menu/menu-root.tsx"],"sourcesContent":["\"use client\";\nimport { use, useCallback, useMemo, useRef } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { ActionDetails, ChangeAction, ChangeReason, StateValue } from \"../internal/types.js\";\nimport { useDelayedPair } from \"../internal/use-delayed-pair.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { usePopoverRoot } from \"../internal/use-popover-root.js\";\nimport { resolveStateValue } from \"../internal/use-render.js\";\nimport { MenuContext, MenuListContext } from \"./menu-context.js\";\nimport type { MenuContextValue, MenuOpenFocus } from \"./menu-context.js\";\n\n/** Details passed as the second argument of `Menu.Root`'s `openChangeAction`. */\nexport interface MenuOpenChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user acted on a part: the Trigger or SubmenuTrigger (click, Enter / Space,\n * arrow keys), an item's activation (the close after its action), ArrowLeft in a submenu, or a\n * foreign invoker.\n * `\"dismiss\"`: Escape, a click outside, Tab or focus leaving the menu, the pointer leaving a\n * hover-opened submenu, the parent menu closing (a submenu when the whole stack closes), script.\n * `\"platform\"`: the menu was found open on mount (opened before hydration); `event` is\n * `undefined`.\n */\n readonly reason: ChangeReason;\n}\n\nexport interface MenuRootState {\n /** Optimistic open state. Content rendered from it must not suspend. */\n readonly open: boolean;\n /** An open or close of this Root is in flight (its `openChangeAction`). */\n readonly pending: boolean;\n /** This Root is a submenu (rendered inside a `Menu.Popup`). */\n readonly submenu: boolean;\n}\n\nexport interface MenuRootProps {\n /** Controlled open state. `undefined` means uncontrolled. */\n open?: boolean;\n /** Initial open state when uncontrolled. Opens after hydration. */\n defaultOpen?: boolean;\n /**\n * Awaited for `true` (the menu opens when it resolves and the items have rendered) and for the\n * closes the user asks a part for (Trigger toggle, an item's activation, ArrowLeft in a\n * submenu); a notification for dismissals (Escape, a click outside, Tab, the parent menu\n * closing). May be a Server Function. A `setState` placed after your own `await` needs\n * `startTransition` (React limitation).\n */\n openChangeAction?: ChangeAction<boolean, MenuOpenChangeDetails>;\n /** Arrow keys wrap from the last item to the first and back. Default `true`. */\n loop?: boolean;\n /** Submenu only: milliseconds from hovering the SubmenuTrigger to the open. Default 100. */\n delay?: number;\n /**\n * Submenu only: milliseconds from the pointer leaving the SubmenuTrigger and the submenu to the\n * close. Default 150.\n */\n closeDelay?: number;\n children?: StateValue<ReactNode, MenuRootState>;\n}\n\n/**\n * Owns the ids, the open state and the context of one menu level. Renders no DOM. A `Menu.Root`\n * rendered inside a `Menu.Popup` is a **submenu**: its Trigger is a `Menu.SubmenuTrigger` (an\n * item of the parent menu) and its Popup opens beside that item.\n *\n * A menu is always in library mode (design/components/menu.md 2.1): opening and the closes a\n * part asks for are transitions (pending Trigger, items render from committed state,\n * `showPopover()` / `hidePopover()` once the change has committed); dismissals are the\n * browser's and `openChangeAction(false)` is a notification.\n */\nconst DEFAULT_FOCUS: MenuOpenFocus = { cause: \"first\", pointer: false };\n\nexport function MenuRoot({\n open,\n defaultOpen,\n openChangeAction,\n loop = true,\n delay,\n closeDelay,\n children,\n}: MenuRootProps) {\n const parent = use(MenuContext);\n const list = use(MenuListContext);\n // A Root inside a Popup's content is a submenu (design 1); the enclosing menu is the Popup's.\n const parentMenu = list !== null ? list.menu : null;\n const submenu = parentMenu !== null;\n\n useDevWarning(\n !submenu && (delay !== undefined || closeDelay !== undefined),\n \"Menu.Root: `delay` and `closeDelay` only apply to a submenu (a Menu.Root inside a \" +\n \"Menu.Popup); a top-level menu does not open on hover.\",\n );\n useDevWarning(\n parent !== null && !submenu,\n \"Menu.Root: rendered inside another Menu.Root but outside its Menu.Popup. A submenu's Root \" +\n \"goes inside the parent's Menu.Popup.\",\n );\n\n const timers = useDelayedPair();\n // The cause recorded by the part about to dispatch an open, and the cause of the open being\n // shown (design 14, \"Open cause\").\n const nextCause = useRef<MenuOpenFocus | null>(null);\n const openCause = useRef<MenuOpenFocus | null>(null);\n\n const popover = usePopoverRoot<MenuOpenChangeDetails>({\n component: \"menu\",\n part: \"Menu.Root\",\n mode: \"library\",\n open,\n defaultOpen,\n openChangeAction,\n onDispatch: (next) => {\n // Any dispatch cancels hover intent (walkthrough 9).\n timers.cancel();\n if (next) openCause.current = nextCause.current ?? DEFAULT_FOCUS;\n nextCause.current = null;\n },\n });\n\n const setOpenCause = useCallback((cause: MenuOpenFocus[\"cause\"], pointer = false) => {\n nextCause.current = { cause, pointer };\n }, []);\n const takeOpenCause = useCallback(() => {\n const cause = openCause.current ?? DEFAULT_FOCUS;\n openCause.current = null;\n return cause;\n }, []);\n\n const resolvedDelay = delay ?? 100;\n const resolvedCloseDelay = closeDelay ?? 150;\n // `popover` is memoised by `usePopoverRoot` (#48) and the other members are stable, so this\n // changes only with the open state, pending and the props listed below.\n const value = useMemo<MenuContextValue>(() => {\n const self: MenuContextValue = {\n popover,\n submenu,\n // The top-level menu: this one, or the parent's root (design 2.2).\n get root() {\n return parentMenu?.root ?? self;\n },\n parent: parentMenu,\n loop,\n delay: resolvedDelay,\n closeDelay: resolvedCloseDelay,\n triggerId: `${popover.ids.popup}-trigger`,\n setOpenCause,\n takeOpenCause,\n timers,\n };\n return self;\n }, [\n popover,\n submenu,\n parentMenu,\n loop,\n resolvedDelay,\n resolvedCloseDelay,\n setOpenCause,\n takeOpenCause,\n timers,\n ]);\n\n const state: MenuRootState = {\n open: popover.optimistic,\n pending: popover.pending,\n submenu,\n };\n\n return <MenuContext value={value}>{resolveStateValue(children, state)}</MenuContext>;\n}\n\nMenuRoot.displayName = \"MenuRoot\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAqEA,MAAM,gBAA+B;CAAE,OAAO;CAAS,SAAS;AAAM;AAEtE,SAAgB,SAAS,EACvB,MACA,aACA,kBACA,OAAO,MACP,OACA,YACA,YACgB;CAChB,MAAM,SAAS,IAAI,WAAW;CAC9B,MAAM,OAAO,IAAI,eAAe;CAEhC,MAAM,aAAa,SAAS,OAAO,KAAK,OAAO;CAC/C,MAAM,UAAU,eAAe;CAE/B,cACE,CAAC,YAAY,UAAU,KAAA,KAAa,eAAe,KAAA,IACnD,yIAEF;CACA,cACE,WAAW,QAAQ,CAAC,SACpB,gIAEF;CAEA,MAAM,SAAS,eAAe;CAG9B,MAAM,YAAY,OAA6B,IAAI;CACnD,MAAM,YAAY,OAA6B,IAAI;CAEnD,MAAM,UAAU,eAAsC;EACpD,WAAW;EACX,MAAM;EACN,MAAM;EACN;EACA;EACA;EACA,aAAa,SAAS;GAEpB,OAAO,OAAO;GACd,IAAI,MAAM,UAAU,UAAU,UAAU,WAAW;GACnD,UAAU,UAAU;EACtB;CACF,CAAC;CAED,MAAM,eAAe,aAAa,OAA+B,UAAU,UAAU;EACnF,UAAU,UAAU;GAAE;GAAO;EAAQ;CACvC,GAAG,CAAC,CAAC;CACL,MAAM,gBAAgB,kBAAkB;EACtC,MAAM,QAAQ,UAAU,WAAW;EACnC,UAAU,UAAU;EACpB,OAAO;CACT,GAAG,CAAC,CAAC;CAEL,MAAM,gBAAgB,SAAS;CAC/B,MAAM,qBAAqB,cAAc;CAGzC,MAAM,QAAQ,cAAgC;EAC5C,MAAM,OAAyB;GAC7B;GACA;GAEA,IAAI,OAAO;IACT,OAAO,YAAY,QAAQ;GAC7B;GACA,QAAQ;GACR;GACA,OAAO;GACP,YAAY;GACZ,WAAW,GAAG,QAAQ,IAAI,MAAM;GAChC;GACA;GACA;EACF;EACA,OAAO;CACT,GAAG;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CAED,MAAM,QAAuB;EAC3B,MAAM,QAAQ;EACd,SAAS,QAAQ;EACjB;CACF;CAEA,OAAO,oBAAC,aAAD;EAAoB;EAAQ,UAAA,kBAAkB,UAAU,KAAK;CAAe,CAAA;AACrF;AAEA,SAAS,cAAc"}
@@ -76,7 +76,9 @@ export interface RadioGroupRootProps extends Omit<ComponentPropsWithRef<"fieldse
76
76
  * Carries `data-pending` while `valueChangeAction` is in flight.
77
77
  *
78
78
  * Owned keys (conventions 1.2): `role`, `aria-required`, `disabled`, `form`, `data-pending`,
79
- * `data-disabled`.
79
+ * `data-disabled`; inside a `Field.Root` also `aria-labelledby` (→ `Field.Label`, token-merged),
80
+ * `aria-describedby` (token-merged), `aria-invalid` and `data-invalid`, and `name` / `required` /
81
+ * `disabled` default from the Field.
80
82
  *
81
83
  * Keep every Item in the same Suspense boundary as its Root when server rendering. Partial
82
84
  * hydration of a group (a boundary between Root and Items that stays dehydrated while the user
@@ -84,7 +86,7 @@ export interface RadioGroupRootProps extends Omit<ComponentPropsWithRef<"fieldse
84
86
  * still dehydrated makes react-dom throw "Mixing React and non-React radio inputs with the same
85
87
  * `name` is not supported". Development warns when Items hydrate after their Root.
86
88
  */
87
- export declare function RadioGroupRoot({ value, defaultValue, valueChangeAction, name: nameProp, form, required, disabled, className, style, children, render, ...rest }: RadioGroupRootProps): import("react").JSX.Element;
89
+ export declare function RadioGroupRoot({ value, defaultValue, valueChangeAction, name: nameProp, form, required: requiredProp, disabled: disabledProp, className, style, children, render, ...rest }: RadioGroupRootProps): import("react").JSX.Element;
88
90
  export declare namespace RadioGroupRoot {
89
91
  var displayName: string;
90
92
  }
@@ -1 +1 @@
1
- {"version":3,"file":"radio-group-root.d.mts","names":[],"sources":["../../src/radio-group/radio-group-root.tsx"],"mappings":";;;;iBAsBiB,qCAAqC;;;;;;;WAO3C,QAAQ;;iBAGF;;WAEN;;WAEA;;WAEA;;iBAGM,4BAA4B,KAC3C;;;;;;;;;;;EAoBA;;;;;EAKA;;;;;;;;;;;;EAYA,oBAAoB,4BAA4B;;;;;;EAMhD;;EAEA;;;;;;EAMA;;EAEA;EACA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,mCAAmC;;;;;;;;;;;;;;;;wBAkCzC,iBACd,OACA,cACA,mBACA,MAAM,UACN,MACA,UACA,UACA,WACA,OACA,UACA,WACG,QACF,sCAAmB,IAAA;yBAbN"}
1
+ {"version":3,"file":"radio-group-root.d.mts","names":[],"sources":["../../src/radio-group/radio-group-root.tsx"],"mappings":";;;;iBA2BiB,qCAAqC;;;;;;;WAO3C,QAAQ;;iBAGF;;WAEN;;WAEA;;WAEA;;iBAGM,4BAA4B,KAC3C;;;;;;;;;;;EAoBA;;;;;EAKA;;;;;;;;;;;;EAYA,oBAAoB,4BAA4B;;;;;;EAMhD;;EAEA;;;;;;EAMA;;EAEA;EACA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,mCAAmC;;;;;;;;;;;;;;;;;;wBAoCzC,iBACd,OACA,cACA,mBACA,MAAM,UACN,MACA,UAAU,cACV,UAAU,cACV,WACA,OACA,UACA,WACG,QACF,sCAAmB,IAAA;yBAbN"}
@@ -4,9 +4,10 @@ import { dataAttr } from "../internal/data-attr.mjs";
4
4
  import { transitionTypes } from "../internal/transition-types.mjs";
5
5
  import { useControllableAction } from "../internal/use-controllable-action.mjs";
6
6
  import { useRender } from "../internal/use-render.mjs";
7
+ import { useFieldControl, useFieldValueChange, useMergedRef } from "../internal/use-field-control.mjs";
7
8
  import { RadioGroupContext } from "./radio-group-context.mjs";
8
9
  import { useNativeGroupReconciliation } from "../internal/use-native-group-reconciliation.mjs";
9
- import { useCallback, useId, useLayoutEffect, useRef } from "react";
10
+ import { useCallback, useId, useLayoutEffect, useMemo, useRef } from "react";
10
11
  import { jsx } from "react/jsx-runtime";
11
12
  //#region src/radio-group/radio-group-root.tsx
12
13
  const isDev = process.env.NODE_ENV !== "production";
@@ -26,7 +27,9 @@ function readGroup(fieldset, name, prop) {
26
27
  * Carries `data-pending` while `valueChangeAction` is in flight.
27
28
  *
28
29
  * Owned keys (conventions 1.2): `role`, `aria-required`, `disabled`, `form`, `data-pending`,
29
- * `data-disabled`.
30
+ * `data-disabled`; inside a `Field.Root` also `aria-labelledby` (→ `Field.Label`, token-merged),
31
+ * `aria-describedby` (token-merged), `aria-invalid` and `data-invalid`, and `name` / `required` /
32
+ * `disabled` default from the Field.
30
33
  *
31
34
  * Keep every Item in the same Suspense boundary as its Root when server rendering. Partial
32
35
  * hydration of a group (a boundary between Root and Items that stays dehydrated while the user
@@ -34,9 +37,18 @@ function readGroup(fieldset, name, prop) {
34
37
  * still dehydrated makes react-dom throw "Mixing React and non-React radio inputs with the same
35
38
  * `name` is not supported". Development warns when Items hydrate after their Root.
36
39
  */
37
- function RadioGroupRoot({ value, defaultValue, valueChangeAction, name: nameProp, form, required = false, disabled = false, className, style, children, render, ...rest }) {
40
+ function RadioGroupRoot({ value, defaultValue, valueChangeAction, name: nameProp, form, required: requiredProp, disabled: disabledProp, className, style, children, render, ...rest }) {
41
+ const field = useFieldControl({
42
+ labelable: false,
43
+ name: nameProp,
44
+ disabled: disabledProp,
45
+ required: requiredProp,
46
+ component: "RadioGroup.Root"
47
+ });
48
+ const required = field.required;
49
+ const disabled = field.disabled;
38
50
  const fallbackName = useId();
39
- const name = nameProp ?? fallbackName;
51
+ const name = field.name ?? fallbackName;
40
52
  const fieldsetRef = useRef(null);
41
53
  const warnedElement = useRef(false);
42
54
  const { optimistic, pending, dispatch, latestInFlight } = useControllableAction({
@@ -50,6 +62,7 @@ function RadioGroupRoot({ value, defaultValue, valueChangeAction, name: nameProp
50
62
  prop: "value"
51
63
  }
52
64
  });
65
+ useFieldValueChange(optimistic);
53
66
  /** What the user intends: the latest in-flight dispatch, else optimistic (conventions 3.8 rule 1). */
54
67
  const base = () => {
55
68
  const inFlight = latestInFlight();
@@ -89,20 +102,32 @@ function RadioGroupRoot({ value, defaultValue, valueChangeAction, name: nameProp
89
102
  fieldsetRef.current = null;
90
103
  };
91
104
  }, []);
92
- const context = {
105
+ const rootRef = useMergedRef(libraryRef, field.ref);
106
+ const contextDefault = value === void 0 ? defaultValue ?? null : void 0;
107
+ const context = useMemo(() => ({
93
108
  name,
94
109
  form,
95
110
  required,
96
111
  optimistic,
97
112
  pending,
98
113
  disabled,
99
- defaultValue: value === void 0 ? defaultValue ?? null : void 0,
114
+ defaultValue: contextDefault,
100
115
  onItemMounted: onItemMountedWithCheck,
101
116
  select: (next, event) => {
102
117
  if (next === optimistic) return;
103
118
  dispatch(next, createActionDetails(event, { reason: "activate" }));
104
119
  }
105
- };
120
+ }), [
121
+ name,
122
+ form,
123
+ required,
124
+ optimistic,
125
+ pending,
126
+ disabled,
127
+ contextDefault,
128
+ onItemMountedWithCheck,
129
+ dispatch
130
+ ]);
106
131
  const element = useRender({
107
132
  defaultElement: "fieldset",
108
133
  render,
@@ -116,6 +141,7 @@ function RadioGroupRoot({ value, defaultValue, valueChangeAction, name: nameProp
116
141
  children,
117
142
  props: rest,
118
143
  libraryProps: {
144
+ ...field.attributes,
119
145
  role: "radiogroup",
120
146
  "aria-required": required ? "true" : void 0,
121
147
  disabled,
@@ -123,7 +149,7 @@ function RadioGroupRoot({ value, defaultValue, valueChangeAction, name: nameProp
123
149
  "data-pending": dataAttr(pending),
124
150
  "data-disabled": dataAttr(disabled)
125
151
  },
126
- ref: libraryRef
152
+ ref: rootRef
127
153
  });
128
154
  return /* @__PURE__ */ jsx(RadioGroupContext, {
129
155
  value: context,
@@ -1 +1 @@
1
- {"version":3,"file":"radio-group-root.mjs","names":[],"sources":["../../src/radio-group/radio-group-root.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useId, useLayoutEffect, useRef } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { transitionTypes } from \"../internal/transition-types.js\";\nimport type {\n ActionDetails,\n ChangeAction,\n ChangeReason,\n RenderProp,\n StateValue,\n} from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { useNativeGroupReconciliation } from \"../internal/use-native-group-reconciliation.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { RadioGroupContext } from \"./radio-group-context.js\";\nimport type { RadioGroupContextValue } from \"./radio-group-context.js\";\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/** Details passed as the second argument of `valueChangeAction`. */\nexport interface RadioGroupValueChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user checked a radio (click, Space, label click, or an arrow key, all of\n * which arrive as a native `click`). `\"platform\"`: the browser changed the checked radio\n * itself: a form reset (`details.event` is the `reset` event), or a click before hydration /\n * Firefox form-state restoration found at mount (`details.event` is `undefined`).\n */\n readonly reason: ChangeReason;\n}\n\nexport interface RadioGroupRootState {\n /** Optimistic checked value, or `null`. Content rendered from it must not suspend. */\n readonly value: string | null;\n /** An action dispatched through this group is in flight. */\n readonly pending: boolean;\n /** The `disabled` prop. */\n readonly disabled: boolean;\n}\n\nexport interface RadioGroupRootProps extends Omit<\n ComponentPropsWithRef<\"fieldset\">,\n | \"className\"\n | \"style\"\n | \"children\"\n | \"defaultValue\"\n | \"defaultChecked\"\n | \"name\"\n | \"disabled\"\n | \"form\"\n> {\n /**\n * Controlled checked value. `null` checks nothing; `undefined` means uncontrolled.\n *\n * A form reset returns each radio to the `checked` it had when *that radio* mounted (React sets\n * `defaultChecked` once, from the initial `checked`). An Item added on the client while `value`\n * equals its value therefore also gets `defaultChecked`, so two radios may carry it and the\n * reset target is not necessarily the group's mount-time value. The reset is offered to\n * `valueChangeAction` as usual; for a stable target remount the group with a `key` or use\n * `defaultValue`, whose latest value every Item mirrors.\n */\n value?: string | null;\n /**\n * Initial value in uncontrolled mode. Defaults to `null` (nothing checked). Also the value a\n * form reset returns to: the latest prop value is mirrored into the radios' `defaultChecked`.\n */\n defaultValue?: string | null;\n /**\n * Runs inside the Root's transition whenever the checked radio changes. May be sync or async\n * (also a Server Function); rejections reach the nearest Error Boundary and roll the selection\n * back.\n *\n * `next` is a string for every user interaction. It is `null` only with\n * `details.reason === \"platform\"`, when a form reset returned the group to \"nothing checked\".\n * Every arrow key press dispatches; the latest dispatch wins. A `setState` placed after your\n * own `await` is not a Transition update; wrap it in `startTransition` (React limitation).\n * `details.event.preventDefault()` is not a veto; use controlled mode to veto a change.\n */\n valueChangeAction?: ChangeAction<string | null, RadioGroupValueChangeDetails>;\n /**\n * The `name` shared by every radio in the group (form data key). Defaults to a `useId()` value;\n * pass a real name when the group is inside a `<form>` you submit. Must be unique within the\n * form: react-dom throws if a non-React radio shares it.\n */\n name?: string;\n /** The `form` attribute, applied to the fieldset and to every radio. */\n form?: string;\n /**\n * Marks every radio `required`, which makes the group required (one checked radio satisfies it),\n * and renders `aria-required=\"true\"` on the fieldset so the radiogroup itself is exposed as\n * required.\n */\n required?: boolean;\n /** Renders `<fieldset disabled>`: every radio is disabled, unfocusable and skipped by arrows. */\n disabled?: boolean;\n className?: StateValue<string | undefined, RadioGroupRootState>;\n style?: StateValue<CSSProperties | undefined, RadioGroupRootState>;\n children?: StateValue<ReactNode, RadioGroupRootState>;\n /** Replace the rendered element. Must keep a `<fieldset>`. */\n render?: RenderProp<ComponentPropsWithRef<\"fieldset\">, RadioGroupRootState>;\n}\n\n/**\n * Value of the group's checked radio as the DOM shows it (`prop: \"checked\"`) or as a form reset\n * leaves it (`prop: \"defaultChecked\"`), or `null`. Only radios of this group's `name` count, so a\n * nested group is ignored.\n */\nfunction readGroup(\n fieldset: HTMLFieldSetElement | null,\n name: string,\n prop: \"checked\" | \"defaultChecked\",\n): string | null {\n if (fieldset === null) return null;\n for (const radio of fieldset.querySelectorAll<HTMLInputElement>('input[type=\"radio\"]')) {\n if (radio.name === name && radio[prop]) return radio.value;\n }\n return null;\n}\n\n/**\n * `<fieldset role=\"radiogroup\">` that owns the group's state, the transition, the shared `name`,\n * the form-reset listener and the mount reconciliation (design/components/radio-group.md 13.3).\n * Carries `data-pending` while `valueChangeAction` is in flight.\n *\n * Owned keys (conventions 1.2): `role`, `aria-required`, `disabled`, `form`, `data-pending`,\n * `data-disabled`.\n *\n * Keep every Item in the same Suspense boundary as its Root when server rendering. Partial\n * hydration of a group (a boundary between Root and Items that stays dehydrated while the user\n * interacts) is not supported by React: clicking a hydrated radio while same-`name` siblings are\n * still dehydrated makes react-dom throw \"Mixing React and non-React radio inputs with the same\n * `name` is not supported\". Development warns when Items hydrate after their Root.\n */\nexport function RadioGroupRoot({\n value,\n defaultValue,\n valueChangeAction,\n name: nameProp,\n form,\n required = false,\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: RadioGroupRootProps) {\n const fallbackName = useId();\n const name = nameProp ?? fallbackName;\n const fieldsetRef = useRef<HTMLFieldSetElement | null>(null);\n const warnedElement = useRef(false);\n\n const { optimistic, pending, dispatch, latestInFlight } = useControllableAction<\n string | null,\n RadioGroupValueChangeDetails\n >({\n controlled: value,\n defaultValue,\n initialValue: null,\n action: valueChangeAction,\n transitionTypes: () => transitionTypes(\"radio-group\"),\n warn: { component: \"RadioGroup.Root\", prop: \"value\" },\n });\n // Deliberately no warning when neither `value` nor `defaultValue` is given: an unchecked group\n // is the HTML default (design decision 2).\n\n /** What the user intends: the latest in-flight dispatch, else optimistic (conventions 3.8 rule 1). */\n const base = (): string | null => {\n const inFlight = latestInFlight();\n // Not `?.next ?? optimistic`: an in-flight reset to `null` is a real intent.\n return inFlight === null ? optimistic : inFlight.next;\n };\n\n // The residual browser paths (design section 5): one dispatch per group, from here. Items that\n // mount after the Root report through `onItemMounted` so the group is reconciled again.\n const { onItemMounted } = useNativeGroupReconciliation<string | null>({\n element: fieldsetRef,\n readCurrent: () => readGroup(fieldsetRef.current, name, \"checked\"),\n readDefault: () => readGroup(fieldsetRef.current, name, \"defaultChecked\"),\n base,\n onPlatformChange: (next, event) =>\n dispatch(next, createActionDetails(event, { reason: \"platform\" })),\n });\n\n // Dev-only partial-hydration detection. The radios present in the fieldset when the Root\n // mounts are either mounted in the Root's commit or still dehydrated server HTML. An Item that\n // mounts later on one of those elements hydrated after the Root (an Item created on the client\n // gets a new element). Items of the Root's own commit run their mount effect before this one,\n // while the snapshot is still `null`; StrictMode's re-run finds it reset by the cleanup.\n const serverRadios = useRef<ReadonlySet<Element> | null>(null);\n const warnedPartialHydration = useRef(false);\n useLayoutEffect(() => {\n if (!isDev) return;\n const el = fieldsetRef.current;\n serverRadios.current = new Set(el === null ? [] : el.querySelectorAll('input[type=\"radio\"]'));\n return () => {\n serverRadios.current = null;\n };\n }, []);\n const onItemMountedWithCheck = useCallback(\n (element?: Element | null) => {\n if (\n isDev &&\n !warnedPartialHydration.current &&\n element != null &&\n serverRadios.current?.has(element) === true\n ) {\n warnedPartialHydration.current = true;\n console.error(\n `RadioGroup.Item: an Item hydrated after its RadioGroup.Root (a Suspense boundary ` +\n `between them). Partial hydration of a radio group is not supported by React: ` +\n `clicking a hydrated radio while same-name radios are still dehydrated makes ` +\n `react-dom throw \"Mixing React and non-React radio inputs with the same name is not ` +\n `supported\". Keep every Item in the same Suspense boundary as its Root.`,\n );\n }\n onItemMounted(element);\n },\n [onItemMounted],\n );\n\n const libraryRef = useCallback((el: HTMLFieldSetElement) => {\n fieldsetRef.current = el;\n if (isDev && !warnedElement.current && el.tagName !== \"FIELDSET\") {\n warnedElement.current = true;\n console.error(\n `RadioGroup.Root: expected to render a <fieldset> element but got ` +\n `<${el.tagName.toLowerCase()}>. The platform semantics (legend naming, disabled ` +\n `inheritance, form reset) need a real <fieldset>.`,\n );\n }\n return () => {\n fieldsetRef.current = null;\n };\n }, []);\n\n const context: RadioGroupContextValue = {\n name,\n form,\n required,\n optimistic,\n pending,\n disabled,\n defaultValue: value === undefined ? (defaultValue ?? null) : undefined,\n onItemMounted: onItemMountedWithCheck,\n select: (next, event) => {\n // No-op rule against the optimistic value (conventions 2.3 item 7, design 4.2).\n if (next === optimistic) return;\n dispatch(next, createActionDetails(event, { reason: \"activate\" }));\n },\n };\n\n const state: RadioGroupRootState = { value: optimistic, pending, disabled };\n\n const element = useRender<ComponentPropsWithRef<\"fieldset\">, RadioGroupRootState>({\n defaultElement: \"fieldset\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n role: \"radiogroup\",\n \"aria-required\": required ? \"true\" : undefined,\n disabled,\n form,\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n },\n ref: libraryRef,\n });\n\n return <RadioGroupContext value={context}>{element}</RadioGroupContext>;\n}\n\nRadioGroupRoot.displayName = \"RadioGroupRoot\";\n"],"mappings":";;;;;;;;;;;AAmBA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;AAyFvC,SAAS,UACP,UACA,MACA,MACe;CACf,IAAI,aAAa,MAAM,OAAO;CAC9B,KAAK,MAAM,SAAS,SAAS,iBAAmC,uBAAqB,GACnF,IAAI,MAAM,SAAS,QAAQ,MAAM,OAAO,OAAO,MAAM;CAEvD,OAAO;AACT;;;;;;;;;;;;;;;AAgBA,SAAgB,eAAe,EAC7B,OACA,cACA,mBACA,MAAM,UACN,MACA,WAAW,OACX,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACmB;CACtB,MAAM,eAAe,MAAM;CAC3B,MAAM,OAAO,YAAY;CACzB,MAAM,cAAc,OAAmC,IAAI;CAC3D,MAAM,gBAAgB,OAAO,KAAK;CAElC,MAAM,EAAE,YAAY,SAAS,UAAU,mBAAmB,sBAGxD;EACA,YAAY;EACZ;EACA,cAAc;EACd,QAAQ;EACR,uBAAuB,gBAAgB,aAAa;EACpD,MAAM;GAAE,WAAW;GAAmB,MAAM;EAAQ;CACtD,CAAC;;CAKD,MAAM,aAA4B;EAChC,MAAM,WAAW,eAAe;EAEhC,OAAO,aAAa,OAAO,aAAa,SAAS;CACnD;CAIA,MAAM,EAAE,kBAAkB,6BAA4C;EACpE,SAAS;EACT,mBAAmB,UAAU,YAAY,SAAS,MAAM,SAAS;EACjE,mBAAmB,UAAU,YAAY,SAAS,MAAM,gBAAgB;EACxE;EACA,mBAAmB,MAAM,UACvB,SAAS,MAAM,oBAAoB,OAAO,EAAE,QAAQ,WAAW,CAAC,CAAC;CACrE,CAAC;CAOD,MAAM,eAAe,OAAoC,IAAI;CAC7D,MAAM,yBAAyB,OAAO,KAAK;CAC3C,sBAAsB;EACpB,IAAI,CAAC,OAAO;EACZ,MAAM,KAAK,YAAY;EACvB,aAAa,UAAU,IAAI,IAAI,OAAO,OAAO,CAAC,IAAI,GAAG,iBAAiB,uBAAqB,CAAC;EAC5F,aAAa;GACX,aAAa,UAAU;EACzB;CACF,GAAG,CAAC,CAAC;CACL,MAAM,yBAAyB,aAC5B,YAA6B;EAC5B,IACE,SACA,CAAC,uBAAuB,WACxB,WAAW,QACX,aAAa,SAAS,IAAI,OAAO,MAAM,MACvC;GACA,uBAAuB,UAAU;GACjC,QAAQ,MACN,uYAKF;EACF;EACA,cAAc,OAAO;CACvB,GACA,CAAC,aAAa,CAChB;CAEA,MAAM,aAAa,aAAa,OAA4B;EAC1D,YAAY,UAAU;EACtB,IAAI,SAAS,CAAC,cAAc,WAAW,GAAG,YAAY,YAAY;GAChE,cAAc,UAAU;GACxB,QAAQ,MACN,qEACM,GAAG,QAAQ,YAAY,EAAE,oGAEjC;EACF;EACA,aAAa;GACX,YAAY,UAAU;EACxB;CACF,GAAG,CAAC,CAAC;CAEL,MAAM,UAAkC;EACtC;EACA;EACA;EACA;EACA;EACA;EACA,cAAc,UAAU,KAAA,IAAa,gBAAgB,OAAQ,KAAA;EAC7D,eAAe;EACf,SAAS,MAAM,UAAU;GAEvB,IAAI,SAAS,YAAY;GACzB,SAAS,MAAM,oBAAoB,OAAO,EAAE,QAAQ,WAAW,CAAC,CAAC;EACnE;CACF;CAIA,MAAM,UAAU,UAAkE;EAChF,gBAAgB;EAChB;EACA,OAAA;GALmC,OAAO;GAAY;GAAS;EAK3D;EACJ;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,MAAM;GACN,iBAAiB,WAAW,SAAS,KAAA;GACrC;GACA;GACA,gBAAgB,SAAS,OAAO;GAChC,iBAAiB,SAAS,QAAQ;EACpC;EACA,KAAK;CACP,CAAC;CAED,OAAO,oBAAC,mBAAD;EAAmB,OAAO;EAAU,UAAA;CAA2B,CAAA;AACxE;AAEA,eAAe,cAAc"}
1
+ {"version":3,"file":"radio-group-root.mjs","names":[],"sources":["../../src/radio-group/radio-group-root.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useId, useLayoutEffect, useMemo, useRef } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { transitionTypes } from \"../internal/transition-types.js\";\nimport type {\n ActionDetails,\n ChangeAction,\n ChangeReason,\n RenderProp,\n StateValue,\n} from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { useNativeGroupReconciliation } from \"../internal/use-native-group-reconciliation.js\";\nimport {\n useFieldControl,\n useFieldValueChange,\n useMergedRef,\n} from \"../internal/use-field-control.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { RadioGroupContext } from \"./radio-group-context.js\";\nimport type { RadioGroupContextValue } from \"./radio-group-context.js\";\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/** Details passed as the second argument of `valueChangeAction`. */\nexport interface RadioGroupValueChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user checked a radio (click, Space, label click, or an arrow key, all of\n * which arrive as a native `click`). `\"platform\"`: the browser changed the checked radio\n * itself: a form reset (`details.event` is the `reset` event), or a click before hydration /\n * Firefox form-state restoration found at mount (`details.event` is `undefined`).\n */\n readonly reason: ChangeReason;\n}\n\nexport interface RadioGroupRootState {\n /** Optimistic checked value, or `null`. Content rendered from it must not suspend. */\n readonly value: string | null;\n /** An action dispatched through this group is in flight. */\n readonly pending: boolean;\n /** The `disabled` prop. */\n readonly disabled: boolean;\n}\n\nexport interface RadioGroupRootProps extends Omit<\n ComponentPropsWithRef<\"fieldset\">,\n | \"className\"\n | \"style\"\n | \"children\"\n | \"defaultValue\"\n | \"defaultChecked\"\n | \"name\"\n | \"disabled\"\n | \"form\"\n> {\n /**\n * Controlled checked value. `null` checks nothing; `undefined` means uncontrolled.\n *\n * A form reset returns each radio to the `checked` it had when *that radio* mounted (React sets\n * `defaultChecked` once, from the initial `checked`). An Item added on the client while `value`\n * equals its value therefore also gets `defaultChecked`, so two radios may carry it and the\n * reset target is not necessarily the group's mount-time value. The reset is offered to\n * `valueChangeAction` as usual; for a stable target remount the group with a `key` or use\n * `defaultValue`, whose latest value every Item mirrors.\n */\n value?: string | null;\n /**\n * Initial value in uncontrolled mode. Defaults to `null` (nothing checked). Also the value a\n * form reset returns to: the latest prop value is mirrored into the radios' `defaultChecked`.\n */\n defaultValue?: string | null;\n /**\n * Runs inside the Root's transition whenever the checked radio changes. May be sync or async\n * (also a Server Function); rejections reach the nearest Error Boundary and roll the selection\n * back.\n *\n * `next` is a string for every user interaction. It is `null` only with\n * `details.reason === \"platform\"`, when a form reset returned the group to \"nothing checked\".\n * Every arrow key press dispatches; the latest dispatch wins. A `setState` placed after your\n * own `await` is not a Transition update; wrap it in `startTransition` (React limitation).\n * `details.event.preventDefault()` is not a veto; use controlled mode to veto a change.\n */\n valueChangeAction?: ChangeAction<string | null, RadioGroupValueChangeDetails>;\n /**\n * The `name` shared by every radio in the group (form data key). Defaults to a `useId()` value;\n * pass a real name when the group is inside a `<form>` you submit. Must be unique within the\n * form: react-dom throws if a non-React radio shares it.\n */\n name?: string;\n /** The `form` attribute, applied to the fieldset and to every radio. */\n form?: string;\n /**\n * Marks every radio `required`, which makes the group required (one checked radio satisfies it),\n * and renders `aria-required=\"true\"` on the fieldset so the radiogroup itself is exposed as\n * required.\n */\n required?: boolean;\n /** Renders `<fieldset disabled>`: every radio is disabled, unfocusable and skipped by arrows. */\n disabled?: boolean;\n className?: StateValue<string | undefined, RadioGroupRootState>;\n style?: StateValue<CSSProperties | undefined, RadioGroupRootState>;\n children?: StateValue<ReactNode, RadioGroupRootState>;\n /** Replace the rendered element. Must keep a `<fieldset>`. */\n render?: RenderProp<ComponentPropsWithRef<\"fieldset\">, RadioGroupRootState>;\n}\n\n/**\n * Value of the group's checked radio as the DOM shows it (`prop: \"checked\"`) or as a form reset\n * leaves it (`prop: \"defaultChecked\"`), or `null`. Only radios of this group's `name` count, so a\n * nested group is ignored.\n */\nfunction readGroup(\n fieldset: HTMLFieldSetElement | null,\n name: string,\n prop: \"checked\" | \"defaultChecked\",\n): string | null {\n if (fieldset === null) return null;\n for (const radio of fieldset.querySelectorAll<HTMLInputElement>('input[type=\"radio\"]')) {\n if (radio.name === name && radio[prop]) return radio.value;\n }\n return null;\n}\n\n/**\n * `<fieldset role=\"radiogroup\">` that owns the group's state, the transition, the shared `name`,\n * the form-reset listener and the mount reconciliation (design/components/radio-group.md 13.3).\n * Carries `data-pending` while `valueChangeAction` is in flight.\n *\n * Owned keys (conventions 1.2): `role`, `aria-required`, `disabled`, `form`, `data-pending`,\n * `data-disabled`; inside a `Field.Root` also `aria-labelledby` (→ `Field.Label`, token-merged),\n * `aria-describedby` (token-merged), `aria-invalid` and `data-invalid`, and `name` / `required` /\n * `disabled` default from the Field.\n *\n * Keep every Item in the same Suspense boundary as its Root when server rendering. Partial\n * hydration of a group (a boundary between Root and Items that stays dehydrated while the user\n * interacts) is not supported by React: clicking a hydrated radio while same-`name` siblings are\n * still dehydrated makes react-dom throw \"Mixing React and non-React radio inputs with the same\n * `name` is not supported\". Development warns when Items hydrate after their Root.\n */\nexport function RadioGroupRoot({\n value,\n defaultValue,\n valueChangeAction,\n name: nameProp,\n form,\n required: requiredProp,\n disabled: disabledProp,\n className,\n style,\n children,\n render,\n ...rest\n}: RadioGroupRootProps) {\n // Inside a Field.Root (design/components/field-form.md 7.2) the fieldset is the Field's group\n // control: `aria-labelledby` → `Field.Label`, the ARIA wiring, and `name` / `required` /\n // `disabled` defaulting from the Field.\n const field = useFieldControl({\n labelable: false,\n name: nameProp,\n disabled: disabledProp,\n required: requiredProp,\n component: \"RadioGroup.Root\",\n });\n const required = field.required;\n const disabled = field.disabled;\n const fallbackName = useId();\n const name = field.name ?? fallbackName;\n const fieldsetRef = useRef<HTMLFieldSetElement | null>(null);\n const warnedElement = useRef(false);\n\n const { optimistic, pending, dispatch, latestInFlight } = useControllableAction<\n string | null,\n RadioGroupValueChangeDetails\n >({\n controlled: value,\n defaultValue,\n initialValue: null,\n action: valueChangeAction,\n transitionTypes: () => transitionTypes(\"radio-group\"),\n warn: { component: \"RadioGroup.Root\", prop: \"value\" },\n });\n // Inside a Field: a value React writes with no event (a rejected action rolling back, a\n // controlled change) re-reads validity and `validate` after the commit.\n useFieldValueChange(optimistic);\n // Deliberately no warning when neither `value` nor `defaultValue` is given: an unchecked group\n // is the HTML default (design decision 2).\n\n /** What the user intends: the latest in-flight dispatch, else optimistic (conventions 3.8 rule 1). */\n const base = (): string | null => {\n const inFlight = latestInFlight();\n // Not `?.next ?? optimistic`: an in-flight reset to `null` is a real intent.\n return inFlight === null ? optimistic : inFlight.next;\n };\n\n // The residual browser paths (design section 5): one dispatch per group, from here. Items that\n // mount after the Root report through `onItemMounted` so the group is reconciled again.\n const { onItemMounted } = useNativeGroupReconciliation<string | null>({\n element: fieldsetRef,\n readCurrent: () => readGroup(fieldsetRef.current, name, \"checked\"),\n readDefault: () => readGroup(fieldsetRef.current, name, \"defaultChecked\"),\n base,\n onPlatformChange: (next, event) =>\n dispatch(next, createActionDetails(event, { reason: \"platform\" })),\n });\n\n // Dev-only partial-hydration detection. The radios present in the fieldset when the Root\n // mounts are either mounted in the Root's commit or still dehydrated server HTML. An Item that\n // mounts later on one of those elements hydrated after the Root (an Item created on the client\n // gets a new element). Items of the Root's own commit run their mount effect before this one,\n // while the snapshot is still `null`; StrictMode's re-run finds it reset by the cleanup.\n const serverRadios = useRef<ReadonlySet<Element> | null>(null);\n const warnedPartialHydration = useRef(false);\n useLayoutEffect(() => {\n if (!isDev) return;\n const el = fieldsetRef.current;\n serverRadios.current = new Set(el === null ? [] : el.querySelectorAll('input[type=\"radio\"]'));\n return () => {\n serverRadios.current = null;\n };\n }, []);\n const onItemMountedWithCheck = useCallback(\n (element?: Element | null) => {\n if (\n isDev &&\n !warnedPartialHydration.current &&\n element != null &&\n serverRadios.current?.has(element) === true\n ) {\n warnedPartialHydration.current = true;\n console.error(\n `RadioGroup.Item: an Item hydrated after its RadioGroup.Root (a Suspense boundary ` +\n `between them). Partial hydration of a radio group is not supported by React: ` +\n `clicking a hydrated radio while same-name radios are still dehydrated makes ` +\n `react-dom throw \"Mixing React and non-React radio inputs with the same name is not ` +\n `supported\". Keep every Item in the same Suspense boundary as its Root.`,\n );\n }\n onItemMounted(element);\n },\n [onItemMounted],\n );\n\n const libraryRef = useCallback((el: HTMLFieldSetElement) => {\n fieldsetRef.current = el;\n if (isDev && !warnedElement.current && el.tagName !== \"FIELDSET\") {\n warnedElement.current = true;\n console.error(\n `RadioGroup.Root: expected to render a <fieldset> element but got ` +\n `<${el.tagName.toLowerCase()}>. The platform semantics (legend naming, disabled ` +\n `inheritance, form reset) need a real <fieldset>.`,\n );\n }\n return () => {\n fieldsetRef.current = null;\n };\n }, []);\n const rootRef = useMergedRef<HTMLFieldSetElement>(libraryRef, field.ref);\n\n // Memoised (#48): `dispatch` and `onItemMountedWithCheck` are stable, so this changes only with\n // the state and the props below.\n const contextDefault = value === undefined ? (defaultValue ?? null) : undefined;\n const context = useMemo<RadioGroupContextValue>(\n () => ({\n name,\n form,\n required,\n optimistic,\n pending,\n disabled,\n defaultValue: contextDefault,\n onItemMounted: onItemMountedWithCheck,\n select: (next, event) => {\n // No-op rule against the optimistic value (conventions 2.3 item 7, design 4.2).\n if (next === optimistic) return;\n dispatch(next, createActionDetails(event, { reason: \"activate\" }));\n },\n }),\n [\n name,\n form,\n required,\n optimistic,\n pending,\n disabled,\n contextDefault,\n onItemMountedWithCheck,\n dispatch,\n ],\n );\n\n const state: RadioGroupRootState = { value: optimistic, pending, disabled };\n\n const element = useRender<ComponentPropsWithRef<\"fieldset\">, RadioGroupRootState>({\n defaultElement: \"fieldset\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n ...field.attributes,\n role: \"radiogroup\",\n \"aria-required\": required ? \"true\" : undefined,\n disabled,\n form,\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n },\n ref: rootRef,\n });\n\n return <RadioGroupContext value={context}>{element}</RadioGroupContext>;\n}\n\nRadioGroupRoot.displayName = \"RadioGroupRoot\";\n"],"mappings":";;;;;;;;;;;;AAwBA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;AAyFvC,SAAS,UACP,UACA,MACA,MACe;CACf,IAAI,aAAa,MAAM,OAAO;CAC9B,KAAK,MAAM,SAAS,SAAS,iBAAmC,uBAAqB,GACnF,IAAI,MAAM,SAAS,QAAQ,MAAM,OAAO,OAAO,MAAM;CAEvD,OAAO;AACT;;;;;;;;;;;;;;;;;AAkBA,SAAgB,eAAe,EAC7B,OACA,cACA,mBACA,MAAM,UACN,MACA,UAAU,cACV,UAAU,cACV,WACA,OACA,UACA,QACA,GAAG,QACmB;CAItB,MAAM,QAAQ,gBAAgB;EAC5B,WAAW;EACX,MAAM;EACN,UAAU;EACV,UAAU;EACV,WAAW;CACb,CAAC;CACD,MAAM,WAAW,MAAM;CACvB,MAAM,WAAW,MAAM;CACvB,MAAM,eAAe,MAAM;CAC3B,MAAM,OAAO,MAAM,QAAQ;CAC3B,MAAM,cAAc,OAAmC,IAAI;CAC3D,MAAM,gBAAgB,OAAO,KAAK;CAElC,MAAM,EAAE,YAAY,SAAS,UAAU,mBAAmB,sBAGxD;EACA,YAAY;EACZ;EACA,cAAc;EACd,QAAQ;EACR,uBAAuB,gBAAgB,aAAa;EACpD,MAAM;GAAE,WAAW;GAAmB,MAAM;EAAQ;CACtD,CAAC;CAGD,oBAAoB,UAAU;;CAK9B,MAAM,aAA4B;EAChC,MAAM,WAAW,eAAe;EAEhC,OAAO,aAAa,OAAO,aAAa,SAAS;CACnD;CAIA,MAAM,EAAE,kBAAkB,6BAA4C;EACpE,SAAS;EACT,mBAAmB,UAAU,YAAY,SAAS,MAAM,SAAS;EACjE,mBAAmB,UAAU,YAAY,SAAS,MAAM,gBAAgB;EACxE;EACA,mBAAmB,MAAM,UACvB,SAAS,MAAM,oBAAoB,OAAO,EAAE,QAAQ,WAAW,CAAC,CAAC;CACrE,CAAC;CAOD,MAAM,eAAe,OAAoC,IAAI;CAC7D,MAAM,yBAAyB,OAAO,KAAK;CAC3C,sBAAsB;EACpB,IAAI,CAAC,OAAO;EACZ,MAAM,KAAK,YAAY;EACvB,aAAa,UAAU,IAAI,IAAI,OAAO,OAAO,CAAC,IAAI,GAAG,iBAAiB,uBAAqB,CAAC;EAC5F,aAAa;GACX,aAAa,UAAU;EACzB;CACF,GAAG,CAAC,CAAC;CACL,MAAM,yBAAyB,aAC5B,YAA6B;EAC5B,IACE,SACA,CAAC,uBAAuB,WACxB,WAAW,QACX,aAAa,SAAS,IAAI,OAAO,MAAM,MACvC;GACA,uBAAuB,UAAU;GACjC,QAAQ,MACN,uYAKF;EACF;EACA,cAAc,OAAO;CACvB,GACA,CAAC,aAAa,CAChB;CAEA,MAAM,aAAa,aAAa,OAA4B;EAC1D,YAAY,UAAU;EACtB,IAAI,SAAS,CAAC,cAAc,WAAW,GAAG,YAAY,YAAY;GAChE,cAAc,UAAU;GACxB,QAAQ,MACN,qEACM,GAAG,QAAQ,YAAY,EAAE,oGAEjC;EACF;EACA,aAAa;GACX,YAAY,UAAU;EACxB;CACF,GAAG,CAAC,CAAC;CACL,MAAM,UAAU,aAAkC,YAAY,MAAM,GAAG;CAIvE,MAAM,iBAAiB,UAAU,KAAA,IAAa,gBAAgB,OAAQ,KAAA;CACtE,MAAM,UAAU,eACP;EACL;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd,eAAe;EACf,SAAS,MAAM,UAAU;GAEvB,IAAI,SAAS,YAAY;GACzB,SAAS,MAAM,oBAAoB,OAAO,EAAE,QAAQ,WAAW,CAAC,CAAC;EACnE;CACF,IACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CACF;CAIA,MAAM,UAAU,UAAkE;EAChF,gBAAgB;EAChB;EACA,OAAA;GALmC,OAAO;GAAY;GAAS;EAK3D;EACJ;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,GAAG,MAAM;GACT,MAAM;GACN,iBAAiB,WAAW,SAAS,KAAA;GACrC;GACA;GACA,gBAAgB,SAAS,OAAO;GAChC,iBAAiB,SAAS,QAAQ;EACpC;EACA,KAAK;CACP,CAAC;CAED,OAAO,oBAAC,mBAAD;EAAmB,OAAO;EAAU,UAAA;CAA2B,CAAA;AACxE;AAEA,eAAe,cAAc"}
@@ -0,0 +1,30 @@
1
+ import "../internal/platform-attributes.mjs";
2
+ import { SelectRoot, SelectRootProps, SelectRootState, SelectValueChangeDetails } from "./select-root.mjs";
3
+ import { SelectGroup, SelectGroupProps } from "./select-group.mjs";
4
+ import { SelectGroupLabel, SelectGroupLabelProps } from "./select-group-label.mjs";
5
+ import { SelectOption, SelectOptionProps, SelectOptionState } from "./select-option.mjs";
6
+ import { SelectSeparator, SelectSeparatorProps } from "./select-separator.mjs";
7
+ import { SelectTrigger, SelectTriggerProps } from "./select-trigger.mjs";
8
+ import { SelectValue, SelectValueProps } from "./select-value.mjs";
9
+ //#region src/select/index.d.ts
10
+ /**
11
+ * Select: a real `<select>` (`Root`), progressively enhanced with customizable select. Optional
12
+ * `Trigger` (`<button>`, first child) and `Value` (`<selectedcontent>`) for rich button content,
13
+ * `Option` (`<option>`), `Group` (`<optgroup>`) with an optional `GroupLabel` (`<legend>`), and
14
+ * `Separator` (`<hr>`). The picker, its keyboard and its placement are the browser's; opt into
15
+ * the styled picker with `appearance: base-select` in your CSS. The selected option is the
16
+ * optimistic value; the Root, the Trigger and the picked Option carry `data-pending` while
17
+ * `valueChangeAction` is in flight.
18
+ */
19
+ export declare const Select: {
20
+ readonly Root: typeof SelectRoot;
21
+ readonly Trigger: typeof SelectTrigger;
22
+ readonly Value: typeof SelectValue;
23
+ readonly Option: typeof SelectOption;
24
+ readonly Group: typeof SelectGroup;
25
+ readonly GroupLabel: typeof SelectGroupLabel;
26
+ readonly Separator: typeof SelectSeparator;
27
+ };
28
+ //#endregion
29
+ export type { SelectGroupLabelProps, SelectGroupProps, SelectOptionProps, SelectOptionState, SelectRootProps, SelectRootState, SelectSeparatorProps, SelectTriggerProps, SelectValueChangeDetails, SelectValueProps };
30
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/select/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;qBAoBa;WACX,aAAI;WACJ,gBAAO;WACP,cAAK;WACL,eAAM;WACN,cAAK;WACL,mBAAU;WACV,kBAAS"}
@@ -0,0 +1,30 @@
1
+ import { SelectGroup } from "./select-group.mjs";
2
+ import { SelectGroupLabel } from "./select-group-label.mjs";
3
+ import { SelectOption } from "./select-option.mjs";
4
+ import { SelectRoot } from "./select-root.mjs";
5
+ import { SelectSeparator } from "./select-separator.mjs";
6
+ import { SelectTrigger } from "./select-trigger.mjs";
7
+ import { SelectValue } from "./select-value.mjs";
8
+ //#region src/select/index.ts
9
+ /**
10
+ * Select: a real `<select>` (`Root`), progressively enhanced with customizable select. Optional
11
+ * `Trigger` (`<button>`, first child) and `Value` (`<selectedcontent>`) for rich button content,
12
+ * `Option` (`<option>`), `Group` (`<optgroup>`) with an optional `GroupLabel` (`<legend>`), and
13
+ * `Separator` (`<hr>`). The picker, its keyboard and its placement are the browser's; opt into
14
+ * the styled picker with `appearance: base-select` in your CSS. The selected option is the
15
+ * optimistic value; the Root, the Trigger and the picked Option carry `data-pending` while
16
+ * `valueChangeAction` is in flight.
17
+ */
18
+ const Select = {
19
+ Root: SelectRoot,
20
+ Trigger: SelectTrigger,
21
+ Value: SelectValue,
22
+ Option: SelectOption,
23
+ Group: SelectGroup,
24
+ GroupLabel: SelectGroupLabel,
25
+ Separator: SelectSeparator
26
+ };
27
+ //#endregion
28
+ export { Select };
29
+
30
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/select/index.ts"],"sourcesContent":["// Ships the platform typings (the `selectedcontent` element) with this subpath (conventions 7.4).\n// Type-only: the built JS has no import of it.\nimport \"../internal/platform-attributes.js\";\nimport { SelectGroup } from \"./select-group.js\";\nimport { SelectGroupLabel } from \"./select-group-label.js\";\nimport { SelectOption } from \"./select-option.js\";\nimport { SelectRoot } from \"./select-root.js\";\nimport { SelectSeparator } from \"./select-separator.js\";\nimport { SelectTrigger } from \"./select-trigger.js\";\nimport { SelectValue } from \"./select-value.js\";\n\n/**\n * Select: a real `<select>` (`Root`), progressively enhanced with customizable select. Optional\n * `Trigger` (`<button>`, first child) and `Value` (`<selectedcontent>`) for rich button content,\n * `Option` (`<option>`), `Group` (`<optgroup>`) with an optional `GroupLabel` (`<legend>`), and\n * `Separator` (`<hr>`). The picker, its keyboard and its placement are the browser's; opt into\n * the styled picker with `appearance: base-select` in your CSS. The selected option is the\n * optimistic value; the Root, the Trigger and the picked Option carry `data-pending` while\n * `valueChangeAction` is in flight.\n */\nexport const Select = {\n Root: SelectRoot,\n Trigger: SelectTrigger,\n Value: SelectValue,\n Option: SelectOption,\n Group: SelectGroup,\n GroupLabel: SelectGroupLabel,\n Separator: SelectSeparator,\n} as const;\n\nexport type { SelectRootProps, SelectRootState, SelectValueChangeDetails } from \"./select-root.js\";\nexport type { SelectTriggerProps } from \"./select-trigger.js\";\nexport type { SelectValueProps } from \"./select-value.js\";\nexport type { SelectOptionProps, SelectOptionState } from \"./select-option.js\";\nexport type { SelectGroupProps } from \"./select-group.js\";\nexport type { SelectGroupLabelProps } from \"./select-group-label.js\";\nexport type { SelectSeparatorProps } from \"./select-separator.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;AAoBA,MAAa,SAAS;CACpB,MAAM;CACN,SAAS;CACT,OAAO;CACP,QAAQ;CACR,OAAO;CACP,YAAY;CACZ,WAAW;AACb"}
@@ -0,0 +1,13 @@
1
+ "use client";
2
+ import { createContext, use } from "react";
3
+ //#region src/select/select-context.ts
4
+ const SelectContext = createContext(null);
5
+ function useSelectContext(part) {
6
+ const context = use(SelectContext);
7
+ if (context === null) throw new Error(`${part} must be rendered inside a Select.Root.`);
8
+ return context;
9
+ }
10
+ //#endregion
11
+ export { SelectContext, useSelectContext };
12
+
13
+ //# sourceMappingURL=select-context.mjs.map