@wordpress/ui 0.11.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 (660) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +4 -4
  3. package/build/alert-dialog/index.cjs +3 -0
  4. package/build/alert-dialog/index.cjs.map +2 -2
  5. package/build/alert-dialog/popup.cjs +120 -55
  6. package/build/alert-dialog/popup.cjs.map +3 -3
  7. package/build/alert-dialog/portal.cjs +38 -0
  8. package/build/alert-dialog/portal.cjs.map +7 -0
  9. package/build/alert-dialog/types.cjs.map +1 -1
  10. package/build/collapsible-card/content.cjs +9 -5
  11. package/build/collapsible-card/content.cjs.map +2 -2
  12. package/build/collapsible-card/header.cjs +14 -4
  13. package/build/collapsible-card/header.cjs.map +3 -3
  14. package/build/dialog/content.cjs +85 -0
  15. package/build/dialog/content.cjs.map +7 -0
  16. package/build/dialog/context.cjs +12 -44
  17. package/build/dialog/context.cjs.map +2 -2
  18. package/build/dialog/description.cjs +59 -0
  19. package/build/dialog/description.cjs.map +7 -0
  20. package/build/dialog/footer.cjs +5 -4
  21. package/build/dialog/footer.cjs.map +2 -2
  22. package/build/dialog/header.cjs +5 -4
  23. package/build/dialog/header.cjs.map +2 -2
  24. package/build/dialog/index.cjs +9 -0
  25. package/build/dialog/index.cjs.map +2 -2
  26. package/build/dialog/popup.cjs +21 -9
  27. package/build/dialog/popup.cjs.map +2 -2
  28. package/build/dialog/portal.cjs +38 -0
  29. package/build/dialog/portal.cjs.map +7 -0
  30. package/build/dialog/root.cjs +3 -2
  31. package/build/dialog/root.cjs.map +2 -2
  32. package/build/dialog/title.cjs +9 -6
  33. package/build/dialog/title.cjs.map +2 -2
  34. package/build/dialog/types.cjs.map +1 -1
  35. package/build/drawer/action.cjs +48 -0
  36. package/build/drawer/action.cjs.map +7 -0
  37. package/build/drawer/close-icon.cjs +58 -0
  38. package/build/drawer/close-icon.cjs.map +7 -0
  39. package/build/drawer/content.cjs +86 -0
  40. package/build/drawer/content.cjs.map +7 -0
  41. package/build/drawer/context.cjs +44 -0
  42. package/build/drawer/context.cjs.map +7 -0
  43. package/build/drawer/description.cjs +47 -0
  44. package/build/drawer/description.cjs.map +7 -0
  45. package/build/drawer/footer.cjs +65 -0
  46. package/build/drawer/footer.cjs.map +7 -0
  47. package/build/drawer/header.cjs +65 -0
  48. package/build/drawer/header.cjs.map +7 -0
  49. package/build/drawer/index.cjs +61 -0
  50. package/build/drawer/index.cjs.map +7 -0
  51. package/build/drawer/popup.cjs +103 -0
  52. package/build/drawer/popup.cjs.map +7 -0
  53. package/build/drawer/portal.cjs +38 -0
  54. package/build/drawer/portal.cjs.map +7 -0
  55. package/build/drawer/root.cjs +49 -0
  56. package/build/drawer/root.cjs.map +7 -0
  57. package/build/drawer/title.cjs +70 -0
  58. package/build/drawer/title.cjs.map +7 -0
  59. package/build/drawer/trigger.cjs +38 -0
  60. package/build/drawer/trigger.cjs.map +7 -0
  61. package/build/drawer/types.cjs +19 -0
  62. package/build/drawer/types.cjs.map +7 -0
  63. package/build/form/primitives/autocomplete/clear.cjs +62 -0
  64. package/build/form/primitives/autocomplete/clear.cjs.map +7 -0
  65. package/build/form/primitives/autocomplete/collection.cjs +38 -0
  66. package/build/form/primitives/autocomplete/collection.cjs.map +7 -0
  67. package/build/form/primitives/autocomplete/empty.cjs +67 -0
  68. package/build/form/primitives/autocomplete/empty.cjs.map +7 -0
  69. package/build/form/primitives/autocomplete/index.cjs +64 -0
  70. package/build/form/primitives/autocomplete/index.cjs.map +7 -0
  71. package/build/form/primitives/autocomplete/input-group.cjs +36 -0
  72. package/build/form/primitives/autocomplete/input-group.cjs.map +7 -0
  73. package/build/form/primitives/autocomplete/input.cjs +47 -0
  74. package/build/form/primitives/autocomplete/input.cjs.map +7 -0
  75. package/build/form/primitives/autocomplete/item.cjs +81 -0
  76. package/build/form/primitives/autocomplete/item.cjs.map +7 -0
  77. package/build/form/primitives/autocomplete/list-body.cjs +57 -0
  78. package/build/form/primitives/autocomplete/list-body.cjs.map +7 -0
  79. package/build/form/primitives/autocomplete/list.cjs +67 -0
  80. package/build/form/primitives/autocomplete/list.cjs.map +7 -0
  81. package/build/form/primitives/autocomplete/popup.cjs +102 -0
  82. package/build/form/primitives/autocomplete/popup.cjs.map +7 -0
  83. package/build/form/primitives/autocomplete/portal.cjs +38 -0
  84. package/build/form/primitives/autocomplete/portal.cjs.map +7 -0
  85. package/build/form/primitives/autocomplete/root.cjs +35 -0
  86. package/build/form/primitives/autocomplete/root.cjs.map +7 -0
  87. package/build/form/primitives/autocomplete/types.cjs +19 -0
  88. package/build/form/primitives/autocomplete/types.cjs.map +7 -0
  89. package/build/form/primitives/autocomplete/value.cjs +35 -0
  90. package/build/form/primitives/autocomplete/value.cjs.map +7 -0
  91. package/build/form/primitives/index.cjs +3 -0
  92. package/build/form/primitives/index.cjs.map +2 -2
  93. package/build/form/primitives/select/index.cjs +3 -0
  94. package/build/form/primitives/select/index.cjs.map +2 -2
  95. package/build/form/primitives/select/item.cjs +4 -5
  96. package/build/form/primitives/select/item.cjs.map +2 -2
  97. package/build/form/primitives/select/popup.cjs +12 -11
  98. package/build/form/primitives/select/popup.cjs.map +2 -2
  99. package/build/form/primitives/select/portal.cjs +38 -0
  100. package/build/form/primitives/select/portal.cjs.map +7 -0
  101. package/build/form/primitives/select/types.cjs.map +1 -1
  102. package/build/index.cjs +3 -0
  103. package/build/index.cjs.map +2 -2
  104. package/build/link/link.cjs +8 -18
  105. package/build/link/link.cjs.map +2 -2
  106. package/build/link/types.cjs.map +1 -1
  107. package/build/notice/action-button.cjs +3 -3
  108. package/build/notice/action-button.cjs.map +2 -2
  109. package/build/notice/action-link.cjs +8 -7
  110. package/build/notice/action-link.cjs.map +2 -2
  111. package/build/notice/actions.cjs +3 -3
  112. package/build/notice/actions.cjs.map +2 -2
  113. package/build/notice/close-icon.cjs +3 -3
  114. package/build/notice/close-icon.cjs.map +2 -2
  115. package/build/notice/description.cjs +3 -3
  116. package/build/notice/description.cjs.map +2 -2
  117. package/build/notice/root.cjs +3 -3
  118. package/build/notice/root.cjs.map +2 -2
  119. package/build/notice/title.cjs +3 -3
  120. package/build/notice/title.cjs.map +2 -2
  121. package/build/popover/arrow.cjs +4 -4
  122. package/build/popover/arrow.cjs.map +2 -2
  123. package/build/popover/context.cjs +4 -44
  124. package/build/popover/context.cjs.map +2 -2
  125. package/build/popover/description.cjs +1 -24
  126. package/build/popover/description.cjs.map +4 -4
  127. package/build/popover/index.cjs +3 -0
  128. package/build/popover/index.cjs.map +2 -2
  129. package/build/popover/popup.cjs +15 -15
  130. package/build/popover/popup.cjs.map +2 -2
  131. package/build/popover/portal.cjs +38 -0
  132. package/build/popover/portal.cjs.map +7 -0
  133. package/build/popover/root.cjs.map +1 -1
  134. package/build/popover/title.cjs +18 -4
  135. package/build/popover/title.cjs.map +3 -3
  136. package/build/popover/types.cjs.map +1 -1
  137. package/build/tabs/context.cjs +9 -22
  138. package/build/tabs/context.cjs.map +2 -2
  139. package/build/tabs/list.cjs +4 -4
  140. package/build/tabs/list.cjs.map +2 -2
  141. package/build/tabs/panel.cjs +19 -6
  142. package/build/tabs/panel.cjs.map +3 -3
  143. package/build/tabs/tab.cjs +4 -4
  144. package/build/tabs/tab.cjs.map +2 -2
  145. package/build/text/text.cjs +2 -2
  146. package/build/text/text.cjs.map +2 -2
  147. package/build/tooltip/index.cjs +3 -0
  148. package/build/tooltip/index.cjs.map +2 -2
  149. package/build/tooltip/popup.cjs +11 -14
  150. package/build/tooltip/popup.cjs.map +3 -3
  151. package/build/tooltip/portal.cjs +38 -0
  152. package/build/tooltip/portal.cjs.map +7 -0
  153. package/build/tooltip/provider.cjs +2 -2
  154. package/build/tooltip/provider.cjs.map +3 -3
  155. package/build/tooltip/root.cjs.map +3 -3
  156. package/build/tooltip/trigger.cjs +2 -2
  157. package/build/tooltip/trigger.cjs.map +3 -3
  158. package/build/tooltip/types.cjs.map +1 -1
  159. package/build/utils/create-overlay-modal-context.cjs +48 -0
  160. package/build/utils/create-overlay-modal-context.cjs.map +7 -0
  161. package/build/utils/create-overlay-title-validation.cjs +93 -0
  162. package/build/utils/create-overlay-title-validation.cjs.map +7 -0
  163. package/build/utils/render-portal-with-children.cjs +37 -0
  164. package/build/utils/render-portal-with-children.cjs.map +7 -0
  165. package/build/utils/use-deprioritized-initial-focus.cjs +8 -8
  166. package/build/utils/use-deprioritized-initial-focus.cjs.map +2 -2
  167. package/build/utils/use-overlay-scroll-state-attributes.cjs +140 -0
  168. package/build/utils/use-overlay-scroll-state-attributes.cjs.map +7 -0
  169. package/build/utils/use-schedule-validation.cjs +59 -0
  170. package/build/utils/use-schedule-validation.cjs.map +7 -0
  171. package/build/visually-hidden/visually-hidden.cjs +5 -1
  172. package/build/visually-hidden/visually-hidden.cjs.map +2 -2
  173. package/build-module/alert-dialog/index.mjs +2 -0
  174. package/build-module/alert-dialog/index.mjs.map +2 -2
  175. package/build-module/alert-dialog/popup.mjs +124 -56
  176. package/build-module/alert-dialog/popup.mjs.map +3 -3
  177. package/build-module/alert-dialog/portal.mjs +13 -0
  178. package/build-module/alert-dialog/portal.mjs.map +7 -0
  179. package/build-module/collapsible-card/content.mjs +9 -5
  180. package/build-module/collapsible-card/content.mjs.map +2 -2
  181. package/build-module/collapsible-card/header.mjs +14 -4
  182. package/build-module/collapsible-card/header.mjs.map +3 -3
  183. package/build-module/dialog/content.mjs +50 -0
  184. package/build-module/dialog/content.mjs.map +7 -0
  185. package/build-module/dialog/context.mjs +10 -51
  186. package/build-module/dialog/context.mjs.map +2 -2
  187. package/build-module/dialog/description.mjs +34 -0
  188. package/build-module/dialog/description.mjs.map +7 -0
  189. package/build-module/dialog/footer.mjs +5 -4
  190. package/build-module/dialog/footer.mjs.map +2 -2
  191. package/build-module/dialog/header.mjs +5 -4
  192. package/build-module/dialog/header.mjs.map +2 -2
  193. package/build-module/dialog/index.mjs +6 -0
  194. package/build-module/dialog/index.mjs.map +2 -2
  195. package/build-module/dialog/popup.mjs +23 -11
  196. package/build-module/dialog/popup.mjs.map +2 -2
  197. package/build-module/dialog/portal.mjs +13 -0
  198. package/build-module/dialog/portal.mjs.map +7 -0
  199. package/build-module/dialog/root.mjs +3 -2
  200. package/build-module/dialog/root.mjs.map +2 -2
  201. package/build-module/dialog/title.mjs +10 -7
  202. package/build-module/dialog/title.mjs.map +2 -2
  203. package/build-module/drawer/action.mjs +23 -0
  204. package/build-module/drawer/action.mjs.map +7 -0
  205. package/build-module/drawer/close-icon.mjs +33 -0
  206. package/build-module/drawer/close-icon.mjs.map +7 -0
  207. package/build-module/drawer/content.mjs +51 -0
  208. package/build-module/drawer/content.mjs.map +7 -0
  209. package/build-module/drawer/context.mjs +16 -0
  210. package/build-module/drawer/context.mjs.map +7 -0
  211. package/build-module/drawer/description.mjs +22 -0
  212. package/build-module/drawer/description.mjs.map +7 -0
  213. package/build-module/drawer/footer.mjs +30 -0
  214. package/build-module/drawer/footer.mjs.map +7 -0
  215. package/build-module/drawer/header.mjs +30 -0
  216. package/build-module/drawer/header.mjs.map +7 -0
  217. package/build-module/drawer/index.mjs +26 -0
  218. package/build-module/drawer/index.mjs.map +7 -0
  219. package/build-module/drawer/popup.mjs +70 -0
  220. package/build-module/drawer/popup.mjs.map +7 -0
  221. package/build-module/drawer/portal.mjs +13 -0
  222. package/build-module/drawer/portal.mjs.map +7 -0
  223. package/build-module/drawer/root.mjs +24 -0
  224. package/build-module/drawer/root.mjs.map +7 -0
  225. package/build-module/drawer/title.mjs +45 -0
  226. package/build-module/drawer/title.mjs.map +7 -0
  227. package/build-module/drawer/trigger.mjs +13 -0
  228. package/build-module/drawer/trigger.mjs.map +7 -0
  229. package/build-module/drawer/types.mjs +1 -0
  230. package/build-module/form/primitives/autocomplete/clear.mjs +37 -0
  231. package/build-module/form/primitives/autocomplete/clear.mjs.map +7 -0
  232. package/build-module/form/primitives/autocomplete/collection.mjs +13 -0
  233. package/build-module/form/primitives/autocomplete/collection.mjs.map +7 -0
  234. package/build-module/form/primitives/autocomplete/empty.mjs +32 -0
  235. package/build-module/form/primitives/autocomplete/empty.mjs.map +7 -0
  236. package/build-module/form/primitives/autocomplete/index.mjs +28 -0
  237. package/build-module/form/primitives/autocomplete/index.mjs.map +7 -0
  238. package/build-module/form/primitives/autocomplete/input-group.mjs +11 -0
  239. package/build-module/form/primitives/autocomplete/input-group.mjs.map +7 -0
  240. package/build-module/form/primitives/autocomplete/input.mjs +22 -0
  241. package/build-module/form/primitives/autocomplete/input.mjs.map +7 -0
  242. package/build-module/form/primitives/autocomplete/item.mjs +46 -0
  243. package/build-module/form/primitives/autocomplete/item.mjs.map +7 -0
  244. package/build-module/form/primitives/autocomplete/list-body.mjs +32 -0
  245. package/build-module/form/primitives/autocomplete/list-body.mjs.map +7 -0
  246. package/build-module/form/primitives/autocomplete/list.mjs +32 -0
  247. package/build-module/form/primitives/autocomplete/list.mjs.map +7 -0
  248. package/build-module/form/primitives/autocomplete/popup.mjs +69 -0
  249. package/build-module/form/primitives/autocomplete/popup.mjs.map +7 -0
  250. package/build-module/form/primitives/autocomplete/portal.mjs +13 -0
  251. package/build-module/form/primitives/autocomplete/portal.mjs.map +7 -0
  252. package/build-module/form/primitives/autocomplete/root.mjs +10 -0
  253. package/build-module/form/primitives/autocomplete/root.mjs.map +7 -0
  254. package/build-module/form/primitives/autocomplete/types.mjs +1 -0
  255. package/build-module/form/primitives/autocomplete/value.mjs +10 -0
  256. package/build-module/form/primitives/autocomplete/value.mjs.map +7 -0
  257. package/build-module/form/primitives/index.mjs +2 -0
  258. package/build-module/form/primitives/index.mjs.map +2 -2
  259. package/build-module/form/primitives/select/index.mjs +2 -0
  260. package/build-module/form/primitives/select/index.mjs.map +2 -2
  261. package/build-module/form/primitives/select/item.mjs +4 -5
  262. package/build-module/form/primitives/select/item.mjs.map +2 -2
  263. package/build-module/form/primitives/select/popup.mjs +12 -11
  264. package/build-module/form/primitives/select/popup.mjs.map +2 -2
  265. package/build-module/form/primitives/select/portal.mjs +13 -0
  266. package/build-module/form/primitives/select/portal.mjs.map +7 -0
  267. package/build-module/index.mjs +2 -0
  268. package/build-module/index.mjs.map +2 -2
  269. package/build-module/link/link.mjs +8 -18
  270. package/build-module/link/link.mjs.map +2 -2
  271. package/build-module/notice/action-button.mjs +3 -3
  272. package/build-module/notice/action-button.mjs.map +2 -2
  273. package/build-module/notice/action-link.mjs +8 -7
  274. package/build-module/notice/action-link.mjs.map +2 -2
  275. package/build-module/notice/actions.mjs +3 -3
  276. package/build-module/notice/actions.mjs.map +2 -2
  277. package/build-module/notice/close-icon.mjs +3 -3
  278. package/build-module/notice/close-icon.mjs.map +2 -2
  279. package/build-module/notice/description.mjs +3 -3
  280. package/build-module/notice/description.mjs.map +2 -2
  281. package/build-module/notice/root.mjs +3 -3
  282. package/build-module/notice/root.mjs.map +2 -2
  283. package/build-module/notice/title.mjs +3 -3
  284. package/build-module/notice/title.mjs.map +2 -2
  285. package/build-module/popover/arrow.mjs +4 -4
  286. package/build-module/popover/arrow.mjs.map +2 -2
  287. package/build-module/popover/context.mjs +4 -51
  288. package/build-module/popover/context.mjs.map +2 -2
  289. package/build-module/popover/description.mjs +1 -14
  290. package/build-module/popover/description.mjs.map +3 -3
  291. package/build-module/popover/index.mjs +2 -0
  292. package/build-module/popover/index.mjs.map +2 -2
  293. package/build-module/popover/popup.mjs +16 -16
  294. package/build-module/popover/popup.mjs.map +2 -2
  295. package/build-module/popover/portal.mjs +13 -0
  296. package/build-module/popover/portal.mjs.map +7 -0
  297. package/build-module/popover/root.mjs.map +1 -1
  298. package/build-module/popover/title.mjs +19 -5
  299. package/build-module/popover/title.mjs.map +3 -3
  300. package/build-module/tabs/context.mjs +11 -24
  301. package/build-module/tabs/context.mjs.map +2 -2
  302. package/build-module/tabs/list.mjs +4 -4
  303. package/build-module/tabs/list.mjs.map +2 -2
  304. package/build-module/tabs/panel.mjs +19 -6
  305. package/build-module/tabs/panel.mjs.map +3 -3
  306. package/build-module/tabs/tab.mjs +4 -4
  307. package/build-module/tabs/tab.mjs.map +2 -2
  308. package/build-module/text/text.mjs +2 -2
  309. package/build-module/text/text.mjs.map +2 -2
  310. package/build-module/tooltip/index.mjs +2 -0
  311. package/build-module/tooltip/index.mjs.map +2 -2
  312. package/build-module/tooltip/popup.mjs +14 -17
  313. package/build-module/tooltip/popup.mjs.map +2 -2
  314. package/build-module/tooltip/portal.mjs +13 -0
  315. package/build-module/tooltip/portal.mjs.map +7 -0
  316. package/build-module/tooltip/provider.mjs +3 -3
  317. package/build-module/tooltip/provider.mjs.map +2 -2
  318. package/build-module/tooltip/root.mjs +2 -2
  319. package/build-module/tooltip/root.mjs.map +2 -2
  320. package/build-module/tooltip/trigger.mjs +3 -3
  321. package/build-module/tooltip/trigger.mjs.map +2 -2
  322. package/build-module/utils/create-overlay-modal-context.mjs +23 -0
  323. package/build-module/utils/create-overlay-modal-context.mjs.map +7 -0
  324. package/build-module/utils/create-overlay-title-validation.mjs +75 -0
  325. package/build-module/utils/create-overlay-title-validation.mjs.map +7 -0
  326. package/build-module/utils/render-portal-with-children.mjs +12 -0
  327. package/build-module/utils/render-portal-with-children.mjs.map +7 -0
  328. package/build-module/utils/use-deprioritized-initial-focus.mjs +9 -9
  329. package/build-module/utils/use-deprioritized-initial-focus.mjs.map +2 -2
  330. package/build-module/utils/use-overlay-scroll-state-attributes.mjs +114 -0
  331. package/build-module/utils/use-overlay-scroll-state-attributes.mjs.map +7 -0
  332. package/build-module/utils/use-schedule-validation.mjs +34 -0
  333. package/build-module/utils/use-schedule-validation.mjs.map +7 -0
  334. package/build-module/visually-hidden/visually-hidden.mjs +5 -1
  335. package/build-module/visually-hidden/visually-hidden.mjs.map +2 -2
  336. package/build-types/alert-dialog/index.d.ts +1 -0
  337. package/build-types/alert-dialog/index.d.ts.map +1 -1
  338. package/build-types/alert-dialog/popup.d.ts.map +1 -1
  339. package/build-types/alert-dialog/portal.d.ts +9 -0
  340. package/build-types/alert-dialog/portal.d.ts.map +1 -0
  341. package/build-types/alert-dialog/stories/index.story.d.ts +29 -1
  342. package/build-types/alert-dialog/stories/index.story.d.ts.map +1 -1
  343. package/build-types/alert-dialog/types.d.ts +25 -3
  344. package/build-types/alert-dialog/types.d.ts.map +1 -1
  345. package/build-types/badge/stories/index.story.d.ts.map +1 -1
  346. package/build-types/card/stories/index.story.d.ts.map +1 -1
  347. package/build-types/collapsible/stories/index.story.d.ts.map +1 -1
  348. package/build-types/collapsible-card/content.d.ts.map +1 -1
  349. package/build-types/collapsible-card/header.d.ts.map +1 -1
  350. package/build-types/collapsible-card/stories/index.story.d.ts.map +1 -1
  351. package/build-types/dialog/content.d.ts +17 -0
  352. package/build-types/dialog/content.d.ts.map +1 -0
  353. package/build-types/dialog/context.d.ts +11 -16
  354. package/build-types/dialog/context.d.ts.map +1 -1
  355. package/build-types/dialog/description.d.ts +9 -0
  356. package/build-types/dialog/description.d.ts.map +1 -0
  357. package/build-types/dialog/footer.d.ts +8 -1
  358. package/build-types/dialog/footer.d.ts.map +1 -1
  359. package/build-types/dialog/header.d.ts +8 -1
  360. package/build-types/dialog/header.d.ts.map +1 -1
  361. package/build-types/dialog/index.d.ts +4 -1
  362. package/build-types/dialog/index.d.ts.map +1 -1
  363. package/build-types/dialog/popup.d.ts +3 -0
  364. package/build-types/dialog/popup.d.ts.map +1 -1
  365. package/build-types/dialog/portal.d.ts +10 -0
  366. package/build-types/dialog/portal.d.ts.map +1 -0
  367. package/build-types/dialog/root.d.ts +14 -4
  368. package/build-types/dialog/root.d.ts.map +1 -1
  369. package/build-types/dialog/stories/index.story.d.ts +29 -6
  370. package/build-types/dialog/stories/index.story.d.ts.map +1 -1
  371. package/build-types/dialog/title.d.ts.map +1 -1
  372. package/build-types/dialog/types.d.ts +60 -7
  373. package/build-types/dialog/types.d.ts.map +1 -1
  374. package/build-types/drawer/action.d.ts +8 -0
  375. package/build-types/drawer/action.d.ts.map +1 -0
  376. package/build-types/drawer/close-icon.d.ts +8 -0
  377. package/build-types/drawer/close-icon.d.ts.map +1 -0
  378. package/build-types/drawer/content.d.ts +21 -0
  379. package/build-types/drawer/content.d.ts.map +1 -0
  380. package/build-types/drawer/context.d.ts +20 -0
  381. package/build-types/drawer/context.d.ts.map +1 -0
  382. package/build-types/drawer/description.d.ts +9 -0
  383. package/build-types/drawer/description.d.ts.map +1 -0
  384. package/build-types/drawer/footer.d.ts +15 -0
  385. package/build-types/drawer/footer.d.ts.map +1 -0
  386. package/build-types/drawer/header.d.ts +15 -0
  387. package/build-types/drawer/header.d.ts.map +1 -0
  388. package/build-types/drawer/index.d.ts +13 -0
  389. package/build-types/drawer/index.d.ts.map +1 -0
  390. package/build-types/drawer/popup.d.ts +16 -0
  391. package/build-types/drawer/popup.d.ts.map +1 -0
  392. package/build-types/drawer/portal.d.ts +10 -0
  393. package/build-types/drawer/portal.d.ts.map +1 -0
  394. package/build-types/drawer/root.d.ts +21 -0
  395. package/build-types/drawer/root.d.ts.map +1 -0
  396. package/build-types/drawer/stories/index.story.d.ts +63 -0
  397. package/build-types/drawer/stories/index.story.d.ts.map +1 -0
  398. package/build-types/drawer/test/index.test.d.ts +2 -0
  399. package/build-types/drawer/test/index.test.d.ts.map +1 -0
  400. package/build-types/drawer/title.d.ts +22 -0
  401. package/build-types/drawer/title.d.ts.map +1 -0
  402. package/build-types/drawer/trigger.d.ts +7 -0
  403. package/build-types/drawer/trigger.d.ts.map +1 -0
  404. package/build-types/drawer/types.d.ts +146 -0
  405. package/build-types/drawer/types.d.ts.map +1 -0
  406. package/build-types/empty-state/stories/index.story.d.ts +1 -1
  407. package/build-types/empty-state/stories/index.story.d.ts.map +1 -1
  408. package/build-types/form/input-control/stories/index.story.d.ts +1 -1
  409. package/build-types/form/input-control/stories/index.story.d.ts.map +1 -1
  410. package/build-types/form/primitives/autocomplete/clear.d.ts +13 -0
  411. package/build-types/form/primitives/autocomplete/clear.d.ts.map +1 -0
  412. package/build-types/form/primitives/autocomplete/collection.d.ts +3 -0
  413. package/build-types/form/primitives/autocomplete/collection.d.ts.map +1 -0
  414. package/build-types/form/primitives/autocomplete/empty.d.ts +10 -0
  415. package/build-types/form/primitives/autocomplete/empty.d.ts.map +1 -0
  416. package/build-types/form/primitives/autocomplete/index.d.ts +13 -0
  417. package/build-types/form/primitives/autocomplete/index.d.ts.map +1 -0
  418. package/build-types/form/primitives/autocomplete/input-group.d.ts +16 -0
  419. package/build-types/form/primitives/autocomplete/input-group.d.ts.map +1 -0
  420. package/build-types/form/primitives/autocomplete/input.d.ts +3 -0
  421. package/build-types/form/primitives/autocomplete/input.d.ts.map +1 -0
  422. package/build-types/form/primitives/autocomplete/item.d.ts +10 -0
  423. package/build-types/form/primitives/autocomplete/item.d.ts.map +1 -0
  424. package/build-types/form/primitives/autocomplete/list-body.d.ts +13 -0
  425. package/build-types/form/primitives/autocomplete/list-body.d.ts.map +1 -0
  426. package/build-types/form/primitives/autocomplete/list.d.ts +11 -0
  427. package/build-types/form/primitives/autocomplete/list.d.ts.map +1 -0
  428. package/build-types/form/primitives/autocomplete/popup.d.ts +11 -0
  429. package/build-types/form/primitives/autocomplete/popup.d.ts.map +1 -0
  430. package/build-types/form/primitives/autocomplete/portal.d.ts +8 -0
  431. package/build-types/form/primitives/autocomplete/portal.d.ts.map +1 -0
  432. package/build-types/form/primitives/autocomplete/root.d.ts +8 -0
  433. package/build-types/form/primitives/autocomplete/root.d.ts.map +1 -0
  434. package/build-types/form/primitives/autocomplete/stories/fixtures.d.ts +8 -0
  435. package/build-types/form/primitives/autocomplete/stories/fixtures.d.ts.map +1 -0
  436. package/build-types/form/primitives/autocomplete/stories/index.story.d.ts +41 -0
  437. package/build-types/form/primitives/autocomplete/stories/index.story.d.ts.map +1 -0
  438. package/build-types/form/primitives/autocomplete/test/index.test.d.ts +2 -0
  439. package/build-types/form/primitives/autocomplete/test/index.test.d.ts.map +1 -0
  440. package/build-types/form/primitives/autocomplete/types.d.ts +44 -0
  441. package/build-types/form/primitives/autocomplete/types.d.ts.map +1 -0
  442. package/build-types/form/primitives/autocomplete/value.d.ts +3 -0
  443. package/build-types/form/primitives/autocomplete/value.d.ts.map +1 -0
  444. package/build-types/form/primitives/field/stories/index.story.d.ts +1 -1
  445. package/build-types/form/primitives/field/stories/index.story.d.ts.map +1 -1
  446. package/build-types/form/primitives/fieldset/stories/index.story.d.ts +1 -1
  447. package/build-types/form/primitives/fieldset/stories/index.story.d.ts.map +1 -1
  448. package/build-types/form/primitives/index.d.ts +1 -0
  449. package/build-types/form/primitives/index.d.ts.map +1 -1
  450. package/build-types/form/primitives/input/stories/index.story.d.ts +1 -1
  451. package/build-types/form/primitives/input/stories/index.story.d.ts.map +1 -1
  452. package/build-types/form/primitives/input-layout/stories/index.story.d.ts +1 -1
  453. package/build-types/form/primitives/input-layout/stories/index.story.d.ts.map +1 -1
  454. package/build-types/form/primitives/select/index.d.ts +1 -0
  455. package/build-types/form/primitives/select/index.d.ts.map +1 -1
  456. package/build-types/form/primitives/select/item.d.ts.map +1 -1
  457. package/build-types/form/primitives/select/popup.d.ts +1 -2
  458. package/build-types/form/primitives/select/popup.d.ts.map +1 -1
  459. package/build-types/form/primitives/select/portal.d.ts +8 -0
  460. package/build-types/form/primitives/select/portal.d.ts.map +1 -0
  461. package/build-types/form/primitives/select/stories/index.story.d.ts +14 -6
  462. package/build-types/form/primitives/select/stories/index.story.d.ts.map +1 -1
  463. package/build-types/form/primitives/select/types.d.ts +7 -2
  464. package/build-types/form/primitives/select/types.d.ts.map +1 -1
  465. package/build-types/index.d.ts +1 -0
  466. package/build-types/index.d.ts.map +1 -1
  467. package/build-types/link/link.d.ts.map +1 -1
  468. package/build-types/link/stories/index.story.d.ts +2 -3
  469. package/build-types/link/stories/index.story.d.ts.map +1 -1
  470. package/build-types/link/types.d.ts +1 -2
  471. package/build-types/link/types.d.ts.map +1 -1
  472. package/build-types/notice/action-link.d.ts.map +1 -1
  473. package/build-types/popover/context.d.ts +6 -13
  474. package/build-types/popover/context.d.ts.map +1 -1
  475. package/build-types/popover/description.d.ts +0 -1
  476. package/build-types/popover/description.d.ts.map +1 -1
  477. package/build-types/popover/index.d.ts +2 -1
  478. package/build-types/popover/index.d.ts.map +1 -1
  479. package/build-types/popover/popup.d.ts +3 -2
  480. package/build-types/popover/popup.d.ts.map +1 -1
  481. package/build-types/popover/portal.d.ts +9 -0
  482. package/build-types/popover/portal.d.ts.map +1 -0
  483. package/build-types/popover/root.d.ts +2 -2
  484. package/build-types/popover/stories/index.story.d.ts +23 -15
  485. package/build-types/popover/stories/index.story.d.ts.map +1 -1
  486. package/build-types/popover/title.d.ts.map +1 -1
  487. package/build-types/popover/types.d.ts +8 -15
  488. package/build-types/popover/types.d.ts.map +1 -1
  489. package/build-types/stack/stories/index.story.d.ts.map +1 -1
  490. package/build-types/tabs/context.d.ts.map +1 -1
  491. package/build-types/tabs/panel.d.ts.map +1 -1
  492. package/build-types/tabs/stories/index.story.d.ts +1 -1
  493. package/build-types/tabs/stories/index.story.d.ts.map +1 -1
  494. package/build-types/text/stories/index.story.d.ts.map +1 -1
  495. package/build-types/tooltip/index.d.ts +2 -1
  496. package/build-types/tooltip/index.d.ts.map +1 -1
  497. package/build-types/tooltip/popup.d.ts.map +1 -1
  498. package/build-types/tooltip/portal.d.ts +8 -0
  499. package/build-types/tooltip/portal.d.ts.map +1 -0
  500. package/build-types/tooltip/provider.d.ts +1 -1
  501. package/build-types/tooltip/provider.d.ts.map +1 -1
  502. package/build-types/tooltip/stories/index.story.d.ts +18 -1
  503. package/build-types/tooltip/stories/index.story.d.ts.map +1 -1
  504. package/build-types/tooltip/stories/usage-guidelines.story.d.ts.map +1 -1
  505. package/build-types/tooltip/trigger.d.ts.map +1 -1
  506. package/build-types/tooltip/types.d.ts +11 -7
  507. package/build-types/tooltip/types.d.ts.map +1 -1
  508. package/build-types/utils/create-overlay-modal-context.d.ts +14 -0
  509. package/build-types/utils/create-overlay-modal-context.d.ts.map +1 -0
  510. package/build-types/utils/create-overlay-title-validation.d.ts +15 -0
  511. package/build-types/utils/create-overlay-title-validation.d.ts.map +1 -0
  512. package/build-types/utils/render-portal-with-children.d.ts +16 -0
  513. package/build-types/utils/render-portal-with-children.d.ts.map +1 -0
  514. package/build-types/utils/use-deprioritized-initial-focus.d.ts +9 -8
  515. package/build-types/utils/use-deprioritized-initial-focus.d.ts.map +1 -1
  516. package/build-types/utils/use-overlay-scroll-state-attributes.d.ts +85 -0
  517. package/build-types/utils/use-overlay-scroll-state-attributes.d.ts.map +1 -0
  518. package/build-types/utils/use-schedule-validation.d.ts +13 -0
  519. package/build-types/utils/use-schedule-validation.d.ts.map +1 -0
  520. package/build-types/visually-hidden/stories/index.story.d.ts.map +1 -1
  521. package/build-types/visually-hidden/visually-hidden.d.ts +4 -20
  522. package/build-types/visually-hidden/visually-hidden.d.ts.map +1 -1
  523. package/package.json +12 -12
  524. package/src/alert-dialog/index.ts +1 -0
  525. package/src/alert-dialog/popup.tsx +114 -45
  526. package/src/alert-dialog/portal.tsx +17 -0
  527. package/src/alert-dialog/stories/index.story.tsx +123 -3
  528. package/src/alert-dialog/style.module.css +13 -4
  529. package/src/alert-dialog/test/index.test.tsx +329 -3
  530. package/src/alert-dialog/types.ts +30 -3
  531. package/src/badge/stories/choosing-intent.story.tsx +1 -1
  532. package/src/badge/stories/index.story.tsx +1 -0
  533. package/src/card/stories/index.story.tsx +1 -0
  534. package/src/collapsible/stories/index.story.tsx +1 -0
  535. package/src/collapsible-card/content.tsx +12 -1
  536. package/src/collapsible-card/header.tsx +2 -0
  537. package/src/collapsible-card/stories/index.story.tsx +1 -0
  538. package/src/collapsible-card/style.module.css +16 -4
  539. package/src/dialog/content.tsx +47 -0
  540. package/src/dialog/context.tsx +14 -98
  541. package/src/dialog/description.tsx +27 -0
  542. package/src/dialog/footer.tsx +10 -2
  543. package/src/dialog/header.tsx +10 -2
  544. package/src/dialog/index.ts +16 -1
  545. package/src/dialog/popup.tsx +28 -8
  546. package/src/dialog/portal.tsx +18 -0
  547. package/src/dialog/root.tsx +22 -5
  548. package/src/dialog/stories/index.story.tsx +195 -51
  549. package/src/dialog/style.module.css +73 -23
  550. package/src/dialog/test/index.test.tsx +849 -149
  551. package/src/dialog/title.tsx +6 -4
  552. package/src/dialog/types.ts +64 -6
  553. package/src/drawer/action.tsx +28 -0
  554. package/src/drawer/close-icon.tsx +33 -0
  555. package/src/drawer/content.tsx +50 -0
  556. package/src/drawer/context.tsx +29 -0
  557. package/src/drawer/description.tsx +25 -0
  558. package/src/drawer/footer.tsx +34 -0
  559. package/src/drawer/header.tsx +34 -0
  560. package/src/drawer/index.ts +25 -0
  561. package/src/drawer/popup.tsx +100 -0
  562. package/src/drawer/portal.tsx +18 -0
  563. package/src/drawer/root.tsx +41 -0
  564. package/src/drawer/stories/index.story.tsx +543 -0
  565. package/src/drawer/style.module.css +324 -0
  566. package/src/drawer/test/index.test.tsx +1097 -0
  567. package/src/drawer/title.tsx +53 -0
  568. package/src/drawer/trigger.tsx +14 -0
  569. package/src/drawer/types.ts +174 -0
  570. package/src/empty-state/stories/index.story.tsx +2 -1
  571. package/src/form/input-control/stories/index.story.tsx +4 -1
  572. package/src/form/primitives/autocomplete/clear.tsx +35 -0
  573. package/src/form/primitives/autocomplete/collection.tsx +13 -0
  574. package/src/form/primitives/autocomplete/empty.tsx +17 -0
  575. package/src/form/primitives/autocomplete/index.ts +12 -0
  576. package/src/form/primitives/autocomplete/input-group.tsx +16 -0
  577. package/src/form/primitives/autocomplete/input.tsx +20 -0
  578. package/src/form/primitives/autocomplete/item.tsx +24 -0
  579. package/src/form/primitives/autocomplete/list-body.tsx +23 -0
  580. package/src/form/primitives/autocomplete/list.tsx +17 -0
  581. package/src/form/primitives/autocomplete/popup.tsx +42 -0
  582. package/src/form/primitives/autocomplete/portal.tsx +16 -0
  583. package/src/form/primitives/autocomplete/root.tsx +11 -0
  584. package/src/form/primitives/autocomplete/stories/fixtures.ts +35 -0
  585. package/src/form/primitives/autocomplete/stories/index.story.tsx +437 -0
  586. package/src/form/primitives/autocomplete/style.module.css +7 -0
  587. package/src/form/primitives/autocomplete/test/index.test.tsx +162 -0
  588. package/src/form/primitives/autocomplete/types.ts +74 -0
  589. package/src/form/primitives/autocomplete/value.tsx +6 -0
  590. package/src/form/primitives/field/stories/index.story.tsx +1 -1
  591. package/src/form/primitives/fieldset/stories/index.story.tsx +1 -1
  592. package/src/form/primitives/index.ts +1 -0
  593. package/src/form/primitives/input/stories/index.story.tsx +2 -1
  594. package/src/form/primitives/input-layout/stories/index.story.tsx +2 -1
  595. package/src/form/primitives/select/index.ts +1 -0
  596. package/src/form/primitives/select/item.tsx +0 -1
  597. package/src/form/primitives/select/popup.tsx +34 -37
  598. package/src/form/primitives/select/portal.tsx +16 -0
  599. package/src/form/primitives/select/stories/index.story.tsx +21 -7
  600. package/src/form/primitives/select/test/index.test.tsx +7 -3
  601. package/src/form/primitives/select/types.ts +9 -2
  602. package/src/index.ts +1 -0
  603. package/src/link/link.tsx +12 -26
  604. package/src/link/stories/index.story.tsx +6 -11
  605. package/src/link/style.module.css +5 -17
  606. package/src/link/test/index.test.tsx +31 -27
  607. package/src/link/types.ts +1 -2
  608. package/src/notice/action-link.tsx +7 -4
  609. package/src/notice/style.module.css +5 -5
  610. package/src/popover/context.tsx +6 -89
  611. package/src/popover/description.tsx +1 -5
  612. package/src/popover/index.ts +2 -1
  613. package/src/popover/popup.tsx +17 -15
  614. package/src/popover/portal.tsx +17 -0
  615. package/src/popover/root.tsx +2 -2
  616. package/src/popover/stories/index.story.tsx +56 -25
  617. package/src/popover/style.module.css +33 -4
  618. package/src/popover/test/index.test.tsx +189 -74
  619. package/src/popover/title.tsx +9 -5
  620. package/src/popover/types.ts +10 -15
  621. package/src/stack/stories/index.story.tsx +1 -0
  622. package/src/tabs/context.tsx +14 -34
  623. package/src/tabs/panel.tsx +7 -2
  624. package/src/tabs/stories/index.story.tsx +2 -1
  625. package/src/tabs/style.module.css +0 -17
  626. package/src/tabs/test/index.test.tsx +7 -3
  627. package/src/text/stories/index.story.tsx +1 -0
  628. package/src/text/text.tsx +2 -2
  629. package/src/tooltip/index.ts +2 -1
  630. package/src/tooltip/popup.tsx +24 -28
  631. package/src/tooltip/portal.tsx +16 -0
  632. package/src/tooltip/provider.tsx +3 -3
  633. package/src/tooltip/root.tsx +2 -2
  634. package/src/tooltip/stories/index.story.tsx +39 -1
  635. package/src/tooltip/stories/usage-guidelines.story.tsx +5 -1
  636. package/src/tooltip/style.module.css +12 -0
  637. package/src/tooltip/test/index.test.tsx +9 -3
  638. package/src/tooltip/trigger.tsx +3 -7
  639. package/src/tooltip/types.ts +13 -7
  640. package/src/utils/create-overlay-modal-context.tsx +34 -0
  641. package/src/utils/create-overlay-title-validation.tsx +116 -0
  642. package/src/utils/css/item-popup.module.css +9 -11
  643. package/src/utils/css/overlay-chrome.module.css +222 -0
  644. package/src/utils/render-portal-with-children.ts +27 -0
  645. package/src/utils/test/use-deprioritized-initial-focus.test.tsx +3 -3
  646. package/src/utils/use-deprioritized-initial-focus.ts +23 -17
  647. package/src/utils/use-overlay-scroll-state-attributes.ts +272 -0
  648. package/src/utils/use-schedule-validation.ts +45 -0
  649. package/src/visually-hidden/stories/index.story.tsx +1 -0
  650. package/src/visually-hidden/visually-hidden.tsx +9 -21
  651. package/build/types/css-modules.d.cjs +0 -2
  652. package/build/types/react.d.cjs +0 -5
  653. package/build/types/react.d.cjs.map +0 -7
  654. package/build-module/types/css-modules.d.mjs +0 -1
  655. package/build-module/types/react.d.mjs +0 -3
  656. package/build-module/types/react.d.mjs.map +0 -7
  657. package/src/types/css-modules.d.ts +0 -4
  658. package/src/types/react.d.ts +0 -7
  659. /package/build-module/{types/css-modules.d.mjs.map → drawer/types.mjs.map} +0 -0
  660. /package/{build/types/css-modules.d.cjs.map → build-module/form/primitives/autocomplete/types.mjs.map} +0 -0
