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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (403) 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 +34 -15
  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.mjs +2 -1
  25. package/dist/combobox/combobox-clear.mjs.map +1 -1
  26. package/dist/combobox/combobox-context.mjs.map +1 -1
  27. package/dist/combobox/combobox-input.d.mts +3 -1
  28. package/dist/combobox/combobox-input.d.mts.map +1 -1
  29. package/dist/combobox/combobox-input.mjs +16 -2
  30. package/dist/combobox/combobox-input.mjs.map +1 -1
  31. package/dist/combobox/combobox-list.mjs +3 -2
  32. package/dist/combobox/combobox-list.mjs.map +1 -1
  33. package/dist/combobox/combobox-popup.d.mts.map +1 -1
  34. package/dist/combobox/combobox-popup.mjs +7 -2
  35. package/dist/combobox/combobox-popup.mjs.map +1 -1
  36. package/dist/combobox/combobox-root.d.mts.map +1 -1
  37. package/dist/combobox/combobox-root.mjs +20 -5
  38. package/dist/combobox/combobox-root.mjs.map +1 -1
  39. package/dist/combobox/combobox-trigger.mjs +2 -1
  40. package/dist/combobox/combobox-trigger.mjs.map +1 -1
  41. package/dist/dialog/dialog-close.d.mts +17 -10
  42. package/dist/dialog/dialog-close.d.mts.map +1 -1
  43. package/dist/dialog/dialog-close.mjs +33 -21
  44. package/dist/dialog/dialog-close.mjs.map +1 -1
  45. package/dist/dialog/dialog-context.mjs.map +1 -1
  46. package/dist/dialog/dialog-form.d.mts +23 -10
  47. package/dist/dialog/dialog-form.d.mts.map +1 -1
  48. package/dist/dialog/dialog-form.mjs +51 -32
  49. package/dist/dialog/dialog-form.mjs.map +1 -1
  50. package/dist/dialog/dialog-popup.d.mts +10 -4
  51. package/dist/dialog/dialog-popup.d.mts.map +1 -1
  52. package/dist/dialog/dialog-popup.mjs +117 -44
  53. package/dist/dialog/dialog-popup.mjs.map +1 -1
  54. package/dist/dialog/dialog-root.d.mts +14 -8
  55. package/dist/dialog/dialog-root.d.mts.map +1 -1
  56. package/dist/dialog/dialog-root.mjs +67 -12
  57. package/dist/dialog/dialog-root.mjs.map +1 -1
  58. package/dist/dialog/dialog-trigger.d.mts +3 -2
  59. package/dist/dialog/dialog-trigger.d.mts.map +1 -1
  60. package/dist/dialog/dialog-trigger.mjs +2 -1
  61. package/dist/dialog/dialog-trigger.mjs.map +1 -1
  62. package/dist/dialog/index.d.mts +4 -2
  63. package/dist/dialog/index.d.mts.map +1 -1
  64. package/dist/dialog/index.mjs +4 -2
  65. package/dist/dialog/index.mjs.map +1 -1
  66. package/dist/field/field-control.d.mts +33 -0
  67. package/dist/field/field-control.d.mts.map +1 -0
  68. package/dist/field/field-control.mjs +68 -0
  69. package/dist/field/field-control.mjs.map +1 -0
  70. package/dist/field/field-description.d.mts +24 -0
  71. package/dist/field/field-description.d.mts.map +1 -0
  72. package/dist/field/field-description.mjs +34 -0
  73. package/dist/field/field-description.mjs.map +1 -0
  74. package/dist/field/field-error.d.mts +30 -0
  75. package/dist/field/field-error.d.mts.map +1 -0
  76. package/dist/field/field-error.mjs +42 -0
  77. package/dist/field/field-error.mjs.map +1 -0
  78. package/dist/field/field-label.d.mts +25 -0
  79. package/dist/field/field-label.d.mts.map +1 -0
  80. package/dist/field/field-label.mjs +46 -0
  81. package/dist/field/field-label.mjs.map +1 -0
  82. package/dist/field/field-root.d.mts +53 -0
  83. package/dist/field/field-root.d.mts.map +1 -0
  84. package/dist/field/field-root.mjs +319 -0
  85. package/dist/field/field-root.mjs.map +1 -0
  86. package/dist/field/index.d.mts +18 -0
  87. package/dist/field/index.d.mts.map +1 -0
  88. package/dist/field/index.mjs +17 -0
  89. package/dist/field/index.mjs.map +1 -0
  90. package/dist/form/form-root.d.mts +58 -0
  91. package/dist/form/form-root.d.mts.map +1 -0
  92. package/dist/form/form-root.mjs +147 -0
  93. package/dist/form/form-root.mjs.map +1 -0
  94. package/dist/form/form-submit.d.mts +31 -0
  95. package/dist/form/form-submit.d.mts.map +1 -0
  96. package/dist/form/form-submit.mjs +53 -0
  97. package/dist/form/form-submit.mjs.map +1 -0
  98. package/dist/form/index.d.mts +11 -0
  99. package/dist/form/index.d.mts.map +1 -0
  100. package/dist/form/index.mjs +11 -0
  101. package/dist/form/index.mjs.map +1 -0
  102. package/dist/index.d.mts +53 -1
  103. package/dist/index.mjs +11 -1
  104. package/dist/internal/announce.mjs +53 -0
  105. package/dist/internal/announce.mjs.map +1 -0
  106. package/dist/internal/content-focus-probe.mjs +42 -0
  107. package/dist/internal/content-focus-probe.mjs.map +1 -0
  108. package/dist/internal/decimal.mjs +27 -0
  109. package/dist/internal/decimal.mjs.map +1 -0
  110. package/dist/internal/dev-warn.mjs +17 -0
  111. package/dist/internal/dev-warn.mjs.map +1 -0
  112. package/dist/internal/field-context.d.mts +19 -0
  113. package/dist/internal/field-context.d.mts.map +1 -0
  114. package/dist/internal/field-context.mjs +14 -0
  115. package/dist/internal/field-context.mjs.map +1 -0
  116. package/dist/internal/focus-first-invalid.mjs +67 -0
  117. package/dist/internal/focus-first-invalid.mjs.map +1 -0
  118. package/dist/internal/form-status-watcher.mjs +31 -0
  119. package/dist/internal/form-status-watcher.mjs.map +1 -0
  120. package/dist/internal/modal-registry.mjs +39 -0
  121. package/dist/internal/modal-registry.mjs.map +1 -0
  122. package/dist/internal/range-value.mjs +51 -0
  123. package/dist/internal/range-value.mjs.map +1 -0
  124. package/dist/internal/render-element.mjs +54 -0
  125. package/dist/internal/render-element.mjs.map +1 -0
  126. package/dist/internal/toolbar-context.mjs +17 -0
  127. package/dist/internal/toolbar-context.mjs.map +1 -0
  128. package/dist/internal/types.d.mts +3 -1
  129. package/dist/internal/types.d.mts.map +1 -1
  130. package/dist/internal/use-controllable-action.mjs +33 -15
  131. package/dist/internal/use-controllable-action.mjs.map +1 -1
  132. package/dist/internal/use-exit-animation.mjs +26 -6
  133. package/dist/internal/use-exit-animation.mjs.map +1 -1
  134. package/dist/internal/use-field-control.mjs +109 -0
  135. package/dist/internal/use-field-control.mjs.map +1 -0
  136. package/dist/internal/use-form-owner-event.mjs +44 -0
  137. package/dist/internal/use-form-owner-event.mjs.map +1 -0
  138. package/dist/internal/use-form-reset.mjs +10 -33
  139. package/dist/internal/use-form-reset.mjs.map +1 -1
  140. package/dist/internal/use-latest.mjs +32 -0
  141. package/dist/internal/use-latest.mjs.map +1 -0
  142. package/dist/internal/use-modal-registration.mjs +47 -0
  143. package/dist/internal/use-modal-registration.mjs.map +1 -0
  144. package/dist/internal/use-native-group-reconciliation.mjs +6 -5
  145. package/dist/internal/use-native-group-reconciliation.mjs.map +1 -1
  146. package/dist/internal/use-native-select-value.mjs +7 -5
  147. package/dist/internal/use-native-select-value.mjs.map +1 -1
  148. package/dist/internal/use-popover-popup.mjs +11 -24
  149. package/dist/internal/use-popover-popup.mjs.map +1 -1
  150. package/dist/internal/use-popover-root.mjs +58 -23
  151. package/dist/internal/use-popover-root.mjs.map +1 -1
  152. package/dist/internal/use-popover-trigger.mjs +1 -1
  153. package/dist/internal/use-render.mjs +11 -34
  154. package/dist/internal/use-render.mjs.map +1 -1
  155. package/dist/internal/use-roving-focus.mjs +255 -0
  156. package/dist/internal/use-roving-focus.mjs.map +1 -0
  157. package/dist/internal/validity.d.mts +24 -0
  158. package/dist/internal/validity.d.mts.map +1 -0
  159. package/dist/internal/validity.mjs +108 -0
  160. package/dist/internal/validity.mjs.map +1 -0
  161. package/dist/menu/menu-checkbox-item.mjs +2 -1
  162. package/dist/menu/menu-checkbox-item.mjs.map +1 -1
  163. package/dist/menu/menu-group.d.mts.map +1 -1
  164. package/dist/menu/menu-group.mjs +3 -2
  165. package/dist/menu/menu-group.mjs.map +1 -1
  166. package/dist/menu/menu-item.mjs +2 -1
  167. package/dist/menu/menu-item.mjs.map +1 -1
  168. package/dist/menu/menu-popup.d.mts.map +1 -1
  169. package/dist/menu/menu-popup.mjs +7 -2
  170. package/dist/menu/menu-popup.mjs.map +1 -1
  171. package/dist/menu/menu-radio-group.mjs +9 -4
  172. package/dist/menu/menu-radio-group.mjs.map +1 -1
  173. package/dist/menu/menu-radio-item.mjs +2 -1
  174. package/dist/menu/menu-radio-item.mjs.map +1 -1
  175. package/dist/menu/menu-root.mjs +1 -1
  176. package/dist/menu/menu-root.mjs.map +1 -1
  177. package/dist/menu/menu-trigger.d.mts +2 -1
  178. package/dist/menu/menu-trigger.d.mts.map +1 -1
  179. package/dist/menu/menu-trigger.mjs +15 -4
  180. package/dist/menu/menu-trigger.mjs.map +1 -1
  181. package/dist/meter/index.d.mts +24 -0
  182. package/dist/meter/index.d.mts.map +1 -0
  183. package/dist/meter/index.mjs +23 -0
  184. package/dist/meter/index.mjs.map +1 -0
  185. package/dist/meter/meter-context.mjs +13 -0
  186. package/dist/meter/meter-context.mjs.map +1 -0
  187. package/dist/meter/meter-indicator.d.mts +19 -0
  188. package/dist/meter/meter-indicator.d.mts.map +1 -0
  189. package/dist/meter/meter-indicator.mjs +25 -0
  190. package/dist/meter/meter-indicator.mjs.map +1 -0
  191. package/dist/meter/meter-label.d.mts +19 -0
  192. package/dist/meter/meter-label.d.mts.map +1 -0
  193. package/dist/meter/meter-label.mjs +28 -0
  194. package/dist/meter/meter-label.mjs.map +1 -0
  195. package/dist/meter/meter-region.d.mts +4 -0
  196. package/dist/meter/meter-region.d.mts.map +1 -0
  197. package/dist/meter/meter-region.mjs +39 -0
  198. package/dist/meter/meter-region.mjs.map +1 -0
  199. package/dist/meter/meter-root.d.mts +71 -0
  200. package/dist/meter/meter-root.d.mts.map +1 -0
  201. package/dist/meter/meter-root.mjs +83 -0
  202. package/dist/meter/meter-root.mjs.map +1 -0
  203. package/dist/meter/meter-track.d.mts +19 -0
  204. package/dist/meter/meter-track.d.mts.map +1 -0
  205. package/dist/meter/meter-track.mjs +25 -0
  206. package/dist/meter/meter-track.mjs.map +1 -0
  207. package/dist/meter/meter-value.d.mts +20 -0
  208. package/dist/meter/meter-value.d.mts.map +1 -0
  209. package/dist/meter/meter-value.mjs +29 -0
  210. package/dist/meter/meter-value.mjs.map +1 -0
  211. package/dist/number-field/index.d.mts +26 -0
  212. package/dist/number-field/index.d.mts.map +1 -0
  213. package/dist/number-field/index.mjs +26 -0
  214. package/dist/number-field/index.mjs.map +1 -0
  215. package/dist/number-field/number-field-arithmetic.mjs +78 -0
  216. package/dist/number-field/number-field-arithmetic.mjs.map +1 -0
  217. package/dist/number-field/number-field-context.d.mts +2 -0
  218. package/dist/number-field/number-field-context.mjs +13 -0
  219. package/dist/number-field/number-field-context.mjs.map +1 -0
  220. package/dist/number-field/number-field-decrement.d.mts +16 -0
  221. package/dist/number-field/number-field-decrement.d.mts.map +1 -0
  222. package/dist/number-field/number-field-decrement.mjs +19 -0
  223. package/dist/number-field/number-field-decrement.mjs.map +1 -0
  224. package/dist/number-field/number-field-format.mjs +279 -0
  225. package/dist/number-field/number-field-format.mjs.map +1 -0
  226. package/dist/number-field/number-field-increment.d.mts +32 -0
  227. package/dist/number-field/number-field-increment.d.mts.map +1 -0
  228. package/dist/number-field/number-field-increment.mjs +68 -0
  229. package/dist/number-field/number-field-increment.mjs.map +1 -0
  230. package/dist/number-field/number-field-input.d.mts +29 -0
  231. package/dist/number-field/number-field-input.d.mts.map +1 -0
  232. package/dist/number-field/number-field-input.mjs +163 -0
  233. package/dist/number-field/number-field-input.mjs.map +1 -0
  234. package/dist/number-field/number-field-label.d.mts +23 -0
  235. package/dist/number-field/number-field-label.d.mts.map +1 -0
  236. package/dist/number-field/number-field-label.mjs +47 -0
  237. package/dist/number-field/number-field-label.mjs.map +1 -0
  238. package/dist/number-field/number-field-root.d.mts +108 -0
  239. package/dist/number-field/number-field-root.d.mts.map +1 -0
  240. package/dist/number-field/number-field-root.mjs +523 -0
  241. package/dist/number-field/number-field-root.mjs.map +1 -0
  242. package/dist/number-field/use-step-button.mjs +107 -0
  243. package/dist/number-field/use-step-button.mjs.map +1 -0
  244. package/dist/popover/popover-close.mjs +2 -1
  245. package/dist/popover/popover-close.mjs.map +1 -1
  246. package/dist/popover/popover-popup.d.mts.map +1 -1
  247. package/dist/popover/popover-popup.mjs +7 -2
  248. package/dist/popover/popover-popup.mjs.map +1 -1
  249. package/dist/popover/popover-root.mjs +1 -1
  250. package/dist/progress/index.d.mts +24 -0
  251. package/dist/progress/index.d.mts.map +1 -0
  252. package/dist/progress/index.mjs +24 -0
  253. package/dist/progress/index.mjs.map +1 -0
  254. package/dist/progress/progress-context.mjs +13 -0
  255. package/dist/progress/progress-context.mjs.map +1 -0
  256. package/dist/progress/progress-indicator.d.mts +19 -0
  257. package/dist/progress/progress-indicator.d.mts.map +1 -0
  258. package/dist/progress/progress-indicator.mjs +29 -0
  259. package/dist/progress/progress-indicator.mjs.map +1 -0
  260. package/dist/progress/progress-label.d.mts +19 -0
  261. package/dist/progress/progress-label.d.mts.map +1 -0
  262. package/dist/progress/progress-label.mjs +30 -0
  263. package/dist/progress/progress-label.mjs.map +1 -0
  264. package/dist/progress/progress-root.d.mts +68 -0
  265. package/dist/progress/progress-root.d.mts.map +1 -0
  266. package/dist/progress/progress-root.mjs +80 -0
  267. package/dist/progress/progress-root.mjs.map +1 -0
  268. package/dist/progress/progress-track.d.mts +19 -0
  269. package/dist/progress/progress-track.d.mts.map +1 -0
  270. package/dist/progress/progress-track.mjs +29 -0
  271. package/dist/progress/progress-track.mjs.map +1 -0
  272. package/dist/progress/progress-value.d.mts +20 -0
  273. package/dist/progress/progress-value.d.mts.map +1 -0
  274. package/dist/progress/progress-value.mjs +33 -0
  275. package/dist/progress/progress-value.mjs.map +1 -0
  276. package/dist/radio-group/radio-group-root.d.mts +4 -2
  277. package/dist/radio-group/radio-group-root.d.mts.map +1 -1
  278. package/dist/radio-group/radio-group-root.mjs +34 -8
  279. package/dist/radio-group/radio-group-root.mjs.map +1 -1
  280. package/dist/select/select-group.d.mts.map +1 -1
  281. package/dist/select/select-group.mjs +6 -4
  282. package/dist/select/select-group.mjs.map +1 -1
  283. package/dist/select/select-root.d.mts +4 -2
  284. package/dist/select/select-root.d.mts.map +1 -1
  285. package/dist/select/select-root.mjs +26 -6
  286. package/dist/select/select-root.mjs.map +1 -1
  287. package/dist/slider/index.d.mts +32 -0
  288. package/dist/slider/index.d.mts.map +1 -0
  289. package/dist/slider/index.mjs +27 -0
  290. package/dist/slider/index.mjs.map +1 -0
  291. package/dist/slider/slider-arithmetic.mjs +104 -0
  292. package/dist/slider/slider-arithmetic.mjs.map +1 -0
  293. package/dist/slider/slider-context.mjs +13 -0
  294. package/dist/slider/slider-context.mjs.map +1 -0
  295. package/dist/slider/slider-indicator.d.mts +22 -0
  296. package/dist/slider/slider-indicator.d.mts.map +1 -0
  297. package/dist/slider/slider-indicator.mjs +36 -0
  298. package/dist/slider/slider-indicator.mjs.map +1 -0
  299. package/dist/slider/slider-label.d.mts +23 -0
  300. package/dist/slider/slider-label.d.mts.map +1 -0
  301. package/dist/slider/slider-label.mjs +44 -0
  302. package/dist/slider/slider-label.mjs.map +1 -0
  303. package/dist/slider/slider-root.d.mts +111 -0
  304. package/dist/slider/slider-root.d.mts.map +1 -0
  305. package/dist/slider/slider-root.mjs +583 -0
  306. package/dist/slider/slider-root.mjs.map +1 -0
  307. package/dist/slider/slider-thumb.d.mts +58 -0
  308. package/dist/slider/slider-thumb.d.mts.map +1 -0
  309. package/dist/slider/slider-thumb.mjs +146 -0
  310. package/dist/slider/slider-thumb.mjs.map +1 -0
  311. package/dist/slider/slider-track.d.mts +29 -0
  312. package/dist/slider/slider-track.d.mts.map +1 -0
  313. package/dist/slider/slider-track.mjs +55 -0
  314. package/dist/slider/slider-track.mjs.map +1 -0
  315. package/dist/slider/slider-value.d.mts +34 -0
  316. package/dist/slider/slider-value.d.mts.map +1 -0
  317. package/dist/slider/slider-value.mjs +38 -0
  318. package/dist/slider/slider-value.mjs.map +1 -0
  319. package/dist/switch/switch.d.mts +5 -3
  320. package/dist/switch/switch.d.mts.map +1 -1
  321. package/dist/switch/switch.mjs +24 -8
  322. package/dist/switch/switch.mjs.map +1 -1
  323. package/dist/tabs/tabs-indicator.mjs +2 -1
  324. package/dist/tabs/tabs-indicator.mjs.map +1 -1
  325. package/dist/tabs/tabs-root.mjs +13 -3
  326. package/dist/tabs/tabs-root.mjs.map +1 -1
  327. package/dist/tabs/tabs-tab.mjs +2 -1
  328. package/dist/tabs/tabs-tab.mjs.map +1 -1
  329. package/dist/toast/index.d.mts +25 -0
  330. package/dist/toast/index.d.mts.map +1 -0
  331. package/dist/toast/index.mjs +26 -0
  332. package/dist/toast/index.mjs.map +1 -0
  333. package/dist/toast/toast-action.d.mts +39 -0
  334. package/dist/toast/toast-action.d.mts.map +1 -0
  335. package/dist/toast/toast-action.mjs +65 -0
  336. package/dist/toast/toast-action.mjs.map +1 -0
  337. package/dist/toast/toast-close.d.mts +25 -0
  338. package/dist/toast/toast-close.d.mts.map +1 -0
  339. package/dist/toast/toast-close.mjs +47 -0
  340. package/dist/toast/toast-close.mjs.map +1 -0
  341. package/dist/toast/toast-context.mjs +19 -0
  342. package/dist/toast/toast-context.mjs.map +1 -0
  343. package/dist/toast/toast-description.d.mts +24 -0
  344. package/dist/toast/toast-description.d.mts.map +1 -0
  345. package/dist/toast/toast-description.mjs +35 -0
  346. package/dist/toast/toast-description.mjs.map +1 -0
  347. package/dist/toast/toast-root.d.mts +43 -0
  348. package/dist/toast/toast-root.d.mts.map +1 -0
  349. package/dist/toast/toast-root.mjs +205 -0
  350. package/dist/toast/toast-root.mjs.map +1 -0
  351. package/dist/toast/toast-title.d.mts +24 -0
  352. package/dist/toast/toast-title.d.mts.map +1 -0
  353. package/dist/toast/toast-title.mjs +33 -0
  354. package/dist/toast/toast-title.mjs.map +1 -0
  355. package/dist/toast/toast-viewport.d.mts +52 -0
  356. package/dist/toast/toast-viewport.d.mts.map +1 -0
  357. package/dist/toast/toast-viewport.mjs +644 -0
  358. package/dist/toast/toast-viewport.mjs.map +1 -0
  359. package/dist/toast/toast.d.mts +119 -0
  360. package/dist/toast/toast.d.mts.map +1 -0
  361. package/dist/toast/toast.mjs +157 -0
  362. package/dist/toast/toast.mjs.map +1 -0
  363. package/dist/toggle/toggle.mjs +2 -1
  364. package/dist/toggle/toggle.mjs.map +1 -1
  365. package/dist/toggle-group/index.d.mts +16 -0
  366. package/dist/toggle-group/index.d.mts.map +1 -0
  367. package/dist/toggle-group/index.mjs +15 -0
  368. package/dist/toggle-group/index.mjs.map +1 -0
  369. package/dist/toggle-group/toggle-group-context.d.mts +4 -0
  370. package/dist/toggle-group/toggle-group-context.d.mts.map +1 -0
  371. package/dist/toggle-group/toggle-group-context.mjs +13 -0
  372. package/dist/toggle-group/toggle-group-context.mjs.map +1 -0
  373. package/dist/toggle-group/toggle-group-item.d.mts +47 -0
  374. package/dist/toggle-group/toggle-group-item.d.mts.map +1 -0
  375. package/dist/toggle-group/toggle-group-item.mjs +63 -0
  376. package/dist/toggle-group/toggle-group-item.mjs.map +1 -0
  377. package/dist/toggle-group/toggle-group-root.d.mts +95 -0
  378. package/dist/toggle-group/toggle-group-root.d.mts.map +1 -0
  379. package/dist/toggle-group/toggle-group-root.mjs +211 -0
  380. package/dist/toggle-group/toggle-group-root.mjs.map +1 -0
  381. package/dist/toolbar/index.d.mts +18 -0
  382. package/dist/toolbar/index.d.mts.map +1 -0
  383. package/dist/toolbar/index.mjs +18 -0
  384. package/dist/toolbar/index.mjs.map +1 -0
  385. package/dist/toolbar/toolbar-button.d.mts +42 -0
  386. package/dist/toolbar/toolbar-button.d.mts.map +1 -0
  387. package/dist/toolbar/toolbar-button.mjs +50 -0
  388. package/dist/toolbar/toolbar-button.mjs.map +1 -0
  389. package/dist/toolbar/toolbar-root.d.mts +38 -0
  390. package/dist/toolbar/toolbar-root.d.mts.map +1 -0
  391. package/dist/toolbar/toolbar-root.mjs +66 -0
  392. package/dist/toolbar/toolbar-root.mjs.map +1 -0
  393. package/dist/toolbar/toolbar-separator.d.mts +25 -0
  394. package/dist/toolbar/toolbar-separator.d.mts.map +1 -0
  395. package/dist/toolbar/toolbar-separator.mjs +31 -0
  396. package/dist/toolbar/toolbar-separator.mjs.map +1 -0
  397. package/dist/tooltip/tooltip-popup.mjs +2 -1
  398. package/dist/tooltip/tooltip-popup.mjs.map +1 -1
  399. package/dist/tooltip/tooltip-root.mjs +16 -2
  400. package/dist/tooltip/tooltip-root.mjs.map +1 -1
  401. package/dist/tooltip/tooltip-trigger.mjs +2 -1
  402. package/dist/tooltip/tooltip-trigger.mjs.map +1 -1
  403. package/package.json +10 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-status-watcher.mjs","names":[],"sources":["../../src/internal/form-status-watcher.tsx"],"sourcesContent":["\"use client\";\nimport { useEffect, useRef } from \"react\";\nimport { useFormStatus } from \"react-dom\";\n\nexport interface FormStatusWatcherProps {\n readonly onPendingChange: (pending: boolean) => void;\n readonly onSettled: () => void;\n}\n\n/**\n * Observes React's form channel (Dialog design 8.1, Field/Form design 3.2): rendered inside a\n * `<form>`, `useFormStatus().pending` covers the action's whole transition, including the\n * Suspense wait of what it re-renders. On `true → false` the submission settled without throwing\n * (a rejection unmounts the form through the nearest Error Boundary, so the edge is never seen).\n * Used by `Dialog.Form` and `Form.Root`.\n */\nexport function FormStatusWatcher({ onPendingChange, onSettled }: FormStatusWatcherProps) {\n const { pending } = useFormStatus();\n const previous = useRef(false);\n useEffect(() => {\n const was = previous.current;\n previous.current = pending;\n if (was === pending) return;\n onPendingChange(pending);\n if (was && !pending) onSettled();\n }, [pending, onPendingChange, onSettled]);\n return null;\n}\n"],"mappings":";;;;;;;;;;;AAgBA,SAAgB,kBAAkB,EAAE,iBAAiB,aAAqC;CACxF,MAAM,EAAE,YAAY,cAAc;CAClC,MAAM,WAAW,OAAO,KAAK;CAC7B,gBAAgB;EACd,MAAM,MAAM,SAAS;EACrB,SAAS,UAAU;EACnB,IAAI,QAAQ,SAAS;EACrB,gBAAgB,OAAO;EACvB,IAAI,OAAO,CAAC,SAAS,UAAU;CACjC,GAAG;EAAC;EAAS;EAAiB;CAAS,CAAC;CACxC,OAAO;AACT"}
