@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":"use-form-owner-event.mjs","names":[],"sources":["../../src/internal/use-form-owner-event.ts"],"sourcesContent":["\"use client\";\nimport { useEffectEvent, useLayoutEffect, useRef } from \"react\";\nimport type { RefObject } from \"react\";\n\n/** A form-associated element whose form owner the hook follows. */\nexport type FormAssociatedElement =\n | HTMLInputElement\n | HTMLFieldSetElement\n | HTMLSelectElement\n | HTMLTextAreaElement;\n\ninterface Subscription {\n readonly form: HTMLFormElement;\n readonly listener: (event: Event) => void;\n}\n\n/**\n * Stable subscription to `type` on the form owner of `ref.current` (conventions 3.8 rule 4,\n * generalised for Field, design/components/field-form.md 14).\n *\n * - The owner (`element.form`, which honours the `form` attribute) is re-read after every commit,\n * but the listener is moved only when the owner changed. It is never removed and re-added on an\n * ordinary re-render: React's automatic reset after a successful `<form action>` runs in the\n * commit's mutation phase, between an effect's cleanup and its next run, so a subscription that\n * followed every commit would miss it.\n * - `handler` always sees the latest committed render's values (`useEffectEvent`).\n * - `ref.current` may be any element: one without a `form` property has no owner.\n */\nexport function useFormOwnerEvent(\n ref: RefObject<Element | null>,\n type: string,\n handler: (event: Event, form: HTMLFormElement) => void,\n): void {\n const handle = useEffectEvent((event: Event, form: HTMLFormElement) => {\n handler(event, form);\n });\n const subscription = useRef<Subscription | null>(null);\n\n // No deps on purpose: follows the form owner after every commit (a changed `form` attribute, the\n // element moved to another form), but re-subscribes only when the owner actually changed.\n useLayoutEffect(() => {\n const element = ref.current as Partial<FormAssociatedElement> | null;\n const form = element?.form ?? null;\n const current = subscription.current;\n if (current !== null && current.form === form) return;\n if (current !== null) current.form.removeEventListener(type, current.listener);\n subscription.current = null;\n if (form === null) return;\n const listener = (event: Event) => handle(event, form);\n form.addEventListener(type, listener);\n subscription.current = { form, listener };\n });\n\n // The only place the subscription is torn down: unmount (and StrictMode's simulated unmount,\n // after which the effect above subscribes again).\n useLayoutEffect(\n () => () => {\n const current = subscription.current;\n if (current !== null) current.form.removeEventListener(type, current.listener);\n subscription.current = null;\n },\n [type],\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AA4BA,SAAgB,kBACd,KACA,MACA,SACM;CACN,MAAM,SAAS,gBAAgB,OAAc,SAA0B;EACrE,QAAQ,OAAO,IAAI;CACrB,CAAC;CACD,MAAM,eAAe,OAA4B,IAAI;CAIrD,sBAAsB;EAEpB,MAAM,OADU,IAAI,SACE,QAAQ;EAC9B,MAAM,UAAU,aAAa;EAC7B,IAAI,YAAY,QAAQ,QAAQ,SAAS,MAAM;EAC/C,IAAI,YAAY,MAAM,QAAQ,KAAK,oBAAoB,MAAM,QAAQ,QAAQ;EAC7E,aAAa,UAAU;EACvB,IAAI,SAAS,MAAM;EACnB,MAAM,YAAY,UAAiB,OAAO,OAAO,IAAI;EACrD,KAAK,iBAAiB,MAAM,QAAQ;EACpC,aAAa,UAAU;GAAE;GAAM;EAAS;CAC1C,CAAC;CAID,4BACc;EACV,MAAM,UAAU,aAAa;EAC7B,IAAI,YAAY,MAAM,QAAQ,KAAK,oBAAoB,MAAM,QAAQ,QAAQ;EAC7E,aAAa,UAAU;CACzB,GACA,CAAC,IAAI,CACP;AACF"}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
- import { useEffectEvent, useLayoutEffect, useRef } from "react";
2
+ import { useLatest } from "./use-latest.mjs";
3
+ import { useFormOwnerEvent } from "./use-form-owner-event.mjs";
3
4
  //#region src/internal/use-form-reset.ts
4
5
  /**
5
6
  * Stable `reset` subscription on the form owner of `ref.current` (conventions 3.8).
@@ -8,45 +9,21 @@ import { useEffectEvent, useLayoutEffect, useRef } from "react";
8
9
  * but the listener is moved only when the owner changed. It is never removed and re-added on an
9
10
  * ordinary re-render: React's automatic reset after a successful `<form action>` runs in the
10
11
  * commit's mutation phase, between an effect's cleanup and its next run, so a subscription that
11
- * followed every commit would miss it (rule 4).
12
+ * followed every commit would miss it (rule 4). Built on `useFormOwnerEvent`.
12
13
  * - The `reset` event fires before the reset algorithm, so `onReset` is called in a microtask,
13
14
  * when the inputs hold their reset values (rule 5), and only if no listener cancelled the reset
14
15
  * (rule 7).
15
- * - `onReset` always sees the latest committed render's values (`useEffectEvent`).
16
+ * - `onReset` always sees the latest committed render's values.
16
17
  */