@@ -0,0 +1,222 @@
1
+ @layer wp-ui-utilities, wp-ui-components, wp-ui-compositions, wp-ui-overrides;
2
+
3
+ @layer wp-ui-components {
4
+ /*
5
+ * Shared chrome + scroll-container primitives for overlay popups
6
+ * (Dialog, AlertDialog, Drawer).
7
+ *
8
+ * Layout model: the popup is a vertical flex column with three regions
9
+ * — `.header`, `.content`, `.footer`. Only `.content` scrolls; the
10
+ * chrome sits at the popup's block-axis edges as natural flex siblings.
11
+ *
12
+ * Sticky opt-out for `Dialog` / `Drawer` is expressed in the DOM by
13
+ * placing `Dialog.Header` / `Dialog.Footer` (or the Drawer equivalents)
14
+ * inside `Dialog.Content` / `Drawer.Content` so they scroll with the
15
+ * body; AlertDialog exposes the same choice via `stickyHeader` /
16
+ * `stickyFooter` props, since it owns its internal DOM.
17
+ */
18
+
19
+ /*
20
+ * Chrome: flex row, no background (the popup already paints it), no
21
+ * sticky positioning. Block-axis padding is asymmetric: the outer
22
+ * side (popup edge) uses `padding-2xl` to match the inset that used
23
+ * to live on the popup; the inner side (toward `.content`) uses the
24
+ * tokenized `gap-lg` in full.
25
+ *
26
+ * No separator border here — the base chrome has none. The pinned
27
+ * case (where a separator is needed) adds a `border-block-*: 1px
28
+ * solid transparent` on the inner side and subtracts 1px from that
29
+ * same padding so the chrome's overall border-box height stays
30
+ * identical to the non-pinned case. See the `:has(~ .content)` /
31
+ * `.content ~` rules below.
32
+ *
33
+ * `min-height` on `.header` keeps the header footprint stable whether
34
+ * or not a close icon is rendered (notably the AlertDialog case, which
35
+ * has no close button).
36
+ */
37
+ .header {
38
+ display: flex;
39
+ align-items: center;
40
+ gap: var(--wpds-dimension-gap-sm);
41
+ padding-block: var(--wpds-dimension-padding-2xl) var(--wpds-dimension-gap-lg);
42
+ padding-inline: var(--wpds-dimension-padding-2xl);
43
+ /* TODO: refactor to a DS element size token when available. */
44
+ min-height: 32px;
45
+ }
46
+
47
+ .footer {
48
+ display: flex;
49
+ justify-content: flex-end;
50
+ align-items: center;
51
+ gap: var(--wpds-dimension-gap-sm);
52
+ padding-block: var(--wpds-dimension-gap-lg) var(--wpds-dimension-padding-2xl);
53
+ padding-inline: var(--wpds-dimension-padding-2xl);
54
+ }
55
+
56
+ /*
57
+ * Overlay heading defaults: anchor the title to the inline start and
58
+ * set the neutral text color explicitly. `--_gcd-heading-color` and
59
+ * `--_gcd-heading-margin` defend against the heading-reset rules in
60
+ * `global-css-defense.module.css`, which would otherwise take over a
61
+ * `<h2>` rendered via `Text`.
62
+ */
63
+ .title {
64
+ color: var(--wpds-color-fg-content-neutral);
65
+ --_gcd-heading-color: var(--wpds-color-fg-content-neutral);
66
+
67
+ margin-inline-end: auto;
68
+ --_gcd-heading-margin: 0 auto 0 0;
69
+
70
+ &:dir(rtl) {
71
+ --_gcd-heading-margin: 0 0 0 auto;
72
+ }
73
+ }
74
+
75
+ /*
76
+ * Scroll container. `flex: 1 1 auto` + `min-block-size: 0` lets it
77
+ * shrink below its content size so `overflow-block: auto` can actually
78
+ * show a scrollbar when content exceeds the available space. Default
79
+ * padding is `padding-2xl` on all sides; the yield rules below strip
80
+ * block padding when chrome occupies that edge.
81
+ *
82
+ * Scrolling is intentionally block-axis-only. `overflow-inline: hidden`
83
+ * clips wide content instead of introducing a horizontal scrollbar:
84
+ * the sibling chrome doesn't scroll inline, the popup edge already
85
+ * clips, and `useOverlayScrollStateAttributes` only tracks block-axis
86
+ * overflow (tabindex + separator state). Overlay consumers should
87
+ * constrain content width rather than rely on horizontal scroll.
88
+ */
89
+ .content {
90
+ flex: 1 1 auto;
91
+ min-block-size: 0;
92
+ overflow-block: auto;
93
+ overflow-inline: hidden;
94
+ padding: var(--wpds-dimension-padding-2xl);
95
+
96
+ /*
97
+ * The shared `outset-ring--focus-visible` utility (composed by
98
+ * the Content components) uses an `outline-offset` of `+1px`,
99
+ * which for this scroll container would put the ring outside
100
+ * the popup's rounded corners and have it clipped by the
101
+ * popup's `overflow: hidden`. Inset the ring by the focus
102
+ * width instead, so it sits flush inside the content box.
103
+ *
104
+ * The shared utility's transition shorthand (`transition:
105
+ * outline …`) only animates outline-color and outline-width,
106
+ * not outline-offset, so the negative offset applied here is
107
+ * static — visually, the ring still fades in/out via color
108
+ * change while the offset stays put.
109
+ */
110
+ &:focus-visible {
111
+ outline-offset: calc(var(--wpds-border-width-focus) * -1);
112
+ }
113
+ }
114
+
115
+ /*
116
+ * Pinned chrome: reserve 1px on the inner side for the separator
117
+ * border and subtract that same 1px from the adjacent padding. Net
118
+ * effect on block-axis height is zero — the chrome's overall
119
+ * border-box matches the non-pinned case — while the border provides
120
+ * a fixed slot that the scroll-state rules below colorize.
121
+ *
122
+ * `.content` then drops its matching block-axis padding on that
123
+ * edge, because the chrome already supplies the popup-edge
124
+ * `padding-2xl` + inner `gap-lg`.
125
+ *
126
+ * Sibling relationships intentionally use the general-sibling
127
+ * combinator (`~`) and `:has(~ …)` instead of the adjacent
128
+ * combinator, so an extra element between header / content / footer
129
+ * (custom wrappers, an a11y live region, etc.) doesn't silently
130
+ * break the pinned layout. The expected DOM shape is still
131
+ * header → content → footer; additional siblings in between are
132
+ * tolerated.
133
+ */
134
+ .header:has(~ .content) {
135
+ padding-block-end: calc(var(--wpds-dimension-gap-lg) - 1px);
136
+ border-block-end: 1px solid transparent;
137
+ }
138
+
139
+ .header ~ .content {
140
+ padding-block-start: 0;
141
+ }
142
+
143
+ .content ~ .footer {
144
+ padding-block-start: calc(var(--wpds-dimension-gap-lg) - 1px);
145
+ border-block-start: 1px solid transparent;
146
+ }
147
+
148
+ .content:has(~ .footer) {
149
+ padding-block-end: 0;
150
+ }
151
+
152
+ /*
153
+ * Non-pinned case: when `.header` / `.footer` live inside `.content`
154
+ * (scrolls with body). The scroll container already supplies the
155
+ * inline popup-edge padding, so nested chrome always collapses its
156
+ * own `padding-inline` regardless of its position inside `.content`
157
+ * (no `:first-child` / `:last-child` guard). Block-axis padding is
158
+ * only collapsed at the scroll edges — the `:first-child` /
159
+ * `:last-child` rules keep the body's `gap-lg` visual separation
160
+ * between the chrome and any preceding / following siblings inside
161
+ * `.content`. No border — the separator is specific to the pinned
162
+ * case.
163
+ */
164
+ .content > .header {
165
+ padding-inline: 0;
166
+ }
167
+
168
+ .content > .header:first-child {
169
+ padding-block-start: 0;
170
+ }
171
+
172
+ .content > .footer {
173
+ padding-inline: 0;
174
+ }
175
+
176
+ .content > .footer:last-child {
177
+ padding-block-end: 0;
178
+ }
179
+
180
+ /*
181
+ * Separator coloring. The transparent border on the pinned chrome is
182
+ * colorized when the adjacent scroll container reports off-screen
183
+ * content in that direction. Toggling the color (not the border's
184
+ * existence) means no layout shift on scroll.
185
+ *
186
+ * `useOverlayScrollStateAttributes` owns the
187
+ * `data-wp-ui-overlay-scrolled-from-*` toggles, so CSS handles the
188
+ * visual state without React re-rendering on scroll.
189
+ *
190
+ * Forced-colors note: `border-color: transparent` is preserved as-is
191
+ * in forced-colors mode per spec, so the default (non-scrolled)
192
+ * state stays invisible. When the color toggles to a token value
193
+ * below, the UA substitutes a system color (typically `CanvasText`),
194
+ * so the separator remains visible in forced-colors mode without
195
+ * any additional rules.
196
+ */
197
+ .header:has(~ [data-wp-ui-overlay-scroll-container][data-wp-ui-overlay-scrolled-from-top]) {
198
+ border-block-end-color: var(--wpds-color-stroke-surface-neutral);
199
+ }
200
+
201
+ [data-wp-ui-overlay-scroll-container][data-wp-ui-overlay-scrolled-from-bottom] ~ .footer {
202
+ border-block-start-color: var(--wpds-color-stroke-surface-neutral);
203
+ }
204
+
205
+ /*
206
+ * `overscroll-behavior: contain` stops scroll chaining from the overlay
207
+ * to whatever sits behind it. We only want that when the overlay is
208
+ * modal: a non-modal overlay lets the page underneath remain
209
+ * interactive, and chaining scroll to the page there is the intuitive
210
+ * behavior.
211
+ *
212
+ * `data-wp-ui-overlay-modal` is toggled on the popup by each component
213
+ * (Dialog / Drawer mirror their `modal` prop; AlertDialog is always
214
+ * modal). The scroll container always lives inside the popup —
215
+ * `Dialog.Content` / `Drawer.Content` for the public API, or an
216
+ * internal container for AlertDialog — so a single descendant
217
+ * selector anchored at the modal popup covers all three.
218
+ */
219
+ [data-wp-ui-overlay-modal] [data-wp-ui-overlay-scroll-container] {
220
+ overscroll-behavior: contain;
221
+ }
222
+ }
@@ -0,0 +1,27 @@
1
+ import { cloneElement } from '@wordpress/element';
2
+ import type { ReactElement, ReactNode } from 'react';
3
+
4
+ /**
5
+ * Renders overlay markup (`children`) through an optional `portal` element from
6
+ * `portal={ <Component.Portal … /> }`, or through the package default portal.
7
+ *
8
+ * Shared by overlay `Popup` components so portal merge behavior stays consistent.
9
+ *
10
+ * @param portal Optional element from the `portal` prop (should have no
11
+ * `children`; callers type this via `Omit<PortalProps,'children'>`).
12
+ * When omitted, `defaultPortal` is used. Injected `children`
13
+ * replace any subtree on the portal element.
14
+ * @param defaultPortal Unpopulated default portal element (e.g. `<Dialog.Portal />`).
15
+ * @param children Popup subtree (backdrop, positioner, etc.) to inject as the portal’s children.
16
+ */
17
+ export function renderPortalWithChildren(
18
+ portal: ReactElement | undefined,
19
+ defaultPortal: ReactElement,
20
+ children: ReactNode
21
+ ): ReactElement {
22
+ const rootPortal = portal ?? defaultPortal;
23
+
24
+ return cloneElement( rootPortal, {
25
+ children,
26
+ } );
27
+ }
@@ -17,7 +17,7 @@ function TestHarness( {
17
17
  } ) {
18
18
  const result = useDeprioritizedInitialFocus( {
19
19
  initialFocus,
20
- deprioritizedAttribute: ATTR,
20
+ deprioritizedAttributes: [ ATTR ],
21
21
  } );
22
22
 
23
23
  useEffect( () => {
@@ -156,7 +156,7 @@ describe( 'useDeprioritizedInitialFocus', () => {
156
156
  } ) {
157
157
  const result = useDeprioritizedInitialFocus( {
158
158
  initialFocus: undefined,
159
- deprioritizedAttribute: ATTR,
159
+ deprioritizedAttributes: [ ATTR ],
160
160
  } );
161
161
 
162
162
  useEffect( () => {
@@ -200,7 +200,7 @@ describe( 'useDeprioritizedInitialFocus', () => {
200
200
  } ) {
201
201
  const result = useDeprioritizedInitialFocus( {
202
202
  initialFocus: undefined,
203
- deprioritizedAttribute: ATTR,
203
+ deprioritizedAttributes: [ ATTR ],
204
204
  } );
205
205
 
206
206
  useEffect( () => {
@@ -1,5 +1,5 @@
1
1
  import type { Popover as _Popover } from '@base-ui/react/popover';
2
- import { useMemo, useRef } from '@wordpress/element';
2
+ import { useRef } from '@wordpress/element';
3
3
  import { tabbable } from 'tabbable';
4
4
 
5
5
  /**
@@ -24,39 +24,43 @@ const getTabbableOptions = () => ( {
24
24
 
25
25
  /**
26
26
  * Returns a resolved `initialFocus` value that deprioritizes elements
27
- * marked with a given data attribute (e.g. a close icon), and an internal
28
- * ref that must be merged onto the popup element.
27
+ * marked with any of the given data attributes (e.g. a close icon, a
28
+ * library-managed scroll container), and an internal ref that must be
29
+ * merged onto the popup element.
29
30
  *
30
31
  * When `initialFocus` is `undefined` or `true` (the default behavior),
31
32
  * the hook replaces it with a callback that:
32
33
  * 1. On touch interactions — focuses the popup element itself (preventing
33
34
  * the virtual keyboard on Android), matching Base UI's default.
34
35
  * 2. On other interactions — returns the first tabbable element that does
35
- * *not* carry `deprioritizedAttribute`. Falls back to Base UI's default
36
- * when the deprioritized element is the only tabbable element.
36
+ * *not* carry any of `deprioritizedAttributes`. Falls back to Base
37
+ * UI's default when every tabbable element is deprioritized.
37
38
  *
38
39
  * All other `initialFocus` values (`false`, `RefObject`, callback) pass
39
40
  * through unchanged.
40
41
  *
41
42
  * @param props
42
- * @param props.initialFocus The consumer-provided `initialFocus` value.
43
- * @param props.deprioritizedAttribute The data attribute whose elements should be deprioritized.
43
+ * @param props.initialFocus The consumer-provided `initialFocus` value.
44
+ * @param props.deprioritizedAttributes The data attributes whose elements should be deprioritized.
44
45
  */
45
46
  export function useDeprioritizedInitialFocus( {
46
47
  initialFocus,
47
- deprioritizedAttribute,
48
+ deprioritizedAttributes,
48
49
  }: {
49
50
  initialFocus: InitialFocus;
50
- deprioritizedAttribute: string;
51
+ deprioritizedAttributes: string[];
51
52
  } ) {
52
53
  const popupRef = useRef< HTMLDivElement >( null );
53
54
 
54
- const resolvedInitialFocus = useMemo( (): InitialFocus => {
55
- if ( initialFocus !== undefined && initialFocus !== true ) {
56
- return initialFocus;
57
- }
58
-
59
- return ( interactionType ): HTMLElement | boolean | null => {
55
+ // Returning a fresh callback on every render is intentional. Base UI
56
+ // stores `initialFocus` via `useValueAsRef` (see its FloatingFocusManager
57
+ // source) and reads it through `ref.current` only at open time, so
58
+ // reference identity doesn't affect behavior. Skipping `useMemo` also
59
+ // avoids either forcing callers to memoize their attributes array or
60
+ // fighting the React Compiler with a stringified dep key.
61
+ let resolvedInitialFocus: InitialFocus = initialFocus;
62
+ if ( initialFocus === undefined || initialFocus === true ) {
63
+ resolvedInitialFocus = ( interactionType ) => {
60
64
  if ( interactionType === 'touch' ) {
61
65
  return popupRef.current ?? true;
62
66
  }
@@ -70,7 +74,9 @@ export function useDeprioritizedInitialFocus( {
70
74
  for ( const el of tabbables ) {
71
75
  if (
72
76
  el instanceof HTMLElement &&
73
- ! el.hasAttribute( deprioritizedAttribute )
77
+ ! deprioritizedAttributes.some( ( attr ) =>
78
+ el.hasAttribute( attr )
79
+ )
74
80
  ) {
75
81
  return el;
76
82
  }
@@ -78,7 +84,7 @@ export function useDeprioritizedInitialFocus( {
78
84
 
79
85
  return true;
80
86
  };
81
- }, [ initialFocus, deprioritizedAttribute ] );
87
+ }
82
88
 
83
89
  return { resolvedInitialFocus, popupRef };
84
90
  }
@@ -0,0 +1,272 @@
1
+ import type { UIEvent, UIEventHandler } from 'react';
2
+ import { useCallback, useLayoutEffect, useState } from '@wordpress/element';
3
+
4
+ export const SCROLL_CONTAINER_ATTR = 'data-wp-ui-overlay-scroll-container';
5
+ const SCROLLED_FROM_TOP_ATTR = 'data-wp-ui-overlay-scrolled-from-top';
6
+ const SCROLLED_FROM_BOTTOM_ATTR = 'data-wp-ui-overlay-scrolled-from-bottom';
7
+ /**
8
+ * Marks a `tabindex` that this hook installed, so subsequent runs can tell
9
+ * a hook-managed tabindex apart from one the consumer set on the element
10
+ * themselves.
11
+ *
12
+ * Internal: the constant is not exported, but the literal string is named
13
+ * in the public JSDoc on `useOverlayScrollStateAttributes` so consumers
14
+ * grepping for "why does this element have a `tabindex='0'` I didn't set?"
15
+ * can find the breadcrumb. If this string changes, update the JSDoc too.
16
+ */
17
+ const SCROLL_TABBABLE_FLAG_ATTR = 'data-wp-ui-overlay-scroll-tabbable';
18
+
19
+ /**
20
+ * Allow fractional-pixel rounding when comparing scroll offsets. Browsers can
21
+ * report `scrollTop + clientHeight` as slightly less than `scrollHeight` even
22
+ * when fully scrolled to the bottom.
23
+ */
24
+ const SCROLL_END_EPSILON = 1;
25
+
26
+ /**
27
+ * Detect consumer takeover of a previously hook-managed `tabindex` after the
28
+ * hook had already installed its own: if the flag is set but the current
29
+ * `tabindex` is no longer `"0"`, the consumer has overridden our value. Drop
30
+ * the flag so subsequent ticks treat the `tabindex` as consumer-owned and
31
+ * never touch it again.
32
+ *
33
+ * Limitation: the heuristic compares the DOM attribute, so a consumer who
34
+ * passes `tabIndex={ 0 }` explicitly is indistinguishable from our own
35
+ * managed `"0"` and would still be cleaned up on the next non-overflow
36
+ * tick. See the contract paragraph on `useOverlayScrollStateAttributes`.
37
+ *
38
+ * @param el The scroll container.
39
+ */
40
+ function reconcileTabbableFlag( el: HTMLElement ) {
41
+ if (
42
+ el.hasAttribute( SCROLL_TABBABLE_FLAG_ATTR ) &&
43
+ el.getAttribute( 'tabindex' ) !== '0'
44
+ ) {
45
+ el.removeAttribute( SCROLL_TABBABLE_FLAG_ATTR );
46
+ }
47
+ }
48
+
49
+ function updateScrollAttributes( el: HTMLElement ) {
50
+ const { scrollTop, clientHeight, scrollHeight } = el;
51
+ const overflows = scrollHeight - clientHeight > SCROLL_END_EPSILON;
52
+
53
+ el.toggleAttribute( SCROLLED_FROM_TOP_ATTR, scrollTop > 0 );
54
+ el.toggleAttribute(
55
+ SCROLLED_FROM_BOTTOM_ATTR,
56
+ scrollTop + clientHeight < scrollHeight - SCROLL_END_EPSILON
57
+ );
58
+
59
+ // Keyboard-scrollable regions must be reachable via Tab (WCAG 2.1.1),
60
+ // but adding a stray tab stop to a non-scrolling `<div>` is an
61
+ // anti-pattern. Toggle `tabindex="0"` only while the element actually
62
+ // overflows. The flag attribute guards against clobbering a
63
+ // consumer-supplied tabindex: we only touch attributes we installed.
64
+
65
+ // Takeover-after-install: detect a consumer who started overriding
66
+ // our managed `"0"` *after* the hook installed it (e.g. a re-render
67
+ // passing `tabIndex={ -1 }`). The flag is dropped so the install /
68
+ // cleanup branches below leave the consumer's value untouched.
69
+ reconcileTabbableFlag( el );
70
+
71
+ if ( overflows ) {
72
+ // Pre-install opt-out: a consumer-supplied `tabindex` (including
73
+ // `tabindex="-1"` to hide the region from Tab order) keeps its
74
+ // value because the flag is never installed in the first place.
75
+ // If that consumer-supplied value is later *removed*, the hook
76
+ // will install its own on the next overflow tick — the DOM
77
+ // attribute alone can't distinguish a prior explicit opt-out
78
+ // from an unconfigured state.
79
+ if (
80
+ ! el.hasAttribute( SCROLL_TABBABLE_FLAG_ATTR ) &&
81
+ el.getAttribute( 'tabindex' ) === null
82
+ ) {
83
+ el.setAttribute( 'tabindex', '0' );
84
+ el.setAttribute( SCROLL_TABBABLE_FLAG_ATTR, '' );
85
+ }
86
+ } else if ( el.hasAttribute( SCROLL_TABBABLE_FLAG_ATTR ) ) {
87
+ el.removeAttribute( 'tabindex' );
88
+ el.removeAttribute( SCROLL_TABBABLE_FLAG_ATTR );
89
+ }
90
+ }
91
+
92
+ const HOOK_OWNED_ATTRS = [
93
+ SCROLL_CONTAINER_ATTR,
94
+ SCROLLED_FROM_TOP_ATTR,
95
+ SCROLLED_FROM_BOTTOM_ATTR,
96
+ ] as const;
97
+
98
+ function cleanupScrollAttributes( el: HTMLElement ) {
99
+ for ( const attr of HOOK_OWNED_ATTRS ) {
100
+ el.removeAttribute( attr );
101
+ }
102
+ // Reconcile first so a flag left over from a consumer-takeover never
103
+ // causes us to clobber the consumer's `tabindex` here.
104
+ reconcileTabbableFlag( el );
105
+ // After reconciliation the flag is set only when the current
106
+ // `tabindex` is still `"0"` (i.e. ours). Any other value belongs to
107
+ // the consumer and is left alone.
108
+ if ( el.hasAttribute( SCROLL_TABBABLE_FLAG_ATTR ) ) {
109
+ el.removeAttribute( 'tabindex' );
110
+ el.removeAttribute( SCROLL_TABBABLE_FLAG_ATTR );
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Keeps `data-wp-ui-overlay-scrolled-from-top` and
116
+ * `data-wp-ui-overlay-scrolled-from-bottom` attributes in sync with a
117
+ * scrollable overlay element's scroll position, and marks the element with
118
+ * `data-wp-ui-overlay-scroll-container` so shared CSS (see
119
+ * `overlay-chrome.module.css`) can target it without coupling to a specific
120
+ * class name. Descendant selectors (e.g. sticky header/footer chrome) read
121
+ * these attributes to toggle their separator border without forcing a React
122
+ * re-render on every scroll frame.
123
+ *
124
+ * When the element overflows, a `tabindex="0"` is also installed so keyboard
125
+ * users can focus the region and arrow-scroll it (WCAG 2.1.1). The tabindex
126
+ * is removed again as soon as the element no longer overflows — a stray tab
127
+ * stop on a non-scrolling region is an anti-pattern. An internal flag
128
+ * attribute (`data-wp-ui-overlay-scroll-tabbable`) marks tabindex values
129
+ * the hook installed, so a consumer-supplied `tabindex` is never
130
+ * overwritten.
131
+ *
132
+ * Tabindex contract:
133
+ * - **Pre-install opt-out**: a `tabindex` set on the element before the
134
+ * first overflow is detected is left alone forever. The flag is never
135
+ * installed, so the hook never owns the attribute. (This means
136
+ * `tabIndex={ -1 }` on `Dialog.Content` / `Drawer.Content` reliably
137
+ * suppresses the auto tab stop.)
138
+ * - **Takeover after install**: if the consumer overrides the hook's
139
+ * `"0"` with a *different* value after the fact, the flag is dropped
140
+ * on the next tick (`reconcileTabbableFlag`) and the consumer's value
141
+ * is preserved through subsequent overflow / non-overflow transitions
142
+ * and through cleanup.
143
+ * - **Indistinguishable case**: a consumer who passes `tabIndex={ 0 }`
144
+ * explicitly while the hook also has `"0"` installed cannot be
145
+ * detected — the DOM attribute is identical to the hook-managed
146
+ * value, so the hook will still strip it on the next non-overflow
147
+ * tick. This is rarely intentional (the consumer's `0` matches the
148
+ * hook's behavior anyway), but consumers needing a guaranteed
149
+ * sticky `0` should avoid relying on it across overflow flips.
150
+ *
151
+ * Overflow detection is block-axis-only. Overlay popups are expected to
152
+ * constrain content width (`overlay-chrome.module.css` clips `.content`
153
+ * with `overflow-inline: hidden`); horizontal scrolling is intentionally
154
+ * not supported, so this hook doesn't toggle tabindex on inline-axis
155
+ * overflow and the scroll-state attributes don't track it.
156
+ *
157
+ * Returns a callback `ref` that the caller must attach to the scroll
158
+ * container, and an `onScroll` handler to wire up to the same element. A
159
+ * callback ref (not a `RefObject`) is used because overlay libraries like
160
+ * Base UI mount the popup DOM lazily when the overlay opens, so the
161
+ * attributes must be initialized the moment the node is attached, not when
162
+ * the host component first renders. `useState` also absorbs repeated
163
+ * attachments of the same node (Strict Mode remount, stable refs) without
164
+ * re-running the effect.
165
+ *
166
+ * Change detection combines a `ResizeObserver` scoped to the container
167
+ * and its direct children (to catch flex-layout growth) with a
168
+ * `MutationObserver` on direct-child additions/removals only (to keep
169
+ * the resize-observer set in sync as direct children come and go).
170
+ *
171
+ * Deeper subtree mutations are intentionally not observed: in practice,
172
+ * any descendant whose growth changes the scroll size also propagates a
173
+ * resize up the layout tree, so the existing `ResizeObserver` on direct
174
+ * children catches it. Watching the full subtree would fan out the
175
+ * mutation callback over every text-node insertion in content-heavy
176
+ * overlays (rich-text editors, virtualized lists), which isn't worth
177
+ * the cost of the rare deep-mutation-without-resize case. Revisit
178
+ * (and consider rAF-coalescing the callback) if a real consumer hits
179
+ * an attribute-staleness regression.
180
+ *
181
+ * Once CSS scroll-state container queries are supported across target
182
+ * browsers, both the data attributes and this hook can be replaced with
183
+ * `@container scroll-state(scrollable: top)` / `(scrollable: bottom)`.
184
+ * See: https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Conditional_rules/Container_scroll-state_queries
185
+ *
186
+ * @param onScroll Optional `onScroll` from the parent; invoked after the
187
+ * overlay scroll-state attributes are updated, so by the
188
+ * time this handler runs, `data-wp-ui-overlay-scrolled-*`
189
+ * on `event.currentTarget` already reflect the post-scroll
190
+ * state.
191
+ */
192
+ export function useOverlayScrollStateAttributes<
193
+ T extends HTMLElement = HTMLElement,
194
+ >( onScroll?: UIEventHandler< T > | undefined ) {
195
+ const [ node, setNode ] = useState< T | null >( null );
196
+
197
+ const ref = useCallback( ( el: T | null ) => {
198
+ setNode( el );
199
+ }, [] );
200
+
201
+ useLayoutEffect( () => {
202
+ if ( ! node ) {
203
+ return;
204
+ }
205
+
206
+ node.setAttribute( SCROLL_CONTAINER_ATTR, '' );
207
+ updateScrollAttributes( node );
208
+
209
+ if ( typeof ResizeObserver === 'undefined' ) {
210
+ return () => {
211
+ cleanupScrollAttributes( node );
212
+ };
213
+ }
214
+
215
+ const resizeObserver = new ResizeObserver( () => {
216
+ updateScrollAttributes( node );
217
+ } );
218
+ resizeObserver.observe( node );
219
+ for ( const child of Array.from( node.children ) ) {
220
+ resizeObserver.observe( child );
221
+ }
222
+
223
+ let mutationObserver: MutationObserver | undefined;
224
+ if ( typeof MutationObserver !== 'undefined' ) {
225
+ mutationObserver = new MutationObserver( ( records ) => {
226
+ for ( const record of records ) {
227
+ // Only direct-child additions/removals affect what the
228
+ // ResizeObserver is observing; deeper descendant changes
229
+ // reach us through this callback for attribute refresh,
230
+ // but we don't observe them individually to keep the
231
+ // cost bounded on large subtrees.
232
+ if ( record.target === node ) {
233
+ for ( const added of Array.from( record.addedNodes ) ) {
234
+ if ( added instanceof Element ) {
235
+ resizeObserver.observe( added );
236
+ }
237
+ }
238
+ for ( const removed of Array.from(
239
+ record.removedNodes
240
+ ) ) {
241
+ if ( removed instanceof Element ) {
242
+ resizeObserver.unobserve( removed );
243
+ }
244
+ }
245
+ }
246
+ }
247
+ updateScrollAttributes( node );
248
+ } );
249
+ // Direct children only — see the JSDoc above for why we
250
+ // intentionally don't observe the full subtree.
251
+ mutationObserver.observe( node, {
252
+ childList: true,
253
+ } );
254
+ }
255
+
256
+ return () => {
257
+ resizeObserver.disconnect();
258
+ mutationObserver?.disconnect();
259
+ cleanupScrollAttributes( node );
260
+ };
261
+ }, [ node ] );
262
+
263
+ const handleScroll = useCallback(
264
+ ( event: UIEvent< T > ) => {
265
+ updateScrollAttributes( event.currentTarget );
266
+ onScroll?.( event );
267
+ },
268
+ [ onScroll ]
269
+ );
270
+
271
+ return { ref, onScroll: handleScroll };
272
+ }
@@ -0,0 +1,45 @@
1
+ import { useCallback, useEffect, useRef } from '@wordpress/element';
2
+
3
+ /**
4
+ * Dev-only hook that returns a stable `scheduleValidation` function.
5
+ *
6
+ * Each call debounces to `setTimeout(…, 0)` so that rapid
7
+ * register / unregister cycles (e.g. React strict-mode double-mount)
8
+ * settle before the check runs. The timer is cleaned up on unmount,
9
+ * and calls after unmount are silently ignored.
10
+ *
11
+ * @param validate Callback that performs the actual validation.
12
+ * Stored in a ref — safe to pass an unstable closure.
13
+ */
14
+ export function useScheduleValidation( validate: () => void ) {
15
+ const validateRef = useRef( validate );
16
+ validateRef.current = validate;
17
+
18
+ const timerRef = useRef< ReturnType< typeof setTimeout > | null >( null );
19
+ const unmountedRef = useRef( false );
20
+
21
+ const scheduleValidation = useCallback( () => {
22
+ if ( unmountedRef.current ) {
23
+ return;
24
+ }
25
+ if ( timerRef.current ) {
26
+ clearTimeout( timerRef.current );
27
+ }
28
+ timerRef.current = setTimeout( () => {
29
+ validateRef.current();
30
+ timerRef.current = null;
31
+ }, 0 );
32
+ }, [] );
33
+
34
+ useEffect( () => {
35
+ unmountedRef.current = false;
36
+ return () => {
37
+ unmountedRef.current = true;
38
+ if ( timerRef.current ) {
39
+ clearTimeout( timerRef.current );
40
+ }
41
+ };
42
+ }, [] );
43
+
44
+ return scheduleValidation;
45
+ }
@@ -3,6 +3,7 @@ import { useId } from '@wordpress/element';
3
3
  import { VisuallyHidden } from '../';
4
4
 
5
5
  const meta: Meta< typeof VisuallyHidden > = {
6
+ tags: [ 'manifest' ],
6
7
  title: 'Design System/Components/VisuallyHidden',
7
8
  component: VisuallyHidden,
8
9
  };