@@ -0,0 +1,39 @@
1
+ //#region src/internal/modal-registry.ts
2
+ const stack = [];
3
+ const listeners = /* @__PURE__ */ new Set();
4
+ function notify() {
5
+ for (const listener of [...listeners]) listener();
6
+ }
7
+ /** Records `dialog` as the topmost open modal dialog (moves it to the top if already present). */
8
+ function pushModal(dialog) {
9
+ const index = stack.indexOf(dialog);
10
+ if (index !== -1 && index === stack.length - 1) return;
11
+ if (index !== -1) stack.splice(index, 1);
12
+ stack.push(dialog);
13
+ notify();
14
+ }
15
+ /** Forgets `dialog` (closed or unmounted). */
16
+ function removeModal(dialog) {
17
+ const index = stack.indexOf(dialog);
18
+ if (index === -1) return;
19
+ stack.splice(index, 1);
20
+ notify();
21
+ }
22
+ function subscribeModals(listener) {
23
+ listeners.add(listener);
24
+ return () => {
25
+ listeners.delete(listener);
26
+ };
27
+ }
28
+ /** The topmost open modal dialog the library opened, or `null`. */
29
+ function getTopmostModal() {
30
+ return stack.at(-1) ?? null;
31
+ }
32
+ /** The server snapshot: nothing is open on the server. */
33
+ function getServerTopmostModal() {
34
+ return null;
35
+ }
36
+ //#endregion
37
+ export { getServerTopmostModal, getTopmostModal, pushModal, removeModal, subscribeModals };
38
+
39
+ //# sourceMappingURL=modal-registry.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modal-registry.mjs","names":[],"sources":["../../src/internal/modal-registry.ts"],"sourcesContent":["// No \"use client\" directive: a plain module-level store, no React and no DOM calls\n// (design/components/toast.md 4.3, conventions 3.5 \"module-level registry\").\n//\n// The stack of open modal `<dialog>` elements the library's Dialog opened. A top-layer element\n// outside the topmost modal dialog is inert and absent from the accessibility tree, so Toast's\n// Viewport portals its region into the topmost one. Writers (`use-modal-registration.ts`) run\n// in layout effects and platform event listeners, never during render; readers subscribe through\n// `useSyncExternalStore` with a server snapshot of `null`. Never the source of truth for a\n// component's own state.\n\nconst stack: Element[] = [];\nconst listeners = new Set<() => void>();\n\nfunction notify(): void {\n for (const listener of [...listeners]) listener();\n}\n\n/** Records `dialog` as the topmost open modal dialog (moves it to the top if already present). */\nexport function pushModal(dialog: Element): void {\n const index = stack.indexOf(dialog);\n if (index !== -1 && index === stack.length - 1) return;\n if (index !== -1) stack.splice(index, 1);\n stack.push(dialog);\n notify();\n}\n\n/** Forgets `dialog` (closed or unmounted). */\nexport function removeModal(dialog: Element): void {\n const index = stack.indexOf(dialog);\n if (index === -1) return;\n stack.splice(index, 1);\n notify();\n}\n\nexport function subscribeModals(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n}\n\n/** The topmost open modal dialog the library opened, or `null`. */\nexport function getTopmostModal(): Element | null {\n return stack.at(-1) ?? null;\n}\n\n/** The server snapshot: nothing is open on the server. */\nexport function getServerTopmostModal(): Element | null {\n return null;\n}\n"],"mappings":";AAUA,MAAM,QAAmB,CAAC;AAC1B,MAAM,4BAAY,IAAI,IAAgB;AAEtC,SAAS,SAAe;CACtB,KAAK,MAAM,YAAY,CAAC,GAAG,SAAS,GAAG,SAAS;AAClD;;AAGA,SAAgB,UAAU,QAAuB;CAC/C,MAAM,QAAQ,MAAM,QAAQ,MAAM;CAClC,IAAI,UAAU,MAAM,UAAU,MAAM,SAAS,GAAG;CAChD,IAAI,UAAU,IAAI,MAAM,OAAO,OAAO,CAAC;CACvC,MAAM,KAAK,MAAM;CACjB,OAAO;AACT;;AAGA,SAAgB,YAAY,QAAuB;CACjD,MAAM,QAAQ,MAAM,QAAQ,MAAM;CAClC,IAAI,UAAU,IAAI;CAClB,MAAM,OAAO,OAAO,CAAC;CACrB,OAAO;AACT;AAEA,SAAgB,gBAAgB,UAAkC;CAChE,UAAU,IAAI,QAAQ;CACtB,aAAa;EACX,UAAU,OAAO,QAAQ;CAC3B;AACF;;AAGA,SAAgB,kBAAkC;CAChD,OAAO,MAAM,GAAG,EAAE,KAAK;AACzB;;AAGA,SAAgB,wBAAwC;CACtD,OAAO;AACT"}
@@ -0,0 +1,51 @@
1
+ //#region src/internal/range-value.ts
2
+ /** The platform's clamp (`<progress>` 150 of 100 is 100). A degenerate range yields `min`. */
3
+ function clamp(value, min, max) {
4
+ return Math.max(min, Math.min(max, value));
5
+ }
6
+ /** Whether `[min, max]` is empty or a point (`max <= min`): a bug at the call site. */
7
+ function isDegenerate(min, max) {
8
+ return !(max > min);
9
+ }
10
+ /** `(value - min) / (max - min)`, `0` for a degenerate range. */
11
+ function fraction(value, min, max) {
12
+ return isDegenerate(min, max) ? 0 : (value - min) / (max - min);
13
+ }
14
+ /** The fraction as a number 0–100. */
15
+ function percentage(value, min, max) {
16
+ return fraction(value, min, max) * 100;
17
+ }
18
+ const PERCENT = { style: "percent" };
19
+ const CACHE_LIMIT = 32;
20
+ const formatters = /* @__PURE__ */ new Map();
21
+ /**
22
+ * One `Intl.NumberFormat` per `locale` + `format`, so an update loop does not construct one per
23
+ * render. Bounded: past 32 entries the oldest is dropped.
24
+ */
25
+ function getNumberFormat(locale, format) {
26
+ const options = format ?? PERCENT;
27
+ const key = `${locale ?? ""}\u0000${JSON.stringify(options)}`;
28
+ let formatter = formatters.get(key);
29
+ if (formatter === void 0) {
30
+ formatter = new Intl.NumberFormat(locale, options);
31
+ if (formatters.size >= CACHE_LIMIT) {
32
+ const oldest = formatters.keys().next().value;
33
+ if (oldest !== void 0) formatters.delete(oldest);
34
+ }
35
+ formatters.set(key, formatter);
36
+ }
37
+ return formatter;
38
+ }
39
+ /**
40
+ * The visible text of a (clamped) value: the fraction of the range for the `percent` style (the
41
+ * default when `format` is absent), the value itself for any other style. A `format` without
42
+ * `style` is `decimal` per `Intl` and formats the value.
43
+ */
44
+ function formatRangeValue(value, min, max, format, locale) {
45
+ const percentStyle = format === void 0 || format.style === "percent";
46
+ return getNumberFormat(locale, format).format(percentStyle ? fraction(value, min, max) : value);
47
+ }
48
+ //#endregion
49
+ export { clamp, formatRangeValue, fraction, getNumberFormat, isDegenerate, percentage };
50
+
51
+ //# sourceMappingURL=range-value.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"range-value.mjs","names":[],"sources":["../../src/internal/range-value.ts"],"sourcesContent":["// No \"use client\": pure arithmetic and formatting shared by the directive-free `Progress.Root`\n// and `Meter.Root` (design/components/progress.md 2.1, 2.2 and implementation note 14.4).\n\n/** The platform's clamp (`<progress>` 150 of 100 is 100). A degenerate range yields `min`. */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(max, value));\n}\n\n/** Whether `[min, max]` is empty or a point (`max <= min`): a bug at the call site. */\nexport function isDegenerate(min: number, max: number): boolean {\n return !(max > min);\n}\n\n/** `(value - min) / (max - min)`, `0` for a degenerate range. */\nexport function fraction(value: number, min: number, max: number): number {\n return isDegenerate(min, max) ? 0 : (value - min) / (max - min);\n}\n\n/** The fraction as a number 0–100. */\nexport function percentage(value: number, min: number, max: number): number {\n return fraction(value, min, max) * 100;\n}\n\nconst PERCENT: Intl.NumberFormatOptions = { style: \"percent\" };\nconst CACHE_LIMIT = 32;\nconst formatters = new Map<string, Intl.NumberFormat>();\n\n/**\n * One `Intl.NumberFormat` per `locale` + `format`, so an update loop does not construct one per\n * render. Bounded: past 32 entries the oldest is dropped.\n */\nexport function getNumberFormat(\n locale: string | undefined,\n format: Intl.NumberFormatOptions | undefined,\n): Intl.NumberFormat {\n const options = format ?? PERCENT;\n const key = `${locale ?? \"\"}\\u0000${JSON.stringify(options)}`;\n let formatter = formatters.get(key);\n if (formatter === undefined) {\n formatter = new Intl.NumberFormat(locale, options);\n if (formatters.size >= CACHE_LIMIT) {\n const oldest = formatters.keys().next().value;\n if (oldest !== undefined) formatters.delete(oldest);\n }\n formatters.set(key, formatter);\n }\n return formatter;\n}\n\n/**\n * The visible text of a (clamped) value: the fraction of the range for the `percent` style (the\n * default when `format` is absent), the value itself for any other style. A `format` without\n * `style` is `decimal` per `Intl` and formats the value.\n */\nexport function formatRangeValue(\n value: number,\n min: number,\n max: number,\n format: Intl.NumberFormatOptions | undefined,\n locale: string | undefined,\n): string {\n const percentStyle = format === undefined || format.style === \"percent\";\n return getNumberFormat(locale, format).format(percentStyle ? fraction(value, min, max) : value);\n}\n"],"mappings":";;AAIA,SAAgB,MAAM,OAAe,KAAa,KAAqB;CACrE,OAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;AAC3C;;AAGA,SAAgB,aAAa,KAAa,KAAsB;CAC9D,OAAO,EAAE,MAAM;AACjB;;AAGA,SAAgB,SAAS,OAAe,KAAa,KAAqB;CACxE,OAAO,aAAa,KAAK,GAAG,IAAI,KAAK,QAAQ,QAAQ,MAAM;AAC7D;;AAGA,SAAgB,WAAW,OAAe,KAAa,KAAqB;CAC1E,OAAO,SAAS,OAAO,KAAK,GAAG,IAAI;AACrC;AAEA,MAAM,UAAoC,EAAE,OAAO,UAAU;AAC7D,MAAM,cAAc;AACpB,MAAM,6BAAa,IAAI,IAA+B;;;;;AAMtD,SAAgB,gBACd,QACA,QACmB;CACnB,MAAM,UAAU,UAAU;CAC1B,MAAM,MAAM,GAAG,UAAU,GAAG,QAAQ,KAAK,UAAU,OAAO;CAC1D,IAAI,YAAY,WAAW,IAAI,GAAG;CAClC,IAAI,cAAc,KAAA,GAAW;EAC3B,YAAY,IAAI,KAAK,aAAa,QAAQ,OAAO;EACjD,IAAI,WAAW,QAAQ,aAAa;GAClC,MAAM,SAAS,WAAW,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;GACxC,IAAI,WAAW,KAAA,GAAW,WAAW,OAAO,MAAM;EACpD;EACA,WAAW,IAAI,KAAK,SAAS;CAC/B;CACA,OAAO;AACT;;;;;;AAOA,SAAgB,iBACd,OACA,KACA,KACA,QACA,QACQ;CACR,MAAM,eAAe,WAAW,KAAA,KAAa,OAAO,UAAU;CAC9D,OAAO,gBAAgB,QAAQ,MAAM,CAAC,CAAC,OAAO,eAAe,SAAS,OAAO,KAAK,GAAG,IAAI,KAAK;AAChG"}
@@ -0,0 +1,54 @@
1
+ import { mergeProps, mergeRefs } from "./merge-props.mjs";
2
+ import { cloneElement, createElement, isValidElement } from "react";
3
+ //#region src/internal/render-element.ts
4
+ function resolveStateValue(value, state) {
5
+ return typeof value === "function" ? value(state) : value;
6
+ }
7
+ /**
8
+ * Whether a part whose default element is a `<button>` should add `type="button"`: no `render`,
9
+ * a `render` element whose type is the intrinsic `"button"`, or the function form (it receives
10
+ * button props and decides itself what to spread). An element of any other type (an `<a>`, a
11
+ * router `Link`) does not get it: `type` means something else, or nothing, there.
12
+ */
13
+ function rendersButton(render) {
14
+ if (render === void 0 || typeof render === "function") return true;
15
+ return isValidElement(render) && render.type === "button";
16
+ }
17
+ /**
18
+ * The ref of a part with no library ref, merged from the consumer's `ref` and a `render`
19
+ * element's ref without memoisation (a directive-free part cannot `useMemo`, design
20
+ * progress.md 14.1). A fresh callback ref per render is correct because every merged callback
21
+ * returns a cleanup; a single ref is passed through untouched.
22
+ */
23
+ function mergeRenderRef(consumerRef, render) {
24
+ const elementRef = isValidElement(render) ? render.props.ref : void 0;
25
+ if (consumerRef != null && elementRef != null) return mergeRefs(consumerRef, elementRef);
26
+ return consumerRef ?? elementRef;
27
+ }
28
+ /**
29
+ * Resolves `render` and the function-form `className` / `style` / `children` of a part against
30
+ * its state and produces the element (conventions 1.2, 1.3, 1.4). Pure: the caller merges the
31
+ * refs (`useRender` memoises the merge; a directive-free part merges unmemoised).
32
+ */
33
+ function renderElement(options) {
34
+ const { defaultElement, render, state, className, style, children, props, libraryProps, ref } = options;
35
+ const partProps = {
36
+ ...props,
37
+ ref,
38
+ className: resolveStateValue(className, state),
39
+ style: resolveStateValue(style, state),
40
+ children: resolveStateValue(children, state)
41
+ };
42
+ const controlled = libraryProps ?? {};
43
+ if (isValidElement(render)) {
44
+ const { ref: _ignored, ...elementProps } = render.props;
45
+ return cloneElement(render, mergeProps(partProps, elementProps, controlled));
46
+ }
47
+ const resolved = mergeProps(partProps, {}, controlled);
48
+ if (typeof render === "function") return render(resolved, state);
49
+ return createElement(defaultElement, resolved);
50
+ }
51
+ //#endregion
52
+ export { mergeRenderRef, renderElement, rendersButton, resolveStateValue };
53
+
54
+ //# sourceMappingURL=render-element.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-element.mjs","names":[],"sources":["../../src/internal/render-element.ts"],"sourcesContent":["// No \"use client\": the pure half of `useRender`, usable from a directive-free part (a Root that\n// may run as a Server Component, design/components/progress.md 8.1 and implementation note 14.1).\nimport { cloneElement, createElement, isValidElement } from \"react\";\nimport type { CSSProperties, ReactNode, Ref } from \"react\";\nimport { mergeProps, mergeRefs } from \"./merge-props.js\";\nimport type { RenderProp, StateValue } from \"./types.js\";\n\nexport interface ElementLikeProps {\n className?: string | undefined;\n style?: CSSProperties | undefined;\n children?: ReactNode;\n ref?: Ref<unknown> | undefined;\n}\n\n/** `data-*` attributes are valid JSX but absent from the element prop types. */\nexport type DataAttributes = { [key: `data-${string}`]: string | undefined };\n\nexport interface RenderElementOptions<Props extends ElementLikeProps, State> {\n /** Intrinsic element rendered when `render` is not given. */\n defaultElement: string;\n render: RenderProp<Props, State> | undefined;\n state: State;\n className: StateValue<string | undefined, State> | undefined;\n style: StateValue<CSSProperties | undefined, State> | undefined;\n children: StateValue<ReactNode, State> | undefined;\n /**\n * The part's pass-through props for the element. A `render` element's own props win over\n * these. Their `ref` is replaced by `ref` below.\n */\n props: Omit<Props, \"className\" | \"style\" | \"children\"> & DataAttributes;\n /**\n * Library-controlled attributes the part computes (`role`, `aria-*` state, id wiring, `data-*`\n * state, the library's own event handlers). These win over a `render` element's props\n * (conventions 1.2).\n */\n libraryProps?: Partial<Omit<Props, \"className\" | \"style\" | \"children\">> & DataAttributes;\n /**\n * The element's ref, already merged from the library's ref, the consumer's `ref` and the\n * `render` element's ref. The `render` element's own ref is not attached separately.\n */\n ref: Ref<unknown> | undefined;\n}\n\nexport function resolveStateValue<T, State>(value: StateValue<T, State>, state: State): T {\n return typeof value === \"function\" ? (value as (state: State) => T)(state) : value;\n}\n\n/**\n * Whether a part whose default element is a `<button>` should add `type=\"button\"`: no `render`,\n * a `render` element whose type is the intrinsic `\"button\"`, or the function form (it receives\n * button props and decides itself what to spread). An element of any other type (an `<a>`, a\n * router `Link`) does not get it: `type` means something else, or nothing, there.\n */\nexport function rendersButton<Props, State>(render: RenderProp<Props, State> | undefined): boolean {\n if (render === undefined || typeof render === \"function\") return true;\n return isValidElement(render) && render.type === \"button\";\n}\n\n/**\n * The ref of a part with no library ref, merged from the consumer's `ref` and a `render`\n * element's ref without memoisation (a directive-free part cannot `useMemo`, design\n * progress.md 14.1). A fresh callback ref per render is correct because every merged callback\n * returns a cleanup; a single ref is passed through untouched.\n */\nexport function mergeRenderRef(\n consumerRef: Ref<unknown> | undefined,\n render: unknown,\n): Ref<unknown> | undefined {\n const elementRef = isValidElement<ElementLikeProps>(render) ? render.props.ref : undefined;\n if (consumerRef != null && elementRef != null) return mergeRefs(consumerRef, elementRef);\n return consumerRef ?? elementRef;\n}\n\n/**\n * Resolves `render` and the function-form `className` / `style` / `children` of a part against\n * its state and produces the element (conventions 1.2, 1.3, 1.4). Pure: the caller merges the\n * refs (`useRender` memoises the merge; a directive-free part merges unmemoised).\n */\nexport function renderElement<Props extends ElementLikeProps, State>(\n options: RenderElementOptions<Props, State>,\n): ReactNode {\n const { defaultElement, render, state, className, style, children, props, libraryProps, ref } =\n options;\n\n const partProps = {\n ...props,\n ref,\n className: resolveStateValue(className, state),\n style: resolveStateValue(style, state),\n children: resolveStateValue(children, state),\n } as Record<string, unknown>;\n const controlled = (libraryProps ?? {}) as Record<string, unknown>;\n\n if (isValidElement<ElementLikeProps>(render)) {\n // The element's ref is already part of `ref`.\n const { ref: _ignored, ...elementProps } = render.props as Record<string, unknown>;\n return cloneElement(render, mergeProps(partProps, elementProps, controlled) as Partial<Props>);\n }\n const resolved = mergeProps(partProps, {}, controlled) as unknown as Props;\n if (typeof render === \"function\") {\n return render(resolved, state);\n }\n return createElement(defaultElement, resolved);\n}\n"],"mappings":";;;AA2CA,SAAgB,kBAA4B,OAA6B,OAAiB;CACxF,OAAO,OAAO,UAAU,aAAc,MAA8B,KAAK,IAAI;AAC/E;;;;;;;AAQA,SAAgB,cAA4B,QAAuD;CACjG,IAAI,WAAW,KAAA,KAAa,OAAO,WAAW,YAAY,OAAO;CACjE,OAAO,eAAe,MAAM,KAAK,OAAO,SAAS;AACnD;;;;;;;AAQA,SAAgB,eACd,aACA,QAC0B;CAC1B,MAAM,aAAa,eAAiC,MAAM,IAAI,OAAO,MAAM,MAAM,KAAA;CACjF,IAAI,eAAe,QAAQ,cAAc,MAAM,OAAO,UAAU,aAAa,UAAU;CACvF,OAAO,eAAe;AACxB;;;;;;AAOA,SAAgB,cACd,SACW;CACX,MAAM,EAAE,gBAAgB,QAAQ,OAAO,WAAW,OAAO,UAAU,OAAO,cAAc,QACtF;CAEF,MAAM,YAAY;EAChB,GAAG;EACH;EACA,WAAW,kBAAkB,WAAW,KAAK;EAC7C,OAAO,kBAAkB,OAAO,KAAK;EACrC,UAAU,kBAAkB,UAAU,KAAK;CAC7C;CACA,MAAM,aAAc,gBAAgB,CAAC;CAErC,IAAI,eAAiC,MAAM,GAAG;EAE5C,MAAM,EAAE,KAAK,UAAU,GAAG,iBAAiB,OAAO;EAClD,OAAO,aAAa,QAAQ,WAAW,WAAW,cAAc,UAAU,CAAmB;CAC/F;CACA,MAAM,WAAW,WAAW,WAAW,CAAC,GAAG,UAAU;CACrD,IAAI,OAAO,WAAW,YACpB,OAAO,OAAO,UAAU,KAAK;CAE/B,OAAO,cAAc,gBAAgB,QAAQ;AAC/C"}
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { createContext, use } from "react";
3
+ //#region src/internal/toolbar-context.ts
4
+ /**
5
+ * Provided by `Toolbar.Root`; read across folders by `ToggleGroup.Root` (joins the Toolbar's
6
+ * sequence instead of running its own engine), `Toolbar.Separator` (perpendicular orientation)
7
+ * and `Menu.Trigger` (opening arrows in a vertical Toolbar). Conventions 7.1.
8
+ */
9
+ const ToolbarContext = createContext(null);
10
+ /** The enclosing Toolbar, or `null` outside one. Works during SSR (context, not DOM). */
11
+ function useToolbarContext() {
12
+ return use(ToolbarContext);
13
+ }
14
+ //#endregion
15
+ export { ToolbarContext, useToolbarContext };
16
+
17
+ //# sourceMappingURL=toolbar-context.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolbar-context.mjs","names":[],"sources":["../../src/internal/toolbar-context.ts"],"sourcesContent":["\"use client\";\nimport { createContext, use } from \"react\";\n\n/** What a part inside a `Toolbar.Root` learns about it (design/components/toggle-group.md 7.3, 7.4). */\nexport interface ToolbarContextValue {\n /** The Toolbar's arrow-key axis. */\n readonly orientation: \"horizontal\" | \"vertical\";\n}\n\n/**\n * Provided by `Toolbar.Root`; read across folders by `ToggleGroup.Root` (joins the Toolbar's\n * sequence instead of running its own engine), `Toolbar.Separator` (perpendicular orientation)\n * and `Menu.Trigger` (opening arrows in a vertical Toolbar). Conventions 7.1.\n */\nexport const ToolbarContext = createContext<ToolbarContextValue | null>(null);\n\n/** The enclosing Toolbar, or `null` outside one. Works during SSR (context, not DOM). */\nexport function useToolbarContext(): ToolbarContextValue | null {\n return use(ToolbarContext);\n}\n"],"mappings":";;;;;;;;AAcA,MAAa,iBAAiB,cAA0C,IAAI;;AAG5E,SAAgB,oBAAgD;CAC9D,OAAO,IAAI,cAAc;AAC3B"}
@@ -32,8 +32,10 @@ export type UrgentChangeAction<T, Details extends ActionDetails = ActionDetails>
32
32
  * `details.event` is the platform's `close` event. Dialog is the first component to use it.