17
18
  function useFormReset(ref, onReset, onResetEvent) {
18
- const handleReset = useEffectEvent((event) => {
19
- onReset(event);
20
- });
21
- const handleResetEvent = useEffectEvent((event) => {
19
+ const latestOnReset = useLatest(onReset);
20
+ useFormOwnerEvent(ref, "reset", (event) => {
22
21
  onResetEvent?.(event);
22
+ queueMicrotask(() => {
23
+ if (event.defaultPrevented) return;
24
+ latestOnReset.current(event);
25
+ });
23
26
  });
24
- const subscription = useRef(null);
25
- useLayoutEffect(() => {
26
- const form = ref.current?.form ?? null;
27
- const current = subscription.current;
28
- if (current !== null && current.form === form) return;
29
- if (current !== null) current.form.removeEventListener("reset", current.listener);
30
- subscription.current = null;
31
- if (form === null) return;
32
- const listener = (event) => {
33
- handleResetEvent(event);
34
- queueMicrotask(() => {
35
- if (event.defaultPrevented) return;
36
- handleReset(event);
37
- });
38
- };
39
- form.addEventListener("reset", listener);
40
- subscription.current = {
41
- form,
42
- listener
43
- };
44
- });
45
- useLayoutEffect(() => () => {
46
- const current = subscription.current;
47
- if (current !== null) current.form.removeEventListener("reset", current.listener);
48
- subscription.current = null;
49
- }, []);
50
27
  }
51
28
  //#endregion
52
29
  export { useFormReset };
@@ -1 +1 @@
1
- {"version":3,"file":"use-form-reset.mjs","names":[],"sources":["../../src/internal/use-form-reset.ts"],"sourcesContent":["\"use client\";\nimport { useEffectEvent, useLayoutEffect, useRef } from \"react\";\nimport type { RefObject } from \"react\";\n\n/** A form-associated element whose form owner the hook follows. */\nexport type FormAssociatedElement = HTMLInputElement | HTMLFieldSetElement | HTMLSelectElement;\n\ninterface Subscription {\n readonly form: HTMLFormElement;\n readonly listener: (event: Event) => void;\n}\n\n/**\n * Stable `reset` subscription on the form owner of `ref.current` (conventions 3.8).\n *\n * - The owner (`element.form`, which honours the `form` attribute) is re-read after every commit,\n * but the listener is moved only when the owner changed. It is never removed and re-added on an\n * ordinary re-render: React's automatic reset after a successful `<form action>` runs in the\n * commit's mutation phase, between an effect's cleanup and its next run, so a subscription that\n * followed every commit would miss it (rule 4).\n * - The `reset` event fires before the reset algorithm, so `onReset` is called in a microtask,\n * when the inputs hold their reset values (rule 5), and only if no listener cancelled the reset\n * (rule 7).\n * - `onReset` always sees the latest committed render's values (`useEffectEvent`).\n */\nexport function useFormReset(\n ref: RefObject<FormAssociatedElement | null>,\n onReset: (event: Event) => void,\n /**\n * Called synchronously from the `reset` event, before the reset algorithm runs: the last\n * moment to write a reset target the browser will restore (Combobox re-applies its mirrored\n * `defaultValue`, which react-dom's attribute syncing overwrites after every keystroke).\n */\n onResetEvent?: (event: Event) => void,\n): void {\n const handleReset = useEffectEvent((event: Event) => {\n onReset(event);\n });\n const handleResetEvent = useEffectEvent((event: Event) => {\n onResetEvent?.(event);\n });\n const subscription = useRef<Subscription | null>(null);\n\n // No deps on purpose: follows the form owner after every commit (a changed `form` attribute, the\n // element moved to another form), but re-subscribes only when the owner actually changed.\n useLayoutEffect(() => {\n const form = ref.current?.form ?? null;\n const current = subscription.current;\n if (current !== null && current.form === form) return;\n if (current !== null) current.form.removeEventListener(\"reset\", current.listener);\n subscription.current = null;\n if (form === null) return;\n const listener = (event: Event) => {\n handleResetEvent(event);\n queueMicrotask(() => {\n if (event.defaultPrevented) return;\n handleReset(event);\n });\n };\n form.addEventListener(\"reset\", listener);\n subscription.current = { form, listener };\n });\n\n // The only place the subscription is torn down: unmount (and StrictMode's simulated unmount,\n // after which the effect above subscribes again).\n useLayoutEffect(\n () => () => {\n const current = subscription.current;\n if (current !== null) current.form.removeEventListener(\"reset\", current.listener);\n subscription.current = null;\n },\n [],\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAyBA,SAAgB,aACd,KACA,SAMA,cACM;CACN,MAAM,cAAc,gBAAgB,UAAiB;EACnD,QAAQ,KAAK;CACf,CAAC;CACD,MAAM,mBAAmB,gBAAgB,UAAiB;EACxD,eAAe,KAAK;CACtB,CAAC;CACD,MAAM,eAAe,OAA4B,IAAI;CAIrD,sBAAsB;EACpB,MAAM,OAAO,IAAI,SAAS,QAAQ;EAClC,MAAM,UAAU,aAAa;EAC7B,IAAI,YAAY,QAAQ,QAAQ,SAAS,MAAM;EAC/C,IAAI,YAAY,MAAM,QAAQ,KAAK,oBAAoB,SAAS,QAAQ,QAAQ;EAChF,aAAa,UAAU;EACvB,IAAI,SAAS,MAAM;EACnB,MAAM,YAAY,UAAiB;GACjC,iBAAiB,KAAK;GACtB,qBAAqB;IACnB,IAAI,MAAM,kBAAkB;IAC5B,YAAY,KAAK;GACnB,CAAC;EACH;EACA,KAAK,iBAAiB,SAAS,QAAQ;EACvC,aAAa,UAAU;GAAE;GAAM;EAAS;CAC1C,CAAC;CAID,4BACc;EACV,MAAM,UAAU,aAAa;EAC7B,IAAI,YAAY,MAAM,QAAQ,KAAK,oBAAoB,SAAS,QAAQ,QAAQ;EAChF,aAAa,UAAU;CACzB,GACA,CAAC,CACH;AACF"}
1
+ {"version":3,"file":"use-form-reset.mjs","names":[],"sources":["../../src/internal/use-form-reset.ts"],"sourcesContent":["\"use client\";\nimport type { RefObject } from \"react\";\nimport { useFormOwnerEvent } from \"./use-form-owner-event.js\";\nimport type { FormAssociatedElement } from \"./use-form-owner-event.js\";\nimport { useLatest } from \"./use-latest.js\";\n\nexport type { FormAssociatedElement } from \"./use-form-owner-event.js\";\n\n/**\n * Stable `reset` subscription on the form owner of `ref.current` (conventions 3.8).\n *\n * - The owner (`element.form`, which honours the `form` attribute) is re-read after every commit,\n * but the listener is moved only when the owner changed. It is never removed and re-added on an\n * ordinary re-render: React's automatic reset after a successful `<form action>` runs in the\n * commit's mutation phase, between an effect's cleanup and its next run, so a subscription that\n * followed every commit would miss it (rule 4). Built on `useFormOwnerEvent`.\n * - The `reset` event fires before the reset algorithm, so `onReset` is called in a microtask,\n * when the inputs hold their reset values (rule 5), and only if no listener cancelled the reset\n * (rule 7).\n * - `onReset` always sees the latest committed render's values.\n */\nexport function useFormReset(\n ref: RefObject<FormAssociatedElement | null>,\n onReset: (event: Event) => void,\n /**\n * Called synchronously from the `reset` event, before the reset algorithm runs: the last\n * moment to write a reset target the browser will restore (Combobox re-applies its mirrored\n * `defaultValue`, which react-dom's attribute syncing overwrites after every keystroke).\n */\n onResetEvent?: (event: Event) => void,\n): void {\n // Read in the microtask, after the commit that may have run the reset (React's own reset runs\n // in a commit's mutation phase): the latest committed `onReset`.\n const latestOnReset = useLatest(onReset);\n useFormOwnerEvent(ref, \"reset\", (event) => {\n onResetEvent?.(event);\n queueMicrotask(() => {\n if (event.defaultPrevented) return;\n latestOnReset.current(event);\n });\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAqBA,SAAgB,aACd,KACA,SAMA,cACM;CAGN,MAAM,gBAAgB,UAAU,OAAO;CACvC,kBAAkB,KAAK,UAAU,UAAU;EACzC,eAAe,KAAK;EACpB,qBAAqB;GACnB,IAAI,MAAM,kBAAkB;GAC5B,cAAc,QAAQ,KAAK;EAC7B,CAAC;CACH,CAAC;AACH"}
@@ -0,0 +1,32 @@
1
+ "use client";
2
+ import { useInsertionEffect, useRef } from "react";
3
+ //#region src/internal/use-latest.ts
4
+ /**
5
+ * A ref holding the value of the last **committed** render, for stable callbacks (handlers,
6
+ * actions, effects) that must read the current props without changing identity on every render.
7
+ *
8
+ * Written in an insertion effect: it runs in the mutation phase, before every layout effect of
9
+ * the same commit (a child's included), so a callback called from a layout effect of the commit
10
+ * that changed the value already sees it. A render that never commits (a discarded concurrent or
11
+ * StrictMode render) never writes it. Never read it during render.
12
+ *
13
+ * Stale window: the write happens in the owner's own insertion effect, and React runs a commit's
14
+ * insertion effects children first, interleaved with that subtree's DOM mutations. So a callback
15
+ * called before the owner's insertion effect of the commit that changed the value still sees the
16
+ * previous commit's value: from a descendant's insertion effect, or from a DOM event the browser
17
+ * fires synchronously during the mutation phase (Chromium fires `blur` / `focusout` when a
18
+ * focused node is removed). This is the same value a per-render closure would have given (the
19
+ * handler attached then was the previous commit's too), so it is not a regression; just do not
20
+ * rely on the new value before the layout phase.
21
+ */
22
+ function useLatest(value) {
23
+ const ref = useRef(value);
24
+ useInsertionEffect(() => {
25
+ ref.current = value;
26
+ });
27
+ return ref;
28
+ }
29
+ //#endregion
30
+ export { useLatest };
31
+
32
+ //# sourceMappingURL=use-latest.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-latest.mjs","names":[],"sources":["../../src/internal/use-latest.ts"],"sourcesContent":["\"use client\";\nimport { useInsertionEffect, useRef } from \"react\";\nimport type { RefObject } from \"react\";\n\n/**\n * A ref holding the value of the last **committed** render, for stable callbacks (handlers,\n * actions, effects) that must read the current props without changing identity on every render.\n *\n * Written in an insertion effect: it runs in the mutation phase, before every layout effect of\n * the same commit (a child's included), so a callback called from a layout effect of the commit\n * that changed the value already sees it. A render that never commits (a discarded concurrent or\n * StrictMode render) never writes it. Never read it during render.\n *\n * Stale window: the write happens in the owner's own insertion effect, and React runs a commit's\n * insertion effects children first, interleaved with that subtree's DOM mutations. So a callback\n * called before the owner's insertion effect of the commit that changed the value still sees the\n * previous commit's value: from a descendant's insertion effect, or from a DOM event the browser\n * fires synchronously during the mutation phase (Chromium fires `blur` / `focusout` when a\n * focused node is removed). This is the same value a per-render closure would have given (the\n * handler attached then was the previous commit's too), so it is not a regression; just do not\n * rely on the new value before the layout phase.\n */\nexport function useLatest<T>(value: T): RefObject<T> {\n const ref = useRef(value);\n useInsertionEffect(() => {\n ref.current = value;\n });\n return ref;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,UAAa,OAAwB;CACnD,MAAM,MAAM,OAAO,KAAK;CACxB,yBAAyB;EACvB,IAAI,UAAU;CAChB,CAAC;CACD,OAAO;AACT"}
@@ -0,0 +1,47 @@
1
+ "use client";
2
+ import { pushModal, removeModal } from "./modal-registry.mjs";
3
+ import { useLayoutEffect } from "react";
4
+ //#region src/internal/use-modal-registration.ts
5
+ function isModal(dialog) {
6
+ if (!dialog.isConnected || !dialog.open) return false;
7
+ try {
8
+ return dialog.matches(":modal");
9
+ } catch {
10
+ return false;
11
+ }
12
+ }
13
+ /**
14
+ * Keeps the modal registry (`modal-registry.ts`) in step with a `<dialog>`: registered while it
15
+ * is open as a modal (`showModal()`), removed when it closes or unmounts. Reads the DOM truth
16
+ * (`:modal`) instead of the component's state, so every way of opening counts.
17
+ *
18
+ * `beforetoggle` fires synchronously inside `showModal()` / `close()`, before the state changes,
19
+ * so that check runs a microtask later; `toggle` and `close` re-check.
20
+ */
21
+ function useModalRegistration(dialog) {
22
+ useLayoutEffect(() => {
23
+ if (dialog === null) return;
24
+ let active = true;
25
+ const sync = () => {
26
+ if (!active) return;
27
+ if (isModal(dialog)) pushModal(dialog);
28
+ else removeModal(dialog);
29
+ };
30
+ const syncSoon = () => queueMicrotask(sync);
31
+ dialog.addEventListener("beforetoggle", syncSoon);
32
+ dialog.addEventListener("toggle", sync);
33
+ dialog.addEventListener("close", sync);
34
+ sync();
35
+ return () => {
36
+ active = false;
37
+ dialog.removeEventListener("beforetoggle", syncSoon);
38
+ dialog.removeEventListener("toggle", sync);
39
+ dialog.removeEventListener("close", sync);
40
+ removeModal(dialog);
41
+ };
42
+ }, [dialog]);
43
+ }
44
+ //#endregion
45
+ export { useModalRegistration };
46
+
47
+ //# sourceMappingURL=use-modal-registration.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-modal-registration.mjs","names":[],"sources":["../../src/internal/use-modal-registration.ts"],"sourcesContent":["\"use client\";\nimport { useLayoutEffect } from \"react\";\nimport { pushModal, removeModal } from \"./modal-registry.js\";\n\nfunction isModal(dialog: HTMLDialogElement): boolean {\n if (!dialog.isConnected || !dialog.open) return false;\n try {\n return dialog.matches(\":modal\");\n } catch {\n return false;\n }\n}\n\n/**\n * Keeps the modal registry (`modal-registry.ts`) in step with a `<dialog>`: registered while it\n * is open as a modal (`showModal()`), removed when it closes or unmounts. Reads the DOM truth\n * (`:modal`) instead of the component's state, so every way of opening counts.\n *\n * `beforetoggle` fires synchronously inside `showModal()` / `close()`, before the state changes,\n * so that check runs a microtask later; `toggle` and `close` re-check.\n */\nexport function useModalRegistration(dialog: HTMLDialogElement | null): void {\n useLayoutEffect(() => {\n if (dialog === null) return;\n let active = true;\n const sync = (): void => {\n if (!active) return;\n if (isModal(dialog)) pushModal(dialog);\n else removeModal(dialog);\n };\n const syncSoon = (): void => queueMicrotask(sync);\n dialog.addEventListener(\"beforetoggle\", syncSoon);\n dialog.addEventListener(\"toggle\", sync);\n dialog.addEventListener(\"close\", sync);\n sync();\n return () => {\n active = false;\n dialog.removeEventListener(\"beforetoggle\", syncSoon);\n dialog.removeEventListener(\"toggle\", sync);\n dialog.removeEventListener(\"close\", sync);\n removeModal(dialog);\n };\n }, [dialog]);\n}\n"],"mappings":";;;;AAIA,SAAS,QAAQ,QAAoC;CACnD,IAAI,CAAC,OAAO,eAAe,CAAC,OAAO,MAAM,OAAO;CAChD,IAAI;EACF,OAAO,OAAO,QAAQ,QAAQ;CAChC,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;AAUA,SAAgB,qBAAqB,QAAwC;CAC3E,sBAAsB;EACpB,IAAI,WAAW,MAAM;EACrB,IAAI,SAAS;EACb,MAAM,aAAmB;GACvB,IAAI,CAAC,QAAQ;GACb,IAAI,QAAQ,MAAM,GAAG,UAAU,MAAM;QAChC,YAAY,MAAM;EACzB;EACA,MAAM,iBAAuB,eAAe,IAAI;EAChD,OAAO,iBAAiB,gBAAgB,QAAQ;EAChD,OAAO,iBAAiB,UAAU,IAAI;EACtC,OAAO,iBAAiB,SAAS,IAAI;EACrC,KAAK;EACL,aAAa;GACX,SAAS;GACT,OAAO,oBAAoB,gBAAgB,QAAQ;GACnD,OAAO,oBAAoB,UAAU,IAAI;GACzC,OAAO,oBAAoB,SAAS,IAAI;GACxC,YAAY,MAAM;EACpB;CACF,GAAG,CAAC,MAAM,CAAC;AACb"}
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { useLatest } from "./use-latest.mjs";
2
3
  import { useFormReset } from "./use-form-reset.mjs";
3
4
  import { useCallback, useEffectEvent, useLayoutEffect, useRef } from "react";
4
5
  //#region src/internal/use-native-group-reconciliation.ts
@@ -19,10 +20,7 @@ import { useCallback, useEffectEvent, useLayoutEffect, useRef } from "react";
19
20
  */
20
21
  function useNativeGroupReconciliation(options) {
21
22
  const { element } = options;
22
- const latest = useRef(options);
23
- useLayoutEffect(() => {
24
- latest.current = options;
25
- });
23
+ const latest = useLatest(options);
26
24
  /** The Root's own mount reconciliation has run (reset by its cleanup, StrictMode included). */
27
25
  const rootReconciled = useRef(false);
28
26
  /** A late reconciliation is queued; members mounting in the same commit share it. */
@@ -48,10 +46,13 @@ function useNativeGroupReconciliation(options) {
48
46
  if (current.element.current === null) return;
49
47
  reconcile(current, current.readCurrent());
50
48
  });
51
- }, []);
49
+ }, [latest]);
52
50
  useFormReset(element, (event) => {
53
51
  if (element.current === null) return;
54
52
  reconcile(options, options.readDefault(), event);
53
+ }, (event) => {
54
+ if (element.current === null) return;
55
+ options.onResetEvent?.(event);
55
56
  });
56
57
  return { onItemMounted };
57
58
  }
