@wordpress/ui 0.11.1-next.v.202604091042.0 → 0.12.0

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 (827) hide show
  1. package/CHANGELOG.md +58 -1
  2. package/CONTRIBUTING.md +124 -0
  3. package/README.md +17 -9
  4. package/build/alert-dialog/index.cjs +3 -0
  5. package/build/alert-dialog/index.cjs.map +2 -2
  6. package/build/alert-dialog/popup.cjs +120 -55
  7. package/build/alert-dialog/popup.cjs.map +3 -3
  8. package/build/alert-dialog/portal.cjs +38 -0
  9. package/build/alert-dialog/portal.cjs.map +7 -0
  10. package/build/alert-dialog/types.cjs.map +1 -1
  11. package/build/badge/badge.cjs +14 -14
  12. package/build/badge/badge.cjs.map +2 -2
  13. package/build/button/button.cjs +6 -6
  14. package/build/button/button.cjs.map +2 -2
  15. package/build/card/content.cjs +4 -4
  16. package/build/card/content.cjs.map +2 -2
  17. package/build/card/full-bleed.cjs +4 -4
  18. package/build/card/full-bleed.cjs.map +2 -2
  19. package/build/card/header.cjs +4 -4
  20. package/build/card/header.cjs.map +2 -2
  21. package/build/card/root.cjs +4 -4
  22. package/build/card/root.cjs.map +2 -2
  23. package/build/card/title.cjs +5 -25
  24. package/build/card/title.cjs.map +4 -4
  25. package/build/collapsible-card/content.cjs +9 -5
  26. package/build/collapsible-card/content.cjs.map +2 -2
  27. package/build/collapsible-card/header.cjs +14 -4
  28. package/build/collapsible-card/header.cjs.map +3 -3
  29. package/build/dialog/content.cjs +85 -0
  30. package/build/dialog/content.cjs.map +7 -0
  31. package/build/dialog/context.cjs +12 -44
  32. package/build/dialog/context.cjs.map +2 -2
  33. package/build/dialog/description.cjs +59 -0
  34. package/build/dialog/description.cjs.map +7 -0
  35. package/build/dialog/footer.cjs +5 -4
  36. package/build/dialog/footer.cjs.map +2 -2
  37. package/build/dialog/header.cjs +5 -4
  38. package/build/dialog/header.cjs.map +2 -2
  39. package/build/dialog/index.cjs +9 -0
  40. package/build/dialog/index.cjs.map +2 -2
  41. package/build/dialog/popup.cjs +21 -8
  42. package/build/dialog/popup.cjs.map +2 -2
  43. package/build/dialog/portal.cjs +38 -0
  44. package/build/dialog/portal.cjs.map +7 -0
  45. package/build/dialog/root.cjs +3 -2
  46. package/build/dialog/root.cjs.map +2 -2
  47. package/build/dialog/title.cjs +16 -22
  48. package/build/dialog/title.cjs.map +3 -3
  49. package/build/dialog/types.cjs.map +1 -1
  50. package/build/drawer/action.cjs +48 -0
  51. package/build/drawer/action.cjs.map +7 -0
  52. package/build/drawer/close-icon.cjs +58 -0
  53. package/build/drawer/close-icon.cjs.map +7 -0
  54. package/build/drawer/content.cjs +86 -0
  55. package/build/drawer/content.cjs.map +7 -0
  56. package/build/drawer/context.cjs +44 -0
  57. package/build/drawer/context.cjs.map +7 -0
  58. package/build/drawer/description.cjs +47 -0
  59. package/build/drawer/description.cjs.map +7 -0
  60. package/build/drawer/footer.cjs +65 -0
  61. package/build/drawer/footer.cjs.map +7 -0
  62. package/build/drawer/header.cjs +65 -0
  63. package/build/drawer/header.cjs.map +7 -0
  64. package/build/drawer/index.cjs +61 -0
  65. package/build/drawer/index.cjs.map +7 -0
  66. package/build/drawer/popup.cjs +103 -0
  67. package/build/drawer/popup.cjs.map +7 -0
  68. package/build/drawer/portal.cjs +38 -0
  69. package/build/drawer/portal.cjs.map +7 -0
  70. package/build/drawer/root.cjs +49 -0
  71. package/build/drawer/root.cjs.map +7 -0
  72. package/build/drawer/title.cjs +70 -0
  73. package/build/drawer/title.cjs.map +7 -0
  74. package/build/drawer/trigger.cjs +38 -0
  75. package/build/drawer/trigger.cjs.map +7 -0
  76. package/build/drawer/types.cjs +19 -0
  77. package/build/drawer/types.cjs.map +7 -0
  78. package/build/empty-state/actions.cjs +3 -3
  79. package/build/empty-state/actions.cjs.map +2 -2
  80. package/build/empty-state/description.cjs +8 -5
  81. package/build/empty-state/description.cjs.map +2 -2
  82. package/build/empty-state/icon.cjs +3 -3
  83. package/build/empty-state/icon.cjs.map +2 -2
  84. package/build/empty-state/root.cjs +3 -3
  85. package/build/empty-state/root.cjs.map +2 -2
  86. package/build/empty-state/title.cjs +8 -5
  87. package/build/empty-state/title.cjs.map +2 -2
  88. package/build/empty-state/visual.cjs +3 -3
  89. package/build/empty-state/visual.cjs.map +2 -2
  90. package/build/form/primitives/autocomplete/clear.cjs +62 -0
  91. package/build/form/primitives/autocomplete/clear.cjs.map +7 -0
  92. package/build/form/primitives/autocomplete/collection.cjs +38 -0
  93. package/build/form/primitives/autocomplete/collection.cjs.map +7 -0
  94. package/build/form/primitives/autocomplete/empty.cjs +67 -0
  95. package/build/form/primitives/autocomplete/empty.cjs.map +7 -0
  96. package/build/form/primitives/autocomplete/index.cjs +64 -0
  97. package/build/form/primitives/autocomplete/index.cjs.map +7 -0
  98. package/build/form/primitives/autocomplete/input-group.cjs +36 -0
  99. package/build/form/primitives/autocomplete/input-group.cjs.map +7 -0
  100. package/build/form/primitives/autocomplete/input.cjs +47 -0
  101. package/build/form/primitives/autocomplete/input.cjs.map +7 -0
  102. package/build/form/primitives/autocomplete/item.cjs +81 -0
  103. package/build/form/primitives/autocomplete/item.cjs.map +7 -0
  104. package/build/form/primitives/autocomplete/list-body.cjs +57 -0
  105. package/build/form/primitives/autocomplete/list-body.cjs.map +7 -0
  106. package/build/form/primitives/autocomplete/list.cjs +67 -0
  107. package/build/form/primitives/autocomplete/list.cjs.map +7 -0
  108. package/build/form/primitives/autocomplete/popup.cjs +102 -0
  109. package/build/form/primitives/autocomplete/popup.cjs.map +7 -0
  110. package/build/form/primitives/autocomplete/portal.cjs +38 -0
  111. package/build/form/primitives/autocomplete/portal.cjs.map +7 -0
  112. package/build/form/primitives/autocomplete/root.cjs +35 -0
  113. package/build/form/primitives/autocomplete/root.cjs.map +7 -0
  114. package/build/form/primitives/autocomplete/types.cjs +19 -0
  115. package/build/form/primitives/autocomplete/types.cjs.map +7 -0
  116. package/build/form/primitives/autocomplete/value.cjs +35 -0
  117. package/build/form/primitives/autocomplete/value.cjs.map +7 -0
  118. package/build/form/primitives/field/description.cjs +6 -6
  119. package/build/form/primitives/field/description.cjs.map +2 -2
  120. package/build/form/primitives/field/details.cjs +4 -4
  121. package/build/form/primitives/field/details.cjs.map +2 -2
  122. package/build/form/primitives/field/label.cjs +8 -8
  123. package/build/form/primitives/field/label.cjs.map +2 -2
  124. package/build/form/primitives/field/root.cjs +2 -2
  125. package/build/form/primitives/field/root.cjs.map +2 -2
  126. package/build/form/primitives/fieldset/description.cjs +6 -6
  127. package/build/form/primitives/fieldset/description.cjs.map +2 -2
  128. package/build/form/primitives/fieldset/details.cjs +3 -3
  129. package/build/form/primitives/fieldset/details.cjs.map +2 -2
  130. package/build/form/primitives/fieldset/legend.cjs +8 -7
  131. package/build/form/primitives/fieldset/legend.cjs.map +2 -2
  132. package/build/form/primitives/index.cjs +3 -0
  133. package/build/form/primitives/index.cjs.map +2 -2
  134. package/build/form/primitives/input/input.cjs +6 -6
  135. package/build/form/primitives/input/input.cjs.map +2 -2
  136. package/build/form/primitives/input-layout/input-layout.cjs +6 -6
  137. package/build/form/primitives/input-layout/input-layout.cjs.map +2 -2
  138. package/build/form/primitives/input-layout/slot.cjs +3 -3
  139. package/build/form/primitives/input-layout/slot.cjs.map +2 -2
  140. package/build/form/primitives/select/index.cjs +3 -0
  141. package/build/form/primitives/select/index.cjs.map +2 -2
  142. package/build/form/primitives/select/item.cjs +4 -5
  143. package/build/form/primitives/select/item.cjs.map +2 -2
  144. package/build/form/primitives/select/popup.cjs +12 -11
  145. package/build/form/primitives/select/popup.cjs.map +2 -2
  146. package/build/form/primitives/select/portal.cjs +38 -0
  147. package/build/form/primitives/select/portal.cjs.map +7 -0
  148. package/build/form/primitives/select/trigger.cjs +3 -3
  149. package/build/form/primitives/select/trigger.cjs.map +1 -1
  150. package/build/form/primitives/select/types.cjs.map +1 -1
  151. package/build/form/primitives/textarea/textarea.cjs +3 -3
  152. package/build/form/primitives/textarea/textarea.cjs.map +2 -2
  153. package/build/index.cjs +3 -0
  154. package/build/index.cjs.map +2 -2
  155. package/build/link/link.cjs +11 -21
  156. package/build/link/link.cjs.map +2 -2
  157. package/build/link/types.cjs.map +1 -1
  158. package/build/notice/action-button.cjs +3 -3
  159. package/build/notice/action-button.cjs.map +2 -2
  160. package/build/notice/action-link.cjs +8 -7
  161. package/build/notice/action-link.cjs.map +2 -2
  162. package/build/notice/actions.cjs +3 -3
  163. package/build/notice/actions.cjs.map +2 -2
  164. package/build/notice/close-icon.cjs +3 -3
  165. package/build/notice/close-icon.cjs.map +2 -2
  166. package/build/notice/description.cjs +3 -3
  167. package/build/notice/description.cjs.map +2 -2
  168. package/build/notice/root.cjs +3 -3
  169. package/build/notice/root.cjs.map +2 -2
  170. package/build/notice/title.cjs +3 -3
  171. package/build/notice/title.cjs.map +2 -2
  172. package/build/popover/arrow.cjs +4 -4
  173. package/build/popover/arrow.cjs.map +2 -2
  174. package/build/popover/context.cjs +4 -44
  175. package/build/popover/context.cjs.map +2 -2
  176. package/build/popover/description.cjs +2 -25
  177. package/build/popover/description.cjs.map +4 -4
  178. package/build/popover/index.cjs +3 -0
  179. package/build/popover/index.cjs.map +2 -2
  180. package/build/popover/popup.cjs +15 -15
  181. package/build/popover/popup.cjs.map +2 -2
  182. package/build/popover/portal.cjs +38 -0
  183. package/build/popover/portal.cjs.map +7 -0
  184. package/build/popover/root.cjs.map +1 -1
  185. package/build/popover/title.cjs +19 -5
  186. package/build/popover/title.cjs.map +3 -3
  187. package/build/popover/types.cjs.map +1 -1
  188. package/build/tabs/context.cjs +9 -22
  189. package/build/tabs/context.cjs.map +2 -2
  190. package/build/tabs/list.cjs +4 -5
  191. package/build/tabs/list.cjs.map +2 -2
  192. package/build/tabs/panel.cjs +19 -6
  193. package/build/tabs/panel.cjs.map +3 -3
  194. package/build/tabs/tab.cjs +4 -4
  195. package/build/tabs/tab.cjs.map +2 -2
  196. package/build/text/text.cjs +8 -8
  197. package/build/text/text.cjs.map +2 -2
  198. package/build/tooltip/index.cjs +3 -0
  199. package/build/tooltip/index.cjs.map +2 -2
  200. package/build/tooltip/popup.cjs +11 -13
  201. package/build/tooltip/popup.cjs.map +3 -3
  202. package/build/tooltip/portal.cjs +38 -0
  203. package/build/tooltip/portal.cjs.map +7 -0
  204. package/build/tooltip/provider.cjs +2 -2
  205. package/build/tooltip/provider.cjs.map +3 -3
  206. package/build/tooltip/root.cjs.map +3 -3
  207. package/build/tooltip/trigger.cjs +2 -2
  208. package/build/tooltip/trigger.cjs.map +3 -3
  209. package/build/tooltip/types.cjs.map +1 -1
  210. package/build/utils/create-overlay-modal-context.cjs +48 -0
  211. package/build/utils/create-overlay-modal-context.cjs.map +7 -0
  212. package/build/utils/create-overlay-title-validation.cjs +93 -0
  213. package/build/utils/create-overlay-title-validation.cjs.map +7 -0
  214. package/build/utils/render-portal-with-children.cjs +37 -0
  215. package/build/utils/render-portal-with-children.cjs.map +7 -0
  216. package/build/utils/types.cjs.map +1 -1
  217. package/build/utils/use-deprioritized-initial-focus.cjs +8 -8
  218. package/build/utils/use-deprioritized-initial-focus.cjs.map +2 -2
  219. package/build/utils/use-overlay-scroll-state-attributes.cjs +140 -0
  220. package/build/utils/use-overlay-scroll-state-attributes.cjs.map +7 -0
  221. package/build/utils/use-schedule-validation.cjs +59 -0
  222. package/build/utils/use-schedule-validation.cjs.map +7 -0
  223. package/build/visually-hidden/visually-hidden.cjs +5 -1
  224. package/build/visually-hidden/visually-hidden.cjs.map +2 -2
  225. package/build-module/alert-dialog/index.mjs +2 -0
  226. package/build-module/alert-dialog/index.mjs.map +2 -2
  227. package/build-module/alert-dialog/popup.mjs +124 -56
  228. package/build-module/alert-dialog/popup.mjs.map +3 -3
  229. package/build-module/alert-dialog/portal.mjs +13 -0
  230. package/build-module/alert-dialog/portal.mjs.map +7 -0
  231. package/build-module/badge/badge.mjs +14 -14
  232. package/build-module/badge/badge.mjs.map +2 -2
  233. package/build-module/button/button.mjs +6 -6
  234. package/build-module/button/button.mjs.map +2 -2
  235. package/build-module/card/content.mjs +4 -4
  236. package/build-module/card/content.mjs.map +2 -2
  237. package/build-module/card/full-bleed.mjs +4 -4
  238. package/build-module/card/full-bleed.mjs.map +2 -2
  239. package/build-module/card/header.mjs +4 -4
  240. package/build-module/card/header.mjs.map +2 -2
  241. package/build-module/card/root.mjs +4 -4
  242. package/build-module/card/root.mjs.map +2 -2
  243. package/build-module/card/title.mjs +5 -15
  244. package/build-module/card/title.mjs.map +3 -3
  245. package/build-module/collapsible-card/content.mjs +9 -5
  246. package/build-module/collapsible-card/content.mjs.map +2 -2
  247. package/build-module/collapsible-card/header.mjs +14 -4
  248. package/build-module/collapsible-card/header.mjs.map +3 -3
  249. package/build-module/dialog/content.mjs +50 -0
  250. package/build-module/dialog/content.mjs.map +7 -0
  251. package/build-module/dialog/context.mjs +10 -51
  252. package/build-module/dialog/context.mjs.map +2 -2
  253. package/build-module/dialog/description.mjs +34 -0
  254. package/build-module/dialog/description.mjs.map +7 -0
  255. package/build-module/dialog/footer.mjs +5 -4
  256. package/build-module/dialog/footer.mjs.map +2 -2
  257. package/build-module/dialog/header.mjs +5 -4
  258. package/build-module/dialog/header.mjs.map +2 -2
  259. package/build-module/dialog/index.mjs +6 -0
  260. package/build-module/dialog/index.mjs.map +2 -2
  261. package/build-module/dialog/popup.mjs +23 -10
  262. package/build-module/dialog/popup.mjs.map +2 -2
  263. package/build-module/dialog/portal.mjs +13 -0
  264. package/build-module/dialog/portal.mjs.map +7 -0
  265. package/build-module/dialog/root.mjs +3 -2
  266. package/build-module/dialog/root.mjs.map +2 -2
  267. package/build-module/dialog/title.mjs +17 -13
  268. package/build-module/dialog/title.mjs.map +2 -2
  269. package/build-module/drawer/action.mjs +23 -0
  270. package/build-module/drawer/action.mjs.map +7 -0
  271. package/build-module/drawer/close-icon.mjs +33 -0
  272. package/build-module/drawer/close-icon.mjs.map +7 -0
  273. package/build-module/drawer/content.mjs +51 -0
  274. package/build-module/drawer/content.mjs.map +7 -0
  275. package/build-module/drawer/context.mjs +16 -0
  276. package/build-module/drawer/context.mjs.map +7 -0
  277. package/build-module/drawer/description.mjs +22 -0
  278. package/build-module/drawer/description.mjs.map +7 -0
  279. package/build-module/drawer/footer.mjs +30 -0
  280. package/build-module/drawer/footer.mjs.map +7 -0
  281. package/build-module/drawer/header.mjs +30 -0
  282. package/build-module/drawer/header.mjs.map +7 -0
  283. package/build-module/drawer/index.mjs +26 -0
  284. package/build-module/drawer/index.mjs.map +7 -0
  285. package/build-module/drawer/popup.mjs +70 -0
  286. package/build-module/drawer/popup.mjs.map +7 -0
  287. package/build-module/drawer/portal.mjs +13 -0
  288. package/build-module/drawer/portal.mjs.map +7 -0
  289. package/build-module/drawer/root.mjs +24 -0
  290. package/build-module/drawer/root.mjs.map +7 -0
  291. package/build-module/drawer/title.mjs +45 -0
  292. package/build-module/drawer/title.mjs.map +7 -0
  293. package/build-module/drawer/trigger.mjs +13 -0
  294. package/build-module/drawer/trigger.mjs.map +7 -0
  295. package/build-module/drawer/types.mjs +1 -0
  296. package/build-module/empty-state/actions.mjs +3 -3
  297. package/build-module/empty-state/actions.mjs.map +2 -2
  298. package/build-module/empty-state/description.mjs +8 -5
  299. package/build-module/empty-state/description.mjs.map +2 -2
  300. package/build-module/empty-state/icon.mjs +3 -3
  301. package/build-module/empty-state/icon.mjs.map +2 -2
  302. package/build-module/empty-state/root.mjs +3 -3
  303. package/build-module/empty-state/root.mjs.map +2 -2
  304. package/build-module/empty-state/title.mjs +8 -5
  305. package/build-module/empty-state/title.mjs.map +2 -2
  306. package/build-module/empty-state/visual.mjs +3 -3
  307. package/build-module/empty-state/visual.mjs.map +2 -2
  308. package/build-module/form/primitives/autocomplete/clear.mjs +37 -0
  309. package/build-module/form/primitives/autocomplete/clear.mjs.map +7 -0
  310. package/build-module/form/primitives/autocomplete/collection.mjs +13 -0
  311. package/build-module/form/primitives/autocomplete/collection.mjs.map +7 -0
  312. package/build-module/form/primitives/autocomplete/empty.mjs +32 -0
  313. package/build-module/form/primitives/autocomplete/empty.mjs.map +7 -0
  314. package/build-module/form/primitives/autocomplete/index.mjs +28 -0
  315. package/build-module/form/primitives/autocomplete/index.mjs.map +7 -0
  316. package/build-module/form/primitives/autocomplete/input-group.mjs +11 -0
  317. package/build-module/form/primitives/autocomplete/input-group.mjs.map +7 -0
  318. package/build-module/form/primitives/autocomplete/input.mjs +22 -0
  319. package/build-module/form/primitives/autocomplete/input.mjs.map +7 -0
  320. package/build-module/form/primitives/autocomplete/item.mjs +46 -0
  321. package/build-module/form/primitives/autocomplete/item.mjs.map +7 -0
  322. package/build-module/form/primitives/autocomplete/list-body.mjs +32 -0
  323. package/build-module/form/primitives/autocomplete/list-body.mjs.map +7 -0
  324. package/build-module/form/primitives/autocomplete/list.mjs +32 -0
  325. package/build-module/form/primitives/autocomplete/list.mjs.map +7 -0
  326. package/build-module/form/primitives/autocomplete/popup.mjs +69 -0
  327. package/build-module/form/primitives/autocomplete/popup.mjs.map +7 -0
  328. package/build-module/form/primitives/autocomplete/portal.mjs +13 -0
  329. package/build-module/form/primitives/autocomplete/portal.mjs.map +7 -0
  330. package/build-module/form/primitives/autocomplete/root.mjs +10 -0
  331. package/build-module/form/primitives/autocomplete/root.mjs.map +7 -0
  332. package/build-module/form/primitives/autocomplete/types.mjs +1 -0
  333. package/build-module/form/primitives/autocomplete/value.mjs +10 -0
  334. package/build-module/form/primitives/autocomplete/value.mjs.map +7 -0
  335. package/build-module/form/primitives/field/description.mjs +6 -6
  336. package/build-module/form/primitives/field/description.mjs.map +2 -2
  337. package/build-module/form/primitives/field/details.mjs +4 -4
  338. package/build-module/form/primitives/field/details.mjs.map +2 -2
  339. package/build-module/form/primitives/field/label.mjs +8 -8
  340. package/build-module/form/primitives/field/label.mjs.map +2 -2
  341. package/build-module/form/primitives/field/root.mjs +2 -2
  342. package/build-module/form/primitives/field/root.mjs.map +2 -2
  343. package/build-module/form/primitives/fieldset/description.mjs +6 -6
  344. package/build-module/form/primitives/fieldset/description.mjs.map +2 -2
  345. package/build-module/form/primitives/fieldset/details.mjs +3 -3
  346. package/build-module/form/primitives/fieldset/details.mjs.map +2 -2
  347. package/build-module/form/primitives/fieldset/legend.mjs +8 -7
  348. package/build-module/form/primitives/fieldset/legend.mjs.map +2 -2
  349. package/build-module/form/primitives/index.mjs +2 -0
  350. package/build-module/form/primitives/index.mjs.map +2 -2
  351. package/build-module/form/primitives/input/input.mjs +6 -6
  352. package/build-module/form/primitives/input/input.mjs.map +2 -2
  353. package/build-module/form/primitives/input-layout/input-layout.mjs +6 -6
  354. package/build-module/form/primitives/input-layout/input-layout.mjs.map +2 -2
  355. package/build-module/form/primitives/input-layout/slot.mjs +3 -3
  356. package/build-module/form/primitives/input-layout/slot.mjs.map +2 -2
  357. package/build-module/form/primitives/select/index.mjs +2 -0
  358. package/build-module/form/primitives/select/index.mjs.map +2 -2
  359. package/build-module/form/primitives/select/item.mjs +4 -5
  360. package/build-module/form/primitives/select/item.mjs.map +2 -2
  361. package/build-module/form/primitives/select/popup.mjs +12 -11
  362. package/build-module/form/primitives/select/popup.mjs.map +2 -2
  363. package/build-module/form/primitives/select/portal.mjs +13 -0
  364. package/build-module/form/primitives/select/portal.mjs.map +7 -0
  365. package/build-module/form/primitives/select/trigger.mjs +3 -3
  366. package/build-module/form/primitives/select/trigger.mjs.map +1 -1
  367. package/build-module/form/primitives/textarea/textarea.mjs +3 -3
  368. package/build-module/form/primitives/textarea/textarea.mjs.map +2 -2
  369. package/build-module/index.mjs +2 -0
  370. package/build-module/index.mjs.map +2 -2
  371. package/build-module/link/link.mjs +11 -21
  372. package/build-module/link/link.mjs.map +2 -2
  373. package/build-module/notice/action-button.mjs +3 -3
  374. package/build-module/notice/action-button.mjs.map +2 -2
  375. package/build-module/notice/action-link.mjs +8 -7
  376. package/build-module/notice/action-link.mjs.map +2 -2
  377. package/build-module/notice/actions.mjs +3 -3
  378. package/build-module/notice/actions.mjs.map +2 -2
  379. package/build-module/notice/close-icon.mjs +3 -3
  380. package/build-module/notice/close-icon.mjs.map +2 -2
  381. package/build-module/notice/description.mjs +3 -3
  382. package/build-module/notice/description.mjs.map +2 -2
  383. package/build-module/notice/root.mjs +3 -3
  384. package/build-module/notice/root.mjs.map +2 -2
  385. package/build-module/notice/title.mjs +3 -3
  386. package/build-module/notice/title.mjs.map +2 -2
  387. package/build-module/popover/arrow.mjs +4 -4
  388. package/build-module/popover/arrow.mjs.map +2 -2
  389. package/build-module/popover/context.mjs +4 -51
  390. package/build-module/popover/context.mjs.map +2 -2
  391. package/build-module/popover/description.mjs +2 -15
  392. package/build-module/popover/description.mjs.map +3 -3
  393. package/build-module/popover/index.mjs +2 -0
  394. package/build-module/popover/index.mjs.map +2 -2
  395. package/build-module/popover/popup.mjs +16 -16
  396. package/build-module/popover/popup.mjs.map +2 -2
  397. package/build-module/popover/portal.mjs +13 -0
  398. package/build-module/popover/portal.mjs.map +7 -0
  399. package/build-module/popover/root.mjs.map +1 -1
  400. package/build-module/popover/title.mjs +20 -6
  401. package/build-module/popover/title.mjs.map +3 -3
  402. package/build-module/tabs/context.mjs +11 -24
  403. package/build-module/tabs/context.mjs.map +2 -2
  404. package/build-module/tabs/list.mjs +4 -5
  405. package/build-module/tabs/list.mjs.map +2 -2
  406. package/build-module/tabs/panel.mjs +19 -6
  407. package/build-module/tabs/panel.mjs.map +3 -3
  408. package/build-module/tabs/tab.mjs +4 -4
  409. package/build-module/tabs/tab.mjs.map +2 -2
  410. package/build-module/text/text.mjs +8 -8
  411. package/build-module/text/text.mjs.map +2 -2
  412. package/build-module/tooltip/index.mjs +2 -0
  413. package/build-module/tooltip/index.mjs.map +2 -2
  414. package/build-module/tooltip/popup.mjs +14 -16
  415. package/build-module/tooltip/popup.mjs.map +2 -2
  416. package/build-module/tooltip/portal.mjs +13 -0
  417. package/build-module/tooltip/portal.mjs.map +7 -0
  418. package/build-module/tooltip/provider.mjs +3 -3
  419. package/build-module/tooltip/provider.mjs.map +2 -2
  420. package/build-module/tooltip/root.mjs +2 -2
  421. package/build-module/tooltip/root.mjs.map +2 -2
  422. package/build-module/tooltip/trigger.mjs +3 -3
  423. package/build-module/tooltip/trigger.mjs.map +2 -2
  424. package/build-module/utils/create-overlay-modal-context.mjs +23 -0
  425. package/build-module/utils/create-overlay-modal-context.mjs.map +7 -0
  426. package/build-module/utils/create-overlay-title-validation.mjs +75 -0
  427. package/build-module/utils/create-overlay-title-validation.mjs.map +7 -0
  428. package/build-module/utils/render-portal-with-children.mjs +12 -0
  429. package/build-module/utils/render-portal-with-children.mjs.map +7 -0
  430. package/build-module/utils/use-deprioritized-initial-focus.mjs +9 -9
  431. package/build-module/utils/use-deprioritized-initial-focus.mjs.map +2 -2
  432. package/build-module/utils/use-overlay-scroll-state-attributes.mjs +114 -0
  433. package/build-module/utils/use-overlay-scroll-state-attributes.mjs.map +7 -0
  434. package/build-module/utils/use-schedule-validation.mjs +34 -0
  435. package/build-module/utils/use-schedule-validation.mjs.map +7 -0
  436. package/build-module/visually-hidden/visually-hidden.mjs +5 -1
  437. package/build-module/visually-hidden/visually-hidden.mjs.map +2 -2
  438. package/build-types/alert-dialog/index.d.ts +1 -0
  439. package/build-types/alert-dialog/index.d.ts.map +1 -1
  440. package/build-types/alert-dialog/popup.d.ts.map +1 -1
  441. package/build-types/alert-dialog/portal.d.ts +9 -0
  442. package/build-types/alert-dialog/portal.d.ts.map +1 -0
  443. package/build-types/alert-dialog/stories/index.story.d.ts +29 -1
  444. package/build-types/alert-dialog/stories/index.story.d.ts.map +1 -1
  445. package/build-types/alert-dialog/types.d.ts +27 -1
  446. package/build-types/alert-dialog/types.d.ts.map +1 -1
  447. package/build-types/badge/badge.d.ts.map +1 -1
  448. package/build-types/badge/stories/index.story.d.ts.map +1 -1
  449. package/build-types/card/stories/index.story.d.ts.map +1 -1
  450. package/build-types/card/title.d.ts.map +1 -1
  451. package/build-types/collapsible/panel.d.ts +2 -1
  452. package/build-types/collapsible/panel.d.ts.map +1 -1
  453. package/build-types/collapsible/root.d.ts +2 -1
  454. package/build-types/collapsible/root.d.ts.map +1 -1
  455. package/build-types/collapsible/stories/index.story.d.ts.map +1 -1
  456. package/build-types/collapsible/trigger.d.ts +2 -1
  457. package/build-types/collapsible/trigger.d.ts.map +1 -1
  458. package/build-types/collapsible-card/content.d.ts.map +1 -1
  459. package/build-types/collapsible-card/header.d.ts.map +1 -1
  460. package/build-types/collapsible-card/stories/index.story.d.ts.map +1 -1
  461. package/build-types/dialog/content.d.ts +17 -0
  462. package/build-types/dialog/content.d.ts.map +1 -0
  463. package/build-types/dialog/context.d.ts +11 -16
  464. package/build-types/dialog/context.d.ts.map +1 -1
  465. package/build-types/dialog/description.d.ts +9 -0
  466. package/build-types/dialog/description.d.ts.map +1 -0
  467. package/build-types/dialog/footer.d.ts +8 -1
  468. package/build-types/dialog/footer.d.ts.map +1 -1
  469. package/build-types/dialog/header.d.ts +8 -1
  470. package/build-types/dialog/header.d.ts.map +1 -1
  471. package/build-types/dialog/index.d.ts +4 -1
  472. package/build-types/dialog/index.d.ts.map +1 -1
  473. package/build-types/dialog/popup.d.ts +3 -0
  474. package/build-types/dialog/popup.d.ts.map +1 -1
  475. package/build-types/dialog/portal.d.ts +10 -0
  476. package/build-types/dialog/portal.d.ts.map +1 -0
  477. package/build-types/dialog/root.d.ts +14 -4
  478. package/build-types/dialog/root.d.ts.map +1 -1
  479. package/build-types/dialog/stories/index.story.d.ts +37 -6
  480. package/build-types/dialog/stories/index.story.d.ts.map +1 -1
  481. package/build-types/dialog/title.d.ts +12 -2
  482. package/build-types/dialog/title.d.ts.map +1 -1
  483. package/build-types/dialog/types.d.ts +66 -6
  484. package/build-types/dialog/types.d.ts.map +1 -1
  485. package/build-types/drawer/action.d.ts +8 -0
  486. package/build-types/drawer/action.d.ts.map +1 -0
  487. package/build-types/drawer/close-icon.d.ts +8 -0
  488. package/build-types/drawer/close-icon.d.ts.map +1 -0
  489. package/build-types/drawer/content.d.ts +21 -0
  490. package/build-types/drawer/content.d.ts.map +1 -0
  491. package/build-types/drawer/context.d.ts +20 -0
  492. package/build-types/drawer/context.d.ts.map +1 -0
  493. package/build-types/drawer/description.d.ts +9 -0
  494. package/build-types/drawer/description.d.ts.map +1 -0
  495. package/build-types/drawer/footer.d.ts +15 -0
  496. package/build-types/drawer/footer.d.ts.map +1 -0
  497. package/build-types/drawer/header.d.ts +15 -0
  498. package/build-types/drawer/header.d.ts.map +1 -0
  499. package/build-types/drawer/index.d.ts +13 -0
  500. package/build-types/drawer/index.d.ts.map +1 -0
  501. package/build-types/drawer/popup.d.ts +16 -0
  502. package/build-types/drawer/popup.d.ts.map +1 -0
  503. package/build-types/drawer/portal.d.ts +10 -0
  504. package/build-types/drawer/portal.d.ts.map +1 -0
  505. package/build-types/drawer/root.d.ts +21 -0
  506. package/build-types/drawer/root.d.ts.map +1 -0
  507. package/build-types/drawer/stories/index.story.d.ts +63 -0
  508. package/build-types/drawer/stories/index.story.d.ts.map +1 -0
  509. package/build-types/drawer/test/index.test.d.ts +2 -0
  510. package/build-types/drawer/test/index.test.d.ts.map +1 -0
  511. package/build-types/drawer/title.d.ts +22 -0
  512. package/build-types/drawer/title.d.ts.map +1 -0
  513. package/build-types/drawer/trigger.d.ts +7 -0
  514. package/build-types/drawer/trigger.d.ts.map +1 -0
  515. package/build-types/drawer/types.d.ts +146 -0
  516. package/build-types/drawer/types.d.ts.map +1 -0
  517. package/build-types/empty-state/description.d.ts.map +1 -1
  518. package/build-types/empty-state/stories/index.story.d.ts +1 -1
  519. package/build-types/empty-state/stories/index.story.d.ts.map +1 -1
  520. package/build-types/empty-state/title.d.ts.map +1 -1
  521. package/build-types/form/input-control/stories/index.story.d.ts +1 -1
  522. package/build-types/form/input-control/stories/index.story.d.ts.map +1 -1
  523. package/build-types/form/primitives/autocomplete/clear.d.ts +13 -0
  524. package/build-types/form/primitives/autocomplete/clear.d.ts.map +1 -0
  525. package/build-types/form/primitives/autocomplete/collection.d.ts +3 -0
  526. package/build-types/form/primitives/autocomplete/collection.d.ts.map +1 -0
  527. package/build-types/form/primitives/autocomplete/empty.d.ts +10 -0
  528. package/build-types/form/primitives/autocomplete/empty.d.ts.map +1 -0
  529. package/build-types/form/primitives/autocomplete/index.d.ts +13 -0
  530. package/build-types/form/primitives/autocomplete/index.d.ts.map +1 -0
  531. package/build-types/form/primitives/autocomplete/input-group.d.ts +16 -0
  532. package/build-types/form/primitives/autocomplete/input-group.d.ts.map +1 -0
  533. package/build-types/form/primitives/autocomplete/input.d.ts +3 -0
  534. package/build-types/form/primitives/autocomplete/input.d.ts.map +1 -0
  535. package/build-types/form/primitives/autocomplete/item.d.ts +10 -0
  536. package/build-types/form/primitives/autocomplete/item.d.ts.map +1 -0
  537. package/build-types/form/primitives/autocomplete/list-body.d.ts +13 -0
  538. package/build-types/form/primitives/autocomplete/list-body.d.ts.map +1 -0
  539. package/build-types/form/primitives/autocomplete/list.d.ts +11 -0
  540. package/build-types/form/primitives/autocomplete/list.d.ts.map +1 -0
  541. package/build-types/form/primitives/autocomplete/popup.d.ts +11 -0
  542. package/build-types/form/primitives/autocomplete/popup.d.ts.map +1 -0
  543. package/build-types/form/primitives/autocomplete/portal.d.ts +8 -0
  544. package/build-types/form/primitives/autocomplete/portal.d.ts.map +1 -0
  545. package/build-types/form/primitives/autocomplete/root.d.ts +8 -0
  546. package/build-types/form/primitives/autocomplete/root.d.ts.map +1 -0
  547. package/build-types/form/primitives/autocomplete/stories/fixtures.d.ts +8 -0
  548. package/build-types/form/primitives/autocomplete/stories/fixtures.d.ts.map +1 -0
  549. package/build-types/form/primitives/autocomplete/stories/index.story.d.ts +41 -0
  550. package/build-types/form/primitives/autocomplete/stories/index.story.d.ts.map +1 -0
  551. package/build-types/form/primitives/autocomplete/test/index.test.d.ts +2 -0
  552. package/build-types/form/primitives/autocomplete/test/index.test.d.ts.map +1 -0
  553. package/build-types/form/primitives/autocomplete/types.d.ts +44 -0
  554. package/build-types/form/primitives/autocomplete/types.d.ts.map +1 -0
  555. package/build-types/form/primitives/autocomplete/value.d.ts +3 -0
  556. package/build-types/form/primitives/autocomplete/value.d.ts.map +1 -0
  557. package/build-types/form/primitives/field/description.d.ts +2 -1
  558. package/build-types/form/primitives/field/description.d.ts.map +1 -1
  559. package/build-types/form/primitives/field/details.d.ts +2 -1
  560. package/build-types/form/primitives/field/details.d.ts.map +1 -1
  561. package/build-types/form/primitives/field/label.d.ts +2 -1
  562. package/build-types/form/primitives/field/label.d.ts.map +1 -1
  563. package/build-types/form/primitives/field/stories/index.story.d.ts +1 -1
  564. package/build-types/form/primitives/field/stories/index.story.d.ts.map +1 -1
  565. package/build-types/form/primitives/fieldset/description.d.ts +2 -1
  566. package/build-types/form/primitives/fieldset/description.d.ts.map +1 -1
  567. package/build-types/form/primitives/fieldset/details.d.ts +2 -1
  568. package/build-types/form/primitives/fieldset/details.d.ts.map +1 -1
  569. package/build-types/form/primitives/fieldset/legend.d.ts +2 -1
  570. package/build-types/form/primitives/fieldset/legend.d.ts.map +1 -1
  571. package/build-types/form/primitives/fieldset/root.d.ts +2 -1
  572. package/build-types/form/primitives/fieldset/root.d.ts.map +1 -1
  573. package/build-types/form/primitives/fieldset/stories/index.story.d.ts +1 -1
  574. package/build-types/form/primitives/fieldset/stories/index.story.d.ts.map +1 -1
  575. package/build-types/form/primitives/index.d.ts +1 -0
  576. package/build-types/form/primitives/index.d.ts.map +1 -1
  577. package/build-types/form/primitives/input/stories/index.story.d.ts +1 -1
  578. package/build-types/form/primitives/input/stories/index.story.d.ts.map +1 -1
  579. package/build-types/form/primitives/input-layout/stories/index.story.d.ts +1 -1
  580. package/build-types/form/primitives/input-layout/stories/index.story.d.ts.map +1 -1
  581. package/build-types/form/primitives/select/index.d.ts +1 -0
  582. package/build-types/form/primitives/select/index.d.ts.map +1 -1
  583. package/build-types/form/primitives/select/item.d.ts +6 -2
  584. package/build-types/form/primitives/select/item.d.ts.map +1 -1
  585. package/build-types/form/primitives/select/popup.d.ts +10 -1
  586. package/build-types/form/primitives/select/popup.d.ts.map +1 -1
  587. package/build-types/form/primitives/select/portal.d.ts +8 -0
  588. package/build-types/form/primitives/select/portal.d.ts.map +1 -0
  589. package/build-types/form/primitives/select/stories/index.story.d.ts +14 -6
  590. package/build-types/form/primitives/select/stories/index.story.d.ts.map +1 -1
  591. package/build-types/form/primitives/select/trigger.d.ts +12 -2
  592. package/build-types/form/primitives/select/trigger.d.ts.map +1 -1
  593. package/build-types/form/primitives/select/types.d.ts +18 -3
  594. package/build-types/form/primitives/select/types.d.ts.map +1 -1
  595. package/build-types/index.d.ts +1 -0
  596. package/build-types/index.d.ts.map +1 -1
  597. package/build-types/link/link.d.ts.map +1 -1
  598. package/build-types/link/stories/index.story.d.ts +2 -3
  599. package/build-types/link/stories/index.story.d.ts.map +1 -1
  600. package/build-types/link/types.d.ts +1 -2
  601. package/build-types/link/types.d.ts.map +1 -1
  602. package/build-types/notice/action-link.d.ts.map +1 -1
  603. package/build-types/popover/context.d.ts +6 -13
  604. package/build-types/popover/context.d.ts.map +1 -1
  605. package/build-types/popover/description.d.ts +0 -1
  606. package/build-types/popover/description.d.ts.map +1 -1
  607. package/build-types/popover/index.d.ts +2 -1
  608. package/build-types/popover/index.d.ts.map +1 -1
  609. package/build-types/popover/popup.d.ts +3 -2
  610. package/build-types/popover/popup.d.ts.map +1 -1
  611. package/build-types/popover/portal.d.ts +9 -0
  612. package/build-types/popover/portal.d.ts.map +1 -0
  613. package/build-types/popover/root.d.ts +2 -2
  614. package/build-types/popover/stories/index.story.d.ts +24 -16
  615. package/build-types/popover/stories/index.story.d.ts.map +1 -1
  616. package/build-types/popover/title.d.ts.map +1 -1
  617. package/build-types/popover/types.d.ts +8 -15
  618. package/build-types/popover/types.d.ts.map +1 -1
  619. package/build-types/stack/stories/index.story.d.ts.map +1 -1
  620. package/build-types/tabs/context.d.ts.map +1 -1
  621. package/build-types/tabs/list.d.ts +2 -1
  622. package/build-types/tabs/list.d.ts.map +1 -1
  623. package/build-types/tabs/panel.d.ts +2 -1
  624. package/build-types/tabs/panel.d.ts.map +1 -1
  625. package/build-types/tabs/root.d.ts +2 -1
  626. package/build-types/tabs/root.d.ts.map +1 -1
  627. package/build-types/tabs/stories/index.story.d.ts +1 -1
  628. package/build-types/tabs/stories/index.story.d.ts.map +1 -1
  629. package/build-types/tabs/tab.d.ts +2 -1
  630. package/build-types/tabs/tab.d.ts.map +1 -1
  631. package/build-types/text/stories/index.story.d.ts.map +1 -1
  632. package/build-types/tooltip/index.d.ts +2 -1
  633. package/build-types/tooltip/index.d.ts.map +1 -1
  634. package/build-types/tooltip/popup.d.ts.map +1 -1
  635. package/build-types/tooltip/portal.d.ts +8 -0
  636. package/build-types/tooltip/portal.d.ts.map +1 -0
  637. package/build-types/tooltip/provider.d.ts +1 -1
  638. package/build-types/tooltip/provider.d.ts.map +1 -1
  639. package/build-types/tooltip/root.d.ts +9 -8
  640. package/build-types/tooltip/root.d.ts.map +1 -1
  641. package/build-types/tooltip/stories/index.story.d.ts +18 -1
  642. package/build-types/tooltip/stories/index.story.d.ts.map +1 -1
  643. package/build-types/tooltip/stories/usage-guidelines.story.d.ts +21 -0
  644. package/build-types/tooltip/stories/usage-guidelines.story.d.ts.map +1 -0
  645. package/build-types/tooltip/trigger.d.ts.map +1 -1
  646. package/build-types/tooltip/types.d.ts +13 -5
  647. package/build-types/tooltip/types.d.ts.map +1 -1
  648. package/build-types/utils/create-overlay-modal-context.d.ts +14 -0
  649. package/build-types/utils/create-overlay-modal-context.d.ts.map +1 -0
  650. package/build-types/utils/create-overlay-title-validation.d.ts +15 -0
  651. package/build-types/utils/create-overlay-title-validation.d.ts.map +1 -0
  652. package/build-types/utils/render-portal-with-children.d.ts +16 -0
  653. package/build-types/utils/render-portal-with-children.d.ts.map +1 -0
  654. package/build-types/utils/types.d.ts +6 -2
  655. package/build-types/utils/types.d.ts.map +1 -1
  656. package/build-types/utils/use-deprioritized-initial-focus.d.ts +9 -8
  657. package/build-types/utils/use-deprioritized-initial-focus.d.ts.map +1 -1
  658. package/build-types/utils/use-overlay-scroll-state-attributes.d.ts +85 -0
  659. package/build-types/utils/use-overlay-scroll-state-attributes.d.ts.map +1 -0
  660. package/build-types/utils/use-schedule-validation.d.ts +13 -0
  661. package/build-types/utils/use-schedule-validation.d.ts.map +1 -0
  662. package/build-types/visually-hidden/stories/index.story.d.ts +7 -0
  663. package/build-types/visually-hidden/stories/index.story.d.ts.map +1 -1
  664. package/build-types/visually-hidden/visually-hidden.d.ts +18 -0
  665. package/build-types/visually-hidden/visually-hidden.d.ts.map +1 -1
  666. package/package.json +12 -12
  667. package/src/alert-dialog/index.ts +1 -0
  668. package/src/alert-dialog/popup.tsx +114 -45
  669. package/src/alert-dialog/portal.tsx +17 -0
  670. package/src/alert-dialog/stories/index.story.tsx +123 -3
  671. package/src/alert-dialog/style.module.css +13 -4
  672. package/src/alert-dialog/test/index.test.tsx +380 -0
  673. package/src/alert-dialog/types.ts +33 -1
  674. package/src/badge/badge.tsx +11 -14
  675. package/src/badge/stories/choosing-intent.story.tsx +1 -1
  676. package/src/badge/stories/index.story.tsx +1 -0
  677. package/src/badge/style.module.css +0 -4
  678. package/src/button/style.module.css +4 -4
  679. package/src/card/stories/index.story.tsx +5 -4
  680. package/src/card/style.module.css +0 -4
  681. package/src/card/test/index.test.tsx +17 -0
  682. package/src/card/title.tsx +6 -5
  683. package/src/collapsible/stories/index.story.tsx +1 -0
  684. package/src/collapsible-card/content.tsx +12 -1
  685. package/src/collapsible-card/header.tsx +2 -0
  686. package/src/collapsible-card/stories/index.story.tsx +6 -5
  687. package/src/collapsible-card/style.module.css +16 -4
  688. package/src/dialog/content.tsx +47 -0
  689. package/src/dialog/context.tsx +14 -98
  690. package/src/dialog/description.tsx +27 -0
  691. package/src/dialog/footer.tsx +10 -2
  692. package/src/dialog/header.tsx +10 -2
  693. package/src/dialog/index.ts +16 -1
  694. package/src/dialog/popup.tsx +28 -7
  695. package/src/dialog/portal.tsx +18 -0
  696. package/src/dialog/root.tsx +22 -5
  697. package/src/dialog/stories/index.story.tsx +224 -47
  698. package/src/dialog/style.module.css +78 -24
  699. package/src/dialog/test/index.test.tsx +907 -148
  700. package/src/dialog/title.tsx +27 -13
  701. package/src/dialog/types.ts +71 -5
  702. package/src/drawer/action.tsx +28 -0
  703. package/src/drawer/close-icon.tsx +33 -0
  704. package/src/drawer/content.tsx +50 -0
  705. package/src/drawer/context.tsx +29 -0
  706. package/src/drawer/description.tsx +25 -0
  707. package/src/drawer/footer.tsx +34 -0
  708. package/src/drawer/header.tsx +34 -0
  709. package/src/drawer/index.ts +25 -0
  710. package/src/drawer/popup.tsx +100 -0
  711. package/src/drawer/portal.tsx +18 -0
  712. package/src/drawer/root.tsx +41 -0
  713. package/src/drawer/stories/index.story.tsx +543 -0
  714. package/src/drawer/style.module.css +324 -0
  715. package/src/drawer/test/index.test.tsx +1097 -0
  716. package/src/drawer/title.tsx +53 -0
  717. package/src/drawer/trigger.tsx +14 -0
  718. package/src/drawer/types.ts +174 -0
  719. package/src/empty-state/description.tsx +6 -2
  720. package/src/empty-state/stories/index.story.tsx +2 -1
  721. package/src/empty-state/style.module.css +1 -1
  722. package/src/empty-state/test/description.test.tsx +13 -0
  723. package/src/empty-state/test/title.test.tsx +13 -0
  724. package/src/empty-state/title.tsx +9 -2
  725. package/src/form/input-control/stories/index.story.tsx +4 -1
  726. package/src/form/primitives/autocomplete/clear.tsx +35 -0
  727. package/src/form/primitives/autocomplete/collection.tsx +13 -0
  728. package/src/form/primitives/autocomplete/empty.tsx +17 -0
  729. package/src/form/primitives/autocomplete/index.ts +12 -0
  730. package/src/form/primitives/autocomplete/input-group.tsx +16 -0
  731. package/src/form/primitives/autocomplete/input.tsx +20 -0
  732. package/src/form/primitives/autocomplete/item.tsx +24 -0
  733. package/src/form/primitives/autocomplete/list-body.tsx +23 -0
  734. package/src/form/primitives/autocomplete/list.tsx +17 -0
  735. package/src/form/primitives/autocomplete/popup.tsx +42 -0
  736. package/src/form/primitives/autocomplete/portal.tsx +16 -0
  737. package/src/form/primitives/autocomplete/root.tsx +11 -0
  738. package/src/form/primitives/autocomplete/stories/fixtures.ts +35 -0
  739. package/src/form/primitives/autocomplete/stories/index.story.tsx +437 -0
  740. package/src/form/primitives/autocomplete/style.module.css +7 -0
  741. package/src/form/primitives/autocomplete/test/index.test.tsx +162 -0
  742. package/src/form/primitives/autocomplete/types.ts +74 -0
  743. package/src/form/primitives/autocomplete/value.tsx +6 -0
  744. package/src/form/primitives/field/details.tsx +4 -2
  745. package/src/form/primitives/field/label.tsx +9 -5
  746. package/src/form/primitives/field/root.tsx +2 -2
  747. package/src/form/primitives/field/stories/index.story.tsx +1 -1
  748. package/src/form/primitives/field/test/index.test.tsx +11 -0
  749. package/src/form/primitives/fieldset/legend.tsx +9 -4
  750. package/src/form/primitives/fieldset/stories/index.story.tsx +1 -1
  751. package/src/form/primitives/fieldset/test/index.test.tsx +22 -0
  752. package/src/form/primitives/index.ts +1 -0
  753. package/src/form/primitives/input/stories/index.story.tsx +2 -1
  754. package/src/form/primitives/input-layout/stories/index.story.tsx +2 -1
  755. package/src/form/primitives/input-layout/style.module.css +3 -3
  756. package/src/form/primitives/select/index.ts +1 -0
  757. package/src/form/primitives/select/item.tsx +0 -1
  758. package/src/form/primitives/select/popup.tsx +34 -34
  759. package/src/form/primitives/select/portal.tsx +16 -0
  760. package/src/form/primitives/select/stories/index.story.tsx +21 -7
  761. package/src/form/primitives/select/test/index.test.tsx +64 -1
  762. package/src/form/primitives/select/types.ts +21 -4
  763. package/src/index.ts +1 -0
  764. package/src/link/link.tsx +12 -26
  765. package/src/link/stories/index.story.tsx +6 -11
  766. package/src/link/style.module.css +6 -18
  767. package/src/link/test/index.test.tsx +31 -27
  768. package/src/link/types.ts +1 -2
  769. package/src/notice/action-link.tsx +7 -4
  770. package/src/notice/style.module.css +6 -6
  771. package/src/popover/context.tsx +6 -89
  772. package/src/popover/description.tsx +2 -11
  773. package/src/popover/index.ts +2 -1
  774. package/src/popover/popup.tsx +17 -15
  775. package/src/popover/portal.tsx +17 -0
  776. package/src/popover/root.tsx +2 -2
  777. package/src/popover/stories/index.story.tsx +57 -26
  778. package/src/popover/style.module.css +36 -7
  779. package/src/popover/test/index.test.tsx +189 -74
  780. package/src/popover/title.tsx +10 -9
  781. package/src/popover/types.ts +10 -15
  782. package/src/stack/stories/index.story.tsx +1 -0
  783. package/src/tabs/context.tsx +14 -34
  784. package/src/tabs/list.tsx +0 -1
  785. package/src/tabs/panel.tsx +7 -2
  786. package/src/tabs/stories/index.story.tsx +2 -1
  787. package/src/tabs/style.module.css +2 -19
  788. package/src/tabs/test/index.test.tsx +7 -3
  789. package/src/text/stories/index.story.tsx +1 -0
  790. package/src/text/style.module.css +49 -49
  791. package/src/text/text.tsx +2 -2
  792. package/src/tooltip/index.ts +2 -1
  793. package/src/tooltip/popup.tsx +24 -27
  794. package/src/tooltip/portal.tsx +16 -0
  795. package/src/tooltip/provider.tsx +3 -3
  796. package/src/tooltip/root.tsx +11 -10
  797. package/src/tooltip/stories/index.story.tsx +39 -1
  798. package/src/tooltip/stories/usage-guidelines.mdx +91 -0
  799. package/src/tooltip/stories/usage-guidelines.story.tsx +123 -0
  800. package/src/tooltip/style.module.css +14 -2
  801. package/src/tooltip/test/index.test.tsx +67 -0
  802. package/src/tooltip/trigger.tsx +3 -7
  803. package/src/tooltip/types.ts +16 -5
  804. package/src/utils/create-overlay-modal-context.tsx +34 -0
  805. package/src/utils/create-overlay-title-validation.tsx +116 -0
  806. package/src/utils/css/field.module.css +10 -10
  807. package/src/utils/css/global-css-defense.module.css +1 -1
  808. package/src/utils/css/item-popup.module.css +11 -13
  809. package/src/utils/css/overlay-chrome.module.css +222 -0
  810. package/src/utils/render-portal-with-children.ts +27 -0
  811. package/src/utils/test/use-deprioritized-initial-focus.test.tsx +3 -3
  812. package/src/utils/types.ts +7 -2
  813. package/src/utils/use-deprioritized-initial-focus.ts +23 -17
  814. package/src/utils/use-overlay-scroll-state-attributes.ts +272 -0
  815. package/src/utils/use-schedule-validation.ts +45 -0
  816. package/src/visually-hidden/stories/index.story.tsx +26 -0
  817. package/src/visually-hidden/visually-hidden.tsx +23 -1
  818. package/build/types/css-modules.d.cjs +0 -2
  819. package/build/types/react.d.cjs +0 -5
  820. package/build/types/react.d.cjs.map +0 -7
  821. package/build-module/types/css-modules.d.mjs +0 -1
  822. package/build-module/types/react.d.mjs +0 -3
  823. package/build-module/types/react.d.mjs.map +0 -7
  824. package/src/types/css-modules.d.ts +0 -4
  825. package/src/types/react.d.ts +0 -7
  826. /package/build-module/{types/css-modules.d.mjs.map → drawer/types.mjs.map} +0 -0
  827. /package/{build/types/css-modules.d.cjs.map → build-module/form/primitives/autocomplete/types.mjs.map} +0 -0