33
33
  * - `"platform"`: the browser changed the state itself: find-in-page, a form reset (`event` is
34
34
  * the `reset` event), or interaction before hydration found at mount (`event` is `undefined`).
35
+ * - `"timeout"`: the library's own timer closed the component; no user gesture and no platform
36
+ * event (`event` is `undefined`). Toast's auto-dismiss is the only user.
35
37
  */
36
- export type ChangeReason = "activate" | "dismiss" | "platform";
38
+ export type ChangeReason = "activate" | "dismiss" | "platform" | "timeout";
37
39
  /** A prop value that is either a plain value or a function of the part's state. */
38
40
  export type StateValue<T, State> = T | ((state: State) => T);
39
41
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../src/internal/types.ts"],"mappings":";;;;;;;;YAQY,OAAO,4CAA4C,MAAM;iBAEpD;;WAEN,OAAO;;;YAIN,aAAa,GAAG,gBAAgB,gBAAgB,iBAAiB,QAC1E,MAAM,GAAG,SAAS;;;;;;;;YAUT,mBAAmB,GAAG,gBAAgB,gBAAgB,kBAChE,MAAM,GACN,SAAS;;;;;;;;;;;;;YAeC;;YAGA,WAAW,GAAG,SAAS,MAAM,OAAO,UAAU;;;;;;YAO9C,WAAW,OAAO,SAC1B,aAAa,QAAQ,YACnB,OAAO,OAAO,OAAO,UAAU"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../src/internal/types.ts"],"mappings":";;;;;;;;YAQY,OAAO,4CAA4C,MAAM;iBAEpD;;WAEN,OAAO;;;YAIN,aAAa,GAAG,gBAAgB,gBAAgB,iBAAiB,QAC1E,MAAM,GAAG,SAAS;;;;;;;;YAUT,mBAAmB,GAAG,gBAAgB,gBAAgB,kBAChE,MAAM,GACN,SAAS;;;;;;;;;;;;;;;YAiBC;;YAGA,WAAW,GAAG,SAAS,MAAM,OAAO,UAAU;;;;;;YAO9C,WAAW,OAAO,SAC1B,aAAa,QAAQ,YACnB,OAAO,OAAO,OAAO,UAAU"}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
- import { addTransitionType, startTransition, useEffect, useOptimistic, useRef, useState, useTransition } from "react";
2
+ import { useLatest } from "./use-latest.mjs";
3
+ import { addTransitionType, startTransition, useCallback, useEffect, useMemo, useOptimistic, useRef, useState, useTransition } from "react";
3
4
  //#region src/internal/use-controllable-action.ts