@@ -1 +1 @@
1
- {"version":3,"file":"use-native-group-reconciliation.mjs","names":[],"sources":["../../src/internal/use-native-group-reconciliation.ts"],"sourcesContent":["\"use client\";\nimport { useCallback, useEffectEvent, useLayoutEffect, useRef } from \"react\";\nimport type { RefObject } from \"react\";\nimport { useFormReset } from \"./use-form-reset.js\";\n\nexport interface UseNativeGroupReconciliationOptions<T> {\n /**\n * The group's form-associated element: `RadioGroup.Root`'s / `CheckboxGroup.Root`'s\n * `<fieldset>`, or `Select.Root`'s `<select>` (whose options are the members).\n */\n readonly element: RefObject<HTMLFieldSetElement | HTMLSelectElement | null>;\n /** The group's value as the DOM shows it now (`checked` of the members). */\n readonly readCurrent: () => T;\n /** The value the browser resets the group to (`defaultChecked` of the members, rule 5). */\n readonly readDefault: () => T;\n /**\n * What the user intends (rule 1): the latest in-flight dispatch's `next`, else the optimistic\n * value. Use an explicit null check, `const f = latestInFlight(); return f === null ?\n * optimistic : f.next;`, not `?.next ?? optimistic`, which drops an in-flight dispatch of a\n * nullish value (a pending reset to \"nothing checked\").\n */\n readonly base: () => T;\n /**\n * The browser changed the group's value on a residual path and it differs from `base()`.\n * `event` is the `reset` event, or `undefined` for mount reconciliation (the Root's mount, or a\n * member mounting later). The Root dispatches once for the whole group with\n * `reason: \"platform\"`.\n */\n readonly onPlatformChange: (next: T, event: Event | undefined) => void;\n /**\n * Equality between a read value and `base()`. Default `Object.is`, right for a primitive value\n * (RadioGroup's `string | null`); a group with a collection value (CheckboxGroup's\n * `ReadonlySet<string>`) passes structural equality, since every read builds a new object.\n */\n readonly equals?: ((a: T, b: T) => boolean) | undefined;\n}\n\nexport interface UseNativeGroupReconciliationResult {\n /**\n * Stable. Pass it to every member, which hands it to\n * `useNativeChecked({ group: true, onItemMounted })`; the hook calls it from the member's mount\n * layout effect. A member that mounts after the Root's own mount reconciliation (inside a\n * Suspense boundary that hydrates later, selective hydration, or a member added on the client)\n * makes the adapter reconcile the group again: `readCurrent()` vs `base()`, in a microtask, once\n * for all members mounted in the same commit. Members mounting in the Root's own commit are\n * covered by the Root's mount reconciliation (their effects run before the Root's) and are\n * ignored.\n *\n * RadioGroup wiring: `const { onItemMounted } = useNativeGroupReconciliation(...)` in the Root,\n * put `onItemMounted` in the group context, and in each Item\n * `useNativeChecked({ type: \"radio\", group: true, onItemMounted: ctx.onItemMounted, ... })`.\n * The member's element argument is not used here; the Root may use it (dev warnings).\n */\n readonly onItemMounted: (element?: Element | null) => void;\n}\n\n/**\n * Group adapter of the native-input machinery (conventions 3.8): one dispatch per group for the\n * residual browser paths, from the Root rather than once per member.\n *\n * - Mount reconciliation, once, in a layout effect: `readCurrent()` vs `base()`. The members'\n * `useNativeChecked({ group: true })` leaves `checked` alone in their mount commit, so this\n * effect, which runs after the members' (children first), still reads the hydrated DOM.\n * - Late members: a member mounting in a later commit (a Suspense boundary that hydrates after the\n * Root, where a click before hydration changed the DOM) reports through `onItemMounted`, and the\n * group is reconciled again the same way (see `UseNativeGroupReconciliationResult`).\n * - Form reset through `useFormReset` on the container: in a microtask after the reset,\n * `readDefault()` vs `base()`.\n *\n * Values are compared with `equals` (default `Object.is`).\n */\nexport function useNativeGroupReconciliation<T>(\n options: UseNativeGroupReconciliationOptions<T>,\n): UseNativeGroupReconciliationResult {\n const { element } = options;\n\n // The latest committed options, for the late reconciliation, which runs in a microtask outside\n // any effect (so it cannot go through an Effect Event).\n const latest = useRef(options);\n useLayoutEffect(() => {\n latest.current = options;\n });\n\n /** The Root's own mount reconciliation has run (reset by its cleanup, StrictMode included). */\n const rootReconciled = useRef(false);\n /** A late reconciliation is queued; members mounting in the same commit share it. */\n const lateQueued = useRef(false);\n\n const reconcileMount = useEffectEvent(() => {\n if (element.current === null) return;\n reconcile(options, options.readCurrent());\n });\n useLayoutEffect(() => {\n reconcileMount();\n rootReconciled.current = true;\n return () => {\n rootReconciled.current = false;\n };\n }, []);\n\n const onItemMounted = useCallback(() => {\n if (!rootReconciled.current || lateQueued.current) return;\n lateQueued.current = true;\n queueMicrotask(() => {\n lateQueued.current = false;\n if (!rootReconciled.current) return;\n const current = latest.current;\n if (current.element.current === null) return;\n reconcile(current, current.readCurrent());\n });\n }, []);\n\n useFormReset(element, (event) => {\n if (element.current === null) return;\n reconcile(options, options.readDefault(), event);\n });\n\n return { onItemMounted };\n}\n\nfunction reconcile<T>(options: UseNativeGroupReconciliationOptions<T>, next: T, event?: Event) {\n const equals = options.equals ?? Object.is;\n if (!equals(next, options.base())) options.onPlatformChange(next, event);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAuEA,SAAgB,6BACd,SACoC;CACpC,MAAM,EAAE,YAAY;CAIpB,MAAM,SAAS,OAAO,OAAO;CAC7B,sBAAsB;EACpB,OAAO,UAAU;CACnB,CAAC;;CAGD,MAAM,iBAAiB,OAAO,KAAK;;CAEnC,MAAM,aAAa,OAAO,KAAK;CAE/B,MAAM,iBAAiB,qBAAqB;EAC1C,IAAI,QAAQ,YAAY,MAAM;EAC9B,UAAU,SAAS,QAAQ,YAAY,CAAC;CAC1C,CAAC;CACD,sBAAsB;EACpB,eAAe;EACf,eAAe,UAAU;EACzB,aAAa;GACX,eAAe,UAAU;EAC3B;CACF,GAAG,CAAC,CAAC;CAEL,MAAM,gBAAgB,kBAAkB;EACtC,IAAI,CAAC,eAAe,WAAW,WAAW,SAAS;EACnD,WAAW,UAAU;EACrB,qBAAqB;GACnB,WAAW,UAAU;GACrB,IAAI,CAAC,eAAe,SAAS;GAC7B,MAAM,UAAU,OAAO;GACvB,IAAI,QAAQ,QAAQ,YAAY,MAAM;GACtC,UAAU,SAAS,QAAQ,YAAY,CAAC;EAC1C,CAAC;CACH,GAAG,CAAC,CAAC;CAEL,aAAa,UAAU,UAAU;EAC/B,IAAI,QAAQ,YAAY,MAAM;EAC9B,UAAU,SAAS,QAAQ,YAAY,GAAG,KAAK;CACjD,CAAC;CAED,OAAO,EAAE,cAAc;AACzB;AAEA,SAAS,UAAa,SAAiD,MAAS,OAAe;CAE7F,IAAI,EADW,QAAQ,UAAU,OAAO,GAAA,CAC5B,MAAM,QAAQ,KAAK,CAAC,GAAG,QAAQ,iBAAiB,MAAM,KAAK;AACzE"}
1
+ {"version":3,"file":"use-native-group-reconciliation.mjs","names":[],"sources":["../../src/internal/use-native-group-reconciliation.ts"],"sourcesContent":["\"use client\";\nimport { useCallback, useEffectEvent, useLayoutEffect, useRef } from \"react\";\nimport type { RefObject } from \"react\";\nimport { useFormReset } from \"./use-form-reset.js\";\nimport type { FormAssociatedElement } from \"./use-form-reset.js\";\nimport { useLatest } from \"./use-latest.js\";\n\nexport interface UseNativeGroupReconciliationOptions<T> {\n /**\n * The group's form-associated element: `RadioGroup.Root`'s / `CheckboxGroup.Root`'s\n * `<fieldset>`, `Select.Root`'s `<select>` (whose options are the members), or any member\n * input when the Root renders no form-associated element (`Slider.Root` subscribes through its\n * first Thumb's `<input type=\"range\">`).\n */\n readonly element: RefObject<FormAssociatedElement | null>;\n /** The group's value as the DOM shows it now (`checked` of the members). */\n readonly readCurrent: () => T;\n /** The value the browser resets the group to (`defaultChecked` of the members, rule 5). */\n readonly readDefault: () => T;\n /**\n * What the user intends (rule 1): the latest in-flight dispatch's `next`, else the optimistic\n * value. Use an explicit null check, `const f = latestInFlight(); return f === null ?\n * optimistic : f.next;`, not `?.next ?? optimistic`, which drops an in-flight dispatch of a\n * nullish value (a pending reset to \"nothing checked\").\n */\n readonly base: () => T;\n /**\n * The browser changed the group's value on a residual path and it differs from `base()`.\n * `event` is the `reset` event, or `undefined` for mount reconciliation (the Root's mount, or a\n * member mounting later). The Root dispatches once for the whole group with\n * `reason: \"platform\"`.\n */\n readonly onPlatformChange: (next: T, event: Event | undefined) => void;\n /**\n * Equality between a read value and `base()`. Default `Object.is`, right for a primitive value\n * (RadioGroup's `string | null`); a group with a collection value (CheckboxGroup's\n * `ReadonlySet<string>`) passes structural equality, since every read builds a new object.\n */\n readonly equals?: ((a: T, b: T) => boolean) | undefined;\n /**\n * Passed through to `useFormReset`: called synchronously from the `reset` event, before the\n * reset algorithm runs. A group of controlled **value** inputs (Slider) writes each input's\n * `defaultValue` to the reset target here, because react-dom keeps that attribute equal to the\n * current value after every update (conventions 3.8 rule 6).\n */\n readonly onResetEvent?: ((event: Event) => void) | undefined;\n}\n\nexport interface UseNativeGroupReconciliationResult {\n /**\n * Stable. Pass it to every member, which hands it to\n * `useNativeChecked({ group: true, onItemMounted })`; the hook calls it from the member's mount\n * layout effect. A member that mounts after the Root's own mount reconciliation (inside a\n * Suspense boundary that hydrates later, selective hydration, or a member added on the client)\n * makes the adapter reconcile the group again: `readCurrent()` vs `base()`, in a microtask, once\n * for all members mounted in the same commit. Members mounting in the Root's own commit are\n * covered by the Root's mount reconciliation (their effects run before the Root's) and are\n * ignored.\n *\n * RadioGroup wiring: `const { onItemMounted } = useNativeGroupReconciliation(...)` in the Root,\n * put `onItemMounted` in the group context, and in each Item\n * `useNativeChecked({ type: \"radio\", group: true, onItemMounted: ctx.onItemMounted, ... })`.\n * The member's element argument is not used here; the Root may use it (dev warnings).\n */\n readonly onItemMounted: (element?: Element | null) => void;\n}\n\n/**\n * Group adapter of the native-input machinery (conventions 3.8): one dispatch per group for the\n * residual browser paths, from the Root rather than once per member.\n *\n * - Mount reconciliation, once, in a layout effect: `readCurrent()` vs `base()`. The members'\n * `useNativeChecked({ group: true })` leaves `checked` alone in their mount commit, so this\n * effect, which runs after the members' (children first), still reads the hydrated DOM.\n * - Late members: a member mounting in a later commit (a Suspense boundary that hydrates after the\n * Root, where a click before hydration changed the DOM) reports through `onItemMounted`, and the\n * group is reconciled again the same way (see `UseNativeGroupReconciliationResult`).\n * - Form reset through `useFormReset` on the container: in a microtask after the reset,\n * `readDefault()` vs `base()`.\n *\n * Values are compared with `equals` (default `Object.is`).\n */\nexport function useNativeGroupReconciliation<T>(\n options: UseNativeGroupReconciliationOptions<T>,\n): UseNativeGroupReconciliationResult {\n const { element } = options;\n\n // The latest committed options, for the late reconciliation, which runs in a microtask outside\n // any effect (so it cannot go through an Effect Event).\n const latest = useLatest(options);\n\n /** The Root's own mount reconciliation has run (reset by its cleanup, StrictMode included). */\n const rootReconciled = useRef(false);\n /** A late reconciliation is queued; members mounting in the same commit share it. */\n const lateQueued = useRef(false);\n\n const reconcileMount = useEffectEvent(() => {\n if (element.current === null) return;\n reconcile(options, options.readCurrent());\n });\n useLayoutEffect(() => {\n reconcileMount();\n rootReconciled.current = true;\n return () => {\n rootReconciled.current = false;\n };\n }, []);\n\n const onItemMounted = useCallback(() => {\n if (!rootReconciled.current || lateQueued.current) return;\n lateQueued.current = true;\n queueMicrotask(() => {\n lateQueued.current = false;\n if (!rootReconciled.current) return;\n const current = latest.current;\n if (current.element.current === null) return;\n reconcile(current, current.readCurrent());\n });\n }, [latest]);\n\n useFormReset(\n element,\n (event) => {\n if (element.current === null) return;\n reconcile(options, options.readDefault(), event);\n },\n (event) => {\n if (element.current === null) return;\n options.onResetEvent?.(event);\n },\n );\n\n return { onItemMounted };\n}\n\nfunction reconcile<T>(options: UseNativeGroupReconciliationOptions<T>, next: T, event?: Event) {\n const equals = options.equals ?? Object.is;\n if (!equals(next, options.base())) options.onPlatformChange(next, event);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAkFA,SAAgB,6BACd,SACoC;CACpC,MAAM,EAAE,YAAY;CAIpB,MAAM,SAAS,UAAU,OAAO;;CAGhC,MAAM,iBAAiB,OAAO,KAAK;;CAEnC,MAAM,aAAa,OAAO,KAAK;CAE/B,MAAM,iBAAiB,qBAAqB;EAC1C,IAAI,QAAQ,YAAY,MAAM;EAC9B,UAAU,SAAS,QAAQ,YAAY,CAAC;CAC1C,CAAC;CACD,sBAAsB;EACpB,eAAe;EACf,eAAe,UAAU;EACzB,aAAa;GACX,eAAe,UAAU;EAC3B;CACF,GAAG,CAAC,CAAC;CAEL,MAAM,gBAAgB,kBAAkB;EACtC,IAAI,CAAC,eAAe,WAAW,WAAW,SAAS;EACnD,WAAW,UAAU;EACrB,qBAAqB;GACnB,WAAW,UAAU;GACrB,IAAI,CAAC,eAAe,SAAS;GAC7B,MAAM,UAAU,OAAO;GACvB,IAAI,QAAQ,QAAQ,YAAY,MAAM;GACtC,UAAU,SAAS,QAAQ,YAAY,CAAC;EAC1C,CAAC;CACH,GAAG,CAAC,MAAM,CAAC;CAEX,aACE,UACC,UAAU;EACT,IAAI,QAAQ,YAAY,MAAM;EAC9B,UAAU,SAAS,QAAQ,YAAY,GAAG,KAAK;CACjD,IACC,UAAU;EACT,IAAI,QAAQ,YAAY,MAAM;EAC9B,QAAQ,eAAe,KAAK;CAC9B,CACF;CAEA,OAAO,EAAE,cAAc;AACzB;AAEA,SAAS,UAAa,SAAiD,MAAS,OAAe;CAE7F,IAAI,EADW,QAAQ,UAAU,OAAO,GAAA,CAC5B,MAAM,QAAQ,KAAK,CAAC,GAAG,QAAQ,iBAAiB,MAAM,KAAK;AACzE"}
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { useLatest } from "./use-latest.mjs";
2
3
  import { useNativeGroupReconciliation } from "./use-native-group-reconciliation.mjs";
3
4
  import { useCallback, useEffectEvent, useLayoutEffect, useMemo, useRef } from "react";
4
5
  //#region src/internal/use-native-select-value.ts
@@ -76,10 +77,7 @@ function useNativeSelectValue(options) {
76
77
  const el = elementRef.current;
77
78
  return el?.tagName === "SELECT" ? el : null;
78
79
  } }), [elementRef]);