@@ -1,36 +1,19 @@
1
- import { render, screen, waitFor } from '@testing-library/react';
1
+ import { act, render, screen, waitFor } from '@testing-library/react';
2
2
  import userEvent from '@testing-library/user-event';
3
- import { Component, createRef } from '@wordpress/element';
4
- import type { ReactNode } from 'react';
3
+ import { createRef, useState } from '@wordpress/element';
5
4
  import * as Dialog from '../index';
6
5
 
7
- class TestErrorBoundary extends Component<
8
- { children: ReactNode; onError: ( error: Error ) => void },
9
- { hasError: boolean }
10
- > {
11
- constructor( props: {
12
- children: ReactNode;
13
- onError: ( error: Error ) => void;
14
- } ) {
15
- super( props );
16
- this.state = { hasError: false };
17
- }
18
-
19
- static getDerivedStateFromError() {
20
- return { hasError: true };
21
- }
22
-
23
- componentDidCatch( error: Error ) {
24
- this.props.onError( error );
25
- }
26
-
27
- render() {
28
- if ( this.state.hasError ) {
29
- return null;
30
- }
31
-
32
- return this.props.children;
33
- }
6
+ function collectUncaughtErrors() {
7
+ const errors: Error[] = [];
8
+ const handler = ( event: ErrorEvent ) => {
9
+ event.preventDefault();
10
+ errors.push( event.error );
11
+ };
12
+ window.addEventListener( 'error', handler );
13
+ return {
14
+ errors,
15
+ cleanup: () => window.removeEventListener( 'error', handler ),
16
+ };
34
17
  }
35
18
 
36
19
  describe( 'Dialog', () => {
@@ -39,10 +22,12 @@ describe( 'Dialog', () => {
39
22
  const triggerRef = createRef< HTMLButtonElement >();
40
23
  const popupRef = createRef< HTMLDivElement >();
41
24
  const actionRef = createRef< HTMLButtonElement >();
42
- const headerRef = createRef< HTMLDivElement >();
25
+ const headerRef = createRef< HTMLElement >();
43
26
  const titleRef = createRef< HTMLHeadingElement >();
27
+ const descriptionRef = createRef< HTMLParagraphElement >();
44
28
  const closeIconRef = createRef< HTMLButtonElement >();
45
- const footerRef = createRef< HTMLDivElement >();
29
+ const footerRef = createRef< HTMLElement >();
30
+ const contentRef = createRef< HTMLDivElement >();
46
31
 
47
32
  render(
48
33
  <Dialog.Root>
@@ -54,6 +39,11 @@ describe( 'Dialog', () => {
54
39
  </Dialog.Title>
55
40
  <Dialog.CloseIcon ref={ closeIconRef } />
56
41
  </Dialog.Header>
42
+ <Dialog.Content ref={ contentRef }>
43
+ <Dialog.Description ref={ descriptionRef }>
44
+ A test description
45
+ </Dialog.Description>
46
+ </Dialog.Content>
57
47
  <Dialog.Footer ref={ footerRef }>
58
48
  <Dialog.Action ref={ actionRef }>Close</Dialog.Action>
59
49
  </Dialog.Footer>
@@ -73,15 +63,221 @@ describe( 'Dialog', () => {
73
63
  } );
74
64
 
75
65
  // Now that the dialog is open, verify all inner refs
76
- expect( headerRef.current ).toBeInstanceOf( HTMLDivElement );
66
+ expect( headerRef.current ).toBeInstanceOf( HTMLElement );
67
+ expect( headerRef.current?.tagName ).toBe( 'HEADER' );
77
68
  expect( titleRef.current ).toBeInstanceOf( HTMLHeadingElement );
69
+ expect( descriptionRef.current ).toBeInstanceOf( HTMLParagraphElement );
78
70
  expect( closeIconRef.current ).toBeInstanceOf( HTMLButtonElement );
79
71
  expect( actionRef.current ).toBeInstanceOf( HTMLButtonElement );
80
- expect( footerRef.current ).toBeInstanceOf( HTMLDivElement );
72
+ expect( footerRef.current ).toBeInstanceOf( HTMLElement );
73
+ expect( footerRef.current?.tagName ).toBe( 'FOOTER' );
74
+ expect( contentRef.current ).toBeInstanceOf( HTMLDivElement );
75
+ } );
76
+
77
+ it( 'merges user `className` on Dialog.Title with the internal one', async () => {
78
+ // Regression test for the shared `useRender` class-name merge
79
+ // that also covers Popover.Title, Dialog.Description and
80
+ // Popover.Description.
81
+ const user = userEvent.setup();
82
+
83
+ render(
84
+ <Dialog.Root>
85
+ <Dialog.Trigger>Open</Dialog.Trigger>
86
+ <Dialog.Popup>
87
+ <Dialog.Title className="custom-title">Title</Dialog.Title>
88
+ </Dialog.Popup>
89
+ </Dialog.Root>
90
+ );
91
+
92
+ await user.click( screen.getByRole( 'button', { name: 'Open' } ) );
93
+
94
+ const heading = await screen.findByRole( 'heading', { name: 'Title' } );
95
+ // The regression this guards against: `useRender` must still forward
96
+ // the user-supplied className to the underlying DOM node. CSS module
97
+ // classes are stubbed in the Jest environment, so we can only assert
98
+ // the user class end-to-end.
99
+ expect( heading ).toHaveClass( 'custom-title' );
100
+ } );
101
+
102
+ it( 'associates Dialog.Description with the popup via aria-describedby', async () => {
103
+ const user = userEvent.setup();
104
+ const popupRef = createRef< HTMLDivElement >();
105
+
106
+ render(
107
+ <Dialog.Root>
108
+ <Dialog.Trigger>Open</Dialog.Trigger>
109
+ <Dialog.Popup ref={ popupRef }>
110
+ <Dialog.Title>Title</Dialog.Title>
111
+ <Dialog.Description>My description</Dialog.Description>
112
+ </Dialog.Popup>
113
+ </Dialog.Root>
114
+ );
115
+
116
+ await user.click( screen.getByRole( 'button', { name: 'Open' } ) );
117
+
118
+ await waitFor( () => {
119
+ expect( popupRef.current ).toHaveAccessibleDescription(
120
+ 'My description'
121
+ );
122
+ } );
123
+ } );
124
+
125
+ it( 'renders Dialog.Footer and supports render/className props', async () => {
126
+ const user = userEvent.setup();
127
+
128
+ render(
129
+ <Dialog.Root>
130
+ <Dialog.Trigger>Open Dialog</Dialog.Trigger>
131
+ <Dialog.Popup>
132
+ <Dialog.Title>Test Dialog</Dialog.Title>
133
+ <Dialog.Footer
134
+ render={ <section data-testid="dialog-footer" /> }
135
+ className="custom-footer"
136
+ >
137
+ <Dialog.Action>Close</Dialog.Action>
138
+ </Dialog.Footer>
139
+ </Dialog.Popup>
140
+ </Dialog.Root>
141
+ );
142
+
143
+ await user.click(
144
+ screen.getByRole( 'button', { name: 'Open Dialog' } )
145
+ );
146
+
147
+ const footer = await screen.findByTestId( 'dialog-footer' );
148
+ expect( footer.tagName ).toBe( 'SECTION' );
149
+ expect( footer ).toHaveClass( 'custom-footer' );
150
+ expect(
151
+ screen.getByRole( 'button', { name: 'Close' } )
152
+ ).toBeInTheDocument();
153
+ } );
154
+
155
+ it( 'renders backdrop only when modal is true', async () => {
156
+ const getBackdrops = () => screen.queryAllByTestId( 'dialog-backdrop' );
157
+
158
+ const view = render(
159
+ <Dialog.Root open modal>
160
+ <Dialog.Popup>
161
+ <Dialog.Title>Modal dialog</Dialog.Title>
162
+ </Dialog.Popup>
163
+ </Dialog.Root>
164
+ );
165
+
166
+ expect( await screen.findByRole( 'dialog' ) ).toBeInTheDocument();
167
+ expect( getBackdrops() ).toHaveLength( 1 );
168
+
169
+ view.rerender(
170
+ <Dialog.Root open modal={ false }>
171
+ <Dialog.Popup>
172
+ <Dialog.Title>Non modal dialog</Dialog.Title>
173
+ </Dialog.Popup>
174
+ </Dialog.Root>
175
+ );
176
+ expect( await screen.findByRole( 'dialog' ) ).toBeInTheDocument();
177
+ expect( getBackdrops() ).toHaveLength( 0 );
178
+
179
+ view.rerender(
180
+ <Dialog.Root open modal="trap-focus">
181
+ <Dialog.Popup>
182
+ <Dialog.Title>Trap focus dialog</Dialog.Title>
183
+ </Dialog.Popup>
184
+ </Dialog.Root>
185
+ );
186
+ expect( await screen.findByRole( 'dialog' ) ).toBeInTheDocument();
187
+ expect( getBackdrops() ).toHaveLength( 0 );
188
+ } );
189
+
190
+ it( 'renders the popup across default and explicit size values', async () => {
191
+ const view = render(
192
+ <Dialog.Root open>
193
+ <Dialog.Popup>
194
+ <Dialog.Title>Default size dialog</Dialog.Title>
195
+ </Dialog.Popup>
196
+ </Dialog.Root>
197
+ );
198
+
199
+ expect( await screen.findByRole( 'dialog' ) ).toBeInTheDocument();
200
+
201
+ for ( const size of [
202
+ 'small',
203
+ 'medium',
204
+ 'large',
205
+ 'stretch',
206
+ 'full',
207
+ ] as const ) {
208
+ view.rerender(
209
+ <Dialog.Root open>
210
+ <Dialog.Popup size={ size }>
211
+ <Dialog.Title>{ size } dialog</Dialog.Title>
212
+ </Dialog.Popup>
213
+ </Dialog.Root>
214
+ );
215
+ expect( await screen.findByRole( 'dialog' ) ).toBeInTheDocument();
216
+ }
217
+ } );
218
+
219
+ it( 'marks Dialog.Action as disabled when loading is true', async () => {
220
+ render(
221
+ <Dialog.Root open>
222
+ <Dialog.Popup>
223
+ <Dialog.Title>Action states</Dialog.Title>
224
+ <Dialog.Footer>
225
+ <Dialog.Action loading>Loading action</Dialog.Action>
226
+ </Dialog.Footer>
227
+ </Dialog.Popup>
228
+ </Dialog.Root>
229
+ );
230
+
231
+ const action = await screen.findByRole( 'button', {
232
+ name: 'Loading action',
233
+ } );
234
+ expect( action ).toHaveAttribute( 'aria-disabled', 'true' );
235
+ } );
236
+
237
+ it( 'marks Dialog.Action as disabled when disabled is true', async () => {
238
+ render(
239
+ <Dialog.Root open>
240
+ <Dialog.Popup>
241
+ <Dialog.Title>Action states</Dialog.Title>
242
+ <Dialog.Footer>
243
+ <Dialog.Action disabled>Disabled action</Dialog.Action>
244
+ </Dialog.Footer>
245
+ </Dialog.Popup>
246
+ </Dialog.Root>
247
+ );
248
+
249
+ const action = await screen.findByRole( 'button', {
250
+ name: 'Disabled action',
251
+ } );
252
+ expect( action ).toHaveAttribute( 'aria-disabled', 'true' );
253
+ } );
254
+
255
+ it( 'lets explicit disabled={ false } override loading on Dialog.Action', async () => {
256
+ // `Dialog.Action` uses `disabled ?? loading`, so an explicit
257
+ // `disabled={ false }` wins over an active loading state.
258
+ render(
259
+ <Dialog.Root open>
260
+ <Dialog.Popup>
261
+ <Dialog.Title>Action states</Dialog.Title>
262
+ <Dialog.Footer>
263
+ <Dialog.Action disabled={ false } loading>
264
+ Explicit not-disabled
265
+ </Dialog.Action>
266
+ </Dialog.Footer>
267
+ </Dialog.Popup>
268
+ </Dialog.Root>
269
+ );
270
+
271
+ const action = await screen.findByRole( 'button', {
272
+ name: 'Explicit not-disabled',
273
+ } );
274
+ expect( action ).not.toHaveAttribute( 'aria-disabled', 'true' );
81
275
  } );
82
276
 
83
277
  describe( 'Development mode validation', () => {
84
- // Suppress React's error boundary logging for these tests.
278
+ // Suppress console.error from React act() warnings and jsdom
279
+ // unhandled-error logging. Validation errors are caught via
280
+ // collectUncaughtErrors (window 'error' event) instead.
85
281
  let originalConsoleError: typeof console.error;
86
282
 
87
283
  beforeEach( () => {
@@ -98,212 +294,313 @@ describe( 'Dialog', () => {
98
294
 
99
295
  it( 'should throw when Dialog.Title is missing', async () => {
100
296
  const user = userEvent.setup();
101
- const onError = jest.fn();
297
+ const { errors, cleanup } = collectUncaughtErrors();
102
298
 
103
299
  render(
104
- <TestErrorBoundary onError={ onError }>
105
- <Dialog.Root>
106
- <Dialog.Trigger>Open Dialog</Dialog.Trigger>
107
- <Dialog.Popup>
108
- <Dialog.Header>
109
- { /* Missing Dialog.Title */ }
110
- </Dialog.Header>
111
- <p>Content without a title</p>
112
- <Dialog.Footer>
113
- <Dialog.Action>Close</Dialog.Action>
114
- </Dialog.Footer>
115
- </Dialog.Popup>
116
- </Dialog.Root>
117
- </TestErrorBoundary>
300
+ <Dialog.Root>
301
+ <Dialog.Trigger>Open Dialog</Dialog.Trigger>
302
+ <Dialog.Popup>
303
+ <Dialog.Header>
304
+ { /* Missing Dialog.Title */ }
305
+ </Dialog.Header>
306
+ <p>Content without a title</p>
307
+ <Dialog.Footer>
308
+ <Dialog.Action>Close</Dialog.Action>
309
+ </Dialog.Footer>
310
+ </Dialog.Popup>
311
+ </Dialog.Root>
118
312
  );
119
313
 
120
- // Open the dialog - this will trigger the error in useEffect
121
314
  await user.click(
122
315
  screen.getByRole( 'button', { name: 'Open Dialog' } )
123
316
  );
124
317
 
125
318
  await waitFor( () => {
126
- expect( onError ).toHaveBeenCalled();
319
+ expect( errors.length ).toBeGreaterThan( 0 );
127
320
  } );
128
321
 
129
- expect( onError.mock.calls[ 0 ][ 0 ] ).toBeInstanceOf( Error );
130
- expect( ( onError.mock.calls[ 0 ][ 0 ] as Error ).message ).toBe(
322
+ expect( errors[ 0 ].message ).toBe(
131
323
  'Dialog: Missing <Dialog.Title>. ' +
132
324
  'For accessibility, every dialog requires a title. ' +
133
325
  'If needed, the title can be visually hidden but must not be omitted.'
134
326
  );
327
+
328
+ cleanup();
135
329
  } );
136
330
 
137
331
  it( 'should not throw before opening the dialog', async () => {
138
- const onError = jest.fn();
332
+ const { errors, cleanup } = collectUncaughtErrors();
139
333
 
140
334
  render(
141
- <TestErrorBoundary onError={ onError }>
142
- <Dialog.Root>
143
- <Dialog.Trigger>Open Dialog</Dialog.Trigger>
144
- <Dialog.Popup>
145
- <Dialog.Header>
146
- <Dialog.Title>My Title</Dialog.Title>
147
- </Dialog.Header>
148
- <p>Content with a title</p>
149
- <Dialog.Footer>
150
- <Dialog.Action>Close</Dialog.Action>
151
- </Dialog.Footer>
152
- </Dialog.Popup>
153
- </Dialog.Root>
154
- </TestErrorBoundary>
335
+ <Dialog.Root>
336
+ <Dialog.Trigger>Open Dialog</Dialog.Trigger>
337
+ <Dialog.Popup>
338
+ <Dialog.Header>
339
+ <Dialog.Title>My Title</Dialog.Title>
340
+ </Dialog.Header>
341
+ <p>Content with a title</p>
342
+ <Dialog.Footer>
343
+ <Dialog.Action>Close</Dialog.Action>
344
+ </Dialog.Footer>
345
+ </Dialog.Popup>
346
+ </Dialog.Root>
155
347
  );
156
348
 
157
- // Check that the dialog itself hasn't been rendered in the DOM.
158
349
  await expect( screen.findByRole( 'dialog' ) ).rejects.toThrow();
350
+ expect( errors ).toHaveLength( 0 );
159
351
 
160
- expect( onError ).not.toHaveBeenCalled();
352
+ cleanup();
161
353
  } );
162
354
 
163
355
  it( 'should not throw when Dialog.Title is present', async () => {
164
356
  const user = userEvent.setup();
165
- const onError = jest.fn();
357
+ const { errors, cleanup } = collectUncaughtErrors();
166
358
 
167
359
  render(
168
- <TestErrorBoundary onError={ onError }>
169
- <Dialog.Root>
170
- <Dialog.Trigger>Open Dialog</Dialog.Trigger>
171
- <Dialog.Popup>
172
- <Dialog.Header>
173
- <Dialog.Title>My Title</Dialog.Title>
174
- </Dialog.Header>
175
- <p>Content with a title</p>
176
- <Dialog.Footer>
177
- <Dialog.Action>Close</Dialog.Action>
178
- </Dialog.Footer>
179
- </Dialog.Popup>
180
- </Dialog.Root>
181
- </TestErrorBoundary>
360
+ <Dialog.Root>
361
+ <Dialog.Trigger>Open Dialog</Dialog.Trigger>
362
+ <Dialog.Popup>
363
+ <Dialog.Header>
364
+ <Dialog.Title>My Title</Dialog.Title>
365
+ </Dialog.Header>
366
+ <p>Content with a title</p>
367
+ <Dialog.Footer>
368
+ <Dialog.Action>Close</Dialog.Action>
369
+ </Dialog.Footer>
370
+ </Dialog.Popup>
371
+ </Dialog.Root>
182
372
  );
183
373
 
184
- // Open the dialog - should not throw
185
374
  await user.click(
186
375
  screen.getByRole( 'button', { name: 'Open Dialog' } )
187
376
  );
188
377
 
189
- // Wait for the dialog to appear and ensure validation does not trigger errors
190
378
  await waitFor( () => {
191
379
  expect( screen.getByRole( 'dialog' ) ).toBeInTheDocument();
192
380
  } );
193
- expect( onError ).not.toHaveBeenCalled();
381
+
382
+ // Allow deferred validation to settle.
383
+ await act(
384
+ () => new Promise( ( resolve ) => setTimeout( resolve, 50 ) )
385
+ );
386
+ expect( errors ).toHaveLength( 0 );
387
+
388
+ cleanup();
194
389
  } );
195
390
 
196
391
  it( 'should throw when Dialog.Title is empty', async () => {
197
392
  const user = userEvent.setup();
198
- const onError = jest.fn();
393
+ const { errors, cleanup } = collectUncaughtErrors();
199
394
 
200
395
  render(
201
- <TestErrorBoundary onError={ onError }>
202
- <Dialog.Root>
203
- <Dialog.Trigger>Open Dialog</Dialog.Trigger>
204
- <Dialog.Popup>
205
- <Dialog.Header>
206
- { /* @ts-expect-error this is just for test purposes */ }
207
- <Dialog.Title>
208
- { /* Empty title */ }
209
- </Dialog.Title>
210
- </Dialog.Header>
211
- <p>Content with empty title</p>
212
- <Dialog.Footer>
213
- <Dialog.Action>Close</Dialog.Action>
214
- </Dialog.Footer>
215
- </Dialog.Popup>
216
- </Dialog.Root>
217
- </TestErrorBoundary>
396
+ <Dialog.Root>
397
+ <Dialog.Trigger>Open Dialog</Dialog.Trigger>
398
+ <Dialog.Popup>
399
+ <Dialog.Header>
400
+ <Dialog.Title />
401
+ </Dialog.Header>
402
+ <p>Content with empty title</p>
403
+ <Dialog.Footer>
404
+ <Dialog.Action>Close</Dialog.Action>
405
+ </Dialog.Footer>
406
+ </Dialog.Popup>
407
+ </Dialog.Root>
218
408
  );
219
409
 
220
- // Open the dialog - this will trigger the error
221
410
  await user.click(
222
411
  screen.getByRole( 'button', { name: 'Open Dialog' } )
223
412
  );
224
413
 
225
414
  await waitFor( () => {
226
- expect( onError ).toHaveBeenCalled();
415
+ expect( errors.length ).toBeGreaterThan( 0 );
227
416
  } );
228
417
 
229
- expect( onError.mock.calls[ 0 ][ 0 ] ).toBeInstanceOf( Error );
230
- expect( ( onError.mock.calls[ 0 ][ 0 ] as Error ).message ).toBe(
418
+ expect( errors[ 0 ].message ).toBe(
231
419
  'Dialog: <Dialog.Title> cannot be empty. ' +
232
420
  'Provide meaningful text content for the dialog title.'
233
421
  );
422
+
423
+ cleanup();
234
424
  } );
235
425
 
236
426
  it( 'should throw when Dialog.Title contains only whitespace', async () => {
237
427
  const user = userEvent.setup();
238
- const onError = jest.fn();
428
+ const { errors, cleanup } = collectUncaughtErrors();
239
429
 
240
430
  render(
241
- <TestErrorBoundary onError={ onError }>
242
- <Dialog.Root>
243
- <Dialog.Trigger>Open Dialog</Dialog.Trigger>
244
- <Dialog.Popup>
245
- <Dialog.Header>
246
- <Dialog.Title> </Dialog.Title>
247
- </Dialog.Header>
248
- <p>Content with whitespace-only title</p>
249
- <Dialog.Footer>
250
- <Dialog.Action>Close</Dialog.Action>
251
- </Dialog.Footer>
252
- </Dialog.Popup>
253
- </Dialog.Root>
254
- </TestErrorBoundary>
431
+ <Dialog.Root>
432
+ <Dialog.Trigger>Open Dialog</Dialog.Trigger>
433
+ <Dialog.Popup>
434
+ <Dialog.Header>
435
+ <Dialog.Title> </Dialog.Title>
436
+ </Dialog.Header>
437
+ <p>Content with whitespace-only title</p>
438
+ <Dialog.Footer>
439
+ <Dialog.Action>Close</Dialog.Action>
440
+ </Dialog.Footer>
441
+ </Dialog.Popup>
442
+ </Dialog.Root>
255
443
  );
256
444
 
257
- // Open the dialog - this will trigger the error
258
445
  await user.click(
259
446
  screen.getByRole( 'button', { name: 'Open Dialog' } )
260
447
  );
261
448
 
262
449
  await waitFor( () => {
263
- expect( onError ).toHaveBeenCalled();
450
+ expect( errors.length ).toBeGreaterThan( 0 );
264
451
  } );
265
452
 
266
- expect( onError.mock.calls[ 0 ][ 0 ] ).toBeInstanceOf( Error );
267
- expect( ( onError.mock.calls[ 0 ][ 0 ] as Error ).message ).toBe(
453
+ expect( errors[ 0 ].message ).toBe(
268
454
  'Dialog: <Dialog.Title> cannot be empty. ' +
269
455
  'Provide meaningful text content for the dialog title.'
270
456
  );
457
+
458
+ cleanup();
271
459
  } );
272
460
 
273
461
  it( 'should not throw when Dialog.Title contains mixed content with text', async () => {
274
462
  const user = userEvent.setup();
275
- const onError = jest.fn();
463
+ const { errors, cleanup } = collectUncaughtErrors();
276
464
 
277
465
  render(
278
- <TestErrorBoundary onError={ onError }>
466
+ <Dialog.Root>
467
+ <Dialog.Trigger>Open Dialog</Dialog.Trigger>
468
+ <Dialog.Popup>
469
+ <Dialog.Header>
470
+ <Dialog.Title>
471
+ <span aria-hidden="true">🎉</span>
472
+ Settings
473
+ </Dialog.Title>
474
+ </Dialog.Header>
475
+ <p>Content with icon and text title</p>
476
+ <Dialog.Footer>
477
+ <Dialog.Action>Close</Dialog.Action>
478
+ </Dialog.Footer>
479
+ </Dialog.Popup>
480
+ </Dialog.Root>
481
+ );
482
+
483
+ await user.click(
484
+ screen.getByRole( 'button', { name: 'Open Dialog' } )
485
+ );
486
+
487
+ await waitFor( () => {
488
+ expect( screen.getByRole( 'dialog' ) ).toBeInTheDocument();
489
+ } );
490
+
491
+ await act(
492
+ () => new Promise( ( resolve ) => setTimeout( resolve, 50 ) )
493
+ );
494
+ expect( errors ).toHaveLength( 0 );
495
+
496
+ cleanup();
497
+ } );
498
+
499
+ it( 'should throw when title is removed after mount', async () => {
500
+ const user = userEvent.setup();
501
+ const { errors, cleanup } = collectUncaughtErrors();
502
+
503
+ function Test() {
504
+ const [ showTitle, setShowTitle ] = useState( true );
505
+ return (
279
506
  <Dialog.Root>
280
- <Dialog.Trigger>Open Dialog</Dialog.Trigger>
507
+ <Dialog.Trigger>Open</Dialog.Trigger>
281
508
  <Dialog.Popup>
282
- <Dialog.Header>
283
- <Dialog.Title>
284
- <span aria-hidden="true">🎉</span>
285
- Settings
286
- </Dialog.Title>
287
- </Dialog.Header>
288
- <p>Content with icon and text title</p>
289
- <Dialog.Footer>
290
- <Dialog.Action>Close</Dialog.Action>
291
- </Dialog.Footer>
509
+ { showTitle && (
510
+ <Dialog.Title>My Title</Dialog.Title>
511
+ ) }
512
+ <button onClick={ () => setShowTitle( false ) }>
513
+ Remove Title
514
+ </button>
292
515
  </Dialog.Popup>
293
516
  </Dialog.Root>
294
- </TestErrorBoundary>
517
+ );
518
+ }
519
+
520
+ render( <Test /> );
521
+
522
+ await user.click( screen.getByRole( 'button', { name: 'Open' } ) );
523
+
524
+ await waitFor( () => {
525
+ expect( screen.getByRole( 'dialog' ) ).toBeInTheDocument();
526
+ } );
527
+
528
+ // Let initial validation settle — no errors expected.
529
+ await act(
530
+ () => new Promise( ( resolve ) => setTimeout( resolve, 50 ) )
295
531
  );
532
+ expect( errors ).toHaveLength( 0 );
296
533
 
297
- // Open the dialog - should not throw
534
+ // Remove the title.
298
535
  await user.click(
299
- screen.getByRole( 'button', { name: 'Open Dialog' } )
536
+ screen.getByRole( 'button', { name: 'Remove Title' } )
300
537
  );
301
538
 
302
- // Wait for the dialog to appear and ensure validation does not trigger errors
539
+ await waitFor( () => {
540
+ expect( errors.length ).toBeGreaterThan( 0 );
541
+ } );
542
+
543
+ expect( errors[ 0 ].message ).toBe(
544
+ 'Dialog: Missing <Dialog.Title>. ' +
545
+ 'For accessibility, every dialog requires a title. ' +
546
+ 'If needed, the title can be visually hidden but must not be omitted.'
547
+ );
548
+
549
+ cleanup();
550
+ } );
551
+
552
+ it( 'should recover when title is added back', async () => {
553
+ const user = userEvent.setup();
554
+ const { errors, cleanup } = collectUncaughtErrors();
555
+
556
+ function Test() {
557
+ const [ showTitle, setShowTitle ] = useState( false );
558
+ return (
559
+ <Dialog.Root>
560
+ <Dialog.Trigger>Open</Dialog.Trigger>
561
+ <Dialog.Popup>
562
+ { showTitle && (
563
+ <Dialog.Title>My Title</Dialog.Title>
564
+ ) }
565
+ <button
566
+ onClick={ () => setShowTitle( ( s ) => ! s ) }
567
+ >
568
+ Toggle Title
569
+ </button>
570
+ </Dialog.Popup>
571
+ </Dialog.Root>
572
+ );
573
+ }
574
+
575
+ render( <Test /> );
576
+
577
+ await user.click( screen.getByRole( 'button', { name: 'Open' } ) );
578
+
303
579
  await waitFor( () => {
304
580
  expect( screen.getByRole( 'dialog' ) ).toBeInTheDocument();
305
581
  } );
306
- expect( onError ).not.toHaveBeenCalled();
582
+
583
+ // Initially no title — should error.
584
+ await waitFor( () => {
585
+ expect( errors.length ).toBeGreaterThan( 0 );
586
+ } );
587
+
588
+ const errorCountAfterInitial = errors.length;
589
+
590
+ // Add the title back.
591
+ await user.click(
592
+ screen.getByRole( 'button', { name: 'Toggle Title' } )
593
+ );
594
+
595
+ // Wait for deferred validation to settle.
596
+ await act(
597
+ () => new Promise( ( resolve ) => setTimeout( resolve, 50 ) )
598
+ );
599
+
600
+ // No new errors should have been thrown.
601
+ expect( errors ).toHaveLength( errorCountAfterInitial );
602
+
603
+ cleanup();
307
604
  } );
308
605
  } );
309
606
 
@@ -423,4 +720,466 @@ describe( 'Dialog', () => {
423
720
  expect( customFocus ).toHaveBeenCalled();
424
721
  } );
425
722
  } );
723
+
724
+ describe( 'portal', () => {
725
+ it( 'should render inside the portal container when a custom target is provided', async () => {
726
+ const user = userEvent.setup();
727
+ const containerRef = createRef< HTMLDivElement >();
728
+
729
+ render(
730
+ <div data-testid="wrapper">
731
+ <Dialog.Root>
732
+ <Dialog.Trigger>Open</Dialog.Trigger>
733
+ <div
734
+ ref={ containerRef }
735
+ data-testid="custom-container"
736
+ />
737
+ <Dialog.Popup
738
+ portal={
739
+ <Dialog.Portal container={ containerRef } />
740
+ }
741
+ >
742
+ <Dialog.Header>
743
+ <Dialog.Title>Title</Dialog.Title>
744
+ </Dialog.Header>
745
+ Dialog content
746
+ </Dialog.Popup>
747
+ </Dialog.Root>
748
+ </div>
749
+ );
750
+
751
+ await user.click( screen.getByRole( 'button', { name: 'Open' } ) );
752
+
753
+ const content = await screen.findByText( 'Dialog content' );
754
+ expect( content ).toBeVisible();
755
+
756
+ expect( screen.getByTestId( 'custom-container' ) ).toContainElement(
757
+ content
758
+ );
759
+ } );
760
+
761
+ it( 'should render with a portal by default', async () => {
762
+ const user = userEvent.setup();
763
+
764
+ render(
765
+ <div data-testid="wrapper">
766
+ <Dialog.Root>
767
+ <Dialog.Trigger>Open</Dialog.Trigger>
768
+ <Dialog.Popup>
769
+ <Dialog.Header>
770
+ <Dialog.Title>Title</Dialog.Title>
771
+ </Dialog.Header>
772
+ Portal content
773
+ </Dialog.Popup>
774
+ </Dialog.Root>
775
+ </div>
776
+ );
777
+
778
+ await user.click( screen.getByRole( 'button', { name: 'Open' } ) );
779
+
780
+ const content = await screen.findByText( 'Portal content' );
781
+ expect( content ).toBeVisible();
782
+
783
+ expect( screen.getByTestId( 'wrapper' ) ).not.toContainElement(
784
+ content
785
+ );
786
+ } );
787
+ } );
788
+
789
+ describe( 'overlay scroll container', () => {
790
+ it( 'marks Dialog.Content with data-wp-ui-overlay-scroll-container', async () => {
791
+ const user = userEvent.setup();
792
+ const contentRef = createRef< HTMLDivElement >();
793
+
794
+ render(
795
+ <Dialog.Root>
796
+ <Dialog.Trigger>Open</Dialog.Trigger>
797
+ <Dialog.Popup>
798
+ <Dialog.Title>Title</Dialog.Title>
799
+ <Dialog.Content ref={ contentRef }>
800
+ <p>Body</p>
801
+ </Dialog.Content>
802
+ </Dialog.Popup>
803
+ </Dialog.Root>
804
+ );
805
+
806
+ await user.click( screen.getByRole( 'button', { name: 'Open' } ) );
807
+ await waitFor( () => {
808
+ expect( contentRef.current ).toBeInstanceOf( HTMLDivElement );
809
+ } );
810
+
811
+ expect( contentRef.current ).toHaveAttribute(
812
+ 'data-wp-ui-overlay-scroll-container'
813
+ );
814
+ } );
815
+
816
+ it( 'sets data-wp-ui-overlay-modal on the popup when modal is true', async () => {
817
+ const user = userEvent.setup();
818
+ const popupRef = createRef< HTMLDivElement >();
819
+
820
+ render(
821
+ <Dialog.Root modal>
822
+ <Dialog.Trigger>Open</Dialog.Trigger>
823
+ <Dialog.Popup ref={ popupRef }>
824
+ <Dialog.Title>Title</Dialog.Title>
825
+ </Dialog.Popup>
826
+ </Dialog.Root>
827
+ );
828
+
829
+ await user.click( screen.getByRole( 'button', { name: 'Open' } ) );
830
+ await waitFor( () => {
831
+ expect( popupRef.current ).toBeInstanceOf( HTMLDivElement );
832
+ } );
833
+
834
+ expect( popupRef.current ).toHaveAttribute(
835
+ 'data-wp-ui-overlay-modal'
836
+ );
837
+ } );
838
+
839
+ it.each( [
840
+ [ 'false', false as const ],
841
+ [ 'trap-focus', 'trap-focus' as const ],
842
+ ] )(
843
+ 'omits data-wp-ui-overlay-modal on the popup when modal is %s',
844
+ async ( _label, modal ) => {
845
+ const user = userEvent.setup();
846
+ const popupRef = createRef< HTMLDivElement >();
847
+
848
+ render(
849
+ <Dialog.Root modal={ modal }>
850
+ <Dialog.Trigger>Open</Dialog.Trigger>
851
+ <Dialog.Popup ref={ popupRef }>
852
+ <Dialog.Title>Title</Dialog.Title>
853
+ </Dialog.Popup>
854
+ </Dialog.Root>
855
+ );
856
+
857
+ await user.click(
858
+ screen.getByRole( 'button', { name: 'Open' } )
859
+ );
860
+ await waitFor( () => {
861
+ expect( popupRef.current ).toBeInstanceOf( HTMLDivElement );
862
+ } );
863
+
864
+ expect( popupRef.current ).not.toHaveAttribute(
865
+ 'data-wp-ui-overlay-modal'
866
+ );
867
+ }
868
+ );
869
+
870
+ it( 'pins Dialog.Header when rendered as a sibling of Dialog.Content', async () => {
871
+ const user = userEvent.setup();
872
+ const popupRef = createRef< HTMLDivElement >();
873
+ const headerRef = createRef< HTMLElement >();
874
+ const contentRef = createRef< HTMLDivElement >();
875
+
876
+ render(
877
+ <Dialog.Root>
878
+ <Dialog.Trigger>Open</Dialog.Trigger>
879
+ <Dialog.Popup ref={ popupRef }>
880
+ <Dialog.Header ref={ headerRef }>
881
+ <Dialog.Title>Title</Dialog.Title>
882
+ </Dialog.Header>
883
+ <Dialog.Content ref={ contentRef }>
884
+ <p>Body</p>
885
+ </Dialog.Content>
886
+ </Dialog.Popup>
887
+ </Dialog.Root>
888
+ );
889
+
890
+ await user.click( screen.getByRole( 'button', { name: 'Open' } ) );
891
+ await waitFor( () => {
892
+ expect( headerRef.current ).toBeInstanceOf( HTMLElement );
893
+ } );
894
+
895
+ // The header is inside the popup but NOT inside the scroll
896
+ // container — it sits outside the scrolling region as a
897
+ // pinned flex sibling of `Content`.
898
+ expect( popupRef.current ).toContainElement( headerRef.current );
899
+ expect( popupRef.current ).toContainElement( contentRef.current );
900
+ expect( contentRef.current ).not.toContainElement(
901
+ headerRef.current
902
+ );
903
+ // And it sits *before* the scroll container — the CSS
904
+ // sticky-separator selectors rely on that DOM order.
905
+ const position = headerRef.current!.compareDocumentPosition(
906
+ contentRef.current!
907
+ );
908
+ expect(
909
+ // eslint-disable-next-line no-bitwise
910
+ position & Node.DOCUMENT_POSITION_FOLLOWING
911
+ ).toBeTruthy();
912
+ } );
913
+
914
+ it( 'scrolls Dialog.Header with the body when nested inside Dialog.Content', async () => {
915
+ const user = userEvent.setup();
916
+ const headerRef = createRef< HTMLElement >();
917
+ const contentRef = createRef< HTMLDivElement >();
918
+
919
+ render(
920
+ <Dialog.Root>
921
+ <Dialog.Trigger>Open</Dialog.Trigger>
922
+ <Dialog.Popup>
923
+ <Dialog.Content ref={ contentRef }>
924
+ <Dialog.Header ref={ headerRef }>
925
+ <Dialog.Title>Title</Dialog.Title>
926
+ </Dialog.Header>
927
+ <p>Body</p>
928
+ </Dialog.Content>
929
+ </Dialog.Popup>
930
+ </Dialog.Root>
931
+ );
932
+
933
+ await user.click( screen.getByRole( 'button', { name: 'Open' } ) );
934
+ await waitFor( () => {
935
+ expect( headerRef.current ).toBeInstanceOf( HTMLElement );
936
+ } );
937
+
938
+ expect( contentRef.current ).toContainElement( headerRef.current );
939
+ } );
940
+
941
+ it( 'invokes a consumer-supplied onScroll on Dialog.Content', async () => {
942
+ const user = userEvent.setup();
943
+ const onScroll = jest.fn();
944
+ const contentRef = createRef< HTMLDivElement >();
945
+
946
+ render(
947
+ <Dialog.Root>
948
+ <Dialog.Trigger>Open</Dialog.Trigger>
949
+ <Dialog.Popup>
950
+ <Dialog.Title>Title</Dialog.Title>
951
+ <Dialog.Content
952
+ ref={ contentRef }
953
+ onScroll={ onScroll }
954
+ >
955
+ <p>Body</p>
956
+ </Dialog.Content>
957
+ </Dialog.Popup>
958
+ </Dialog.Root>
959
+ );
960
+
961
+ await user.click( screen.getByRole( 'button', { name: 'Open' } ) );
962
+ await waitFor( () => {
963
+ expect( contentRef.current ).toBeInstanceOf( HTMLDivElement );
964
+ } );
965
+
966
+ act( () => {
967
+ contentRef.current?.dispatchEvent(
968
+ new Event( 'scroll', { bubbles: true } )
969
+ );
970
+ } );
971
+
972
+ expect( onScroll ).toHaveBeenCalledTimes( 1 );
973
+ } );
974
+
975
+ it( 'toggles tabindex="0" on Dialog.Content based on overflow', async () => {
976
+ const user = userEvent.setup();
977
+ const contentRef = createRef< HTMLDivElement >();
978
+
979
+ render(
980
+ <Dialog.Root>
981
+ <Dialog.Trigger>Open</Dialog.Trigger>
982
+ <Dialog.Popup>
983
+ <Dialog.Title>Title</Dialog.Title>
984
+ <Dialog.Content ref={ contentRef }>
985
+ <p>Body</p>
986
+ </Dialog.Content>
987
+ </Dialog.Popup>
988
+ </Dialog.Root>
989
+ );
990
+
991
+ await user.click( screen.getByRole( 'button', { name: 'Open' } ) );
992
+ await waitFor( () => {
993
+ expect( contentRef.current ).toBeInstanceOf( HTMLDivElement );
994
+ } );
995
+
996
+ // JSDOM reports 0/0 dimensions by default, so the initial
997
+ // mount sees no overflow and installs no tabindex. Stub
998
+ // layout metrics, dispatch a scroll to re-run the update,
999
+ // and verify the tabindex is installed.
1000
+ const content = contentRef.current!;
1001
+ Object.defineProperty( content, 'scrollHeight', {
1002
+ configurable: true,
1003
+ value: 500,
1004
+ } );
1005
+ Object.defineProperty( content, 'clientHeight', {
1006
+ configurable: true,
1007
+ value: 100,
1008
+ } );
1009
+ Object.defineProperty( content, 'scrollTop', {
1010
+ configurable: true,
1011
+ value: 0,
1012
+ } );
1013
+
1014
+ act( () => {
1015
+ content.dispatchEvent(
1016
+ new Event( 'scroll', { bubbles: true } )
1017
+ );
1018
+ } );
1019
+
1020
+ expect( content ).toHaveAttribute( 'tabindex', '0' );
1021
+
1022
+ // Shrink content so it no longer overflows and verify the
1023
+ // hook removes its managed tabindex.
1024
+ Object.defineProperty( content, 'scrollHeight', {
1025
+ configurable: true,
1026
+ value: 100,
1027
+ } );
1028
+
1029
+ act( () => {
1030
+ content.dispatchEvent(
1031
+ new Event( 'scroll', { bubbles: true } )
1032
+ );
1033
+ } );
1034
+
1035
+ expect( content ).not.toHaveAttribute( 'tabindex' );
1036
+ } );
1037
+
1038
+ // This test exercises the `updateScrollAttributes` path for
1039
+ // consumer takeover (overflow flips off while the override is
1040
+ // in place). The matching `cleanupScrollAttributes` path —
1041
+ // popup unmounts while the override is in place — is covered
1042
+ // transitively because both paths share a single
1043
+ // `reconcileTabbableFlag` helper inside the hook. If that
1044
+ // shared helper is ever inlined or split, add an explicit
1045
+ // unmount-after-takeover test to keep both paths regressions-
1046
+ // guarded.
1047
+ it( 'preserves a consumer-supplied tabindex set after the hook installed its own', async () => {
1048
+ const user = userEvent.setup();
1049
+ const contentRef = createRef< HTMLDivElement >();
1050
+
1051
+ render(
1052
+ <Dialog.Root>
1053
+ <Dialog.Trigger>Open</Dialog.Trigger>
1054
+ <Dialog.Popup>
1055
+ <Dialog.Title>Title</Dialog.Title>
1056
+ <Dialog.Content ref={ contentRef }>
1057
+ <p>Body</p>
1058
+ </Dialog.Content>
1059
+ </Dialog.Popup>
1060
+ </Dialog.Root>
1061
+ );
1062
+
1063
+ await user.click( screen.getByRole( 'button', { name: 'Open' } ) );
1064
+ await waitFor( () => {
1065
+ expect( contentRef.current ).toBeInstanceOf( HTMLDivElement );
1066
+ } );
1067
+
1068
+ const content = contentRef.current!;
1069
+ Object.defineProperty( content, 'scrollHeight', {
1070
+ configurable: true,
1071
+ value: 500,
1072
+ } );
1073
+ Object.defineProperty( content, 'clientHeight', {
1074
+ configurable: true,
1075
+ value: 100,
1076
+ } );
1077
+ Object.defineProperty( content, 'scrollTop', {
1078
+ configurable: true,
1079
+ value: 0,
1080
+ } );
1081
+
1082
+ act( () => {
1083
+ content.dispatchEvent(
1084
+ new Event( 'scroll', { bubbles: true } )
1085
+ );
1086
+ } );
1087
+
1088
+ expect( content ).toHaveAttribute( 'tabindex', '0' );
1089
+
1090
+ // Simulate the consumer taking over the tabindex after the
1091
+ // hook installed its own (e.g. a re-render with an explicit
1092
+ // `tabIndex={ -1 }` prop). The hook should detect the
1093
+ // consumer takeover and not clobber that value on the next
1094
+ // non-overflow tick.
1095
+ content.setAttribute( 'tabindex', '-1' );
1096
+
1097
+ Object.defineProperty( content, 'scrollHeight', {
1098
+ configurable: true,
1099
+ value: 100,
1100
+ } );
1101
+
1102
+ act( () => {
1103
+ content.dispatchEvent(
1104
+ new Event( 'scroll', { bubbles: true } )
1105
+ );
1106
+ } );
1107
+
1108
+ expect( content ).toHaveAttribute( 'tabindex', '-1' );
1109
+ } );
1110
+
1111
+ it( 'toggles data-wp-ui-overlay-scrolled-from-* based on scroll position', async () => {
1112
+ const user = userEvent.setup();
1113
+ const contentRef = createRef< HTMLDivElement >();
1114
+
1115
+ render(
1116
+ <Dialog.Root>
1117
+ <Dialog.Trigger>Open</Dialog.Trigger>
1118
+ <Dialog.Popup>
1119
+ <Dialog.Title>Title</Dialog.Title>
1120
+ <Dialog.Content ref={ contentRef }>
1121
+ <p>Body</p>
1122
+ </Dialog.Content>
1123
+ </Dialog.Popup>
1124
+ </Dialog.Root>
1125
+ );
1126
+
1127
+ await user.click( screen.getByRole( 'button', { name: 'Open' } ) );
1128
+ await waitFor( () => {
1129
+ expect( contentRef.current ).toBeInstanceOf( HTMLDivElement );
1130
+ } );
1131
+
1132
+ // JSDOM doesn't lay out elements, so we simulate an
1133
+ // overflowing scroll container by stubbing layout metrics
1134
+ // per scenario and dispatching a scroll event.
1135
+ const content = contentRef.current!;
1136
+ Object.defineProperty( content, 'scrollHeight', {
1137
+ configurable: true,
1138
+ value: 500,
1139
+ } );
1140
+ Object.defineProperty( content, 'clientHeight', {
1141
+ configurable: true,
1142
+ value: 100,
1143
+ } );
1144
+
1145
+ const setScrollTop = ( value: number ) => {
1146
+ Object.defineProperty( content, 'scrollTop', {
1147
+ configurable: true,
1148
+ value,
1149
+ } );
1150
+ act( () => {
1151
+ content.dispatchEvent(
1152
+ new Event( 'scroll', { bubbles: true } )
1153
+ );
1154
+ } );
1155
+ };
1156
+
1157
+ // At the top: only "from-bottom" is set (content below).
1158
+ setScrollTop( 0 );
1159
+ expect( content ).not.toHaveAttribute(
1160
+ 'data-wp-ui-overlay-scrolled-from-top'
1161
+ );
1162
+ expect( content ).toHaveAttribute(
1163
+ 'data-wp-ui-overlay-scrolled-from-bottom'
1164
+ );
1165
+
1166
+ // In the middle: both are set.
1167
+ setScrollTop( 200 );
1168
+ expect( content ).toHaveAttribute(
1169
+ 'data-wp-ui-overlay-scrolled-from-top'
1170
+ );
1171
+ expect( content ).toHaveAttribute(
1172
+ 'data-wp-ui-overlay-scrolled-from-bottom'
1173
+ );
1174
+
1175
+ // At the bottom: only "from-top" is set (content above).
1176
+ setScrollTop( 400 );
1177
+ expect( content ).toHaveAttribute(
1178
+ 'data-wp-ui-overlay-scrolled-from-top'
1179
+ );
1180
+ expect( content ).not.toHaveAttribute(
1181
+ 'data-wp-ui-overlay-scrolled-from-bottom'
1182
+ );
1183
+ } );
1184
+ } );
426
1185
  } );