4
5
  const isDev = process.env.NODE_ENV !== "production";
5
6
  function capitalize(s) {
@@ -39,8 +40,18 @@ function useControllableAction(options) {
39
40
  mounted.current = false;
40
41
  };
41
42
  }, []);
42
- function dispatch(next, details) {
43
- const prev = committed;
43
+ const props = useLatest({
44
+ committed,
45
+ isControlled,
46
+ action,
47
+ transitionTypes
48
+ });
49
+ const settle = useCallback(() => {
50
+ inFlight.current.count -= 1;
51
+ if (inFlight.current.count === 0) inFlight.current.next = void 0;
52
+ }, []);
53
+ const dispatch = useCallback((next, details) => {
54
+ const { committed: prev, isControlled, action, transitionTypes } = props.current;
44
55
  const id = ++latest.current;
45
56
  inFlight.current.count += 1;
46
57
  inFlight.current.next = next;
@@ -67,26 +78,33 @@ function useControllableAction(options) {
67
78
  settle();
68
79
  if (!isControlled && id === latest.current) startTransition(() => setInternal(next));
69
80
  });
70
- }
71
- function settle() {
72
- inFlight.current.count -= 1;
73
- if (inFlight.current.count === 0) inFlight.current.next = void 0;
74
- }
75
- function latestInFlight() {
81
+ }, [
82
+ props,
83
+ settle,
84
+ setOptimistic
85
+ ]);
86
+ const latestInFlight = useCallback(() => {
76
87
  const { count, next } = inFlight.current;
77
88
  return count > 0 ? { next } : null;
78
- }
79
- function adopt(next) {
80
- if (!isControlled) setInternal(next);
81
- }
82
- return {
89
+ }, []);
90
+ const adopt = useCallback((next) => {
91
+ if (!props.current.isControlled) setInternal(next);
92
+ }, [props]);
93
+ return useMemo(() => ({
83
94
  committed,
84
95
  optimistic,
85
96
  pending,
86
97
  dispatch,
87
98
  latestInFlight,
88
99
  adopt
89
- };
100
+ }), [
101
+ committed,
102
+ optimistic,
103
+ pending,
104
+ dispatch,
105
+ latestInFlight,
106
+ adopt
107
+ ]);
90
108
  }
