@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
@@ -1 +1 @@
1
- {"version":3,"file":"dialog-close.mjs","names":[],"sources":["../../src/dialog/dialog-close.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, MouseEvent, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { Action, ActionDetails, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useActionTrigger } from \"../internal/use-action-trigger.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogCloseState {\n /** The `action` is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\n/** Details passed to `Dialog.Close`'s `action`. */\nexport interface DialogCloseDetails extends ActionDetails {\n /** The `value` the dialog closes with (`dialog.returnValue`); `\"\"` without `value`. */\n readonly returnValue: string;\n}\n\nexport interface DialogCloseProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"value\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Runs inside this part's own transition, then closes the dialog with `close(value)`. A\n * rejection reaches the nearest Error Boundary above the Close part and leaves the dialog open.\n * Without `action` the button is a native `command=\"request-close\"` invoker (JS-free).\n */\n action?: Action<[details: DialogCloseDetails]>;\n /** Becomes `dialog.returnValue` (and `details.returnValue` of the close notification). */\n value?: string;\n disabled?: boolean;\n className?: StateValue<string | undefined, DialogCloseState>;\n style?: StateValue<CSSProperties | undefined, DialogCloseState>;\n children?: StateValue<ReactNode, DialogCloseState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, DialogCloseState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * A close button: `<button type=\"button\" commandfor command=\"request-close\">`. Without `action`\n * the browser closes the dialog (a close request: `cancel`, then `close`), with `value` as the\n * `returnValue`. With `action` the click is cancelled, the action runs in this part's own\n * transition (`data-pending` on the button) and the dialog closes with `close(value)` once it\n * resolves; a rejection leaves the dialog open.\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `commandfor`,\n * `command`, `value`, `onClick` (with `action`), `data-pending`, `data-disabled`.\n */\nexport function DialogClose({\n action,\n value,\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogCloseProps) {\n const ctx = useDialogContext(\"Dialog.Close\");\n // The open-generation guard: a close from a previous opening must not close a new one.\n const { pending, run } = useActionTrigger(ctx.openGeneration);\n const { ids, dialogRef, recordCloseIntent } = ctx;\n const hasAction = action !== undefined;\n\n const state: DialogCloseState = { pending, disabled };\n\n const devRef = useCallback(\n (element: HTMLElement) => {\n if (isDev && !hasAction && element.tagName !== \"BUTTON\") {\n console.error(\n `Dialog.Close: rendered a <${element.tagName.toLowerCase()}> without \\`action\\`. Only a ` +\n '<button> runs `command=\"request-close\"`; this element will not close the dialog.',\n );\n }\n // A cleanup, so the merged ref never calls back with `null`.\n return () => {};\n },\n [hasAction],\n );\n\n const onClick = (event: MouseEvent<HTMLElement>) => {\n if (action === undefined) return;\n // Cancelling the click cancels the command.\n event.preventDefault();\n if (disabled) return;\n const native = event.nativeEvent;\n const returnValue = value ?? \"\";\n const details = createActionDetails(native, { returnValue });\n run(\n () => action(details),\n () => {\n // Repeated clicks re-run the action; the first `close()` wins (design 3.4).\n const dialog = dialogRef.current;\n if (dialog === null || !dialog.open) return;\n recordCloseIntent({ reason: \"activate\", event: native });\n dialog.close(returnValue);\n },\n );\n };\n\n const libraryProps: ComponentPropsWithRef<\"button\"> & DataAttributes = {\n ...(rendersButton(render) && { type: \"button\" as const }),\n // Owned: a render element must not re-enable a disabled Close.\n disabled,\n commandfor: ids.dialog,\n command: \"request-close\",\n value,\n ...(hasAction && { onClick }),\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n };\n\n return useRender<ComponentPropsWithRef<\"button\">, DialogCloseState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps,\n ref: devRef,\n });\n}\n\nDialogClose.displayName = \"DialogClose\";\n"],"mappings":";;;;;;;;AA0CA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;;;;;;AAYvC,SAAgB,YAAY,EAC1B,QACA,OACA,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACgB;CACnB,MAAM,MAAM,iBAAiB,cAAc;CAE3C,MAAM,EAAE,SAAS,QAAQ,iBAAiB,IAAI,cAAc;CAC5D,MAAM,EAAE,KAAK,WAAW,sBAAsB;CAC9C,MAAM,YAAY,WAAW,KAAA;CAE7B,MAAM,QAA0B;EAAE;EAAS;CAAS;CAEpD,MAAM,SAAS,aACZ,YAAyB;EACxB,IAAI,SAAS,CAAC,aAAa,QAAQ,YAAY,UAC7C,QAAQ,MACN,6BAA6B,QAAQ,QAAQ,YAAY,EAAE,gHAE7D;EAGF,aAAa,CAAC;CAChB,GACA,CAAC,SAAS,CACZ;CAEA,MAAM,WAAW,UAAmC;EAClD,IAAI,WAAW,KAAA,GAAW;EAE1B,MAAM,eAAe;EACrB,IAAI,UAAU;EACd,MAAM,SAAS,MAAM;EACrB,MAAM,cAAc,SAAS;EAC7B,MAAM,UAAU,oBAAoB,QAAQ,EAAE,YAAY,CAAC;EAC3D,UACQ,OAAO,OAAO,SACd;GAEJ,MAAM,SAAS,UAAU;GACzB,IAAI,WAAW,QAAQ,CAAC,OAAO,MAAM;GACrC,kBAAkB;IAAE,QAAQ;IAAY,OAAO;GAAO,CAAC;GACvD,OAAO,MAAM,WAAW;EAC1B,CACF;CACF;CAEA,MAAM,eAAiE;EACrE,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;EAEvD;EACA,YAAY,IAAI;EAChB,SAAS;EACT;EACA,GAAI,aAAa,EAAE,QAAQ;EAC3B,gBAAgB,SAAS,OAAO;EAChC,iBAAiB,SAAS,QAAQ;CACpC;CAEA,OAAO,UAA6D;EAClE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;EACA,KAAK;CACP,CAAC;AACH;AAEA,YAAY,cAAc"}
1
+ {"version":3,"file":"dialog-close.mjs","names":[],"sources":["../../src/dialog/dialog-close.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, MouseEvent, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { Action, ActionDetails, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useActionTrigger } from \"../internal/use-action-trigger.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogCloseState {\n /** The `action` is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\n/** Details passed to `Dialog.Close`'s `action`. */\nexport interface DialogCloseDetails extends ActionDetails {\n /** The `value` the dialog closes with (`dialog.returnValue`); `\"\"` without `value`. */\n readonly returnValue: string;\n}\n\nexport interface DialogCloseProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"value\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Runs inside this part's own transition (`data-pending` on the button), then closes the\n * dialog with `value` as `returnValue`: in library mode through the Root\n * (`openChangeAction(false)` awaited, view transition if set), in JS-free mode with\n * `close(value)`. A rejection reaches the nearest Error Boundary above the Close part and\n * leaves the dialog open. If the dialog was dismissed and opened again while the action ran,\n * the close is dropped: it belonged to the earlier open. Without `action` in JS-free mode the\n * button is a native `command=\"request-close\"` invoker.\n */\n action?: Action<[details: DialogCloseDetails]>;\n /** Becomes `dialog.returnValue` (and `details.returnValue` of `openChangeAction(false)`). */\n value?: string;\n /** Native `disabled`; owned, so a `render` element cannot re-enable the button. */\n disabled?: boolean;\n className?: StateValue<string | undefined, DialogCloseState>;\n style?: StateValue<CSSProperties | undefined, DialogCloseState>;\n children?: StateValue<ReactNode, DialogCloseState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, DialogCloseState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * A close button: `<button type=\"button\" commandfor command=\"request-close\">`. In JS-free mode\n * without `action` the browser closes the dialog (a close request: `cancel`, then `close`), with\n * `value` as the `returnValue`. In library mode the click is always cancelled and the close goes\n * through the Root (`openChangeAction(false)` awaited, then `close(value)` in the commit, inside\n * the view transition if the Popup's `transition` is set). With `action` the action runs first in\n * this part's own transition (`data-pending` on the button); a rejection leaves the dialog open.\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `commandfor`,\n * `command`, `value`, `onClick` (library mode, or with `action`), `data-pending`,\n * `data-disabled`.\n */\nexport function DialogClose({\n action,\n value,\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogCloseProps) {\n const ctx = useDialogContext(\"Dialog.Close\");\n // The open-generation guard: a close from a previous opening must not close a new one.\n const { pending, run } = useActionTrigger(ctx.openGeneration);\n const { ids, dialogRef, recordCloseIntent } = ctx;\n const isLibrary = ctx.mode === \"library\";\n const hasAction = action !== undefined;\n const installClick = isLibrary || hasAction;\n\n const state: DialogCloseState = { pending, disabled };\n\n const devRef = useCallback(\n (element: HTMLElement) => {\n if (isDev && !installClick && element.tagName !== \"BUTTON\") {\n console.error(\n `Dialog.Close: rendered a <${element.tagName.toLowerCase()}> in JS-free mode without ` +\n '`action`. Only a <button> runs `command=\"request-close\"`; this element will not ' +\n \"close the dialog.\",\n );\n }\n // A cleanup, so the merged ref never calls back with `null`.\n return () => {};\n },\n [installClick],\n );\n\n const close = (event: Event, returnValue: string): void => {\n if (isLibrary) {\n // A library-owned close (design 2.3): awaited, then `close(returnValue)` in the commit. A\n // no-op when `false` is already intended (a repeated click, or a dismissal meanwhile).\n ctx.requestClose(event, \"activate\", returnValue);\n return;\n }\n // JS-free mode with `action`: a browser-owned close; there is no Root state to go through.\n const dialog = dialogRef.current;\n if (dialog === null || !dialog.open) return;\n recordCloseIntent({ reason: \"activate\", event });\n dialog.close(returnValue);\n };\n\n const onClick = (event: MouseEvent<HTMLElement>) => {\n // Cancelling the click cancels the command.\n event.preventDefault();\n if (disabled) return;\n const native = event.nativeEvent;\n const returnValue = value ?? \"\";\n if (action === undefined) {\n close(native, returnValue);\n return;\n }\n const details = createActionDetails(native, { returnValue });\n // Repeated clicks re-run the action; the first close wins (design 3.4). A close whose open\n // was superseded by a newer one (dismissed and re-opened meanwhile) is dropped by the hook.\n run(\n () => action(details),\n () => close(native, returnValue),\n );\n };\n\n const libraryProps: ComponentPropsWithRef<\"button\"> & DataAttributes = {\n ...(rendersButton(render) && { type: \"button\" as const }),\n // Owned: a render element must not re-enable a disabled Close.\n disabled,\n commandfor: ids.dialog,\n command: \"request-close\",\n value,\n ...(installClick && { onClick }),\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n };\n\n return useRender<ComponentPropsWithRef<\"button\">, DialogCloseState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps,\n ref: devRef,\n });\n}\n\nDialogClose.displayName = \"DialogClose\";\n"],"mappings":";;;;;;;;;AA+CA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;;;;;;;;AAcvC,SAAgB,YAAY,EAC1B,QACA,OACA,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACgB;CACnB,MAAM,MAAM,iBAAiB,cAAc;CAE3C,MAAM,EAAE,SAAS,QAAQ,iBAAiB,IAAI,cAAc;CAC5D,MAAM,EAAE,KAAK,WAAW,sBAAsB;CAC9C,MAAM,YAAY,IAAI,SAAS;CAE/B,MAAM,eAAe,aADH,WAAW,KAAA;CAG7B,MAAM,QAA0B;EAAE;EAAS;CAAS;CAEpD,MAAM,SAAS,aACZ,YAAyB;EACxB,IAAI,SAAS,CAAC,gBAAgB,QAAQ,YAAY,UAChD,QAAQ,MACN,6BAA6B,QAAQ,QAAQ,YAAY,EAAE,gIAG7D;EAGF,aAAa,CAAC;CAChB,GACA,CAAC,YAAY,CACf;CAEA,MAAM,SAAS,OAAc,gBAA8B;EACzD,IAAI,WAAW;GAGb,IAAI,aAAa,OAAO,YAAY,WAAW;GAC/C;EACF;EAEA,MAAM,SAAS,UAAU;EACzB,IAAI,WAAW,QAAQ,CAAC,OAAO,MAAM;EACrC,kBAAkB;GAAE,QAAQ;GAAY;EAAM,CAAC;EAC/C,OAAO,MAAM,WAAW;CAC1B;CAEA,MAAM,WAAW,UAAmC;EAElD,MAAM,eAAe;EACrB,IAAI,UAAU;EACd,MAAM,SAAS,MAAM;EACrB,MAAM,cAAc,SAAS;EAC7B,IAAI,WAAW,KAAA,GAAW;GACxB,MAAM,QAAQ,WAAW;GACzB;EACF;EACA,MAAM,UAAU,oBAAoB,QAAQ,EAAE,YAAY,CAAC;EAG3D,UACQ,OAAO,OAAO,SACd,MAAM,QAAQ,WAAW,CACjC;CACF;CAEA,MAAM,eAAiE;EACrE,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;EAEvD;EACA,YAAY,IAAI;EAChB,SAAS;EACT;EACA,GAAI,gBAAgB,EAAE,QAAQ;EAC9B,gBAAgB,SAAS,OAAO;EAChC,iBAAiB,SAAS,QAAQ;CACpC;CAEA,OAAO,UAA6D;EAClE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;EACA,KAAK;CACP,CAAC;AACH;AAEA,YAAY,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"dialog-context.mjs","names":[],"sources":["../../src/dialog/dialog-context.ts"],"sourcesContent":["\"use client\";\nimport { createContext, use } from \"react\";\nimport type { RefObject } from \"react\";\nimport type { ChangeReason } from \"../internal/types.js\";\n\n/**\n * \"static\": JS-free mode, the browser owns open and close through Invoker Commands. \"library\":\n * open is library-owned (a transition keyed on committed state), close is browser-owned and\n * notified (design section 2).\n */\nexport type DialogMode = \"static\" | \"library\";\n\n/**\n * What caused the next `close` event, recorded just before the close happens (design 2.3). The\n * `close` event carries no source; without an intent the close is a `\"dismiss\"`.\n */\nexport interface DialogCloseIntent {\n readonly reason: ChangeReason;\n readonly event: Event | undefined;\n}\n\nexport interface DialogContextValue {\n readonly mode: DialogMode;\n /** `useId`-based ids: the `<dialog>`, its Title and its Description. */\n readonly ids: { readonly dialog: string; readonly title: string; readonly description: string };\n readonly modal: boolean;\n /** Committed open state (library mode); `false` in static mode. */\n readonly committed: boolean;\n /** Optimistic open state (library mode); `false` in static mode. */\n readonly optimistic: boolean;\n /** The Root's transition is pending (library mode). */\n readonly pending: boolean;\n /**\n * Open the dialog through the Root's transition (library mode): a Trigger click, a foreign\n * `show-modal` command, or mount reconciliation. A no-op when the user already intends `true`.\n */\n readonly requestOpen: (event: Event | undefined, reason: ChangeReason) => void;\n /**\n * The dialog closed natively: dispatch the `false` notification unless the user (or the\n * library's own echo) already intends `false`.\n */\n readonly notifyClosed: (event: Event, reason: ChangeReason, returnValue: string) => void;\n /** From `useControllableAction`: the latest in-flight intent, or `null`. */\n readonly latestInFlight: () => { readonly next: boolean } | null;\n /** The registered Trigger element, for the focus-return fallback (design 6.2). */\n readonly triggerRef: RefObject<HTMLElement | null>;\n /** The `<dialog>`, set by the Popup's ref; how Close and Form reach `close(value)`. */\n readonly dialogRef: RefObject<HTMLDialogElement | null>;\n /** Record what causes the next close. Cleared after a task if the dialog did not close. */\n readonly recordCloseIntent: (intent: DialogCloseIntent | undefined) => void;\n /** Read and clear the recorded intent (the Popup's `close` listener). */\n readonly takeCloseIntent: () => DialogCloseIntent | undefined;\n /**\n * Counts the opens of this dialog. A Close action reads it when it starts and skips its close\n * if a newer open started meanwhile (the dialog was dismissed and opened again).\n */\n readonly openGeneration: () => number;\n /** Advance `openGeneration` (an open dispatch, the `<dialog>`'s native `beforetoggle` to open). */\n readonly nextOpenGeneration: () => void;\n}\n\nexport const DialogContext = createContext<DialogContextValue | null>(null);\n\nexport function useDialogContext(part: string): DialogContextValue {\n const context = use(DialogContext);\n if (context === null) {\n throw new Error(`${part} must be rendered inside a Dialog.Root.`);\n }\n return context;\n}\n"],"mappings":";;;AA6DA,MAAa,gBAAgB,cAAyC,IAAI;AAE1E,SAAgB,iBAAiB,MAAkC;CACjE,MAAM,UAAU,IAAI,aAAa;CACjC,IAAI,YAAY,MACd,MAAM,IAAI,MAAM,GAAG,KAAK,wCAAwC;CAElE,OAAO;AACT"}
1
+ {"version":3,"file":"dialog-context.mjs","names":[],"sources":["../../src/dialog/dialog-context.ts"],"sourcesContent":["\"use client\";\nimport { createContext, use } from \"react\";\nimport type { RefObject } from \"react\";\nimport type { ChangeReason } from \"../internal/types.js\";\n\n/**\n * \"static\": JS-free mode, the browser owns open and close through Invoker Commands. \"library\":\n * opening and the closes a part asks for are library-owned (transitions keyed on committed\n * state); dismissals are browser-owned and notified (design section 2).\n */\nexport type DialogMode = \"static\" | \"library\";\n\n/**\n * What caused the next `close` event, recorded just before the close happens (design 2.3). The\n * `close` event carries no source; without an intent the close is a `\"dismiss\"`.\n */\nexport interface DialogCloseIntent {\n readonly reason: ChangeReason;\n readonly event: Event | undefined;\n}\n\nexport interface DialogContextValue {\n readonly mode: DialogMode;\n /** `useId`-based ids: the `<dialog>`, its Title and its Description. */\n readonly ids: { readonly dialog: string; readonly title: string; readonly description: string };\n readonly modal: boolean;\n /** Committed open state (library mode); `false` in static mode. */\n readonly committed: boolean;\n /** Optimistic open state (library mode); `false` in static mode. */\n readonly optimistic: boolean;\n /** The Root's transition is pending (library mode). */\n readonly pending: boolean;\n /**\n * Open the dialog through the Root's transition (library mode): a Trigger click, a foreign\n * `show-modal` command, or mount reconciliation. A no-op when the user already intends `true`.\n */\n readonly requestOpen: (event: Event | undefined, reason: ChangeReason) => void;\n /**\n * The dialog closed natively (a dismissal, not the library's own `close()`): dispatch the\n * `false` notification unless the user already intends `false`.\n */\n readonly notifyClosed: (event: Event, reason: ChangeReason, returnValue: string) => void;\n /**\n * What the user already intends: the latest in-flight dispatch's `next`, else the optimistic\n * value of the last commit. Safe to call from handlers and effects (layout effects included,\n * also in the commit that changed it), and after an `await`. Not during render.\n */\n readonly intended: () => boolean;\n /**\n * A library-owned close (library mode, design 2.3): a Close part, a `method=\"dialog\"` submit,\n * a Form's settle close, a foreign close command. Records `returnValue` for the apply effect's\n * `close()` and dispatches `false` (`openChangeAction(false)` awaited). A no-op when `false` is\n * already intended.\n */\n readonly requestClose: (\n event: Event | undefined,\n reason: ChangeReason,\n returnValue: string,\n ) => void;\n /**\n * Read and clear the `returnValue` of the pending part-initiated close, for the apply effect's\n * `close()`; `\"\"` when there is none (a controlled close).\n */\n readonly takeCloseReturnValue: () => string;\n /** From `useControllableAction`: the latest in-flight intent, or `null`. */\n readonly latestInFlight: () => { readonly next: boolean } | null;\n /** The registered Trigger element, for the focus-return fallback (design 6.2). */\n readonly triggerRef: RefObject<HTMLElement | null>;\n /** The `<dialog>`, set by the Popup's ref; how Close and Form reach `close(value)`. */\n readonly dialogRef: RefObject<HTMLDialogElement | null>;\n /** Record what causes the next close. Cleared after a task if the dialog did not close. */\n readonly recordCloseIntent: (intent: DialogCloseIntent | undefined) => void;\n /** Read and clear the recorded intent (the Popup's `close` listener). */\n readonly takeCloseIntent: () => DialogCloseIntent | undefined;\n /**\n * Counts the opens of this dialog. A Close action reads it when it starts and skips its close\n * if a newer open started meanwhile (the dialog was dismissed and opened again).\n */\n readonly openGeneration: () => number;\n /** Advance `openGeneration` (an open dispatch, the `<dialog>`'s native `beforetoggle` to open). */\n readonly nextOpenGeneration: () => void;\n}\n\nexport const DialogContext = createContext<DialogContextValue | null>(null);\n\nexport function useDialogContext(part: string): DialogContextValue {\n const context = use(DialogContext);\n if (context === null) {\n throw new Error(`${part} must be rendered inside a Dialog.Root.`);\n }\n return context;\n}\n"],"mappings":";;;AAmFA,MAAa,gBAAgB,cAAyC,IAAI;AAE1E,SAAgB,iBAAiB,MAAkC;CACjE,MAAM,UAAU,IAAI,aAAa;CACjC,IAAI,YAAY,MACd,MAAM,IAAI,MAAM,GAAG,KAAK,wCAAwC;CAElE,OAAO;AACT"}
@@ -9,19 +9,29 @@ export interface DialogFormProps extends Omit<ComponentPropsWithRef<"form">, "me
9
9
  /**
10
10
  * React form action (`(formData) => unknown`, may be a Server Function). Passed to the form
11
11
  * unwrapped so React's progressive enhancement is kept; the dialog closes with the submitter's
12
- * `value` as `returnValue` once the action's transition settles (see `closeOnSettle`). A
13
- * rejection leaves the dialog open. Without `action` the form renders `method="dialog"`:
14
- * submitting closes the dialog natively. A string `action` (a URL) is a plain navigating form.
12
+ * `value` as `returnValue` once the action's transition settles (see `closeOnSettle`): in
13
+ * library mode through the Root (`openChangeAction(false)` awaited, view transition if set),
14
+ * in JS-free mode with `close(value)`. A rejection leaves the dialog open. Without `action`
15
+ * the form renders `method="dialog"`: submitting closes the dialog, natively in JS-free mode
16
+ * and through the Root in library mode (a `preventDefault()` in your own `onSubmit` blocks
17
+ * both; `stopPropagation()` there leaves the close to the browser, notified as a dismissal).
18
+ * A string `action` (a URL) is a plain navigating form.
15
19
  */
16
20
  action?: string | ((formData: FormData) => unknown);
17
21
  /**
18
22
  * With a function `action`: close the dialog once the action's transition settles without
19
- * throwing. Default `true`.
23
+ * throwing and, when this form is rendered as a `Form.Root`
24
+ * (`render={<Form.Root errors={state.errors} />}`), with no field error: a settle whose
25
+ * `errors` has a non-empty entry (hidden stale ones included, the `autoReset` rule) keeps
26
+ * the dialog open and the Form focuses the first errored field. Default `true`.
20
27
  *
21
- * Any result that does not throw counts as success, including a `useActionState`-style result
22
- * that reports validation errors: with the default the dialog closes and those errors are never
23
- * seen. Throw to keep the dialog open, or set `closeOnSettle={false}` and close it yourself:
24
- * a `Dialog.Close`, a submitter with `formMethod="dialog"`, or a controlled `open`.
28
+ * Without a `Form.Root` the library never sees a result, so any settle that does not throw
29
+ * counts, including a `useActionState` result that reports errors: render the form as a
30
+ * `Form.Root` to route them, throw to keep the dialog open, or set `closeOnSettle={false}`
31
+ * and close it yourself (a `Dialog.Close`, a submit button in a separate `method="dialog"`
32
+ * form, or a controlled `open`). A `formMethod="dialog"` submitter inside this form cannot
33
+ * close it: React prevents every submit of a form with a function `action` and runs the
34
+ * action again.
25
35
  *
26
36
  * The dialog is not closed either when it was closed (Escape, a Close part) while the action
27
37
  * ran, also if the user re-opened it meanwhile.
@@ -39,10 +49,13 @@ export interface DialogFormProps extends Omit<ComponentPropsWithRef<"form">, "me
39
49
  }
40
50
  /**
41
51
  * A form that completes the dialog. Without `action`: `<form method="dialog">`, so a submit
42
- * closes the dialog natively (JS-free) with the submitter's `value` as `returnValue`. With a
52
+ * closes the dialog with the submitter's `value` as `returnValue` (natively in JS-free mode;
53
+ * in library mode the Popup cancels the submission and closes through the Root). With a
43
54
  * function `action`: `<form action={action}>` (React overrides the method) and a status watcher
44
55
  * that closes the dialog once the action has settled, with the submitter's `value`, unless
45
- * `closeOnSettle` is `false` or the dialog was closed while the action ran.
56
+ * `closeOnSettle` is `false`, the dialog was closed while the action ran, or the form is rendered
57
+ * as a `Form.Root` and the result has a field error (`render={<Form.Root errors={...} />}`, the
58
+ * recommended composition for validation).
46
59
  *
47
60
  * `DialogFormState.pending` is this form's own status; it is not emitted as `data-pending` and
48
61
  * is not folded into any control's `pending` (conventions 2.7). Submit buttons read
@@ -1 +1 @@
1
- {"version":3,"file":"dialog-form.d.mts","names":[],"sources":["../../src/dialog/dialog-form.tsx"],"mappings":";;;iBAQiB;;WAEN;;iBAGM,wBAAwB,KACvC;;;;;;;;EAUA,oBAAoB,UAAU;;;;;;;;;;;;;EAa9B;;;;;EAKA,SAAS;EACT,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,+BAA+B;;;;;;;;;;;;;;;wBAwCrC,aACd,QACA,eACA,QACA,WACA,OACA,UACA,WACG,QACF,kBAAe;yBATF"}
1
+ {"version":3,"file":"dialog-form.d.mts","names":[],"sources":["../../src/dialog/dialog-form.tsx"],"mappings":";;;iBAWiB;;WAEN;;iBAGM,wBAAwB,KACvC;;;;;;;;;;;;EAcA,oBAAoB,UAAU;;;;;;;;;;;;;;;;;;;EAmB9B;;;;;EAKA,SAAS;EACT,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,+BAA+B;;;;;;;;;;;;;;;;;;wBAmBrC,aACd,QACA,eACA,QACA,WACA,OACA,UACA,WACG,QACF,kBAAe;yBATF"}
@@ -1,38 +1,23 @@
1
1
  "use client";
2
- import { resolveStateValue, useRender } from "../internal/use-render.mjs";
2
+ import { resolveStateValue } from "../internal/render-element.mjs";
3
+ import { useRender } from "../internal/use-render.mjs";
4
+ import { useDevWarning } from "../internal/use-dev-warning.mjs";
5
+ import { FormContext } from "../internal/field-context.mjs";
3
6
  import { useDialogContext } from "./dialog-context.mjs";
4
- import { useCallback, useEffect, useRef, useState } from "react";
7
+ import { FormStatusWatcher } from "../internal/form-status-watcher.mjs";
8
+ import { hasAnyError } from "../internal/validity.mjs";
9
+ import { use, useCallback, useRef, useState } from "react";
5
10
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
- import { useFormStatus } from "react-dom";
7
11
  //#region src/dialog/dialog-form.tsx
8
12
  /**
9
- * Observes React's form channel (design 8.1): `useFormStatus().pending` covers the action's
10
- * whole transition, including the Suspense wait of what it re-renders. On `true → false` the
11
- * submission succeeded (a rejection unmounts the form through the nearest Error Boundary, so the
12
- * edge is never seen).
13
- */
14
- function FormStatusWatcher({ onPendingChange, onSettled }) {
15
- const { pending } = useFormStatus();
16
- const previous = useRef(false);
17
- useEffect(() => {
18
- const was = previous.current;
19
- previous.current = pending;
20
- if (was === pending) return;
21
- onPendingChange(pending);
22
- if (was && !pending) onSettled();
23
- }, [
24
- pending,
25
- onPendingChange,
26
- onSettled
27
- ]);
28
- return null;
29
- }
30
- /**
31
13
  * A form that completes the dialog. Without `action`: `<form method="dialog">`, so a submit
32
- * closes the dialog natively (JS-free) with the submitter's `value` as `returnValue`. With a
14
+ * closes the dialog with the submitter's `value` as `returnValue` (natively in JS-free mode;
15
+ * in library mode the Popup cancels the submission and closes through the Root). With a
33
16
  * function `action`: `<form action={action}>` (React overrides the method) and a status watcher
34
17
  * that closes the dialog once the action has settled, with the submitter's `value`, unless
35
- * `closeOnSettle` is `false` or the dialog was closed while the action ran.
18
+ * `closeOnSettle` is `false`, the dialog was closed while the action ran, or the form is rendered
19
+ * as a `Form.Root` and the result has a field error (`render={<Form.Root errors={...} />}`, the
20
+ * recommended composition for validation).
36
21
  *
37
22
  * `DialogFormState.pending` is this form's own status; it is not emitted as `data-pending` and
38
23
  * is not folded into any control's `pending` (conventions 2.7). Submit buttons read
@@ -41,7 +26,9 @@ function FormStatusWatcher({ onPendingChange, onSettled }) {
41
26
  * Owned keys (conventions 1.2): `method` (except with a string `action`), `action`.
42
27
  */
43
28
  function DialogForm({ action, closeOnSettle = true, method, className, style, children, render, ...rest }) {
44
- const { dialogRef, recordCloseIntent } = useDialogContext("Dialog.Form");
29
+ const ctx = useDialogContext("Dialog.Form");
30
+ const { dialogRef, recordCloseIntent, requestClose } = ctx;
31
+ const isLibrary = ctx.mode === "library";
45
32
  const [pending, setPending] = useState(false);
46
33
  const submission = useRef(null);
47
34
  const closedSinceSubmit = useRef(false);
@@ -67,21 +54,29 @@ function DialogForm({ action, closeOnSettle = true, method, className, style, ch
67
54
  dialog?.removeEventListener("close", onClose);
68
55
  };
69
56
  }, []);
70
- const onSettled = useCallback(() => {
57
+ const onSettled = useCallback((clean) => {
71
58
  const dialog = dialogRef.current;
72
59
  const submitted = submission.current;
73
60
  submission.current = null;
74
61
  if (!closeOnSettle || closedSinceSubmit.current) return;
62
+ if (!clean) return;
75
63
  if (dialog === null || !dialog.open) return;
64
+ const returnValue = submitted?.returnValue ?? "";
65
+ if (isLibrary) {
66
+ requestClose(submitted?.event, "activate", returnValue);
67
+ return;
68
+ }
76
69
  recordCloseIntent({
77
70
  reason: "activate",
78
71
  event: submitted?.event
79
72
  });
80
- dialog.close(submitted?.returnValue ?? "");
73
+ dialog.close(returnValue);
81
74
  }, [
82
75
  closeOnSettle,
83
76
  dialogRef,
84
- recordCloseIntent
77
+ isLibrary,
78
+ recordCloseIntent,
79
+ requestClose
85
80
  ]);
86
81
  return useRender({
87
82
  defaultElement: "form",
@@ -89,7 +84,9 @@ function DialogForm({ action, closeOnSettle = true, method, className, style, ch
89
84
  state: { pending },
90
85
  className,
91
86
  style,
92
- children: (s) => /* @__PURE__ */ jsxs(Fragment, { children: [resolveStateValue(children, s), watches && /* @__PURE__ */ jsx(FormStatusWatcher, {
87
+ children: (s) => /* @__PURE__ */ jsxs(Fragment, { children: [resolveStateValue(children, s), /* @__PURE__ */ jsx(DialogFormSettle, {
88
+ watches,
89
+ missingAction: action === void 0,
93
90
  onPendingChange: setPending,
94
91
  onSettled
95
92
  })] }),
@@ -102,6 +99,28 @@ function DialogForm({ action, closeOnSettle = true, method, className, style, ch
102
99
  });
103
100
  }
104
101
  DialogForm.displayName = "DialogForm";
102
+ /**
103
+ * Rendered among the form's children, so it reads the `FormContext` exactly when the form is a
104
+ * `Form.Root` (either orientation of the `render` composition): the settle close then waits for
105
+ * a result with no field error (`hasAnyError`, the `autoReset` predicate, hidden stale entries
106
+ * included).
107
+ */
108
+ function DialogFormSettle({ watches, missingAction, onPendingChange, onSettled }) {
109
+ const form = use(FormContext);
110
+ useDevWarning(form !== null && missingAction, "`Dialog.Form` rendered as a `Form.Root` needs a function `action`; without one the form navigates instead of closing the dialog. Render a plain `<Dialog.Form>` for a `method=\"dialog\"` form.");
111
+ const isForm = form !== null;
112
+ const errors = form?.errors;
113
+ const settled = useCallback(() => onSettled(!isForm || !hasAnyError(errors)), [
114
+ isForm,
115
+ errors,
116
+ onSettled
117
+ ]);
118
+ if (!watches) return null;
119
+ return /* @__PURE__ */ jsx(FormStatusWatcher, {
120
+ onPendingChange,
121
+ onSettled: settled
122
+ });
123
+ }
105
124
  //#endregion
106
125
  export { DialogForm };
107
126
 
@@ -1 +1 @@
1
- {"version":3,"file":"dialog-form.mjs","names":[],"sources":["../../src/dialog/dialog-form.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { useFormStatus } from \"react-dom\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogFormState {\n /** `useFormStatus().pending` of this form, exposed for `className` / `children` functions. */\n readonly pending: boolean;\n}\n\nexport interface DialogFormProps extends Omit<\n ComponentPropsWithRef<\"form\">,\n \"method\" | \"action\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * React form action (`(formData) => unknown`, may be a Server Function). Passed to the form\n * unwrapped so React's progressive enhancement is kept; the dialog closes with the submitter's\n * `value` as `returnValue` once the action's transition settles (see `closeOnSettle`). A\n * rejection leaves the dialog open. Without `action` the form renders `method=\"dialog\"`:\n * submitting closes the dialog natively. A string `action` (a URL) is a plain navigating form.\n */\n action?: string | ((formData: FormData) => unknown);\n /**\n * With a function `action`: close the dialog once the action's transition settles without\n * throwing. Default `true`.\n *\n * Any result that does not throw counts as success, including a `useActionState`-style result\n * that reports validation errors: with the default the dialog closes and those errors are never\n * seen. Throw to keep the dialog open, or set `closeOnSettle={false}` and close it yourself:\n * a `Dialog.Close`, a submitter with `formMethod=\"dialog\"`, or a controlled `open`.\n *\n * The dialog is not closed either when it was closed (Escape, a Close part) while the action\n * ran, also if the user re-opened it meanwhile.\n */\n closeOnSettle?: boolean;\n /**\n * Only with a string `action` (a navigating form, for example `\"post\"`). Owned otherwise:\n * `\"dialog\"` without `action`, React's own with a function `action`.\n */\n method?: ComponentPropsWithRef<\"form\">[\"method\"];\n className?: StateValue<string | undefined, DialogFormState>;\n style?: StateValue<CSSProperties | undefined, DialogFormState>;\n children?: StateValue<ReactNode, DialogFormState>;\n render?: RenderProp<ComponentPropsWithRef<\"form\">, DialogFormState>;\n}\n\ninterface FormStatusWatcherProps {\n readonly onPendingChange: (pending: boolean) => void;\n readonly onSettled: () => void;\n}\n\n/**\n * Observes React's form channel (design 8.1): `useFormStatus().pending` covers the action's\n * whole transition, including the Suspense wait of what it re-renders. On `true → false` the\n * submission succeeded (a rejection unmounts the form through the nearest Error Boundary, so the\n * edge is never seen).\n */\nfunction FormStatusWatcher({ onPendingChange, onSettled }: FormStatusWatcherProps) {\n const { pending } = useFormStatus();\n const previous = useRef(false);\n useEffect(() => {\n const was = previous.current;\n previous.current = pending;\n if (was === pending) return;\n onPendingChange(pending);\n if (was && !pending) onSettled();\n }, [pending, onPendingChange, onSettled]);\n return null;\n}\n\n/**\n * A form that completes the dialog. Without `action`: `<form method=\"dialog\">`, so a submit\n * closes the dialog natively (JS-free) with the submitter's `value` as `returnValue`. With a\n * function `action`: `<form action={action}>` (React overrides the method) and a status watcher\n * that closes the dialog once the action has settled, with the submitter's `value`, unless\n * `closeOnSettle` is `false` or the dialog was closed while the action ran.\n *\n * `DialogFormState.pending` is this form's own status; it is not emitted as `data-pending` and\n * is not folded into any control's `pending` (conventions 2.7). Submit buttons read\n * `useFormStatus()` themselves.\n *\n * Owned keys (conventions 1.2): `method` (except with a string `action`), `action`.\n */\nexport function DialogForm({\n action,\n closeOnSettle = true,\n method,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogFormProps) {\n const ctx = useDialogContext(\"Dialog.Form\");\n const { dialogRef, recordCloseIntent } = ctx;\n const [pending, setPending] = useState(false);\n const submission = useRef<{ event: Event; returnValue: string } | null>(null);\n // The dialog closed after the last submit (Escape, a Close part): the settle must not close it\n // again, also when the user re-opened it meanwhile (the review's walkthrough 11.8 variant).\n const closedSinceSubmit = useRef(false);\n const watches = typeof action === \"function\";\n\n // The submitter's `value` comes from the native `submit` event (capture, before React's\n // handler runs the action).\n const libraryRef = useCallback((form: HTMLFormElement) => {\n const onSubmit = (event: Event) => {\n if (event.target !== form) return;\n const submitter = (event as SubmitEvent).submitter as\n | HTMLButtonElement\n | HTMLInputElement\n | null;\n submission.current = { event, returnValue: submitter?.value ?? \"\" };\n closedSinceSubmit.current = false;\n };\n const dialog = form.closest(\"dialog\");\n const onClose = (event: Event) => {\n if (event.target === dialog) closedSinceSubmit.current = true;\n };\n form.addEventListener(\"submit\", onSubmit, true);\n dialog?.addEventListener(\"close\", onClose);\n return () => {\n form.removeEventListener(\"submit\", onSubmit, true);\n dialog?.removeEventListener(\"close\", onClose);\n };\n }, []);\n\n const onSettled = useCallback(() => {\n const dialog = dialogRef.current;\n const submitted = submission.current;\n submission.current = null;\n if (!closeOnSettle || closedSinceSubmit.current) return;\n if (dialog === null || !dialog.open) return;\n recordCloseIntent({ reason: \"activate\", event: submitted?.event });\n dialog.close(submitted?.returnValue ?? \"\");\n }, [closeOnSettle, dialogRef, recordCloseIntent]);\n\n const state: DialogFormState = { pending };\n\n return useRender<ComponentPropsWithRef<\"form\">, DialogFormState>({\n defaultElement: \"form\",\n render,\n state,\n className,\n style,\n children: (s) => (\n <>\n {resolveStateValue(children, s)}\n {watches && <FormStatusWatcher onPendingChange={setPending} onSettled={onSettled} />}\n </>\n ),\n props: rest,\n libraryProps: {\n method: action === undefined ? \"dialog\" : typeof action === \"string\" ? method : undefined,\n // React's own type wants `void | Promise<void>`; the library's actions return `unknown`\n // (conventions 2.2) and React ignores the result.\n action: action as ComponentPropsWithRef<\"form\">[\"action\"],\n },\n ref: libraryRef,\n });\n}\n\nDialogForm.displayName = \"DialogForm\";\n"],"mappings":";;;;;;;;;;;;;AA4DA,SAAS,kBAAkB,EAAE,iBAAiB,aAAqC;CACjF,MAAM,EAAE,YAAY,cAAc;CAClC,MAAM,WAAW,OAAO,KAAK;CAC7B,gBAAgB;EACd,MAAM,MAAM,SAAS;EACrB,SAAS,UAAU;EACnB,IAAI,QAAQ,SAAS;EACrB,gBAAgB,OAAO;EACvB,IAAI,OAAO,CAAC,SAAS,UAAU;CACjC,GAAG;EAAC;EAAS;EAAiB;CAAS,CAAC;CACxC,OAAO;AACT;;;;;;;;;;;;;;AAeA,SAAgB,WAAW,EACzB,QACA,gBAAgB,MAChB,QACA,WACA,OACA,UACA,QACA,GAAG,QACe;CAElB,MAAM,EAAE,WAAW,sBADP,iBAAiB,aACc;CAC3C,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,MAAM,aAAa,OAAqD,IAAI;CAG5E,MAAM,oBAAoB,OAAO,KAAK;CACtC,MAAM,UAAU,OAAO,WAAW;CAIlC,MAAM,aAAa,aAAa,SAA0B;EACxD,MAAM,YAAY,UAAiB;GACjC,IAAI,MAAM,WAAW,MAAM;GAC3B,MAAM,YAAa,MAAsB;GAIzC,WAAW,UAAU;IAAE;IAAO,aAAa,WAAW,SAAS;GAAG;GAClE,kBAAkB,UAAU;EAC9B;EACA,MAAM,SAAS,KAAK,QAAQ,QAAQ;EACpC,MAAM,WAAW,UAAiB;GAChC,IAAI,MAAM,WAAW,QAAQ,kBAAkB,UAAU;EAC3D;EACA,KAAK,iBAAiB,UAAU,UAAU,IAAI;EAC9C,QAAQ,iBAAiB,SAAS,OAAO;EACzC,aAAa;GACX,KAAK,oBAAoB,UAAU,UAAU,IAAI;GACjD,QAAQ,oBAAoB,SAAS,OAAO;EAC9C;CACF,GAAG,CAAC,CAAC;CAEL,MAAM,YAAY,kBAAkB;EAClC,MAAM,SAAS,UAAU;EACzB,MAAM,YAAY,WAAW;EAC7B,WAAW,UAAU;EACrB,IAAI,CAAC,iBAAiB,kBAAkB,SAAS;EACjD,IAAI,WAAW,QAAQ,CAAC,OAAO,MAAM;EACrC,kBAAkB;GAAE,QAAQ;GAAY,OAAO,WAAW;EAAM,CAAC;EACjE,OAAO,MAAM,WAAW,eAAe,EAAE;CAC3C,GAAG;EAAC;EAAe;EAAW;CAAiB,CAAC;CAIhD,OAAO,UAA0D;EAC/D,gBAAgB;EAChB;EACA,OAAA,EAL+B,QAK3B;EACJ;EACA;EACA,WAAW,MACT,qBAAA,UAAA,EAAA,UAAA,CACG,kBAAkB,UAAU,CAAC,GAC7B,WAAW,oBAAC,mBAAD;GAAmB,iBAAiB;GAAuB;EAAY,CAAA,CACnF,EAAA,CAAA;EAEJ,OAAO;EACP,cAAc;GACZ,QAAQ,WAAW,KAAA,IAAY,WAAW,OAAO,WAAW,WAAW,SAAS,KAAA;GAGxE;EACV;EACA,KAAK;CACP,CAAC;AACH;AAEA,WAAW,cAAc"}
1
+ {"version":3,"file":"dialog-form.mjs","names":[],"sources":["../../src/dialog/dialog-form.tsx"],"sourcesContent":["\"use client\";\nimport { use, useCallback, useRef, useState } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { FormContext } from \"../internal/field-context.js\";\nimport { FormStatusWatcher } from \"../internal/form-status-watcher.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport { hasAnyError } from \"../internal/validity.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogFormState {\n /** `useFormStatus().pending` of this form, exposed for `className` / `children` functions. */\n readonly pending: boolean;\n}\n\nexport interface DialogFormProps extends Omit<\n ComponentPropsWithRef<\"form\">,\n \"method\" | \"action\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * React form action (`(formData) => unknown`, may be a Server Function). Passed to the form\n * unwrapped so React's progressive enhancement is kept; the dialog closes with the submitter's\n * `value` as `returnValue` once the action's transition settles (see `closeOnSettle`): in\n * library mode through the Root (`openChangeAction(false)` awaited, view transition if set),\n * in JS-free mode with `close(value)`. A rejection leaves the dialog open. Without `action`\n * the form renders `method=\"dialog\"`: submitting closes the dialog, natively in JS-free mode\n * and through the Root in library mode (a `preventDefault()` in your own `onSubmit` blocks\n * both; `stopPropagation()` there leaves the close to the browser, notified as a dismissal).\n * A string `action` (a URL) is a plain navigating form.\n */\n action?: string | ((formData: FormData) => unknown);\n /**\n * With a function `action`: close the dialog once the action's transition settles without\n * throwing and, when this form is rendered as a `Form.Root`\n * (`render={<Form.Root errors={state.errors} />}`), with no field error: a settle whose\n * `errors` has a non-empty entry (hidden stale ones included, the `autoReset` rule) keeps\n * the dialog open and the Form focuses the first errored field. Default `true`.\n *\n * Without a `Form.Root` the library never sees a result, so any settle that does not throw\n * counts, including a `useActionState` result that reports errors: render the form as a\n * `Form.Root` to route them, throw to keep the dialog open, or set `closeOnSettle={false}`\n * and close it yourself (a `Dialog.Close`, a submit button in a separate `method=\"dialog\"`\n * form, or a controlled `open`). A `formMethod=\"dialog\"` submitter inside this form cannot\n * close it: React prevents every submit of a form with a function `action` and runs the\n * action again.\n *\n * The dialog is not closed either when it was closed (Escape, a Close part) while the action\n * ran, also if the user re-opened it meanwhile.\n */\n closeOnSettle?: boolean;\n /**\n * Only with a string `action` (a navigating form, for example `\"post\"`). Owned otherwise:\n * `\"dialog\"` without `action`, React's own with a function `action`.\n */\n method?: ComponentPropsWithRef<\"form\">[\"method\"];\n className?: StateValue<string | undefined, DialogFormState>;\n style?: StateValue<CSSProperties | undefined, DialogFormState>;\n children?: StateValue<ReactNode, DialogFormState>;\n render?: RenderProp<ComponentPropsWithRef<\"form\">, DialogFormState>;\n}\n\n/**\n * A form that completes the dialog. Without `action`: `<form method=\"dialog\">`, so a submit\n * closes the dialog with the submitter's `value` as `returnValue` (natively in JS-free mode;\n * in library mode the Popup cancels the submission and closes through the Root). With a\n * function `action`: `<form action={action}>` (React overrides the method) and a status watcher\n * that closes the dialog once the action has settled, with the submitter's `value`, unless\n * `closeOnSettle` is `false`, the dialog was closed while the action ran, or the form is rendered\n * as a `Form.Root` and the result has a field error (`render={<Form.Root errors={...} />}`, the\n * recommended composition for validation).\n *\n * `DialogFormState.pending` is this form's own status; it is not emitted as `data-pending` and\n * is not folded into any control's `pending` (conventions 2.7). Submit buttons read\n * `useFormStatus()` themselves.\n *\n * Owned keys (conventions 1.2): `method` (except with a string `action`), `action`.\n */\nexport function DialogForm({\n action,\n closeOnSettle = true,\n method,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogFormProps) {\n const ctx = useDialogContext(\"Dialog.Form\");\n const { dialogRef, recordCloseIntent, requestClose } = ctx;\n const isLibrary = ctx.mode === \"library\";\n const [pending, setPending] = useState(false);\n const submission = useRef<{ event: Event; returnValue: string } | null>(null);\n // The dialog closed after the last submit (Escape, a Close part): the settle must not close it\n // again, also when the user re-opened it meanwhile (the review's walkthrough 11.8 variant).\n const closedSinceSubmit = useRef(false);\n const watches = typeof action === \"function\";\n\n // The submitter's `value` comes from the native `submit` event (capture, before React's\n // handler runs the action).\n const libraryRef = useCallback((form: HTMLFormElement) => {\n const onSubmit = (event: Event) => {\n if (event.target !== form) return;\n const submitter = (event as SubmitEvent).submitter as\n | HTMLButtonElement\n | HTMLInputElement\n | null;\n submission.current = { event, returnValue: submitter?.value ?? \"\" };\n closedSinceSubmit.current = false;\n };\n const dialog = form.closest(\"dialog\");\n const onClose = (event: Event) => {\n if (event.target === dialog) closedSinceSubmit.current = true;\n };\n form.addEventListener(\"submit\", onSubmit, true);\n dialog?.addEventListener(\"close\", onClose);\n return () => {\n form.removeEventListener(\"submit\", onSubmit, true);\n dialog?.removeEventListener(\"close\", onClose);\n };\n }, []);\n\n // `clean`: no field error in the settle's result (always `true` without a `Form.Root`).\n const onSettled = useCallback(\n (clean: boolean) => {\n const dialog = dialogRef.current;\n const submitted = submission.current;\n submission.current = null;\n if (!closeOnSettle || closedSinceSubmit.current) return;\n // A result with field errors keeps the dialog open; the Form.Root focuses the first errored\n // field (design/components/dialog.md, \"Dialog.Form and Form.Root\").\n if (!clean) return;\n if (dialog === null || !dialog.open) return;\n const returnValue = submitted?.returnValue ?? \"\";\n if (isLibrary) {\n // A library-owned close (design 2.3): `openChangeAction(false)` awaited, then\n // `close(returnValue)` in the commit, inside the view transition if set.\n requestClose(submitted?.event, \"activate\", returnValue);\n return;\n }\n recordCloseIntent({ reason: \"activate\", event: submitted?.event });\n dialog.close(returnValue);\n },\n [closeOnSettle, dialogRef, isLibrary, recordCloseIntent, requestClose],\n );\n\n const state: DialogFormState = { pending };\n\n return useRender<ComponentPropsWithRef<\"form\">, DialogFormState>({\n defaultElement: \"form\",\n render,\n state,\n className,\n style,\n children: (s) => (\n <>\n {resolveStateValue(children, s)}\n <DialogFormSettle\n watches={watches}\n missingAction={action === undefined}\n onPendingChange={setPending}\n onSettled={onSettled}\n />\n </>\n ),\n props: rest,\n libraryProps: {\n method: action === undefined ? \"dialog\" : typeof action === \"string\" ? method : undefined,\n // React's own type wants `void | Promise<void>`; the library's actions return `unknown`\n // (conventions 2.2) and React ignores the result.\n action: action as ComponentPropsWithRef<\"form\">[\"action\"],\n },\n ref: libraryRef,\n });\n}\n\nDialogForm.displayName = \"DialogForm\";\n\n/**\n * Rendered among the form's children, so it reads the `FormContext` exactly when the form is a\n * `Form.Root` (either orientation of the `render` composition): the settle close then waits for\n * a result with no field error (`hasAnyError`, the `autoReset` predicate, hidden stale entries\n * included).\n */\nfunction DialogFormSettle({\n watches,\n missingAction,\n onPendingChange,\n onSettled,\n}: {\n readonly watches: boolean;\n readonly missingAction: boolean;\n readonly onPendingChange: (pending: boolean) => void;\n readonly onSettled: (clean: boolean) => void;\n}) {\n const form = use(FormContext);\n useDevWarning(\n form !== null && missingAction,\n \"`Dialog.Form` rendered as a `Form.Root` needs a function `action`; without one the form \" +\n \"navigates instead of closing the dialog. Render a plain `<Dialog.Form>` for a \" +\n '`method=\"dialog\"` form.',\n );\n const isForm = form !== null;\n const errors = form?.errors;\n const settled = useCallback(\n () => onSettled(!isForm || !hasAnyError(errors)),\n [isForm, errors, onSettled],\n );\n if (!watches) return null;\n return <FormStatusWatcher onPendingChange={onPendingChange} onSettled={settled} />;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA8EA,SAAgB,WAAW,EACzB,QACA,gBAAgB,MAChB,QACA,WACA,OACA,UACA,QACA,GAAG,QACe;CAClB,MAAM,MAAM,iBAAiB,aAAa;CAC1C,MAAM,EAAE,WAAW,mBAAmB,iBAAiB;CACvD,MAAM,YAAY,IAAI,SAAS;CAC/B,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,MAAM,aAAa,OAAqD,IAAI;CAG5E,MAAM,oBAAoB,OAAO,KAAK;CACtC,MAAM,UAAU,OAAO,WAAW;CAIlC,MAAM,aAAa,aAAa,SAA0B;EACxD,MAAM,YAAY,UAAiB;GACjC,IAAI,MAAM,WAAW,MAAM;GAC3B,MAAM,YAAa,MAAsB;GAIzC,WAAW,UAAU;IAAE;IAAO,aAAa,WAAW,SAAS;GAAG;GAClE,kBAAkB,UAAU;EAC9B;EACA,MAAM,SAAS,KAAK,QAAQ,QAAQ;EACpC,MAAM,WAAW,UAAiB;GAChC,IAAI,MAAM,WAAW,QAAQ,kBAAkB,UAAU;EAC3D;EACA,KAAK,iBAAiB,UAAU,UAAU,IAAI;EAC9C,QAAQ,iBAAiB,SAAS,OAAO;EACzC,aAAa;GACX,KAAK,oBAAoB,UAAU,UAAU,IAAI;GACjD,QAAQ,oBAAoB,SAAS,OAAO;EAC9C;CACF,GAAG,CAAC,CAAC;CAGL,MAAM,YAAY,aACf,UAAmB;EAClB,MAAM,SAAS,UAAU;EACzB,MAAM,YAAY,WAAW;EAC7B,WAAW,UAAU;EACrB,IAAI,CAAC,iBAAiB,kBAAkB,SAAS;EAGjD,IAAI,CAAC,OAAO;EACZ,IAAI,WAAW,QAAQ,CAAC,OAAO,MAAM;EACrC,MAAM,cAAc,WAAW,eAAe;EAC9C,IAAI,WAAW;GAGb,aAAa,WAAW,OAAO,YAAY,WAAW;GACtD;EACF;EACA,kBAAkB;GAAE,QAAQ;GAAY,OAAO,WAAW;EAAM,CAAC;EACjE,OAAO,MAAM,WAAW;CAC1B,GACA;EAAC;EAAe;EAAW;EAAW;EAAmB;CAAY,CACvE;CAIA,OAAO,UAA0D;EAC/D,gBAAgB;EAChB;EACA,OAAA,EAL+B,QAK3B;EACJ;EACA;EACA,WAAW,MACT,qBAAA,UAAA,EAAA,UAAA,CACG,kBAAkB,UAAU,CAAC,GAC9B,oBAAC,kBAAD;GACW;GACT,eAAe,WAAW,KAAA;GAC1B,iBAAiB;GACN;EACZ,CAAA,CACD,EAAA,CAAA;EAEJ,OAAO;EACP,cAAc;GACZ,QAAQ,WAAW,KAAA,IAAY,WAAW,OAAO,WAAW,WAAW,SAAS,KAAA;GAGxE;EACV;EACA,KAAK;CACP,CAAC;AACH;AAEA,WAAW,cAAc;;;;;;;AAQzB,SAAS,iBAAiB,EACxB,SACA,eACA,iBACA,aAMC;CACD,MAAM,OAAO,IAAI,WAAW;CAC5B,cACE,SAAS,QAAQ,eACjB,iMAGF;CACA,MAAM,SAAS,SAAS;CACxB,MAAM,SAAS,MAAM;CACrB,MAAM,UAAU,kBACR,UAAU,CAAC,UAAU,CAAC,YAAY,MAAM,CAAC,GAC/C;EAAC;EAAQ;EAAQ;CAAS,CAC5B;CACA,IAAI,CAAC,SAAS,OAAO;CACrB,OAAO,oBAAC,mBAAD;EAAoC;EAAiB,WAAW;CAAU,CAAA;AACnF"}
@@ -44,8 +44,10 @@ export interface DialogPopupProps extends Omit<ComponentPropsWithRef<"dialog">,
44
44
  experimental_closedBy?: boolean;
45
45
  /**
46
46
  * Library mode only. Forwarded to the `<ViewTransition default>` around the dialog. Animates
47
- * the opening commit and content changes while open; the close is the browser's and animates
48
- * with CSS. Ignored (dev warning) in JS-free mode.
47
+ * the opening commit, content changes while open, and every library-driven close (a Close
48
+ * part, a Form submit or settled action, a foreign close command, a controlled
49
+ * `open={false}`), in all engines; dismissals (Escape, a click outside) are the browser's and
50
+ * animate with CSS only. Ignored (dev warning) in JS-free mode.
49
51
  *
50
52
  * Static: without `transition` the part renders no `<ViewTransition>`; changing it between
51
53
  * `undefined` and a value remounts the element. Any value, `"none"` included, makes a mount or
@@ -67,8 +69,12 @@ export interface DialogPopupProps extends Omit<ComponentPropsWithRef<"dialog">,
67
69
  *
68
70
  * In library mode the children render from committed state, so content that suspends keeps the
69
71
  * dialog closed (and the Trigger pending) until it is ready; `showModal()` runs in the layout
70
- * effect of the commit that mounted them. Closing is the browser's: the native `close` event
71
- * becomes `openChangeAction(false, { reason, returnValue })`.
72
+ * effect of the commit that mounted them. A close a part asks for (Close, a `method="dialog"`
73
+ * submit, a Form's settle close, a foreign close command) is cancelled and goes through the
74
+ * Root: `openChangeAction(false)` is awaited and `close(returnValue)` runs in the layout effect
75
+ * of the commit that ends it, inside the view transition if `transition` is set. A dismissal
76
+ * (Escape, a click outside) is the browser's: the native `close` event becomes
77
+ * `openChangeAction(false, { reason: "dismiss", returnValue })`.
72
78
  *
73
79
  * Owned keys (conventions 1.2): `id`, `open`, `closedby`, `aria-labelledby` and
74
80
  * `aria-describedby` (token-merged), `data-modal`, `suppressHydrationWarning`.
@@ -1 +1 @@
1
- {"version":3,"file":"dialog-popup.d.mts","names":[],"sources":["../../src/dialog/dialog-popup.tsx"],"mappings":";;;iBA0BiB;;WAEN;WACA;;iBAGM,yBAAyB,KACxC;;;;;;;;;;;EAaA;;;;;;;;;;EAUA,eAAe,UAAU;;;;;;;EAOzB;;;;;;;EAOA;;;;;;;;;;;EAWA,aAAa;EACb,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,iCAAiC;;;;;;;;;;;;;;;;wBA2FvC,cACd,aACA,cACA,cACA,uBACA,YACA,WACA,OACA,UACA,WACG,QACF,mBAAgB;yBAXH"}
1
+ {"version":3,"file":"dialog-popup.d.mts","names":[],"sources":["../../src/dialog/dialog-popup.tsx"],"mappings":";;;iBA6BiB;;WAEN;WACA;;iBAGM,yBAAyB,KACxC;;;;;;;;;;;EAaA;;;;;;;;;;EAUA,eAAe,UAAU;;;;;;;EAOzB;;;;;;;EAOA;;;;;;;;;;;;;EAaA,aAAa;EACb,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,iCAAiC;;;;;;;;;;;;;;;;;;;;wBAiGvC,cACd,aACA,cACA,cACA,uBACA,YACA,WACA,OACA,UACA,WACG,QACF,mBAAgB;yBAXH"}
@@ -1,10 +1,15 @@
1
1
  "use client";
2
2
  import { useTransitionBoundary } from "../internal/transition-boundary.mjs";
3
3
  import { dataAttr } from "../internal/data-attr.mjs";
4
- import { resolveStateValue, useRender } from "../internal/use-render.mjs";
4
+ import { resolveStateValue } from "../internal/render-element.mjs";
5
+ import { useRender } from "../internal/use-render.mjs";
6
+ import { ToolbarContext } from "../internal/toolbar-context.mjs";
5
7
  import { useDevWarning } from "../internal/use-dev-warning.mjs";
6
8
  import { useDialogContext } from "./dialog-context.mjs";
7
- import { Activity, startTransition, useCallback, useEffect, useEffectEvent, useLayoutEffect, useRef, useState } from "react";
9
+ import { useContentFocusProbe } from "../internal/content-focus-probe.mjs";
10
+ import { useExitAnimation } from "../internal/use-exit-animation.mjs";
11
+ import { useModalRegistration } from "../internal/use-modal-registration.mjs";
12
+ import { Activity, useCallback, useEffect, useEffectEvent, useLayoutEffect, useRef, useState } from "react";
8
13
  import { jsx } from "react/jsx-runtime";
9
14
  //#region src/dialog/dialog-popup.tsx
10
15
  const isDev = process.env.NODE_ENV !== "production";
@@ -52,10 +57,10 @@ function onBackdrop(dialog, event) {
52
57
  const rect = dialog.getBoundingClientRect();
53
58
  return !(rect.top <= clientY && clientY <= rect.bottom && rect.left <= clientX && clientX <= rect.right);
54
59
  }
55
- /** An animation that ends: infinite ones (a spinner) would hold the content forever. */
56
- function ends(animation) {
57
- const end = animation.effect?.getComputedTiming().endTime;
58
- return typeof end === "number" && Number.isFinite(end);
60
+ /** The submitter's or invoker's `value` (`""` without one, or where the engine gives no source). */
61
+ function valueOf(element) {
62
+ if (element instanceof HTMLButtonElement || element instanceof HTMLInputElement) return element.value;
63
+ return "";
59
64
  }
60
65
  /**
61
66
  * The dialog itself: a `<dialog>` with the id the Trigger's `commandfor` names, wired to Title
@@ -65,8 +70,12 @@ function ends(animation) {
65
70
  *
66
71
  * In library mode the children render from committed state, so content that suspends keeps the
67
72
  * dialog closed (and the Trigger pending) until it is ready; `showModal()` runs in the layout
68
- * effect of the commit that mounted them. Closing is the browser's: the native `close` event
69
- * becomes `openChangeAction(false, { reason, returnValue })`.
73
+ * effect of the commit that mounted them. A close a part asks for (Close, a `method="dialog"`
74
+ * submit, a Form's settle close, a foreign close command) is cancelled and goes through the
75
+ * Root: `openChangeAction(false)` is awaited and `close(returnValue)` runs in the layout effect
76
+ * of the commit that ends it, inside the view transition if `transition` is set. A dismissal
77
+ * (Escape, a click outside) is the browser's: the native `close` event becomes
78
+ * `openChangeAction(false, { reason: "dismiss", returnValue })`.
70
79
  *
71
80
  * Owned keys (conventions 1.2): `id`, `open`, `closedby`, `aria-labelledby` and
72
81
  * `aria-describedby` (token-merged), `data-modal`, `suppressHydrationWarning`.
@@ -76,10 +85,19 @@ function DialogPopup({ keepMounted, initialFocus, lightDismiss = false, experime
76
85
  const isLibrary = ctx.mode === "library";
77
86
  const { modal, ids, dialogRef, triggerRef } = ctx;
78
87
  const [dialog, setDialog] = useState(null);
79
- const [exiting, setExiting] = useState(false);
80
- const exitGeneration = useRef(0);
81
88
  const awaitingClose = useRef(false);
89
+ const libraryClose = useRef(false);
90
+ const dismissedWhilePending = useRef(null);
82
91
  const warnedFocus = useRef(false);
92
+ const exit = useExitAnimation();
93
+ const [prevCommitted, setPrevCommitted] = useState(ctx.committed);
94
+ if (prevCommitted !== ctx.committed) {
95
+ setPrevCommitted(ctx.committed);
96
+ if (isLibrary && prevCommitted && transition === void 0) exit.holdDuringRender();
97
+ }
98
+ const contentProbe = useContentFocusProbe(dialogRef);
99
+ const { wrapContent } = contentProbe;
100
+ useModalRegistration(dialog);
83
101
  useDevWarning(!isLibrary && transition !== void 0, "Dialog.Popup: `transition` has no effect in JS-free mode; the browser opens and closes the dialog outside any React transition. Animate it with CSS (`@starting-style`).");
84
102
  useDevWarning(!isLibrary && keepMounted === false, "Dialog.Popup: `keepMounted={false}` cannot be honoured in JS-free mode; the content is always mounted so that the server HTML is complete.");
85
103
  const libraryRef = useCallback((element) => {
@@ -120,18 +138,45 @@ function DialogPopup({ keepMounted, initialFocus, lightDismiss = false, experime
120
138
  useLayoutEffect(() => {
121
139
  if (isLibrary) reconcileOnMount();
122
140
  }, [isLibrary]);
141
+ /**
142
+ * Design 6.2 rule 2, after a close whose commit unmounted the focused content: when focus is
143
+ * now on `<body>` or still inside the closed dialog, focus the Trigger (if connected), else
144
+ * blur what holds focus. A modal dialog never needs it (the platform restores focus on
145
+ * `close()` unconditionally); a non-modal one restores only when focus is inside.
146
+ */
147
+ const returnFocus = (element) => {
148
+ const doc = element.ownerDocument;
149
+ const active = doc.activeElement;
150
+ if (active !== null && active !== doc.body && !element.contains(active)) return;
151
+ const trigger = triggerRef.current;
152
+ if (trigger !== null && trigger.isConnected) trigger.focus();
153
+ else if (active instanceof HTMLElement && active !== doc.body) active.blur();
154
+ };
123
155
  useLayoutEffect(() => {
124
156
  if (!isLibrary) return;
125
157
  const element = dialogRef.current;
126
158
  if (element === null || !element.isConnected) return;
127
- if (ctx.pending || ctx.latestInFlight() !== null) return;
128
- if (ctx.committed && !element.open) {
129
- if (awaitingClose.current) return;
130
- openDialog(element);
131
- } else if (!ctx.committed && element.open) {
132
- ctx.recordCloseIntent(void 0);
133
- element.close();
159
+ if (!ctx.pending && ctx.latestInFlight() === null) {
160
+ const dismissed = dismissedWhilePending.current;
161
+ if (ctx.committed && !element.open) {
162
+ if (!awaitingClose.current) {
163
+ dismissedWhilePending.current = null;
164
+ if (dismissed !== null && dismissed.generation === ctx.openGeneration()) ctx.notifyClosed(dismissed.event, "dismiss", dismissed.returnValue);
165
+ else openDialog(element);
166
+ }
167
+ } else if (!ctx.committed && element.open) {
168
+ ctx.recordCloseIntent(void 0);
169
+ libraryClose.current = true;
170
+ element.close(ctx.takeCloseReturnValue());
171
+ if (contentProbe.hadFocus()) {
172
+ contentProbe.forget();
173
+ returnFocus(element);
174
+ }
175
+ }
134
176
  }
177
+ if (!ctx.committed && dismissedWhilePending.current !== null && ctx.latestInFlight() === null) dismissedWhilePending.current = null;
178
+ if (ctx.committed && element.open) contentProbe.forget();
179
+ if (exit.held && !ctx.committed && !exit.waiting() && !awaitingClose.current && !element.open) exit.release();
135
180
  });
136
181
  useEffect(() => {
137
182
  if (isLibrary || initialFocus === void 0 || dialog === null) return;
@@ -151,22 +196,36 @@ function DialogPopup({ keepMounted, initialFocus, lightDismiss = false, experime
151
196
  if (!isLibrary) return;
152
197
  event.preventDefault();
153
198
  ctx.requestOpen(event, "activate");
154
- } else if (command === "request-close" || command === "close") ctx.recordCloseIntent({
155
- reason: "activate",
156
- event
157
- });
199
+ } else if (command === "request-close" || command === "close") {
200
+ if (isLibrary) {
201
+ event.preventDefault();
202
+ const source = event.source;
203
+ ctx.requestClose(event, "activate", valueOf(source));
204
+ } else ctx.recordCloseIntent({
205
+ reason: "activate",
206
+ event
207
+ });
208
+ }
158
209
  });
159
210
  const onSubmit = useEffectEvent((event) => {
160
211
  const element = dialogRef.current;
161
212
  const form = event.target;
162
213
  if (element === null || !(form instanceof HTMLFormElement)) return;
163
214
  if (form.closest("dialog") !== element) return;
164
- const formMethod = event.submitter?.getAttribute("formmethod");
215
+ const submitter = event.submitter;
216
+ const formMethod = submitter?.getAttribute("formmethod");
165
217
  if ((formMethod != null ? formMethod.toLowerCase() : form.method) !== "dialog") return;
166
- ctx.recordCloseIntent({
167
- reason: "activate",
168
- event
169
- });
218
+ if (!isLibrary) {
219
+ ctx.recordCloseIntent({
220
+ reason: "activate",
221
+ event
222
+ });
223
+ return;
224
+ }
225
+ if (event.defaultPrevented) return;
226
+ if (event.cancelBubble) return;
227
+ event.preventDefault();
228
+ ctx.requestClose(event, "activate", valueOf(submitter));
170
229
  });
171
230
  const onClose = useEffectEvent((event) => {
172
231
  const element = dialogRef.current;
@@ -180,29 +239,39 @@ function DialogPopup({ keepMounted, initialFocus, lightDismiss = false, experime
180
239
  else if (active instanceof HTMLElement) active.blur();
181
240
  }
182
241
  const intent = ctx.takeCloseIntent();
242
+ const echo = libraryClose.current;
243
+ libraryClose.current = false;
183
244
  if (!isLibrary) return;
184
- if (element.isConnected && typeof element.getAnimations === "function") {
185
- getComputedStyle(element).opacity;
186
- const animations = element.getAnimations({ subtree: true }).filter(ends);
187
- if (animations.length > 0) {
188
- const generation = ++exitGeneration.current;
189
- setExiting(true);
190
- Promise.allSettled(animations.map((animation) => animation.finished)).then(() => {
191
- if (exitGeneration.current === generation) startTransition(() => setExiting(false));
192
- });
193
- }
245
+ if (!echo || transition === void 0) {
246
+ const animations = exit.exitAnimations(element);
247
+ if (animations.length > 0) exit.holdUntil(animations);
248
+ else exit.release();
194
249
  }
195
- ctx.notifyClosed(intent?.event ?? event, intent?.reason ?? "dismiss", element.returnValue);
250
+ if (echo) return;
251
+ ctx.takeCloseReturnValue();
252
+ if (ctx.intended()) ctx.notifyClosed(intent?.event ?? event, intent?.reason ?? "dismiss", element.returnValue);
253
+ else dismissedWhilePending.current = {
254
+ event: intent?.event ?? event,
255
+ returnValue: element.returnValue,
256
+ generation: ctx.openGeneration()
257
+ };
196
258
  });
197
259
  const onBeforeToggle = useEffectEvent((event) => {
198
- if (event.target !== dialogRef.current) return;
260
+ const element = dialogRef.current;
261
+ if (element === null || event.target !== element) return;
199
262
  const newState = event.newState;
200
263
  awaitingClose.current = newState === "closed";
201
- if (newState === "open") ctx.nextOpenGeneration();
264
+ if (newState === "closed") exit.recordBaseline(element);
265
+ if (newState === "open") {
266
+ dismissedWhilePending.current = null;
267
+ ctx.nextOpenGeneration();
268
+ contentProbe.forget();
269
+ }
202
270
  });
203
271
  useEffect(() => {
204
272
  if (dialog === null) return;
205
273
  const doc = dialog.ownerDocument;
274
+ const root = dialog.getRootNode();
206
275
  const nativeClosedBy = experimental_closedBy && "closedBy" in dialog;
207
276
  const listeners = [
208
277
  [
@@ -212,7 +281,7 @@ function DialogPopup({ keepMounted, initialFocus, lightDismiss = false, experime
212
281
  false
213
282
  ],
214
283
  [
215
- dialog,
284
+ root,
216
285
  "submit",
217
286
  onSubmit,
218
287
  false
@@ -329,14 +398,18 @@ function DialogPopup({ keepMounted, initialFocus, lightDismiss = false, experime
329
398
  open: ctx.committed,
330
399
  modal
331
400
  };
332
- const visible = ctx.committed || exiting;
333
- const content = (s) => {
401
+ const visible = ctx.committed || exit.held;
402
+ const content = (s) => /* @__PURE__ */ jsx(ToolbarContext, {
403
+ value: null,
404
+ children: contentOf(s)
405
+ });
406
+ const contentOf = (s) => {
334
407
  if (!isLibrary) return resolveStateValue(children, s);
335
408
  if (keepMounted === true) return /* @__PURE__ */ jsx(Activity, {
336
409
  mode: visible ? "visible" : "hidden",
337
- children: resolveStateValue(children, s)
410
+ children: wrapContent(resolveStateValue(children, s))
338
411
  });
339
- return visible ? resolveStateValue(children, s) : null;
412
+ return visible ? wrapContent(resolveStateValue(children, s)) : null;
340
413
  };
341
414
  const libraryProps = {
342
415
  id: ids.dialog,