79
- const latest = useRef(options);
80
- useLayoutEffect(() => {
81
- latest.current = options;
82
- });
80
+ const latest = useLatest(options);
83
81
  /**
84
82
  * A default adopted in this layout phase. Until the adoption's render (a sync update, flushed
85
83
  * right after this commit) the state still says the old value, so this is what the user
@@ -174,7 +172,11 @@ function useNativeSelectValue(options) {
174
172
  if (el.value === intended(current) || el.value === resetTargetOf(current)) applySelection(select, intended(current), resetTargetOf(current));
175
173
  }
176
174
  if (hydrated) onItemMounted(el);
177
- }, [ref, onItemMounted]) };
175
+ }, [
176
+ ref,
177
+ latest,
178
+ onItemMounted
179
+ ]) };
178
180
  }
179
181
  //#endregion
180
182
  export { applySelection, findOption, implicitDefault, readResetValue, useNativeSelectValue };
@@ -1 +1 @@
1
- {"version":3,"file":"use-native-select-value.mjs","names":[],"sources":["../../src/internal/use-native-select-value.ts"],"sourcesContent":["\"use client\";\nimport { useCallback, useEffectEvent, useLayoutEffect, useMemo, useRef } from \"react\";\nimport type { RefObject } from \"react\";\nimport { useNativeGroupReconciliation } from \"./use-native-group-reconciliation.js\";\n\nexport interface UseNativeSelectValueOptions {\n /** The `<select>`. */\n readonly ref: RefObject<HTMLSelectElement | null>;\n /** The optimistic value (what the `<select>` renders). */\n readonly optimistic: string;\n /** From `useControllableAction`: the latest in-flight dispatch, if any. */\n readonly latestInFlight: () => { readonly next: string } | null;\n /**\n * The value a form reset must return to, mirrored into `option.defaultSelected` after every\n * commit: uncontrolled, the latest `defaultValue`; controlled, the Root's mount-time value.\n * Ignored in adopt mode (`adoptDefault` set), where the target is the value adopted at mount.\n */\n readonly resetTarget: string | undefined;\n /**\n * Uncontrolled without `defaultValue` (design/components/select.md 5): adopts the browser's\n * choice as the committed value, without a dispatch. `undefined` in every other mode.\n */\n readonly adoptDefault: AdoptDefault | undefined;\n /**\n * The browser changed the value on a residual path (pre-hydration pick, Firefox form-state\n * restoration, a late hydrated option, a form reset) and it differs from what the user intends.\n * `event` is the `reset` event or `undefined`. The Root dispatches once with\n * `reason: \"platform\"`.\n */\n readonly onPlatformChange: (next: string, event: Event | undefined) => void;\n}\n\nexport interface AdoptDefault {\n /** `adopt` of `useControllableAction`: writes the committed value, no action. */\n readonly adopt: (next: string) => void;\n /**\n * A dispatch (a pick or a platform change) has set the value. From then on the browser's\n * default is never adopted again: late options only get the re-apply, so options that suspend\n * again (a keyed boundary, a refetch) and come back keep the user's value.\n */\n readonly established: () => boolean;\n}\n\nexport interface UseNativeSelectValueResult {\n /**\n * Stable. Each `Select.Option` calls it from its mount layout effect with its element and\n * whether that render hydrated server HTML (`useHydratedMount`). Reports from the Root's own\n * commit are ignored (the Root's effects cover them). A later report re-applies the intended\n * option (React never selects options mounted after the select's own commit); a hydrated one\n * is also reconciled, from a DOM snapshot taken before the re-apply.\n */\n readonly optionMounted: (el: HTMLOptionElement | null, hydrated: boolean) => void;\n}\n\n/** The first option whose value is `value` (react-dom's `updateOptions` selects the first). */\nexport function findOption(select: HTMLSelectElement, value: string): HTMLOptionElement | null {\n for (const option of select.options) {\n if (option.value === value) return option;\n }\n return null;\n}\n\nfunction isEnabled(option: HTMLOptionElement): boolean {\n if (option.disabled) return false;\n const parent = option.parentElement;\n return !(parent?.tagName === \"OPTGROUP\" && (parent as HTMLOptGroupElement).disabled);\n}\n\nfunction firstEnabled(select: HTMLSelectElement): string {\n for (const option of select.options) {\n if (isEnabled(option)) return option.value;\n }\n return \"\";\n}\n\n/**\n * The spec's reset result for a single select: the last option whose `defaultSelected` is true,\n * else the first enabled option, else `\"\"` (nothing selected).\n */\nexport function readResetValue(select: HTMLSelectElement): string {\n let last: HTMLOptionElement | null = null;\n for (const option of select.options) {\n if (option.defaultSelected) last = option;\n }\n return last !== null ? last.value : firstEnabled(select);\n}\n\n/**\n * The option a `<select>` rendered with the value `\"\"` selects: an option whose value is `\"\"`\n * (react-dom's `updateOptions(\"\")` and Fizz's `selected` attribute both pick it, even when it is\n * disabled), else the first enabled option (the selectedness setting algorithm), else `\"\"`.\n */\nexport function implicitDefault(select: HTMLSelectElement): string {\n return findOption(select, \"\") !== null ? \"\" : firstEnabled(select);\n}\n\n/**\n * The reset-target mirror followed by the re-apply, in one synchronous step\n * (design/components/select.md 6.1). Writing `defaultSelected = true` on an option whose\n * dirtiness is false selects it, so the intended option is re-selected right after. Writes only\n * what differs: the common commit writes nothing.\n */\nexport function applySelection(\n select: HTMLSelectElement,\n target: string,\n resetTarget: string | undefined,\n): void {\n if (resetTarget !== undefined) {\n // Only the first match, the option react-dom would select for that value.\n const def = findOption(select, resetTarget);\n for (const option of select.options) {\n const want = option === def;\n if (option.defaultSelected !== want) option.defaultSelected = want;\n }\n }\n const option = findOption(select, target);\n if (option !== null && !option.selected) option.selected = true;\n}\n\ninterface Snapshot {\n readonly value: string;\n readonly index: number;\n}\n\nconst read = (select: HTMLSelectElement): Snapshot => ({\n value: select.value,\n index: select.selectedIndex,\n});\n\n/**\n * Native-`<select>` machinery for `Select.Root` (conventions 3.8, the select analogue of\n * `useNativeChecked`), on top of `useNativeGroupReconciliation`:\n *\n * 1. Adoption (uncontrolled without `defaultValue`): at mount the browser's default becomes the\n * committed value with no dispatch, and the mount reconciliation compares against it, so a\n * pre-hydration pick of another option still dispatches. It is also the reset target (the\n * value Fizz marks `selected` when it comes from a `\"\"` option). While no dispatch has set\n * the value, late options mounting without the state's option adopt again (loading).\n * 2. Mount reconciliation and form reset through the group adapter: one dispatch per Select, and\n * only when the intended option exists in the DOM (a value whose option is still loading is\n * never \"reconciled\" to the fallback).\n * 3. After every Root commit: the reset-target mirror, then the re-apply of the intended option.\n * 4. Late options: the same, once per commit; a hydrated late option is also reconciled, from a\n * snapshot of the selection taken before the re-apply.\n */\nexport function useNativeSelectValue(\n options: UseNativeSelectValueOptions,\n): UseNativeSelectValueResult {\n const { ref: elementRef } = options;\n // A `render` element that is not a <select> (dev warning in the Root) gets no DOM work.\n const ref = useMemo(\n () => ({\n get current(): HTMLSelectElement | null {\n const el = elementRef.current;\n // Tag, not `instanceof`: the element may come from another realm (an iframe).\n return el?.tagName === \"SELECT\" ? (el as HTMLSelectElement) : null;\n },\n }),\n [elementRef],\n );\n\n const latest = useRef(options);\n useLayoutEffect(() => {\n latest.current = options;\n });\n\n /**\n * A default adopted in this layout phase. Until the adoption's render (a sync update, flushed\n * right after this commit) the state still says the old value, so this is what the user\n * intends unless a dispatch is in flight. Cleared in a microtask, after that render.\n */\n const adopted = useRef<string | null>(null);\n const intended = (o: UseNativeSelectValueOptions): string => {\n const f = o.latestInFlight();\n if (f !== null) return f.next;\n return adopted.current ?? o.optimistic;\n };\n\n const adoptNow = (value: string, adopt: (next: string) => void) => {\n adopted.current = value;\n adopt(value);\n queueMicrotask(() => {\n adopted.current = null;\n });\n };\n\n /** Adopt mode: the value adopted at mount, the reset target (as the mount value is, controlled). */\n const mountAdopted = useRef<string | undefined>(undefined);\n const resetTargetOf = (o: UseNativeSelectValueOptions): string | undefined =>\n o.adoptDefault !== undefined ? mountAdopted.current : o.resetTarget;\n\n /** The selection read in the layout phase of a late-option commit (conventions 3.8 rule 3). */\n const lateSnapshot = useRef<Snapshot | null>(null);\n\n // 1. Adoption at mount. Declared before the adapter so that its mount reconciliation compares\n // the DOM against the adopted default.\n const adoptAtMount = useEffectEvent(() => {\n const select = ref.current;\n const adoptDefault = options.adoptDefault;\n // `established`: StrictMode's re-run after a mount reconciliation already dispatched.\n if (select === null || adoptDefault === undefined || adoptDefault.established()) return;\n const value = implicitDefault(select);\n // `\"\"` from a select with nothing selectable is no reset target (design 6.1): only a `\"\"`\n // option, the placeholder react-dom and Fizz select, is mirrored.\n mountAdopted.current = value === \"\" && findOption(select, \"\") === null ? undefined : value;\n adoptNow(value, adoptDefault.adopt);\n });\n useLayoutEffect(() => {\n adoptAtMount();\n }, []);\n\n // 2. Residual paths: one dispatch per Select.\n const { onItemMounted } = useNativeGroupReconciliation<string>({\n element: ref,\n readCurrent: () => {\n const select = ref.current;\n const snapshot = lateSnapshot.current;\n lateSnapshot.current = null;\n const base = intended(latest.current);\n if (select === null) return base;\n const current = snapshot ?? read(select);\n // The dispatch gate: nothing selected, or the intended option absent, is not a pick.\n if (current.index === -1 || findOption(select, base) === null) return base;\n return current.value;\n },\n readDefault: () => {\n const select = ref.current;\n return select === null ? intended(latest.current) : readResetValue(select);\n },\n base: () => intended(latest.current),\n onPlatformChange: (next, event) => latest.current.onPlatformChange(next, event),\n });\n\n // 3. After every commit. Declared after the adapter, so the mount commit's DOM is read by the\n // mount reconciliation before anything is re-applied.\n useLayoutEffect(() => {\n const select = ref.current;\n if (select === null) return;\n applySelection(select, intended(options), resetTargetOf(options));\n });\n\n /** The Root's own mount effects have run: later Option mounts are late. */\n const rootMounted = useRef(false);\n useLayoutEffect(() => {\n rootMounted.current = true;\n return () => {\n rootMounted.current = false;\n };\n }, []);\n /**\n * This commit's late options were handled (snapshot, adoption, re-apply). A microtask window,\n * not a per-commit flag: an Option's layout effect cannot tell which commit it belongs to (the\n * Root does not commit with a boundary that resolves inside it). A second commit in the same\n * task is covered by the per-report re-apply below; its hydrated options are reconciled against\n * the first commit's snapshot (theoretical: no part triggers such a commit).\n */\n const lateHandled = useRef(false);\n\n // 4. Late options.\n const optionMounted = useCallback(\n (el: HTMLOptionElement | null, hydrated: boolean) => {\n if (!rootMounted.current) return;\n const select = ref.current;\n if (select === null) return;\n if (!lateHandled.current) {\n lateHandled.current = true;\n // Every option of this commit is in the DOM already (the mutation phase precedes layout).\n lateSnapshot.current = read(select);\n const current = latest.current;\n const adoptDefault = current.adoptDefault;\n if (\n adoptDefault !== undefined &&\n !adoptDefault.established() &&\n current.latestInFlight() === null &&\n findOption(select, intended(current)) === null\n ) {\n // Still loading and nothing chosen yet (the fallback-only case): adopt the browser's\n // default. Never after a dispatch: that would overwrite the user's value silently.\n adoptNow(implicitDefault(select), adoptDefault.adopt);\n }\n applySelection(select, intended(current), resetTargetOf(current));\n // Cleared after the adapter's microtask, which a later report of this same commit may\n // queue after this point: two hops keep the snapshot for it.\n queueMicrotask(() =>\n queueMicrotask(() => {\n lateHandled.current = false;\n lateSnapshot.current = null;\n }),\n );\n } else if (el !== null) {\n // Per report, for an option that matters (cheap: at most the intended and the reset\n // target's options): a second commit within the window gets its re-apply too.\n const current = latest.current;\n if (el.value === intended(current) || el.value === resetTargetOf(current)) {\n applySelection(select, intended(current), resetTargetOf(current));\n }\n }\n if (hydrated) onItemMounted(el);\n },\n [ref, onItemMounted],\n );\n\n return { optionMounted };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-native-select-value.mjs","names":[],"sources":["../../src/internal/use-native-select-value.ts"],"sourcesContent":["\"use client\";\nimport { useCallback, useEffectEvent, useLayoutEffect, useMemo, useRef } from \"react\";\nimport type { RefObject } from \"react\";\nimport { useLatest } from \"./use-latest.js\";\nimport { useNativeGroupReconciliation } from \"./use-native-group-reconciliation.js\";\n\nexport interface UseNativeSelectValueOptions {\n /** The `<select>`. */\n readonly ref: RefObject<HTMLSelectElement | null>;\n /** The optimistic value (what the `<select>` renders). */\n readonly optimistic: string;\n /** From `useControllableAction`: the latest in-flight dispatch, if any. */\n readonly latestInFlight: () => { readonly next: string } | null;\n /**\n * The value a form reset must return to, mirrored into `option.defaultSelected` after every\n * commit: uncontrolled, the latest `defaultValue`; controlled, the Root's mount-time value.\n * Ignored in adopt mode (`adoptDefault` set), where the target is the value adopted at mount.\n */\n readonly resetTarget: string | undefined;\n /**\n * Uncontrolled without `defaultValue` (design/components/select.md 5): adopts the browser's\n * choice as the committed value, without a dispatch. `undefined` in every other mode.\n */\n readonly adoptDefault: AdoptDefault | undefined;\n /**\n * The browser changed the value on a residual path (pre-hydration pick, Firefox form-state\n * restoration, a late hydrated option, a form reset) and it differs from what the user intends.\n * `event` is the `reset` event or `undefined`. The Root dispatches once with\n * `reason: \"platform\"`.\n */\n readonly onPlatformChange: (next: string, event: Event | undefined) => void;\n}\n\nexport interface AdoptDefault {\n /** `adopt` of `useControllableAction`: writes the committed value, no action. */\n readonly adopt: (next: string) => void;\n /**\n * A dispatch (a pick or a platform change) has set the value. From then on the browser's\n * default is never adopted again: late options only get the re-apply, so options that suspend\n * again (a keyed boundary, a refetch) and come back keep the user's value.\n */\n readonly established: () => boolean;\n}\n\nexport interface UseNativeSelectValueResult {\n /**\n * Stable. Each `Select.Option` calls it from its mount layout effect with its element and\n * whether that render hydrated server HTML (`useHydratedMount`). Reports from the Root's own\n * commit are ignored (the Root's effects cover them). A later report re-applies the intended\n * option (React never selects options mounted after the select's own commit); a hydrated one\n * is also reconciled, from a DOM snapshot taken before the re-apply.\n */\n readonly optionMounted: (el: HTMLOptionElement | null, hydrated: boolean) => void;\n}\n\n/** The first option whose value is `value` (react-dom's `updateOptions` selects the first). */\nexport function findOption(select: HTMLSelectElement, value: string): HTMLOptionElement | null {\n for (const option of select.options) {\n if (option.value === value) return option;\n }\n return null;\n}\n\nfunction isEnabled(option: HTMLOptionElement): boolean {\n if (option.disabled) return false;\n const parent = option.parentElement;\n return !(parent?.tagName === \"OPTGROUP\" && (parent as HTMLOptGroupElement).disabled);\n}\n\nfunction firstEnabled(select: HTMLSelectElement): string {\n for (const option of select.options) {\n if (isEnabled(option)) return option.value;\n }\n return \"\";\n}\n\n/**\n * The spec's reset result for a single select: the last option whose `defaultSelected` is true,\n * else the first enabled option, else `\"\"` (nothing selected).\n */\nexport function readResetValue(select: HTMLSelectElement): string {\n let last: HTMLOptionElement | null = null;\n for (const option of select.options) {\n if (option.defaultSelected) last = option;\n }\n return last !== null ? last.value : firstEnabled(select);\n}\n\n/**\n * The option a `<select>` rendered with the value `\"\"` selects: an option whose value is `\"\"`\n * (react-dom's `updateOptions(\"\")` and Fizz's `selected` attribute both pick it, even when it is\n * disabled), else the first enabled option (the selectedness setting algorithm), else `\"\"`.\n */\nexport function implicitDefault(select: HTMLSelectElement): string {\n return findOption(select, \"\") !== null ? \"\" : firstEnabled(select);\n}\n\n/**\n * The reset-target mirror followed by the re-apply, in one synchronous step\n * (design/components/select.md 6.1). Writing `defaultSelected = true` on an option whose\n * dirtiness is false selects it, so the intended option is re-selected right after. Writes only\n * what differs: the common commit writes nothing.\n */\nexport function applySelection(\n select: HTMLSelectElement,\n target: string,\n resetTarget: string | undefined,\n): void {\n if (resetTarget !== undefined) {\n // Only the first match, the option react-dom would select for that value.\n const def = findOption(select, resetTarget);\n for (const option of select.options) {\n const want = option === def;\n if (option.defaultSelected !== want) option.defaultSelected = want;\n }\n }\n const option = findOption(select, target);\n if (option !== null && !option.selected) option.selected = true;\n}\n\ninterface Snapshot {\n readonly value: string;\n readonly index: number;\n}\n\nconst read = (select: HTMLSelectElement): Snapshot => ({\n value: select.value,\n index: select.selectedIndex,\n});\n\n/**\n * Native-`<select>` machinery for `Select.Root` (conventions 3.8, the select analogue of\n * `useNativeChecked`), on top of `useNativeGroupReconciliation`:\n *\n * 1. Adoption (uncontrolled without `defaultValue`): at mount the browser's default becomes the\n * committed value with no dispatch, and the mount reconciliation compares against it, so a\n * pre-hydration pick of another option still dispatches. It is also the reset target (the\n * value Fizz marks `selected` when it comes from a `\"\"` option). While no dispatch has set\n * the value, late options mounting without the state's option adopt again (loading).\n * 2. Mount reconciliation and form reset through the group adapter: one dispatch per Select, and\n * only when the intended option exists in the DOM (a value whose option is still loading is\n * never \"reconciled\" to the fallback).\n * 3. After every Root commit: the reset-target mirror, then the re-apply of the intended option.\n * 4. Late options: the same, once per commit; a hydrated late option is also reconciled, from a\n * snapshot of the selection taken before the re-apply.\n */\nexport function useNativeSelectValue(\n options: UseNativeSelectValueOptions,\n): UseNativeSelectValueResult {\n const { ref: elementRef } = options;\n // A `render` element that is not a <select> (dev warning in the Root) gets no DOM work.\n const ref = useMemo(\n () => ({\n get current(): HTMLSelectElement | null {\n const el = elementRef.current;\n // Tag, not `instanceof`: the element may come from another realm (an iframe).\n return el?.tagName === \"SELECT\" ? (el as HTMLSelectElement) : null;\n },\n }),\n [elementRef],\n );\n\n // The latest committed options. Written in the mutation phase (`useLatest`), so an Option's\n // mount layout effect, which runs before this Root's layout effects, already reads this commit's.\n const latest = useLatest(options);\n\n /**\n * A default adopted in this layout phase. Until the adoption's render (a sync update, flushed\n * right after this commit) the state still says the old value, so this is what the user\n * intends unless a dispatch is in flight. Cleared in a microtask, after that render.\n */\n const adopted = useRef<string | null>(null);\n const intended = (o: UseNativeSelectValueOptions): string => {\n const f = o.latestInFlight();\n if (f !== null) return f.next;\n return adopted.current ?? o.optimistic;\n };\n\n const adoptNow = (value: string, adopt: (next: string) => void) => {\n adopted.current = value;\n adopt(value);\n queueMicrotask(() => {\n adopted.current = null;\n });\n };\n\n /** Adopt mode: the value adopted at mount, the reset target (as the mount value is, controlled). */\n const mountAdopted = useRef<string | undefined>(undefined);\n const resetTargetOf = (o: UseNativeSelectValueOptions): string | undefined =>\n o.adoptDefault !== undefined ? mountAdopted.current : o.resetTarget;\n\n /** The selection read in the layout phase of a late-option commit (conventions 3.8 rule 3). */\n const lateSnapshot = useRef<Snapshot | null>(null);\n\n // 1. Adoption at mount. Declared before the adapter so that its mount reconciliation compares\n // the DOM against the adopted default.\n const adoptAtMount = useEffectEvent(() => {\n const select = ref.current;\n const adoptDefault = options.adoptDefault;\n // `established`: StrictMode's re-run after a mount reconciliation already dispatched.\n if (select === null || adoptDefault === undefined || adoptDefault.established()) return;\n const value = implicitDefault(select);\n // `\"\"` from a select with nothing selectable is no reset target (design 6.1): only a `\"\"`\n // option, the placeholder react-dom and Fizz select, is mirrored.\n mountAdopted.current = value === \"\" && findOption(select, \"\") === null ? undefined : value;\n adoptNow(value, adoptDefault.adopt);\n });\n useLayoutEffect(() => {\n adoptAtMount();\n }, []);\n\n // 2. Residual paths: one dispatch per Select.\n const { onItemMounted } = useNativeGroupReconciliation<string>({\n element: ref,\n readCurrent: () => {\n const select = ref.current;\n const snapshot = lateSnapshot.current;\n lateSnapshot.current = null;\n const base = intended(latest.current);\n if (select === null) return base;\n const current = snapshot ?? read(select);\n // The dispatch gate: nothing selected, or the intended option absent, is not a pick.\n if (current.index === -1 || findOption(select, base) === null) return base;\n return current.value;\n },\n readDefault: () => {\n const select = ref.current;\n return select === null ? intended(latest.current) : readResetValue(select);\n },\n base: () => intended(latest.current),\n onPlatformChange: (next, event) => latest.current.onPlatformChange(next, event),\n });\n\n // 3. After every commit. Declared after the adapter, so the mount commit's DOM is read by the\n // mount reconciliation before anything is re-applied.\n useLayoutEffect(() => {\n const select = ref.current;\n if (select === null) return;\n applySelection(select, intended(options), resetTargetOf(options));\n });\n\n /** The Root's own mount effects have run: later Option mounts are late. */\n const rootMounted = useRef(false);\n useLayoutEffect(() => {\n rootMounted.current = true;\n return () => {\n rootMounted.current = false;\n };\n }, []);\n /**\n * This commit's late options were handled (snapshot, adoption, re-apply). A microtask window,\n * not a per-commit flag: an Option's layout effect cannot tell which commit it belongs to (the\n * Root does not commit with a boundary that resolves inside it). A second commit in the same\n * task is covered by the per-report re-apply below; its hydrated options are reconciled against\n * the first commit's snapshot (theoretical: no part triggers such a commit).\n */\n const lateHandled = useRef(false);\n\n // 4. Late options.\n const optionMounted = useCallback(\n (el: HTMLOptionElement | null, hydrated: boolean) => {\n if (!rootMounted.current) return;\n const select = ref.current;\n if (select === null) return;\n if (!lateHandled.current) {\n lateHandled.current = true;\n // Every option of this commit is in the DOM already (the mutation phase precedes layout).\n lateSnapshot.current = read(select);\n const current = latest.current;\n const adoptDefault = current.adoptDefault;\n if (\n adoptDefault !== undefined &&\n !adoptDefault.established() &&\n current.latestInFlight() === null &&\n findOption(select, intended(current)) === null\n ) {\n // Still loading and nothing chosen yet (the fallback-only case): adopt the browser's\n // default. Never after a dispatch: that would overwrite the user's value silently.\n adoptNow(implicitDefault(select), adoptDefault.adopt);\n }\n applySelection(select, intended(current), resetTargetOf(current));\n // Cleared after the adapter's microtask, which a later report of this same commit may\n // queue after this point: two hops keep the snapshot for it.\n queueMicrotask(() =>\n queueMicrotask(() => {\n lateHandled.current = false;\n lateSnapshot.current = null;\n }),\n );\n } else if (el !== null) {\n // Per report, for an option that matters (cheap: at most the intended and the reset\n // target's options): a second commit within the window gets its re-apply too.\n const current = latest.current;\n if (el.value === intended(current) || el.value === resetTargetOf(current)) {\n applySelection(select, intended(current), resetTargetOf(current));\n }\n }\n if (hydrated) onItemMounted(el);\n },\n [ref, latest, onItemMounted],\n );\n\n return { optionMounted };\n}\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  "use client";
2
- import { exitAnimations, useExitAnimation } from "./use-exit-animation.mjs";
3
- import { createElement, useCallback, useEffect, useEffectEvent, useInsertionEffect, useLayoutEffect, useRef, useState } from "react";
2
+ import { useContentFocusProbe } from "./content-focus-probe.mjs";
3
+ import { useExitAnimation } from "./use-exit-animation.mjs";
4
+ import { useCallback, useEffect, useEffectEvent, useInsertionEffect, useLayoutEffect, useRef, useState } from "react";
4
5
  //#region src/internal/use-popover-popup.ts
5
6
  const isDev = process.env.NODE_ENV !== "production";
6
7
  function isPopoverOpen(element) {
@@ -42,15 +43,6 @@ function insideHiddenPopover(element) {
42
43
  return false;
43
44
  }
44
45
  /**
45
- * Records, from its layout-effect cleanup, whether focus is inside the popup when the content
46
- * goes away: React runs a deleted component's layout cleanups before it detaches the host nodes,
47
- * so `document.activeElement` still names the focused element there.
48
- */
49
- function ContentFocusProbe({ onDetach, children }) {
50
- useLayoutEffect(() => onDetach, [onDetach]);
51
- return children;
52
- }
53
- /**
54
46
  * Everything in a popover-like Popup except rendering (design/components/popover.md 14.2): mount
55
47
  * reconciliation, the apply effect, the native `beforetoggle` / `toggle` / `command` listeners,
56
48
  * close intents, the focus-return fallback, the JS-free `aria-expanded` sync, the deferred
@@ -64,7 +56,6 @@ function usePopoverPopup(ctx, options) {
64
56
  const opening = useRef(false);
65
57
  const awaitingClose = useRef(false);
66
58
  const focusWasInside = useRef(false);
67
- const contentHadFocus = useRef(false);
68
59
  const warnedFocus = useRef(false);
69
60
  const exit = useExitAnimation();
70
61
  const [prevCommitted, setPrevCommitted] = useState(ctx.committed);
@@ -72,13 +63,8 @@ function usePopoverPopup(ctx, options) {
72
63
  setPrevCommitted(ctx.committed);
73
64
  if (isLibrary && prevCommitted && transition === void 0) exit.holdDuringRender();
74
65
  }
75
- const recordContentFocus = useCallback(() => {
76
- const element = popupRef.current;
77
- if (element === null) return;
78
- const active = element.ownerDocument.activeElement;
79
- if (active !== null && active !== element && element.contains(active)) contentHadFocus.current = true;
80
- }, [popupRef]);
81
- const wrapContent = useCallback((children) => createElement(ContentFocusProbe, { onDetach: recordContentFocus }, children), [recordContentFocus]);
66
+ const contentProbe = useContentFocusProbe(popupRef);
67
+ const { wrapContent } = contentProbe;
82
68
  const syncExpanded = (element) => {
83
69
  triggerRef.current?.setAttribute("aria-expanded", String(isPopoverOpen(element)));
84
70
  };
@@ -153,7 +139,7 @@ function usePopoverPopup(ctx, options) {
153
139
  if (focusWasInside.current) returnFocus(element);
154
140
  }
155
141
  }
156
- if (ctx.committed && isPopoverOpen(element)) contentHadFocus.current = false;
142
+ if (ctx.committed && isPopoverOpen(element)) contentProbe.forget();
157
143
  if (exit.held && !ctx.committed && !exit.waiting() && !awaitingClose.current && !isPopoverOpen(element)) exit.release();
158
144
  });
159
145
  const onBeforeToggle = (event) => {
@@ -165,12 +151,13 @@ function usePopoverPopup(ctx, options) {
165
151
  event.preventDefault();
166
152
  ctx.requestOpen(event, "activate");
167
153
  } else ctx.nextOpenGeneration();
168
- contentHadFocus.current = false;
154
+ contentProbe.forget();
169
155
  } else {
170
156
  awaitingClose.current = true;
157
+ exit.recordBaseline(element);
171
158
  const active = element.ownerDocument.activeElement;
172
- focusWasInside.current = active !== null && element.contains(active) || contentHadFocus.current;
173
- contentHadFocus.current = false;
159
+ focusWasInside.current = active !== null && element.contains(active) || contentProbe.hadFocus();
160
+ contentProbe.forget();
174
161
  }
175
162
  onBeforeToggleOption?.(toggle);
176
163
  };
@@ -186,7 +173,7 @@ function usePopoverPopup(ctx, options) {
186
173
  const intent = ctx.takeCloseIntent();
187
174
  if (isLibrary) {
188
175
  if (!!ctx.intended() || transition === void 0) {
189
- const animations = exitAnimations(element);
176
+ const animations = exit.exitAnimations(element);
190
177
  if (animations.length > 0) exit.holdUntil(animations);
191
178
  else exit.release();
192
179
  }
@@ -1 +1 @@
1
- {"version":3,"file":"use-popover-popup.mjs","names":[],"sources":["../../src/internal/use-popover-popup.ts"],"sourcesContent":["\"use client\";\nimport {\n createElement,\n useCallback,\n useEffect,\n useEffectEvent,\n useInsertionEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport type { ReactNode, RefObject, ViewTransitionClass } from \"react\";\nimport { exitAnimations, useExitAnimation } from \"./use-exit-animation.js\";\nimport type { PopoverRootValue } from \"./use-popover-root.js\";\n\nexport interface UsePopoverPopupOptions {\n /** Public name of the part for development warnings (`\"Popover.Popup\"`). */\n readonly part: string;\n /** Element to focus on open: a ref, or a selector resolved inside the popup (design 6.1). */\n readonly initialFocus: RefObject<HTMLElement | null> | string | undefined;\n /**\n * Replaces `initialFocus` with a function called right before each library open (Menu: the\n * first or last item by open cause). `null` leaves the choice to the platform. A\n * `QuietFocusTarget` asks for focus without the focus ring (a pointer-opened menu).\n */\n readonly resolveInitialFocus?: (popup: HTMLElement) => HTMLElement | QuietFocusTarget | null;\n readonly keepMounted: boolean | undefined;\n /** The consumer's `transition`; decides how a library-driven close keeps its content (4.4). */\n readonly transition: ViewTransitionClass | undefined;\n /** Runs after the library's own `beforetoggle` handling (Menu: focus bookkeeping). */\n readonly onBeforeToggle?: (event: ToggleEvent) => void;\n /** Runs after the library's own `toggle` handling (Menu: close the stack on Tab-out). */\n readonly onToggle?: (event: ToggleEvent) => void;\n}\n\n/**\n * An initial focus target opened by a pointer: where the engine reads `focus({ focusVisible })`,\n * the library focuses it with `focusVisible: false` right after `showPopover()` instead of\n * writing `autofocus` (WebKit shows the ring for an `autofocus` target after a click; review H1).\n * Elsewhere it is an ordinary target.\n */\nexport interface QuietFocusTarget {\n readonly element: HTMLElement;\n readonly focusVisible: false;\n}\n\nexport interface PopoverPopupHook {\n /** Callback ref for the popup element; pass as `useRender`'s `ref`. */\n readonly setElement: (element: HTMLElement) => () => void;\n /**\n * Whether the children are shown: committed open, or held while the hide animates (4.4).\n * Library mode only; JS-free mode always renders them.\n */\n readonly visible: boolean;\n /**\n * Wraps the library-mode content: call it only while `visible` (`visible ? wrapContent(x) :\n * null`), or inside the `<Activity>` for `keepMounted`. When a close commit unmounts the\n * content while it holds focus (a library-driven close with `transition`, where the focused\n * element is gone before `hidePopover()` runs and the platform restores nothing), the hide\n * that follows returns focus to the Trigger (design 6.2).\n */\n readonly wrapContent: (children: ReactNode) => ReactNode;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nfunction isPopoverOpen(element: Element): boolean {\n try {\n return element.matches(\":popover-open\");\n } catch {\n return false;\n }\n}\n\nlet focusVisibleOption: boolean | undefined;\n\n/**\n * The engine reads the `focusVisible` member of `focus()`'s options (Chromium 145, Firefox 104,\n * Safari 18.4). Detected once through a getter: the bindings convert the dictionary before the\n * method runs, so a detached element is enough and nothing is focused.\n */\nfunction supportsFocusVisibleOption(doc: Document): boolean {\n if (focusVisibleOption === undefined) {\n let read = false;\n try {\n doc.createElement(\"div\").focus({\n get focusVisible() {\n read = true;\n return false;\n },\n } as FocusOptions);\n } catch {\n read = false;\n }\n focusVisibleOption = read;\n }\n return focusVisibleOption;\n}\n\n/** Resolves `initialFocus` against the popup (a ref, or a selector inside the popup). */\nfunction resolveInitialFocus(\n popup: HTMLElement,\n initialFocus: RefObject<HTMLElement | null> | string,\n): HTMLElement | null {\n if (typeof initialFocus === \"string\") return popup.querySelector<HTMLElement>(initialFocus);\n return initialFocus.current;\n}\n\n/** Whether `element` sits inside a popover that is not shown (an outer popup that closed). */\nfunction insideHiddenPopover(element: Element): boolean {\n for (let node = element.parentElement; node !== null; node = node.parentElement) {\n if (node.hasAttribute(\"popover\") && !isPopoverOpen(node)) return true;\n }\n return false;\n}\n\n/**\n * Records, from its layout-effect cleanup, whether focus is inside the popup when the content\n * goes away: React runs a deleted component's layout cleanups before it detaches the host nodes,\n * so `document.activeElement` still names the focused element there.\n */\nfunction ContentFocusProbe({\n onDetach,\n children,\n}: {\n onDetach: () => void;\n children?: ReactNode;\n}): ReactNode {\n useLayoutEffect(() => onDetach, [onDetach]);\n return children;\n}\n\n/**\n * Everything in a popover-like Popup except rendering (design/components/popover.md 14.2): mount\n * reconciliation, the apply effect, the native `beforetoggle` / `toggle` / `command` listeners,\n * close intents, the focus-return fallback, the JS-free `aria-expanded` sync, the deferred\n * unmount while a hide animates, and `initialFocus`. Shared by Popover and Menu.\n */\nexport function usePopoverPopup(\n ctx: PopoverRootValue,\n options: UsePopoverPopupOptions,\n): PopoverPopupHook {\n const { part, initialFocus, transition, onBeforeToggle: onBeforeToggleOption } = options;\n const onToggleOption = options.onToggle;\n const isLibrary = ctx.mode === \"library\";\n const { popupRef, triggerRef } = ctx;\n\n // The opening `beforetoggle` lets this one through: the library's own `showPopover()`.\n const opening = useRef(false);\n // Set by the synchronous `beforetoggle` of a close, cleared by `toggle` a task later: the\n // popup is hidden but its `toggle` has not arrived, so the apply effect must not re-open it\n // from a commit that lands in between (design 2.5).\n const awaitingClose = useRef(false);\n // Whether focus was inside the popup when its last close began (design 6.2 rule 1).\n const focusWasInside = useRef(false);\n // Whether the content unmounted while holding focus since the popup was last shown (H1).\n const contentHadFocus = useRef(false);\n const warnedFocus = useRef(false);\n\n // --- `held`: children stay mounted while the hide animates (design 4.4) -------------------\n\n const exit = useExitAnimation();\n\n // A library-driven close commits `false` first and hides in the layout effect, so without\n // `transition` the children are held through the close commit (\"previous value in state\").\n // With `transition` the close commit unmounts them inside the view transition, whose old\n // snapshot carries the content out.\n const [prevCommitted, setPrevCommitted] = useState(ctx.committed);\n if (prevCommitted !== ctx.committed) {\n setPrevCommitted(ctx.committed);\n if (isLibrary && prevCommitted && transition === undefined) exit.holdDuringRender();\n }\n\n const recordContentFocus = useCallback(() => {\n const element = popupRef.current;\n if (element === null) return;\n const active = element.ownerDocument.activeElement;\n if (active !== null && active !== element && element.contains(active)) {\n contentHadFocus.current = true;\n }\n }, [popupRef]);\n\n const wrapContent = useCallback(\n (children: ReactNode): ReactNode =>\n createElement(ContentFocusProbe, { onDetach: recordContentFocus }, children),\n [recordContentFocus],\n );\n\n const syncExpanded = (element: HTMLElement): void => {\n // JS-free mode only (design 9): read the DOM, not `newState`, so a stale task cannot lie.\n triggerRef.current?.setAttribute(\"aria-expanded\", String(isPopoverOpen(element)));\n };\n\n /**\n * Design 6.2 rule 2, for a close that began with focus inside: when focus is now on `<body>`\n * or still inside the hidden popup, focus the Trigger (connected, not inside a hidden\n * popover), else blur what holds focus.\n */\n const returnFocus = (element: HTMLElement): void => {\n const doc = element.ownerDocument;\n const active = doc.activeElement;\n if (active !== null && active !== doc.body && !element.contains(active)) return;\n const trigger = triggerRef.current;\n if (trigger !== null && trigger.isConnected && !insideHiddenPopover(trigger)) {\n trigger.focus();\n } else if (active instanceof HTMLElement && active !== doc.body) {\n active.blur();\n }\n };\n\n // --- Opening ------------------------------------------------------------------------------\n\n const resolveFocusTarget = (element: HTMLElement): HTMLElement | QuietFocusTarget | null => {\n if (options.resolveInitialFocus !== undefined) {\n const resolved = options.resolveInitialFocus(element);\n if (resolved === null) return null;\n const target = resolved instanceof HTMLElement ? resolved : resolved.element;\n return element.contains(target) ? resolved : null;\n }\n if (initialFocus === undefined) return null;\n const target = resolveInitialFocus(element, initialFocus);\n if (target !== null && element.contains(target)) return target;\n if (isDev && !warnedFocus.current) {\n warnedFocus.current = true;\n console.error(\n target === null\n ? `${part}: \\`initialFocus\\` resolved to no element; the platform picks the focus target.`\n : `${part}: \\`initialFocus\\` is outside the popup; the platform picks the focus target.`,\n );\n }\n return null;\n };\n\n const showPopup = (element: HTMLElement): void => {\n const resolved = resolveFocusTarget(element);\n const target =\n resolved === null || resolved instanceof HTMLElement ? resolved : resolved.element;\n // A pointer open asks for no focus ring: `focus({ focusVisible: false })` after the show,\n // where the engine reads that option (QuietFocusTarget).\n const quiet =\n target !== null &&\n !(resolved instanceof HTMLElement) &&\n supportsFocusVisibleOption(element.ownerDocument);\n // Otherwise the popover focusing steps honour `autofocus` (and run the dialog focusing steps\n // for a <dialog>, in every engine); React drops `autoFocus` on client-rendered content.\n // Never `.focus()` (6.1) except for the quiet target above.\n const added = target !== null && !quiet && !target.hasAttribute(\"autofocus\");\n if (added) target.setAttribute(\"autofocus\", \"\");\n opening.current = true;\n try {\n const source = triggerRef.current;\n element.showPopover(source !== null && source.isConnected ? { source } : undefined);\n } finally {\n opening.current = false;\n if (added) target.removeAttribute(\"autofocus\");\n }\n if (quiet && isPopoverOpen(element) && element.contains(target)) {\n target.focus({ focusVisible: false } as FocusOptions);\n }\n };\n\n // Mount reconciliation (design 2.4): a click before hydration opened the popover natively.\n // Runs before the apply effect; the dispatch makes the apply effect wait for it.\n const reconcileOnMount = useEffectEvent(() => {\n const element = popupRef.current;\n if (element === null) return;\n if (!isLibrary) {\n syncExpanded(element);\n return;\n }\n if (isPopoverOpen(element)) ctx.requestOpen(undefined, \"platform\");\n });\n useLayoutEffect(() => {\n reconcileOnMount();\n }, []);\n\n // The apply effect (design 2.5): after every commit, once nothing is in flight, the DOM\n // follows committed state. One effect covers the normal open, `defaultOpen`, a controlled open\n // or close, a library-driven close after `openChangeAction(false)` settled, the re-open of a\n // refused dismissal, and the close after a refused or failed pre-hydration open.\n useLayoutEffect(() => {\n if (!isLibrary) return;\n const element = popupRef.current;\n if (element === null || !element.isConnected) return;\n if (!ctx.pending && ctx.latestInFlight() === null) {\n const shown = isPopoverOpen(element);\n if (ctx.committed && !shown) {\n if (!awaitingClose.current) showPopup(element);\n } else if (!ctx.committed && shown) {\n // An echo: the `toggle` listener finds `false` intended and skips the dispatch.\n ctx.recordCloseIntent(undefined);\n element.hidePopover();\n // The close commit may have unmounted the focused content before the hide (H1): the\n // platform restores focus only from inside the popover, so do it now.\n if (focusWasInside.current) returnFocus(element);\n }\n }\n // Content that unmounted while the popup stays open (not a close): forget it.\n if (ctx.committed && isPopoverOpen(element)) contentHadFocus.current = false;\n // A hold the close commit set (or a dismissal's notification commit) that no hide animation\n // is going to release: the popup is already hidden and its `toggle` has been handled.\n if (\n exit.held &&\n !ctx.committed &&\n !exit.waiting() &&\n !awaitingClose.current &&\n !isPopoverOpen(element)\n ) {\n exit.release();\n }\n });\n\n // --- Native listeners (conventions 7.4; filtered on target: React 19.3.1 emulates bubbling\n // of `toggle`, and a nested popover's events reach this element through the real DOM too) ---\n\n const onBeforeToggle = (event: Event): void => {\n const element = popupRef.current;\n if (element === null || event.target !== element) return;\n const toggle = event as ToggleEvent;\n if (toggle.newState === \"open\") {\n // Every open the library did not start itself is routed through the Root (design 2.2):\n // a foreign `show-popover` / `toggle-popover` invoker, a script `showPopover()`.\n if (isLibrary && !opening.current) {\n event.preventDefault();\n ctx.requestOpen(event, \"activate\");\n } else {\n // A Close action that resolves after this open must not close it (design 3.4).\n ctx.nextOpenGeneration();\n }\n contentHadFocus.current = false;\n } else {\n awaitingClose.current = true;\n const active = element.ownerDocument.activeElement;\n focusWasInside.current =\n (active !== null && element.contains(active)) || contentHadFocus.current;\n contentHadFocus.current = false;\n }\n onBeforeToggleOption?.(toggle);\n };\n\n const onToggle = (event: Event): void => {\n const element = popupRef.current;\n if (element === null || event.target !== element) return;\n awaitingClose.current = false;\n if (!isLibrary) syncExpanded(element);\n\n if (!isPopoverOpen(element)) {\n // Focus return for what the platform leaves out (design 6.2): a nested popover restores\n // nothing, and an opener that is gone leaves focus inside the hidden popup.\n const wasInside = focusWasInside.current;\n focusWasInside.current = false;\n if (wasInside) returnFocus(element);\n\n const intent = ctx.takeCloseIntent();\n if (isLibrary) {\n // `false` already intended: the library's own hide (or a dismissal during a pending\n // part-initiated close). With `transition` such a close needs no hold (4.4).\n const echo = !ctx.intended();\n if (!echo || transition === undefined) {\n const animations = exitAnimations(element);\n if (animations.length > 0) {\n // Before the notification, so its commit keeps the children (urgent, internal).\n exit.holdUntil(animations);\n } else {\n // Nothing animates (Firefox, or no CSS exit): release a hold now; the commit is\n // invisible, the popup is already `display: none`.\n exit.release();\n }\n }\n ctx.notifyClosed(intent?.event ?? event, intent?.reason ?? \"dismiss\");\n }\n }\n onToggleOption?.(event as ToggleEvent);\n };\n\n const onCommand = (event: Event): void => {\n const element = popupRef.current;\n if (element === null || event.target !== element || event.defaultPrevented) return;\n const command = String((event as Event & { command?: string }).command ?? \"\").toLowerCase();\n // A foreign invoker closing the popup (design 2.3). Opens are caught at `beforetoggle`.\n if (command === \"hide-popover\" || (command === \"toggle-popover\" && isPopoverOpen(element))) {\n ctx.recordCloseIntent({ reason: \"activate\", event });\n }\n };\n\n // The listeners the callback ref attaches call the latest render's handlers. An insertion\n // effect publishes them before refs attach and before any layout effect (whose\n // `hidePopover()` dispatches `beforetoggle` synchronously).\n const handlers = useRef({ onBeforeToggle, onToggle, onCommand });\n useInsertionEffect(() => {\n handlers.current = { onBeforeToggle, onToggle, onCommand };\n });\n\n // --- Element ------------------------------------------------------------------------------\n\n // The listeners attach in the callback ref, so they are in place from the commit that mounts\n // the element (no passive-effect window, no extra render). JS-free mode: there is no \"right\n // before showPopover()\" (the browser runs the command), so the `autofocus` attribute stays on\n // the `initialFocus` target while it is mounted (as Dialog).\n const setElement = useCallback(\n (element: HTMLElement) => {\n if (isDev && element.tagName !== \"DIALOG\" && element.tagName !== \"DIV\") {\n console.error(\n `${part}: expected to render a <dialog> or a <div> but got ` +\n `<${element.tagName.toLowerCase()}>.`,\n );\n }\n popupRef.current = element;\n const listeners: [string, EventListener][] = [\n [\"beforetoggle\", (event) => handlers.current.onBeforeToggle(event)],\n [\"toggle\", (event) => handlers.current.onToggle(event)],\n [\"command\", (event) => handlers.current.onCommand(event)],\n ];\n for (const [type, listener] of listeners) element.addEventListener(type, listener);\n\n let autofocused: HTMLElement | null = null;\n if (!isLibrary && initialFocus !== undefined) {\n const target = resolveInitialFocus(element, initialFocus);\n if (target !== null && element.contains(target) && !target.hasAttribute(\"autofocus\")) {\n target.setAttribute(\"autofocus\", \"\");\n autofocused = target;\n }\n }\n\n return () => {\n for (const [type, listener] of listeners) element.removeEventListener(type, listener);\n autofocused?.removeAttribute(\"autofocus\");\n if (popupRef.current === element) popupRef.current = null;\n };\n },\n [part, popupRef, isLibrary, initialFocus],\n );\n\n // Development check (design 9): `role=\"dialog\"` needs a name once the popup is (or can be)\n // shown.\n const shown = !isLibrary || ctx.committed;\n const warnedName = useRef(false);\n useEffect(() => {\n const element = popupRef.current;\n if (!isDev || element === null || !shown || warnedName.current) return;\n if (element.hasAttribute(\"aria-label\")) return;\n const labelledBy = (element.getAttribute(\"aria-labelledby\") ?? \"\").split(/\\s+/);\n const doc = element.ownerDocument;\n if (labelledBy.some((id) => id !== \"\" && doc.getElementById(id) !== null)) return;\n const role = element.getAttribute(\"role\");\n if (role !== \"dialog\" && role !== \"alertdialog\" && role !== \"menu\") return;\n warnedName.current = true;\n console.error(\n `${part}: the popup has no accessible name. Render a Title, or give the Popup an ` +\n \"`aria-label`.\",\n );\n }, [popupRef, shown, part]);\n\n return { setElement, visible: ctx.committed || exit.held, wrapContent };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-popover-popup.mjs","names":[],"sources":["../../src/internal/use-popover-popup.ts"],"sourcesContent":["\"use client\";\nimport {\n useCallback,\n useEffect,\n useEffectEvent,\n useInsertionEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport type { ReactNode, RefObject, ViewTransitionClass } from \"react\";\nimport { useContentFocusProbe } from \"./content-focus-probe.js\";\nimport { useExitAnimation } from \"./use-exit-animation.js\";\nimport type { PopoverRootValue } from \"./use-popover-root.js\";\n\nexport interface UsePopoverPopupOptions {\n /** Public name of the part for development warnings (`\"Popover.Popup\"`). */\n readonly part: string;\n /** Element to focus on open: a ref, or a selector resolved inside the popup (design 6.1). */\n readonly initialFocus: RefObject<HTMLElement | null> | string | undefined;\n /**\n * Replaces `initialFocus` with a function called right before each library open (Menu: the\n * first or last item by open cause). `null` leaves the choice to the platform. A\n * `QuietFocusTarget` asks for focus without the focus ring (a pointer-opened menu).\n */\n readonly resolveInitialFocus?: (popup: HTMLElement) => HTMLElement | QuietFocusTarget | null;\n readonly keepMounted: boolean | undefined;\n /** The consumer's `transition`; decides how a library-driven close keeps its content (4.4). */\n readonly transition: ViewTransitionClass | undefined;\n /** Runs after the library's own `beforetoggle` handling (Menu: focus bookkeeping). */\n readonly onBeforeToggle?: (event: ToggleEvent) => void;\n /** Runs after the library's own `toggle` handling (Menu: close the stack on Tab-out). */\n readonly onToggle?: (event: ToggleEvent) => void;\n}\n\n/**\n * An initial focus target opened by a pointer: where the engine reads `focus({ focusVisible })`,\n * the library focuses it with `focusVisible: false` right after `showPopover()` instead of\n * writing `autofocus` (WebKit shows the ring for an `autofocus` target after a click; review H1).\n * Elsewhere it is an ordinary target.\n */\nexport interface QuietFocusTarget {\n readonly element: HTMLElement;\n readonly focusVisible: false;\n}\n\nexport interface PopoverPopupHook {\n /** Callback ref for the popup element; pass as `useRender`'s `ref`. */\n readonly setElement: (element: HTMLElement) => () => void;\n /**\n * Whether the children are shown: committed open, or held while the hide animates (4.4).\n * Library mode only; JS-free mode always renders them.\n */\n readonly visible: boolean;\n /**\n * Wraps the library-mode content: call it only while `visible` (`visible ? wrapContent(x) :\n * null`), or inside the `<Activity>` for `keepMounted`. When a close commit unmounts the\n * content while it holds focus (a library-driven close with `transition`, where the focused\n * element is gone before `hidePopover()` runs and the platform restores nothing), the hide\n * that follows returns focus to the Trigger (design 6.2).\n */\n readonly wrapContent: (children: ReactNode) => ReactNode;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nfunction isPopoverOpen(element: Element): boolean {\n try {\n return element.matches(\":popover-open\");\n } catch {\n return false;\n }\n}\n\nlet focusVisibleOption: boolean | undefined;\n\n/**\n * The engine reads the `focusVisible` member of `focus()`'s options (Chromium 145, Firefox 104,\n * Safari 18.4). Detected once through a getter: the bindings convert the dictionary before the\n * method runs, so a detached element is enough and nothing is focused.\n */\nfunction supportsFocusVisibleOption(doc: Document): boolean {\n if (focusVisibleOption === undefined) {\n let read = false;\n try {\n doc.createElement(\"div\").focus({\n get focusVisible() {\n read = true;\n return false;\n },\n } as FocusOptions);\n } catch {\n read = false;\n }\n focusVisibleOption = read;\n }\n return focusVisibleOption;\n}\n\n/** Resolves `initialFocus` against the popup (a ref, or a selector inside the popup). */\nfunction resolveInitialFocus(\n popup: HTMLElement,\n initialFocus: RefObject<HTMLElement | null> | string,\n): HTMLElement | null {\n if (typeof initialFocus === \"string\") return popup.querySelector<HTMLElement>(initialFocus);\n return initialFocus.current;\n}\n\n/** Whether `element` sits inside a popover that is not shown (an outer popup that closed). */\nfunction insideHiddenPopover(element: Element): boolean {\n for (let node = element.parentElement; node !== null; node = node.parentElement) {\n if (node.hasAttribute(\"popover\") && !isPopoverOpen(node)) return true;\n }\n return false;\n}\n\n/**\n * Everything in a popover-like Popup except rendering (design/components/popover.md 14.2): mount\n * reconciliation, the apply effect, the native `beforetoggle` / `toggle` / `command` listeners,\n * close intents, the focus-return fallback, the JS-free `aria-expanded` sync, the deferred\n * unmount while a hide animates, and `initialFocus`. Shared by Popover and Menu.\n */\nexport function usePopoverPopup(\n ctx: PopoverRootValue,\n options: UsePopoverPopupOptions,\n): PopoverPopupHook {\n const { part, initialFocus, transition, onBeforeToggle: onBeforeToggleOption } = options;\n const onToggleOption = options.onToggle;\n const isLibrary = ctx.mode === \"library\";\n const { popupRef, triggerRef } = ctx;\n\n // The opening `beforetoggle` lets this one through: the library's own `showPopover()`.\n const opening = useRef(false);\n // Set by the synchronous `beforetoggle` of a close, cleared by `toggle` a task later: the\n // popup is hidden but its `toggle` has not arrived, so the apply effect must not re-open it\n // from a commit that lands in between (design 2.5).\n const awaitingClose = useRef(false);\n // Whether focus was inside the popup when its last close began (design 6.2 rule 1).\n const focusWasInside = useRef(false);\n const warnedFocus = useRef(false);\n\n // --- `held`: children stay mounted while the hide animates (design 4.4) -------------------\n\n const exit = useExitAnimation();\n\n // A library-driven close commits `false` first and hides in the layout effect, so without\n // `transition` the children are held through the close commit (\"previous value in state\").\n // With `transition` the close commit unmounts them inside the view transition, whose old\n // snapshot carries the content out.\n const [prevCommitted, setPrevCommitted] = useState(ctx.committed);\n if (prevCommitted !== ctx.committed) {\n setPrevCommitted(ctx.committed);\n if (isLibrary && prevCommitted && transition === undefined) exit.holdDuringRender();\n }\n\n // Whether the content unmounted while holding focus since the popup was last shown (H1).\n const contentProbe = useContentFocusProbe(popupRef);\n const { wrapContent } = contentProbe;\n\n const syncExpanded = (element: HTMLElement): void => {\n // JS-free mode only (design 9): read the DOM, not `newState`, so a stale task cannot lie.\n triggerRef.current?.setAttribute(\"aria-expanded\", String(isPopoverOpen(element)));\n };\n\n /**\n * Design 6.2 rule 2, for a close that began with focus inside: when focus is now on `<body>`\n * or still inside the hidden popup, focus the Trigger (connected, not inside a hidden\n * popover), else blur what holds focus.\n */\n const returnFocus = (element: HTMLElement): void => {\n const doc = element.ownerDocument;\n const active = doc.activeElement;\n if (active !== null && active !== doc.body && !element.contains(active)) return;\n const trigger = triggerRef.current;\n if (trigger !== null && trigger.isConnected && !insideHiddenPopover(trigger)) {\n trigger.focus();\n } else if (active instanceof HTMLElement && active !== doc.body) {\n active.blur();\n }\n };\n\n // --- Opening ------------------------------------------------------------------------------\n\n const resolveFocusTarget = (element: HTMLElement): HTMLElement | QuietFocusTarget | null => {\n if (options.resolveInitialFocus !== undefined) {\n const resolved = options.resolveInitialFocus(element);\n if (resolved === null) return null;\n const target = resolved instanceof HTMLElement ? resolved : resolved.element;\n return element.contains(target) ? resolved : null;\n }\n if (initialFocus === undefined) return null;\n const target = resolveInitialFocus(element, initialFocus);\n if (target !== null && element.contains(target)) return target;\n if (isDev && !warnedFocus.current) {\n warnedFocus.current = true;\n console.error(\n target === null\n ? `${part}: \\`initialFocus\\` resolved to no element; the platform picks the focus target.`\n : `${part}: \\`initialFocus\\` is outside the popup; the platform picks the focus target.`,\n );\n }\n return null;\n };\n\n const showPopup = (element: HTMLElement): void => {\n const resolved = resolveFocusTarget(element);\n const target =\n resolved === null || resolved instanceof HTMLElement ? resolved : resolved.element;\n // A pointer open asks for no focus ring: `focus({ focusVisible: false })` after the show,\n // where the engine reads that option (QuietFocusTarget).\n const quiet =\n target !== null &&\n !(resolved instanceof HTMLElement) &&\n supportsFocusVisibleOption(element.ownerDocument);\n // Otherwise the popover focusing steps honour `autofocus` (and run the dialog focusing steps\n // for a <dialog>, in every engine); React drops `autoFocus` on client-rendered content.\n // Never `.focus()` (6.1) except for the quiet target above.\n const added = target !== null && !quiet && !target.hasAttribute(\"autofocus\");\n if (added) target.setAttribute(\"autofocus\", \"\");\n opening.current = true;\n try {\n const source = triggerRef.current;\n element.showPopover(source !== null && source.isConnected ? { source } : undefined);\n } finally {\n opening.current = false;\n if (added) target.removeAttribute(\"autofocus\");\n }\n if (quiet && isPopoverOpen(element) && element.contains(target)) {\n target.focus({ focusVisible: false } as FocusOptions);\n }\n };\n\n // Mount reconciliation (design 2.4): a click before hydration opened the popover natively.\n // Runs before the apply effect; the dispatch makes the apply effect wait for it.\n const reconcileOnMount = useEffectEvent(() => {\n const element = popupRef.current;\n if (element === null) return;\n if (!isLibrary) {\n syncExpanded(element);\n return;\n }\n if (isPopoverOpen(element)) ctx.requestOpen(undefined, \"platform\");\n });\n useLayoutEffect(() => {\n reconcileOnMount();\n }, []);\n\n // The apply effect (design 2.5): after every commit that re-renders the Popup, once nothing is\n // in flight, the DOM follows committed state. Since the Root's value is memoised (#48) an\n // unrelated Root render does not re-render the Popup, so this does not run for it; every path\n // that makes the DOM diverge from state goes through a dispatch, whose `pending` flips re-render\n // the Popup (review of #48, finding 4). Do not rely on an unrelated render to re-apply state.\n // One effect covers the normal open, `defaultOpen`, a controlled open\n // or close, a library-driven close after `openChangeAction(false)` settled, the re-open of a\n // refused dismissal, and the close after a refused or failed pre-hydration open.\n useLayoutEffect(() => {\n if (!isLibrary) return;\n const element = popupRef.current;\n if (element === null || !element.isConnected) return;\n if (!ctx.pending && ctx.latestInFlight() === null) {\n const shown = isPopoverOpen(element);\n if (ctx.committed && !shown) {\n if (!awaitingClose.current) showPopup(element);\n } else if (!ctx.committed && shown) {\n // An echo: the `toggle` listener finds `false` intended and skips the dispatch.\n ctx.recordCloseIntent(undefined);\n element.hidePopover();\n // The close commit may have unmounted the focused content before the hide (H1): the\n // platform restores focus only from inside the popover, so do it now.\n if (focusWasInside.current) returnFocus(element);\n }\n }\n // Content that unmounted while the popup stays open (not a close): forget it.\n if (ctx.committed && isPopoverOpen(element)) contentProbe.forget();\n // A hold the close commit set (or a dismissal's notification commit) that no hide animation\n // is going to release: the popup is already hidden and its `toggle` has been handled.\n if (\n exit.held &&\n !ctx.committed &&\n !exit.waiting() &&\n !awaitingClose.current &&\n !isPopoverOpen(element)\n ) {\n exit.release();\n }\n });\n\n // --- Native listeners (conventions 7.4; filtered on target: React 19.3.1 emulates bubbling\n // of `toggle`, and a nested popover's events reach this element through the real DOM too) ---\n\n const onBeforeToggle = (event: Event): void => {\n const element = popupRef.current;\n if (element === null || event.target !== element) return;\n const toggle = event as ToggleEvent;\n if (toggle.newState === \"open\") {\n // Every open the library did not start itself is routed through the Root (design 2.2):\n // a foreign `show-popover` / `toggle-popover` invoker, a script `showPopover()`.\n if (isLibrary && !opening.current) {\n event.preventDefault();\n ctx.requestOpen(event, \"activate\");\n } else {\n // A Close action that resolves after this open must not close it (design 3.4).\n ctx.nextOpenGeneration();\n }\n contentProbe.forget();\n } else {\n awaitingClose.current = true;\n exit.recordBaseline(element);\n const active = element.ownerDocument.activeElement;\n focusWasInside.current =\n (active !== null && element.contains(active)) || contentProbe.hadFocus();\n contentProbe.forget();\n }\n onBeforeToggleOption?.(toggle);\n };\n\n const onToggle = (event: Event): void => {\n const element = popupRef.current;\n if (element === null || event.target !== element) return;\n awaitingClose.current = false;\n if (!isLibrary) syncExpanded(element);\n\n if (!isPopoverOpen(element)) {\n // Focus return for what the platform leaves out (design 6.2): a nested popover restores\n // nothing, and an opener that is gone leaves focus inside the hidden popup.\n const wasInside = focusWasInside.current;\n focusWasInside.current = false;\n if (wasInside) returnFocus(element);\n\n const intent = ctx.takeCloseIntent();\n if (isLibrary) {\n // `false` already intended: the library's own hide (or a dismissal during a pending\n // part-initiated close). With `transition` such a close needs no hold (4.4).\n const echo = !ctx.intended();\n if (!echo || transition === undefined) {\n const animations = exit.exitAnimations(element);\n if (animations.length > 0) {\n // Before the notification, so its commit keeps the children (urgent, internal).\n exit.holdUntil(animations);\n } else {\n // Nothing animates (Firefox, or no CSS exit): release a hold now; the commit is\n // invisible, the popup is already `display: none`.\n exit.release();\n }\n }\n ctx.notifyClosed(intent?.event ?? event, intent?.reason ?? \"dismiss\");\n }\n }\n onToggleOption?.(event as ToggleEvent);\n };\n\n const onCommand = (event: Event): void => {\n const element = popupRef.current;\n if (element === null || event.target !== element || event.defaultPrevented) return;\n const command = String((event as Event & { command?: string }).command ?? \"\").toLowerCase();\n // A foreign invoker closing the popup (design 2.3). Opens are caught at `beforetoggle`.\n if (command === \"hide-popover\" || (command === \"toggle-popover\" && isPopoverOpen(element))) {\n ctx.recordCloseIntent({ reason: \"activate\", event });\n }\n };\n\n // The listeners the callback ref attaches call the latest render's handlers. An insertion\n // effect publishes them before refs attach and before any layout effect (whose\n // `hidePopover()` dispatches `beforetoggle` synchronously).\n const handlers = useRef({ onBeforeToggle, onToggle, onCommand });\n useInsertionEffect(() => {\n handlers.current = { onBeforeToggle, onToggle, onCommand };\n });\n\n // --- Element ------------------------------------------------------------------------------\n\n // The listeners attach in the callback ref, so they are in place from the commit that mounts\n // the element (no passive-effect window, no extra render). JS-free mode: there is no \"right\n // before showPopover()\" (the browser runs the command), so the `autofocus` attribute stays on\n // the `initialFocus` target while it is mounted (as Dialog).\n const setElement = useCallback(\n (element: HTMLElement) => {\n if (isDev && element.tagName !== \"DIALOG\" && element.tagName !== \"DIV\") {\n console.error(\n `${part}: expected to render a <dialog> or a <div> but got ` +\n `<${element.tagName.toLowerCase()}>.`,\n );\n }\n popupRef.current = element;\n const listeners: [string, EventListener][] = [\n [\"beforetoggle\", (event) => handlers.current.onBeforeToggle(event)],\n [\"toggle\", (event) => handlers.current.onToggle(event)],\n [\"command\", (event) => handlers.current.onCommand(event)],\n ];\n for (const [type, listener] of listeners) element.addEventListener(type, listener);\n\n let autofocused: HTMLElement | null = null;\n if (!isLibrary && initialFocus !== undefined) {\n const target = resolveInitialFocus(element, initialFocus);\n if (target !== null && element.contains(target) && !target.hasAttribute(\"autofocus\")) {\n target.setAttribute(\"autofocus\", \"\");\n autofocused = target;\n }\n }\n\n return () => {\n for (const [type, listener] of listeners) element.removeEventListener(type, listener);\n autofocused?.removeAttribute(\"autofocus\");\n if (popupRef.current === element) popupRef.current = null;\n };\n },\n [part, popupRef, isLibrary, initialFocus],\n );\n\n // Development check (design 9): `role=\"dialog\"` needs a name once the popup is (or can be)\n // shown.\n const shown = !isLibrary || ctx.committed;\n const warnedName = useRef(false);\n useEffect(() => {\n const element = popupRef.current;\n if (!isDev || element === null || !shown || warnedName.current) return;\n if (element.hasAttribute(\"aria-label\")) return;\n const labelledBy = (element.getAttribute(\"aria-labelledby\") ?? \"\").split(/\\s+/);\n const doc = element.ownerDocument;\n if (labelledBy.some((id) => id !== \"\" && doc.getElementById(id) !== null)) return;\n const role = element.getAttribute(\"role\");\n if (role !== \"dialog\" && role !== \"alertdialog\" && role !== \"menu\") return;\n warnedName.current = true;\n console.error(\n `${part}: the popup has no accessible name. Render a Title, or give the Popup an ` +\n \"`aria-label`.\",\n );\n }, [popupRef, shown, part]);\n\n return { setElement, visible: ctx.committed || exit.held, wrapContent };\n}\n"],"mappings":"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"}