91
109
  function useDevWarnings(isControlled, hasDefault, warn) {
92
110
  const initialMode = useRef(isControlled);
@@ -1 +1 @@
1
- {"version":3,"file":"use-controllable-action.mjs","names":[],"sources":["../../src/internal/use-controllable-action.ts"],"sourcesContent":["\"use client\";\nimport {\n addTransitionType,\n startTransition,\n useEffect,\n useOptimistic,\n useRef,\n useState,\n useTransition,\n} from \"react\";\nimport type { ActionDetails, ChangeAction } from \"./types.js\";\n\nexport interface UseControllableActionOptions<T, Details extends ActionDetails> {\n /** The controlled prop (`x`). `undefined` means uncontrolled. */\n controlled: T | undefined;\n /** The `defaultX` prop as the consumer passed it; `undefined` when omitted. */\n defaultValue: T | undefined;\n /** Initial uncontrolled value when neither `x` nor `defaultX` is given. */\n initialValue: T;\n /** The `xChangeAction` prop. Awaited inside this hook's transition. */\n action: ChangeAction<T, Details> | undefined;\n /**\n * View Transition types to attach to the commit that ends a dispatch. Receives the previous\n * committed value and the details object (Tabs derives next/prev direction from them).\n */\n transitionTypes?: (next: T, prev: T, details: Details) => readonly string[];\n /**\n * Optimistic reducer (conventions 3.4). Default: replace the value with `next`. Multi-item\n * states (Accordion's open set) pass a reducer keyed by item, reading the item from `details`,\n * so stacked optimistic patches re-base onto a changed committed value.\n */\n reducer?: (prev: T, next: T, details: Details) => T;\n /** Names used in development warnings. */\n warn?: { readonly component: string; readonly prop: string };\n}\n\nexport interface ControllableAction<T, Details extends ActionDetails> {\n /** Committed value (`x ?? internal`). Content regions render this. */\n readonly committed: T;\n /** Optimistic value; trigger parts render this. */\n readonly optimistic: T;\n /** An action dispatched through this hook is in flight. */\n readonly pending: boolean;\n /** Runs the action inside the hook's transition with optimistic feedback. */\n readonly dispatch: (next: T, details: Details) => void;\n /**\n * The `next` of the most recent dispatch while any dispatch's action is still unsettled, else\n * `null`. Lets a component with a browser-owned path (Accordion's find-in-page reveal) build on\n * what the user already intends instead of on the DOM. Read it from handlers and effects only.\n */\n readonly latestInFlight: () => { readonly next: T } | null;\n /**\n * Uncontrolled mode only (a no-op when controlled): writes the committed value without calling\n * the action, without an optimistic step and without a transition. For a component that adopts\n * a default the browser chose (Select with neither `value` nor `defaultValue` takes the first\n * enabled option, design/components/select.md 5). Call it from a layout effect so the write is\n * flushed before paint.\n */\n readonly adopt: (next: T) => void;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nfunction capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1);\n}\n\n/** Reports an error that cannot reach a boundary (conventions 2.4: never swallow). */\nfunction report(error: unknown): void {\n if (typeof reportError === \"function\") reportError(error);\n else console.error(error);\n}\n\n/**\n * The one place the state model of conventions section 3 is implemented: committed state,\n * optimistic state (`useOptimistic`, reducer form), the component's own transition, the\n * post-`await` write of uncontrolled state, the latest-dispatch guard, and the surfacing of\n * superseded rejections (conventions 2.4).\n */\nexport function useControllableAction<T, Details extends ActionDetails = ActionDetails>(\n options: UseControllableActionOptions<T, Details>,\n): ControllableAction<T, Details> {\n const { controlled, defaultValue, initialValue, action, transitionTypes, reducer, warn } =\n options;\n\n // React keeps only the newest thenable per `useTransition`, so a rejection from a dispatch that\n // was superseded by a later one would be dropped entirely (no boundary, no `reportError`). The\n // hook catches those itself, keeps the first, and throws it from render so it reaches the same\n // nearest Error Boundary as the latest dispatch's rejection does through React.\n //\n // The throw sits before `useTransition` on purpose: React retries an errored render with every\n // pending lane, and in that retry `useTransition` unwraps the still-pending action thenable and\n // suspends, which would hold the boundary commit until the newer action settles.\n const [supersededError, setSupersededError] = useState<{ readonly error: unknown } | null>(null);\n const hasSupersededError = useRef(false);\n if (supersededError !== null) throw supersededError.error;\n\n const [internal, setInternal] = useState<T>(\n defaultValue === undefined ? initialValue : defaultValue,\n );\n const isControlled = controlled !== undefined;\n const committed = isControlled ? controlled : internal;\n const [optimistic, setOptimistic] = useOptimistic(\n committed,\n (prev: T, patch: { next: T; details: Details }) =>\n reducer ? reducer(prev, patch.next, patch.details) : patch.next,\n );\n const [pending, startOwnTransition] = useTransition();\n const latest = useRef(0);\n const inFlight = useRef<{ count: number; next: T | undefined }>({ count: 0, next: undefined });\n const mounted = useRef(false);\n\n useDevWarnings(isControlled, defaultValue !== undefined, warn);\n\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n function dispatch(next: T, details: Details): void {\n const prev = committed;\n const id = ++latest.current;\n inFlight.current.count += 1;\n inFlight.current.next = next;\n startOwnTransition(async () => {\n for (const type of transitionTypes?.(next, prev, details) ?? []) addTransitionType(type);\n setOptimistic({ next, details });\n try {\n await action?.(next, details);\n } catch (error) {\n settle();\n // The latest dispatch's rejection takes React's own route to the boundary.\n if (id === latest.current) throw error;\n // Nothing to throw to once unmounted (React drops its own thenables the same way).\n if (!mounted.current) return;\n if (hasSupersededError.current) {\n // One boundary reset per burst is enough; later errors are still reported.\n report(error);\n return;\n }\n hasSupersededError.current = true;\n // Outside the transition by now (after an await), so this is an urgent update: the owner\n // re-renders promptly and throws.\n setSupersededError({ error });\n return;\n }\n settle();\n if (!isControlled && id === latest.current) {\n startTransition(() => setInternal(next));\n }\n });\n }\n\n function settle(): void {\n inFlight.current.count -= 1;\n if (inFlight.current.count === 0) inFlight.current.next = undefined;\n }\n\n function latestInFlight(): { readonly next: T } | null {\n const { count, next } = inFlight.current;\n return count > 0 ? { next: next as T } : null;\n }\n\n function adopt(next: T): void {\n if (!isControlled) setInternal(next);\n }\n\n return { committed, optimistic, pending, dispatch, latestInFlight, adopt };\n}\n\nfunction useDevWarnings(\n isControlled: boolean,\n hasDefault: boolean,\n warn: { readonly component: string; readonly prop: string } | undefined,\n): void {\n const initialMode = useRef(isControlled);\n const warnedBoth = useRef(false);\n const warnedSwitch = useRef(false);\n const component = warn?.component ?? \"A component\";\n const prop = warn?.prop ?? \"value\";\n\n useEffect(() => {\n if (!isDev) return;\n const defaultProp = `default${capitalize(prop)}`;\n\n if (isControlled && hasDefault && !warnedBoth.current) {\n warnedBoth.current = true;\n console.error(\n `${component}: both \\`${prop}\\` and \\`${defaultProp}\\` are set. ` +\n `Use \\`${prop}\\` for controlled mode or \\`${defaultProp}\\` for uncontrolled mode, not both.`,\n );\n }\n\n if (initialMode.current !== isControlled && !warnedSwitch.current) {\n warnedSwitch.current = true;\n const from = initialMode.current ? \"controlled\" : \"uncontrolled\";\n const to = isControlled ? \"controlled\" : \"uncontrolled\";\n console.error(\n `${component}: \\`${prop}\\` changed from ${from} to ${to}. Switching modes after mount ` +\n `is not supported; keep \\`${prop}\\` either always defined or always undefined.`,\n );\n }\n }, [component, prop, isControlled, hasDefault]);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-controllable-action.mjs","names":[],"sources":["../../src/internal/use-controllable-action.ts"],"sourcesContent":["\"use client\";\nimport {\n addTransitionType,\n startTransition,\n useCallback,\n useEffect,\n useMemo,\n useOptimistic,\n useRef,\n useState,\n useTransition,\n} from \"react\";\nimport type { ActionDetails, ChangeAction } from \"./types.js\";\nimport { useLatest } from \"./use-latest.js\";\n\nexport interface UseControllableActionOptions<T, Details extends ActionDetails> {\n /** The controlled prop (`x`). `undefined` means uncontrolled. */\n controlled: T | undefined;\n /** The `defaultX` prop as the consumer passed it; `undefined` when omitted. */\n defaultValue: T | undefined;\n /** Initial uncontrolled value when neither `x` nor `defaultX` is given. */\n initialValue: T;\n /** The `xChangeAction` prop. Awaited inside this hook's transition. */\n action: ChangeAction<T, Details> | undefined;\n /**\n * View Transition types to attach to the commit that ends a dispatch. Receives the previous\n * committed value and the details object (Tabs derives next/prev direction from them).\n */\n transitionTypes?: (next: T, prev: T, details: Details) => readonly string[];\n /**\n * Optimistic reducer (conventions 3.4). Default: replace the value with `next`. Multi-item\n * states (Accordion's open set) pass a reducer keyed by item, reading the item from `details`,\n * so stacked optimistic patches re-base onto a changed committed value.\n */\n reducer?: (prev: T, next: T, details: Details) => T;\n /** Names used in development warnings. */\n warn?: { readonly component: string; readonly prop: string };\n}\n\nexport interface ControllableAction<T, Details extends ActionDetails> {\n /** Committed value (`x ?? internal`). Content regions render this. */\n readonly committed: T;\n /** Optimistic value; trigger parts render this. */\n readonly optimistic: T;\n /** An action dispatched through this hook is in flight. */\n readonly pending: boolean;\n /** Runs the action inside the hook's transition with optimistic feedback. */\n readonly dispatch: (next: T, details: Details) => void;\n /**\n * The `next` of the most recent dispatch while any dispatch's action is still unsettled, else\n * `null`. Lets a component with a browser-owned path (Accordion's find-in-page reveal) build on\n * what the user already intends instead of on the DOM. Read it from handlers and effects only.\n */\n readonly latestInFlight: () => { readonly next: T } | null;\n /**\n * Uncontrolled mode only (a no-op when controlled): writes the committed value without calling\n * the action, without an optimistic step and without a transition. For a component that adopts\n * a default the browser chose (Select with neither `value` nor `defaultValue` takes the first\n * enabled option, design/components/select.md 5). Call it from a layout effect so the write is\n * flushed before paint.\n */\n readonly adopt: (next: T) => void;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nfunction capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1);\n}\n\n/** Reports an error that cannot reach a boundary (conventions 2.4: never swallow). */\nfunction report(error: unknown): void {\n if (typeof reportError === \"function\") reportError(error);\n else console.error(error);\n}\n\n/**\n * The one place the state model of conventions section 3 is implemented: committed state,\n * optimistic state (`useOptimistic`, reducer form), the component's own transition, the\n * post-`await` write of uncontrolled state, the latest-dispatch guard, and the surfacing of\n * superseded rejections (conventions 2.4).\n */\nexport function useControllableAction<T, Details extends ActionDetails = ActionDetails>(\n options: UseControllableActionOptions<T, Details>,\n): ControllableAction<T, Details> {\n const { controlled, defaultValue, initialValue, action, transitionTypes, reducer, warn } =\n options;\n\n // React keeps only the newest thenable per `useTransition`, so a rejection from a dispatch that\n // was superseded by a later one would be dropped entirely (no boundary, no `reportError`). The\n // hook catches those itself, keeps the first, and throws it from render so it reaches the same\n // nearest Error Boundary as the latest dispatch's rejection does through React.\n //\n // The throw sits before `useTransition` on purpose: React retries an errored render with every\n // pending lane, and in that retry `useTransition` unwraps the still-pending action thenable and\n // suspends, which would hold the boundary commit until the newer action settles.\n const [supersededError, setSupersededError] = useState<{ readonly error: unknown } | null>(null);\n const hasSupersededError = useRef(false);\n if (supersededError !== null) throw supersededError.error;\n\n const [internal, setInternal] = useState<T>(\n defaultValue === undefined ? initialValue : defaultValue,\n );\n const isControlled = controlled !== undefined;\n const committed = isControlled ? controlled : internal;\n const [optimistic, setOptimistic] = useOptimistic(\n committed,\n (prev: T, patch: { next: T; details: Details }) =>\n reducer ? reducer(prev, patch.next, patch.details) : patch.next,\n );\n const [pending, startOwnTransition] = useTransition();\n const latest = useRef(0);\n const inFlight = useRef<{ count: number; next: T | undefined }>({ count: 0, next: undefined });\n const mounted = useRef(false);\n\n useDevWarnings(isControlled, defaultValue !== undefined, warn);\n\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n // The functions below keep one identity for the hook's lifetime, so the returned object changes\n // only with `committed`, `optimistic` and `pending`, and the components' memoised context values\n // are reused across renders that change none of them (#48). They read the props of the last\n // committed render through `props`; they run from handlers, actions and effects, never render.\n const props = useLatest({ committed, isControlled, action, transitionTypes });\n\n const settle = useCallback((): void => {\n inFlight.current.count -= 1;\n if (inFlight.current.count === 0) inFlight.current.next = undefined;\n }, []);\n\n const dispatch = useCallback(\n (next: T, details: Details): void => {\n // Read once, at the call, as the per-render closure did: the action, the mode and the\n // previous value belong to the UI the user acted on, even after the `await`.\n const { committed: prev, isControlled, action, transitionTypes } = props.current;\n const id = ++latest.current;\n inFlight.current.count += 1;\n inFlight.current.next = next;\n startOwnTransition(async () => {\n for (const type of transitionTypes?.(next, prev, details) ?? []) addTransitionType(type);\n setOptimistic({ next, details });\n try {\n await action?.(next, details);\n } catch (error) {\n settle();\n // The latest dispatch's rejection takes React's own route to the boundary.\n if (id === latest.current) throw error;\n // Nothing to throw to once unmounted (React drops its own thenables the same way).\n if (!mounted.current) return;\n if (hasSupersededError.current) {\n // One boundary reset per burst is enough; later errors are still reported.\n report(error);\n return;\n }\n hasSupersededError.current = true;\n // Outside the transition by now (after an await), so this is an urgent update: the\n // owner re-renders promptly and throws.\n setSupersededError({ error });\n return;\n }\n settle();\n if (!isControlled && id === latest.current) {\n startTransition(() => setInternal(next));\n }\n });\n },\n [props, settle, setOptimistic],\n );\n\n const latestInFlight = useCallback((): { readonly next: T } | null => {\n const { count, next } = inFlight.current;\n return count > 0 ? { next: next as T } : null;\n }, []);\n\n const adopt = useCallback(\n (next: T): void => {\n if (!props.current.isControlled) setInternal(next);\n },\n [props],\n );\n\n return useMemo(\n () => ({ committed, optimistic, pending, dispatch, latestInFlight, adopt }),\n [committed, optimistic, pending, dispatch, latestInFlight, adopt],\n );\n}\n\nfunction useDevWarnings(\n isControlled: boolean,\n hasDefault: boolean,\n warn: { readonly component: string; readonly prop: string } | undefined,\n): void {\n const initialMode = useRef(isControlled);\n const warnedBoth = useRef(false);\n const warnedSwitch = useRef(false);\n const component = warn?.component ?? \"A component\";\n const prop = warn?.prop ?? \"value\";\n\n useEffect(() => {\n if (!isDev) return;\n const defaultProp = `default${capitalize(prop)}`;\n\n if (isControlled && hasDefault && !warnedBoth.current) {\n warnedBoth.current = true;\n console.error(\n `${component}: both \\`${prop}\\` and \\`${defaultProp}\\` are set. ` +\n `Use \\`${prop}\\` for controlled mode or \\`${defaultProp}\\` for uncontrolled mode, not both.`,\n );\n }\n\n if (initialMode.current !== isControlled && !warnedSwitch.current) {\n warnedSwitch.current = true;\n const from = initialMode.current ? \"controlled\" : \"uncontrolled\";\n const to = isControlled ? \"controlled\" : \"uncontrolled\";\n console.error(\n `${component}: \\`${prop}\\` changed from ${from} to ${to}. Switching modes after mount ` +\n `is not supported; keep \\`${prop}\\` either always defined or always undefined.`,\n );\n }\n }, [component, prop, isControlled, hasDefault]);\n}\n"],"mappings":"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"}
@@ -7,14 +7,25 @@ function ends(animation) {
7
7
  return typeof end === "number" && Number.isFinite(end);
8
8
  }
9
9
  /**
10
+ * The animations running in an element's subtree right before it closes (the synchronous
11
+ * `beforetoggle` of the hide, Toast's `data-ending` write): the baseline that `exitAnimations`
12
+ * excludes, so a finite animation already running inside (a progress bar) does not hold the exit
13
+ * (Toast 10.2, #64). Empty where `getAnimations` is absent (jsdom).
14
+ */
15
+ function animationBaseline(element) {
16
+ if (!element.isConnected || typeof element.getAnimations !== "function") return /* @__PURE__ */ new Set();
17
+ return new Set(element.getAnimations({ subtree: true }));
18
+ }
19
+ /**
10
20
  * The running exit animations of a top-layer element that has just been hidden, read after a
11
- * style flush so that `transition-behavior: allow-discrete` exits exist. Empty where the engine
12
- * does not animate the exit (Firefox) or there is no CSS exit.
21
+ * style flush so that `transition-behavior: allow-discrete` exits exist, minus the `baseline`
22
+ * of animations that were already running before the close. Empty where the engine does not
23
+ * animate the exit (Firefox) or there is no CSS exit.
13
24
  */
14
- function exitAnimations(element) {
25
+ function exitAnimations(element, baseline) {
15
26
  if (!element.isConnected || typeof element.getAnimations !== "function") return [];
16
27
  getComputedStyle(element).opacity;
17
- return element.getAnimations({ subtree: true }).filter(ends);
28
+ return element.getAnimations({ subtree: true }).filter((animation) => ends(animation) && !(baseline?.has(animation) ?? false));
18
29
  }
19
30
  /**
20
31
  * The deferred unmount of a top-layer popup's content while its hide animates. `held` is cleared
@@ -26,6 +37,7 @@ function useExitAnimation() {
26
37
  const [held, setHeld] = useState(false);
27
38
  const wait = useRef(null);
28
39
  const generation = useRef(0);
40
+ const baseline = useRef(null);
29
41
  const holdUntil = (animations) => {
30
42
  const current = ++generation.current;
31
43
  wait.current = current;
@@ -46,10 +58,18 @@ function useExitAnimation() {
46
58
  holdUntil,
47
59
  release,
48
60
  holdDuringRender: () => setHeld(true),
49
- waiting: () => wait.current !== null
61
+ waiting: () => wait.current !== null,
62
+ recordBaseline: (element) => {
63
+ baseline.current = animationBaseline(element);
64
+ },
65
+ exitAnimations: (element) => {
66
+ const recorded = baseline.current ?? void 0;
67
+ baseline.current = null;
68
+ return exitAnimations(element, recorded);
69
+ }
50
70
  };
51
71
  }
52
72
  //#endregion
53
- export { exitAnimations, useExitAnimation };
73
+ export { animationBaseline, exitAnimations, useExitAnimation };
54
74
 
55
75
  //# sourceMappingURL=use-exit-animation.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-exit-animation.mjs","names":[],"sources":["../../src/internal/use-exit-animation.ts"],"sourcesContent":["\"use client\";\nimport { useRef, useState } from \"react\";\n\n/** An animation that ends: infinite ones (a spinner) would hold the content forever. */\nfunction ends(animation: Animation): boolean {\n const end = animation.effect?.getComputedTiming().endTime;\n return typeof end === \"number\" && Number.isFinite(end);\n}\n\n/**\n * The running exit animations of a top-layer element that has just been hidden, read after a\n * style flush so that `transition-behavior: allow-discrete` exits exist. Empty where the engine\n * does not animate the exit (Firefox) or there is no CSS exit.\n */\nexport function exitAnimations(element: Element): Animation[] {\n if (!element.isConnected || typeof element.getAnimations !== \"function\") return [];\n void getComputedStyle(element).opacity;\n return element.getAnimations({ subtree: true }).filter(ends);\n}\n\nexport interface ExitAnimation {\n /** Keep the content mounted (design/components/popover.md 4.4). */\n readonly held: boolean;\n /** Set `held` now (urgent, library-internal) and clear it when `animations` settle. */\n readonly holdUntil: (animations: readonly Animation[]) => void;\n /** Clear `held` now (a normal update, never a transition), cancelling any wait in progress. */\n readonly release: () => void;\n /** Set `held` during render (the \"previous value in state\" pattern). */\n readonly holdDuringRender: () => void;\n /** Whether a `holdUntil` wait is still running. Handlers and effects only. */\n readonly waiting: () => boolean;\n}\n\n/**\n * The deferred unmount of a top-layer popup's content while its hide animates. `held` is cleared\n * with a normal (urgent) update, never in a transition: the popup is already hidden, nothing\n * visible changes, and only transition commits start a document view transition, so a dismissal\n * runs one (its notification commit) instead of two (design 4.4).\n */\nexport function useExitAnimation(): ExitAnimation {\n const [held, setHeld] = useState(false);\n // The generation of the wait in progress, or null; a newer wait supersedes an older one.\n const wait = useRef<number | null>(null);\n const generation = useRef(0);\n\n const holdUntil = (animations: readonly Animation[]): void => {\n const current = ++generation.current;\n wait.current = current;\n setHeld(true);\n void Promise.allSettled(animations.map((animation) => animation.finished)).then(() => {\n if (generation.current !== current) return;\n wait.current = null;\n setHeld(false);\n });\n };\n\n const release = (): void => {\n generation.current++;\n wait.current = null;\n setHeld(false);\n };\n\n return {\n held,\n holdUntil,\n release,\n holdDuringRender: () => setHeld(true),\n waiting: () => wait.current !== null,\n };\n}\n"],"mappings":";;;;AAIA,SAAS,KAAK,WAA+B;CAC3C,MAAM,MAAM,UAAU,QAAQ,kBAAkB,CAAC,CAAC;CAClD,OAAO,OAAO,QAAQ,YAAY,OAAO,SAAS,GAAG;AACvD;;;;;;AAOA,SAAgB,eAAe,SAA+B;CAC5D,IAAI,CAAC,QAAQ,eAAe,OAAO,QAAQ,kBAAkB,YAAY,OAAO,CAAC;CACjF,iBAAsB,OAAO,CAAC,CAAC;CAC/B,OAAO,QAAQ,cAAc,EAAE,SAAS,KAAK,CAAC,CAAC,CAAC,OAAO,IAAI;AAC7D;;;;;;;AAqBA,SAAgB,mBAAkC;CAChD,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CAEtC,MAAM,OAAO,OAAsB,IAAI;CACvC,MAAM,aAAa,OAAO,CAAC;CAE3B,MAAM,aAAa,eAA2C;EAC5D,MAAM,UAAU,EAAE,WAAW;EAC7B,KAAK,UAAU;EACf,QAAQ,IAAI;EACZ,QAAa,WAAW,WAAW,KAAK,cAAc,UAAU,QAAQ,CAAC,CAAC,CAAC,WAAW;GACpF,IAAI,WAAW,YAAY,SAAS;GACpC,KAAK,UAAU;GACf,QAAQ,KAAK;EACf,CAAC;CACH;CAEA,MAAM,gBAAsB;EAC1B,WAAW;EACX,KAAK,UAAU;EACf,QAAQ,KAAK;CACf;CAEA,OAAO;EACL;EACA;EACA;EACA,wBAAwB,QAAQ,IAAI;EACpC,eAAe,KAAK,YAAY;CAClC;AACF"}
1
+ {"version":3,"file":"use-exit-animation.mjs","names":[],"sources":["../../src/internal/use-exit-animation.ts"],"sourcesContent":["\"use client\";\nimport { useRef, useState } from \"react\";\n\n/** An animation that ends: infinite ones (a spinner) would hold the content forever. */\nfunction ends(animation: Animation): boolean {\n const end = animation.effect?.getComputedTiming().endTime;\n return typeof end === \"number\" && Number.isFinite(end);\n}\n\n/**\n * The animations running in an element's subtree right before it closes (the synchronous\n * `beforetoggle` of the hide, Toast's `data-ending` write): the baseline that `exitAnimations`\n * excludes, so a finite animation already running inside (a progress bar) does not hold the exit\n * (Toast 10.2, #64). Empty where `getAnimations` is absent (jsdom).\n */\nexport function animationBaseline(element: Element): ReadonlySet<Animation> {\n if (!element.isConnected || typeof element.getAnimations !== \"function\") return new Set();\n return new Set(element.getAnimations({ subtree: true }));\n}\n\n/**\n * The running exit animations of a top-layer element that has just been hidden, read after a\n * style flush so that `transition-behavior: allow-discrete` exits exist, minus the `baseline`\n * of animations that were already running before the close. Empty where the engine does not\n * animate the exit (Firefox) or there is no CSS exit.\n */\nexport function exitAnimations(element: Element, baseline?: ReadonlySet<Animation>): Animation[] {\n if (!element.isConnected || typeof element.getAnimations !== \"function\") return [];\n void getComputedStyle(element).opacity;\n return element\n .getAnimations({ subtree: true })\n .filter((animation) => ends(animation) && !(baseline?.has(animation) ?? false));\n}\n\nexport interface ExitAnimation {\n /** Keep the content mounted (design/components/popover.md 4.4). */\n readonly held: boolean;\n /** Set `held` now (urgent, library-internal) and clear it when `animations` settle. */\n readonly holdUntil: (animations: readonly Animation[]) => void;\n /** Clear `held` now (a normal update, never a transition), cancelling any wait in progress. */\n readonly release: () => void;\n /** Set `held` during render (the \"previous value in state\" pattern). */\n readonly holdDuringRender: () => void;\n /** Whether a `holdUntil` wait is still running. Handlers and effects only. */\n readonly waiting: () => boolean;\n /**\n * Record the animations already running in `element`'s subtree. Call from the synchronous\n * `beforetoggle` of a close, before the hide starts the exit (#64).\n */\n readonly recordBaseline: (element: Element) => void;\n /** `exitAnimations(element)` minus the recorded baseline, which it consumes. */\n readonly exitAnimations: (element: Element) => Animation[];\n}\n\n/**\n * The deferred unmount of a top-layer popup's content while its hide animates. `held` is cleared\n * with a normal (urgent) update, never in a transition: the popup is already hidden, nothing\n * visible changes, and only transition commits start a document view transition, so a dismissal\n * runs one (its notification commit) instead of two (design 4.4).\n */\nexport function useExitAnimation(): ExitAnimation {\n const [held, setHeld] = useState(false);\n // The generation of the wait in progress, or null; a newer wait supersedes an older one.\n const wait = useRef<number | null>(null);\n const generation = useRef(0);\n // The animations running right before the latest close (#64), consumed by `exitAnimations`.\n const baseline = useRef<ReadonlySet<Animation> | null>(null);\n\n const holdUntil = (animations: readonly Animation[]): void => {\n const current = ++generation.current;\n wait.current = current;\n setHeld(true);\n void Promise.allSettled(animations.map((animation) => animation.finished)).then(() => {\n if (generation.current !== current) return;\n wait.current = null;\n setHeld(false);\n });\n };\n\n const release = (): void => {\n generation.current++;\n wait.current = null;\n setHeld(false);\n };\n\n return {\n held,\n holdUntil,\n release,\n holdDuringRender: () => setHeld(true),\n waiting: () => wait.current !== null,\n recordBaseline: (element) => {\n baseline.current = animationBaseline(element);\n },\n exitAnimations: (element) => {\n const recorded = baseline.current ?? undefined;\n baseline.current = null;\n return exitAnimations(element, recorded);\n },\n };\n}\n"],"mappings":";;;;AAIA,SAAS,KAAK,WAA+B;CAC3C,MAAM,MAAM,UAAU,QAAQ,kBAAkB,CAAC,CAAC;CAClD,OAAO,OAAO,QAAQ,YAAY,OAAO,SAAS,GAAG;AACvD;;;;;;;AAQA,SAAgB,kBAAkB,SAA0C;CAC1E,IAAI,CAAC,QAAQ,eAAe,OAAO,QAAQ,kBAAkB,YAAY,uBAAO,IAAI,IAAI;CACxF,OAAO,IAAI,IAAI,QAAQ,cAAc,EAAE,SAAS,KAAK,CAAC,CAAC;AACzD;;;;;;;AAQA,SAAgB,eAAe,SAAkB,UAAgD;CAC/F,IAAI,CAAC,QAAQ,eAAe,OAAO,QAAQ,kBAAkB,YAAY,OAAO,CAAC;CACjF,iBAAsB,OAAO,CAAC,CAAC;CAC/B,OAAO,QACJ,cAAc,EAAE,SAAS,KAAK,CAAC,CAAC,CAChC,QAAQ,cAAc,KAAK,SAAS,KAAK,EAAE,UAAU,IAAI,SAAS,KAAK,MAAM;AAClF;;;;;;;AA4BA,SAAgB,mBAAkC;CAChD,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CAEtC,MAAM,OAAO,OAAsB,IAAI;CACvC,MAAM,aAAa,OAAO,CAAC;CAE3B,MAAM,WAAW,OAAsC,IAAI;CAE3D,MAAM,aAAa,eAA2C;EAC5D,MAAM,UAAU,EAAE,WAAW;EAC7B,KAAK,UAAU;EACf,QAAQ,IAAI;EACZ,QAAa,WAAW,WAAW,KAAK,cAAc,UAAU,QAAQ,CAAC,CAAC,CAAC,WAAW;GACpF,IAAI,WAAW,YAAY,SAAS;GACpC,KAAK,UAAU;GACf,QAAQ,KAAK;EACf,CAAC;CACH;CAEA,MAAM,gBAAsB;EAC1B,WAAW;EACX,KAAK,UAAU;EACf,QAAQ,KAAK;CACf;CAEA,OAAO;EACL;EACA;EACA;EACA,wBAAwB,QAAQ,IAAI;EACpC,eAAe,KAAK,YAAY;EAChC,iBAAiB,YAAY;GAC3B,SAAS,UAAU,kBAAkB,OAAO;EAC9C;EACA,iBAAiB,YAAY;GAC3B,MAAM,WAAW,SAAS,WAAW,KAAA;GACrC,SAAS,UAAU;GACnB,OAAO,eAAe,SAAS,QAAQ;EACzC;CACF;AACF"}
@@ -0,0 +1,109 @@
1
+ "use client";
2
+ import { dataAttr } from "./data-attr.mjs";
3
+ import { mergeRefs } from "./merge-props.mjs";
4
+ import { useDevWarning } from "./use-dev-warning.mjs";
5
+ import { FieldContext } from "./field-context.mjs";
6
+ import { use, useCallback, useLayoutEffect, useMemo, useRef } from "react";
7
+ //#region src/internal/use-field-control.ts
8
+ /**
9
+ * The owned props of a labelable native control (`<input>`, `<select>`) inside a Field: the
10
+ * ARIA wiring plus `name` and `required` ("own or the Field's"). Empty outside a Field, so the
11
+ * consumer's own `id` / `name` / `required` keep passing through untouched there.
12
+ */
13
+ function fieldInputProps(field) {
14
+ if (!field.inField) return {};
15
+ return {
16
+ ...field.attributes,
17
+ name: field.name,
18
+ required: field.required
19
+ };
20
+ }
21
+ /** `mergeRefs` with a stable identity while both refs keep theirs. */
22
+ function useMergedRef(a, b) {
23
+ return useMemo(() => b === void 0 ? a : a === void 0 ? b : mergeRefs(a, b), [a, b]);
24
+ }
25
+ /**
26
+ * The contract between a Field and its control (design/components/field-form.md 7.1), called by
27
+ * `Field.Control` and by the owner part of every FUNSTACK form control. Outside a `Field.Root` it
28
+ * returns the control's own props and nothing to merge.
29
+ *
30
+ * Inside a Field: registers the element from the returned ref (the Field attaches its listeners
31
+ * and reads validity there; a group's `<fieldset>` makes `Field.Label` drop `for`), resolves "own
32
+ * or the Field's" for `name` / `disabled` / `required`, and returns the owned ARIA wiring.
33
+ */
34
+ function useFieldControl(options) {
35
+ const { labelable, name, disabled = false, required = false, id, component, validityElement } = options;
36
+ const field = use(FieldContext);
37
+ const registerControl = field?.registerControl;
38
+ const ref = useCallback((element) => {
39
+ if (registerControl === void 0) return () => {};
40
+ return registerControl({
41
+ element,
42
+ labelable,
43
+ required,
44
+ disabled,
45
+ validityElement
46
+ });
47
+ }, [
48
+ registerControl,
49
+ labelable,
50
+ required,
51
+ disabled,
52
+ validityElement
53
+ ]);
54
+ useDevWarning(field !== null && id !== void 0, `${component}: the \`id\` prop is ignored inside a Field.Root; the Field owns the control's id so that Field.Label can point at it.`);
55
+ useDevWarning(field !== null && name !== void 0 && field.name !== void 0 && name !== field.name, `${component}: its \`name\` ("${name}") differs from the Field.Root's ("${field?.name}"). The Field reads server errors and \`validate\`'s value by the Field's name.`);
56
+ if (field === null) return {
57
+ inField: false,
58
+ ref: void 0,
59
+ id,
60
+ name,
61
+ disabled,
62
+ required,
63
+ attributes: {},
64
+ state: void 0
65
+ };
66
+ const { ids, state } = field;
67
+ const attributes = {
68
+ "aria-describedby": state.invalid ? `${ids.description} ${ids.error}` : ids.description,
69
+ "aria-invalid": state.invalid ? "true" : "false",
70
+ "data-invalid": dataAttr(state.invalid)
71
+ };
72
+ if (labelable) attributes.id = ids.control;
73
+ else attributes["aria-labelledby"] = ids.label;
74
+ return {
75
+ inField: true,
76
+ ref,
77
+ id: labelable ? ids.control : void 0,
78
+ name: name ?? field.name,
79
+ disabled: disabled || field.disabled,
80
+ required: required || field.required,
81
+ attributes,
82
+ state
83
+ };
84
+ }
85
+ /**
86
+ * Tells the enclosing Field (if any) that the control's value changed, after the commit that
87
+ * wrote it to the DOM (design/components/field-form.md 2.1: `validate` and the mirror follow the
88
+ * value). Needed where the DOM changes without an `input` / `change` event the Field hears: a
89
+ * Combobox selection (React writes the hidden input), a CheckboxGroup's derived `required`, a
90
+ * Parent checking Items, a rejected action rolling the value back. Nothing at mount.
91
+ *
92
+ * `takeEdit` is read when the value changed: `true` when the user made this change (a shown
93
+ * server error is dismissed, like an `input` event would). Native controls whose own events
94
+ * already dismiss pass nothing.
95
+ */
96
+ function useFieldValueChange(value, takeEdit) {
97
+ const notifyChange = use(FieldContext)?.notifyChange;
98
+ const previous = useRef(value);
99
+ useLayoutEffect(() => {
100
+ if (Object.is(previous.current, value)) return;
101
+ previous.current = value;
102
+ const edit = takeEdit?.() ?? false;
103
+ notifyChange?.(edit);
104
+ });
105
+ }
106
+ //#endregion
107
+ export { fieldInputProps, useFieldControl, useFieldValueChange, useMergedRef };
108
+
109
+ //# sourceMappingURL=use-field-control.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-field-control.mjs","names":[],"sources":["../../src/internal/use-field-control.ts"],"sourcesContent":["\"use client\";\nimport { use, useCallback, useLayoutEffect, useMemo, useRef } from \"react\";\nimport type { Ref, RefCallback } from \"react\";\nimport { dataAttr } from \"./data-attr.js\";\nimport { FieldContext } from \"./field-context.js\";\nimport type { FieldState } from \"./field-context.js\";\nimport { mergeRefs } from \"./merge-props.js\";\nimport { useDevWarning } from \"./use-dev-warning.js\";\nimport type { ValidatableElement } from \"./validity.js\";\n\nexport interface UseFieldControlOptions {\n /** `<label for>` can target the element (input, select, textarea). Groups pass `false`. */\n readonly labelable: boolean;\n /** The control's own props, so \"own or the Field's\" can be computed. */\n readonly name?: string | undefined;\n readonly disabled?: boolean | undefined;\n readonly required?: boolean | undefined;\n /** The consumer's own `id` prop: ignored inside a Field (the Field owns it), with a warning. */\n readonly id?: string | undefined;\n /** Dev warning tag. */\n readonly component: string;\n /**\n * The element whose validity is real when it is not the registered one (NumberField's hidden\n * number input; field-form.md amendment `validityElement`). Keep its identity stable.\n */\n readonly validityElement?: (() => ValidatableElement | null) | undefined;\n}\n\n/** The ARIA and state attributes a part spreads into its owned layer. */\nexport interface FieldControlAttributes {\n /** Labelable controls only: the id `Field.Label`'s `for` points at. */\n id?: string;\n \"aria-labelledby\"?: string;\n \"aria-describedby\"?: string;\n \"aria-invalid\"?: \"true\" | \"false\";\n \"data-invalid\"?: \"\" | undefined;\n}\n\nexport interface FieldControlBinding {\n /** The control is inside a `Field.Root`. */\n readonly inField: boolean;\n /** Merge into the part's library ref: registers the element with the Field. `undefined` outside a Field. */\n readonly ref: RefCallback<HTMLElement> | undefined;\n /** The control id the Field's Label points at (labelable controls inside a Field), else the consumer's. */\n readonly id: string | undefined;\n /** Own `name` ?? the Field's. */\n readonly name: string | undefined;\n /** Own `disabled` || the Field's. */\n readonly disabled: boolean;\n /** Own `required` || the Field's. */\n readonly required: boolean;\n /**\n * Owned attributes: `id` (labelable controls), `aria-invalid`, `data-invalid`,\n * `aria-labelledby` (groups) and `aria-describedby` (both token-merged by the owned layer, the\n * consumer's tokens first). Empty outside a Field, so nothing is owned there.\n */\n readonly attributes: FieldControlAttributes;\n /** The Field's state, `undefined` outside a Field. */\n readonly state: FieldState | undefined;\n}\n\n/**\n * The owned props of a labelable native control (`<input>`, `<select>`) inside a Field: the\n * ARIA wiring plus `name` and `required` (\"own or the Field's\"). Empty outside a Field, so the\n * consumer's own `id` / `name` / `required` keep passing through untouched there.\n */\nexport function fieldInputProps(field: FieldControlBinding): FieldControlAttributes & {\n name?: string | undefined;\n required?: boolean;\n} {\n if (!field.inField) return {};\n return { ...field.attributes, name: field.name, required: field.required };\n}\n\n/** `mergeRefs` with a stable identity while both refs keep theirs. */\nexport function useMergedRef<T>(\n a: Ref<T> | undefined,\n b: Ref<T> | undefined,\n): RefCallback<T> | Ref<T> | undefined {\n return useMemo(() => (b === undefined ? a : a === undefined ? b : mergeRefs(a, b)), [a, b]);\n}\n\n/**\n * The contract between a Field and its control (design/components/field-form.md 7.1), called by\n * `Field.Control` and by the owner part of every FUNSTACK form control. Outside a `Field.Root` it\n * returns the control's own props and nothing to merge.\n *\n * Inside a Field: registers the element from the returned ref (the Field attaches its listeners\n * and reads validity there; a group's `<fieldset>` makes `Field.Label` drop `for`), resolves \"own\n * or the Field's\" for `name` / `disabled` / `required`, and returns the owned ARIA wiring.\n */\nexport function useFieldControl(options: UseFieldControlOptions): FieldControlBinding {\n const {\n labelable,\n name,\n disabled = false,\n required = false,\n id,\n component,\n validityElement,\n } = options;\n const field = use(FieldContext);\n const registerControl = field?.registerControl;\n\n const ref = useCallback(\n (element: HTMLElement) => {\n if (registerControl === undefined) return () => {};\n return registerControl({ element, labelable, required, disabled, validityElement });\n },\n [registerControl, labelable, required, disabled, validityElement],\n );\n\n useDevWarning(\n field !== null && id !== undefined,\n `${component}: the \\`id\\` prop is ignored inside a Field.Root; the Field owns the control's ` +\n \"id so that Field.Label can point at it.\",\n );\n useDevWarning(\n field !== null && name !== undefined && field.name !== undefined && name !== field.name,\n `${component}: its \\`name\\` (\"${name}\") differs from the Field.Root's (\"${field?.name}\"). ` +\n \"The Field reads server errors and `validate`'s value by the Field's name.\",\n );\n\n if (field === null) {\n return {\n inField: false,\n ref: undefined,\n id,\n name,\n disabled,\n required,\n attributes: {},\n state: undefined,\n };\n }\n\n const { ids, state } = field;\n const attributes: FieldControlAttributes = {\n \"aria-describedby\": state.invalid ? `${ids.description} ${ids.error}` : ids.description,\n \"aria-invalid\": state.invalid ? \"true\" : \"false\",\n \"data-invalid\": dataAttr(state.invalid),\n };\n if (labelable) attributes.id = ids.control;\n else attributes[\"aria-labelledby\"] = ids.label;\n\n return {\n inField: true,\n ref: ref as RefCallback<HTMLElement>,\n id: labelable ? ids.control : undefined,\n name: name ?? field.name,\n disabled: disabled || field.disabled,\n required: required || field.required,\n attributes,\n state,\n };\n}\n\n/**\n * Tells the enclosing Field (if any) that the control's value changed, after the commit that\n * wrote it to the DOM (design/components/field-form.md 2.1: `validate` and the mirror follow the\n * value). Needed where the DOM changes without an `input` / `change` event the Field hears: a\n * Combobox selection (React writes the hidden input), a CheckboxGroup's derived `required`, a\n * Parent checking Items, a rejected action rolling the value back. Nothing at mount.\n *\n * `takeEdit` is read when the value changed: `true` when the user made this change (a shown\n * server error is dismissed, like an `input` event would). Native controls whose own events\n * already dismiss pass nothing.\n */\nexport function useFieldValueChange(value: unknown, takeEdit?: () => boolean): void {\n const notifyChange = use(FieldContext)?.notifyChange;\n const previous = useRef(value);\n // No deps: compares with the last committed value after every commit of the caller.\n useLayoutEffect(() => {\n if (Object.is(previous.current, value)) return;\n previous.current = value;\n const edit = takeEdit?.() ?? false;\n notifyChange?.(edit);\n });\n}\n"],"mappings":";;;;;;;;;;;;AAkEA,SAAgB,gBAAgB,OAG9B;CACA,IAAI,CAAC,MAAM,SAAS,OAAO,CAAC;CAC5B,OAAO;EAAE,GAAG,MAAM;EAAY,MAAM,MAAM;EAAM,UAAU,MAAM;CAAS;AAC3E;;AAGA,SAAgB,aACd,GACA,GACqC;CACrC,OAAO,cAAe,MAAM,KAAA,IAAY,IAAI,MAAM,KAAA,IAAY,IAAI,UAAU,GAAG,CAAC,GAAI,CAAC,GAAG,CAAC,CAAC;AAC5F;;;;;;;;;;AAWA,SAAgB,gBAAgB,SAAsD;CACpF,MAAM,EACJ,WACA,MACA,WAAW,OACX,WAAW,OACX,IACA,WACA,oBACE;CACJ,MAAM,QAAQ,IAAI,YAAY;CAC9B,MAAM,kBAAkB,OAAO;CAE/B,MAAM,MAAM,aACT,YAAyB;EACxB,IAAI,oBAAoB,KAAA,GAAW,aAAa,CAAC;EACjD,OAAO,gBAAgB;GAAE;GAAS;GAAW;GAAU;GAAU;EAAgB,CAAC;CACpF,GACA;EAAC;EAAiB;EAAW;EAAU;EAAU;CAAe,CAClE;CAEA,cACE,UAAU,QAAQ,OAAO,KAAA,GACzB,GAAG,UAAU,uHAEf;CACA,cACE,UAAU,QAAQ,SAAS,KAAA,KAAa,MAAM,SAAS,KAAA,KAAa,SAAS,MAAM,MACnF,GAAG,UAAU,mBAAmB,KAAK,qCAAqC,OAAO,KAAK,gFAExF;CAEA,IAAI,UAAU,MACZ,OAAO;EACL,SAAS;EACT,KAAK,KAAA;EACL;EACA;EACA;EACA;EACA,YAAY,CAAC;EACb,OAAO,KAAA;CACT;CAGF,MAAM,EAAE,KAAK,UAAU;CACvB,MAAM,aAAqC;EACzC,oBAAoB,MAAM,UAAU,GAAG,IAAI,YAAY,GAAG,IAAI,UAAU,IAAI;EAC5E,gBAAgB,MAAM,UAAU,SAAS;EACzC,gBAAgB,SAAS,MAAM,OAAO;CACxC;CACA,IAAI,WAAW,WAAW,KAAK,IAAI;MAC9B,WAAW,qBAAqB,IAAI;CAEzC,OAAO;EACL,SAAS;EACJ;EACL,IAAI,YAAY,IAAI,UAAU,KAAA;EAC9B,MAAM,QAAQ,MAAM;EACpB,UAAU,YAAY,MAAM;EAC5B,UAAU,YAAY,MAAM;EAC5B;EACA;CACF;AACF;;;;;;;;;;;;AAaA,SAAgB,oBAAoB,OAAgB,UAAgC;CAClF,MAAM,eAAe,IAAI,YAAY,CAAC,EAAE;CACxC,MAAM,WAAW,OAAO,KAAK;CAE7B,sBAAsB;EACpB,IAAI,OAAO,GAAG,SAAS,SAAS,KAAK,GAAG;EACxC,SAAS,UAAU;EACnB,MAAM,OAAO,WAAW,KAAK;EAC7B,eAAe,IAAI;CACrB,CAAC;AACH"}
@@ -0,0 +1,44 @@
1
+ "use client";
2
+ import { useEffectEvent, useLayoutEffect, useRef } from "react";
3
+ //#region src/internal/use-form-owner-event.ts
4
+ /**
5
+ * Stable subscription to `type` on the form owner of `ref.current` (conventions 3.8 rule 4,
6
+ * generalised for Field, design/components/field-form.md 14).
7
+ *
8
+ * - The owner (`element.form`, which honours the `form` attribute) is re-read after every commit,
9
+ * but the listener is moved only when the owner changed. It is never removed and re-added on an
10
+ * ordinary re-render: React's automatic reset after a successful `<form action>` runs in the
11
+ * commit's mutation phase, between an effect's cleanup and its next run, so a subscription that
12
+ * followed every commit would miss it.
13
+ * - `handler` always sees the latest committed render's values (`useEffectEvent`).
14
+ * - `ref.current` may be any element: one without a `form` property has no owner.
15
+ */
16
+ function useFormOwnerEvent(ref, type, handler) {
17
+ const handle = useEffectEvent((event, form) => {
18
+ handler(event, form);
19
+ });
20
+ const subscription = useRef(null);
21
+ useLayoutEffect(() => {
22
+ const form = ref.current?.form ?? null;
23
+ const current = subscription.current;
24
+ if (current !== null && current.form === form) return;
25
+ if (current !== null) current.form.removeEventListener(type, current.listener);
26
+ subscription.current = null;
27
+ if (form === null) return;
28
+ const listener = (event) => handle(event, form);
29
+ form.addEventListener(type, listener);
30
+ subscription.current = {
31
+ form,
32
+ listener
33
+ };
34
+ });
35
+ useLayoutEffect(() => () => {
36
+ const current = subscription.current;
37
+ if (current !== null) current.form.removeEventListener(type, current.listener);
38
+ subscription.current = null;
39
+ }, [type]);
40
+ }
41
+ //#endregion
42
+ export { useFormOwnerEvent };
43
+
44
+ //# sourceMappingURL=use-form-owner-event.mjs.map