@wordpress/ui 0.9.1-next.v.202603161435.0 → 0.10.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 (478) hide show
  1. package/CHANGELOG.md +38 -1
  2. package/CONTRIBUTING.md +31 -0
  3. package/build/alert-dialog/context.cjs +34 -0
  4. package/build/alert-dialog/context.cjs.map +7 -0
  5. package/build/alert-dialog/index.cjs +37 -0
  6. package/build/alert-dialog/index.cjs.map +7 -0
  7. package/build/alert-dialog/popup.cjs +93 -0
  8. package/build/alert-dialog/popup.cjs.map +7 -0
  9. package/build/alert-dialog/root.cjs +52 -0
  10. package/build/alert-dialog/root.cjs.map +7 -0
  11. package/build/alert-dialog/trigger.cjs +48 -0
  12. package/build/alert-dialog/trigger.cjs.map +7 -0
  13. package/build/alert-dialog/types.cjs +19 -0
  14. package/build/alert-dialog/types.cjs.map +7 -0
  15. package/build/badge/badge.cjs +2 -2
  16. package/build/badge/badge.cjs.map +1 -1
  17. package/build/button/button.cjs +7 -7
  18. package/build/button/button.cjs.map +2 -2
  19. package/build/card/content.cjs +3 -3
  20. package/build/card/content.cjs.map +2 -2
  21. package/build/card/full-bleed.cjs +3 -3
  22. package/build/card/full-bleed.cjs.map +2 -2
  23. package/build/card/header.cjs +3 -3
  24. package/build/card/header.cjs.map +2 -2
  25. package/build/card/root.cjs +5 -5
  26. package/build/card/root.cjs.map +2 -2
  27. package/build/card/title.cjs +26 -13
  28. package/build/card/title.cjs.map +3 -3
  29. package/build/collapsible-card/content.cjs +24 -3
  30. package/build/collapsible-card/content.cjs.map +4 -4
  31. package/build/collapsible-card/context.cjs +35 -0
  32. package/build/collapsible-card/context.cjs.map +7 -0
  33. package/build/collapsible-card/header-description.cjs +52 -0
  34. package/build/collapsible-card/header-description.cjs.map +7 -0
  35. package/build/collapsible-card/header.cjs +38 -17
  36. package/build/collapsible-card/header.cjs.map +2 -2
  37. package/build/collapsible-card/index.cjs +3 -0
  38. package/build/collapsible-card/index.cjs.map +2 -2
  39. package/build/collapsible-card/types.cjs.map +1 -1
  40. package/build/dialog/action.cjs +4 -2
  41. package/build/dialog/action.cjs.map +2 -2
  42. package/build/dialog/close-icon.cjs +2 -1
  43. package/build/dialog/close-icon.cjs.map +2 -2
  44. package/build/dialog/footer.cjs +3 -3
  45. package/build/dialog/footer.cjs.map +2 -2
  46. package/build/dialog/header.cjs +3 -3
  47. package/build/dialog/header.cjs.map +2 -2
  48. package/build/dialog/popup.cjs +22 -5
  49. package/build/dialog/popup.cjs.map +2 -2
  50. package/build/dialog/title.cjs +3 -3
  51. package/build/dialog/title.cjs.map +2 -2
  52. package/build/dialog/types.cjs.map +1 -1
  53. package/build/empty-state/actions.cjs +66 -0
  54. package/build/empty-state/actions.cjs.map +7 -0
  55. package/build/empty-state/description.cjs +66 -0
  56. package/build/empty-state/description.cjs.map +7 -0
  57. package/build/empty-state/icon.cjs +69 -0
  58. package/build/empty-state/icon.cjs.map +7 -0
  59. package/build/empty-state/index.cjs +46 -0
  60. package/build/empty-state/index.cjs.map +7 -0
  61. package/build/empty-state/root.cjs +66 -0
  62. package/build/empty-state/root.cjs.map +7 -0
  63. package/build/empty-state/title.cjs +68 -0
  64. package/build/empty-state/title.cjs.map +7 -0
  65. package/build/empty-state/types.cjs +19 -0
  66. package/build/empty-state/types.cjs.map +7 -0
  67. package/build/empty-state/visual.cjs +66 -0
  68. package/build/empty-state/visual.cjs.map +7 -0
  69. package/build/form/index.cjs +27 -0
  70. package/build/form/index.cjs.map +7 -0
  71. package/build/form/input-control/index.cjs +31 -0
  72. package/build/form/input-control/index.cjs.map +7 -0
  73. package/build/form/input-control/input-control.cjs +50 -0
  74. package/build/form/input-control/input-control.cjs.map +7 -0
  75. package/build/form/input-control/types.cjs +19 -0
  76. package/build/form/input-control/types.cjs.map +7 -0
  77. package/build/form/primitives/field/description.cjs +2 -2
  78. package/build/form/primitives/field/description.cjs.map +1 -1
  79. package/build/form/primitives/field/details.cjs +2 -2
  80. package/build/form/primitives/field/details.cjs.map +1 -1
  81. package/build/form/primitives/field/label.cjs +2 -2
  82. package/build/form/primitives/field/label.cjs.map +1 -1
  83. package/build/form/primitives/field/root.cjs +4 -4
  84. package/build/form/primitives/field/root.cjs.map +2 -2
  85. package/build/form/primitives/fieldset/description.cjs +2 -2
  86. package/build/form/primitives/fieldset/description.cjs.map +1 -1
  87. package/build/form/primitives/fieldset/details.cjs +2 -2
  88. package/build/form/primitives/fieldset/details.cjs.map +1 -1
  89. package/build/form/primitives/fieldset/legend.cjs +2 -2
  90. package/build/form/primitives/fieldset/legend.cjs.map +1 -1
  91. package/build/form/primitives/fieldset/root.cjs +2 -2
  92. package/build/form/primitives/fieldset/root.cjs.map +1 -1
  93. package/build/form/primitives/input/input.cjs +4 -4
  94. package/build/form/primitives/input/input.cjs.map +1 -1
  95. package/build/form/primitives/input-layout/input-layout.cjs +4 -4
  96. package/build/form/primitives/input-layout/input-layout.cjs.map +1 -1
  97. package/build/form/primitives/input-layout/slot.cjs +5 -4
  98. package/build/form/primitives/input-layout/slot.cjs.map +2 -2
  99. package/build/form/primitives/select/item.cjs +5 -5
  100. package/build/form/primitives/select/item.cjs.map +2 -2
  101. package/build/form/primitives/select/popup.cjs +7 -7
  102. package/build/form/primitives/select/popup.cjs.map +2 -2
  103. package/build/form/primitives/select/trigger.cjs +5 -5
  104. package/build/form/primitives/select/trigger.cjs.map +2 -2
  105. package/build/form/primitives/textarea/textarea.cjs +3 -3
  106. package/build/form/primitives/textarea/textarea.cjs.map +2 -2
  107. package/build/form/types.cjs +19 -0
  108. package/build/form/types.cjs.map +7 -0
  109. package/build/icon-button/icon-button.cjs +2 -2
  110. package/build/icon-button/icon-button.cjs.map +1 -1
  111. package/build/index.cjs +8 -2
  112. package/build/index.cjs.map +2 -2
  113. package/build/link/link.cjs +6 -6
  114. package/build/link/link.cjs.map +1 -1
  115. package/build/notice/action-button.cjs +2 -2
  116. package/build/notice/action-button.cjs.map +1 -1
  117. package/build/notice/action-link.cjs +2 -2
  118. package/build/notice/action-link.cjs.map +1 -1
  119. package/build/notice/actions.cjs +2 -2
  120. package/build/notice/actions.cjs.map +1 -1
  121. package/build/notice/close-icon.cjs +2 -2
  122. package/build/notice/close-icon.cjs.map +1 -1
  123. package/build/notice/description.cjs +2 -2
  124. package/build/notice/description.cjs.map +1 -1
  125. package/build/notice/index.cjs.map +1 -1
  126. package/build/notice/root.cjs +4 -4
  127. package/build/notice/root.cjs.map +1 -1
  128. package/build/notice/title.cjs +2 -2
  129. package/build/notice/title.cjs.map +1 -1
  130. package/build/stack/stack.cjs +2 -2
  131. package/build/stack/stack.cjs.map +1 -1
  132. package/build/tabs/context.cjs +121 -0
  133. package/build/tabs/context.cjs.map +7 -0
  134. package/build/tabs/list.cjs +3 -3
  135. package/build/tabs/list.cjs.map +2 -2
  136. package/build/tabs/panel.cjs +5 -3
  137. package/build/tabs/panel.cjs.map +2 -2
  138. package/build/tabs/root.cjs +2 -1
  139. package/build/tabs/root.cjs.map +2 -2
  140. package/build/tabs/tab.cjs +5 -3
  141. package/build/tabs/tab.cjs.map +2 -2
  142. package/build/text/text.cjs +2 -2
  143. package/build/text/text.cjs.map +1 -1
  144. package/build/tooltip/popup.cjs +4 -4
  145. package/build/tooltip/popup.cjs.map +1 -1
  146. package/build/tooltip/root.cjs.map +2 -2
  147. package/build/utils/use-deprioritized-initial-focus.cjs +64 -0
  148. package/build/utils/use-deprioritized-initial-focus.cjs.map +7 -0
  149. package/build/visually-hidden/visually-hidden.cjs +2 -2
  150. package/build/visually-hidden/visually-hidden.cjs.map +1 -1
  151. package/build-module/alert-dialog/context.mjs +9 -0
  152. package/build-module/alert-dialog/context.mjs.map +7 -0
  153. package/build-module/alert-dialog/index.mjs +10 -0
  154. package/build-module/alert-dialog/index.mjs.map +7 -0
  155. package/build-module/alert-dialog/popup.mjs +58 -0
  156. package/build-module/alert-dialog/popup.mjs.map +7 -0
  157. package/build-module/alert-dialog/root.mjs +27 -0
  158. package/build-module/alert-dialog/root.mjs.map +7 -0
  159. package/build-module/alert-dialog/trigger.mjs +13 -0
  160. package/build-module/alert-dialog/trigger.mjs.map +7 -0
  161. package/build-module/alert-dialog/types.mjs +1 -0
  162. package/build-module/alert-dialog/types.mjs.map +7 -0
  163. package/build-module/badge/badge.mjs +2 -2
  164. package/build-module/badge/badge.mjs.map +1 -1
  165. package/build-module/button/button.mjs +7 -7
  166. package/build-module/button/button.mjs.map +2 -2
  167. package/build-module/card/content.mjs +3 -3
  168. package/build-module/card/content.mjs.map +2 -2
  169. package/build-module/card/full-bleed.mjs +3 -3
  170. package/build-module/card/full-bleed.mjs.map +2 -2
  171. package/build-module/card/header.mjs +3 -3
  172. package/build-module/card/header.mjs.map +2 -2
  173. package/build-module/card/root.mjs +5 -5
  174. package/build-module/card/root.mjs.map +2 -2
  175. package/build-module/card/title.mjs +16 -13
  176. package/build-module/card/title.mjs.map +2 -2
  177. package/build-module/collapsible-card/content.mjs +24 -3
  178. package/build-module/collapsible-card/content.mjs.map +3 -3
  179. package/build-module/collapsible-card/context.mjs +10 -0
  180. package/build-module/collapsible-card/context.mjs.map +7 -0
  181. package/build-module/collapsible-card/header-description.mjs +27 -0
  182. package/build-module/collapsible-card/header-description.mjs.map +7 -0
  183. package/build-module/collapsible-card/header.mjs +39 -18
  184. package/build-module/collapsible-card/header.mjs.map +2 -2
  185. package/build-module/collapsible-card/index.mjs +2 -0
  186. package/build-module/collapsible-card/index.mjs.map +2 -2
  187. package/build-module/dialog/action.mjs +4 -2
  188. package/build-module/dialog/action.mjs.map +2 -2
  189. package/build-module/dialog/close-icon.mjs +2 -1
  190. package/build-module/dialog/close-icon.mjs.map +2 -2
  191. package/build-module/dialog/footer.mjs +3 -3
  192. package/build-module/dialog/footer.mjs.map +2 -2
  193. package/build-module/dialog/header.mjs +3 -3
  194. package/build-module/dialog/header.mjs.map +2 -2
  195. package/build-module/dialog/popup.mjs +22 -5
  196. package/build-module/dialog/popup.mjs.map +2 -2
  197. package/build-module/dialog/title.mjs +3 -3
  198. package/build-module/dialog/title.mjs.map +2 -2
  199. package/build-module/empty-state/actions.mjs +31 -0
  200. package/build-module/empty-state/actions.mjs.map +7 -0
  201. package/build-module/empty-state/description.mjs +31 -0
  202. package/build-module/empty-state/description.mjs.map +7 -0
  203. package/build-module/empty-state/icon.mjs +34 -0
  204. package/build-module/empty-state/icon.mjs.map +7 -0
  205. package/build-module/empty-state/index.mjs +16 -0
  206. package/build-module/empty-state/index.mjs.map +7 -0
  207. package/build-module/empty-state/root.mjs +31 -0
  208. package/build-module/empty-state/root.mjs.map +7 -0
  209. package/build-module/empty-state/title.mjs +33 -0
  210. package/build-module/empty-state/title.mjs.map +7 -0
  211. package/build-module/empty-state/types.mjs +1 -0
  212. package/build-module/empty-state/types.mjs.map +7 -0
  213. package/build-module/empty-state/visual.mjs +31 -0
  214. package/build-module/empty-state/visual.mjs.map +7 -0
  215. package/build-module/form/index.mjs +4 -0
  216. package/build-module/form/index.mjs.map +7 -0
  217. package/build-module/form/input-control/index.mjs +6 -0
  218. package/build-module/form/input-control/index.mjs.map +7 -0
  219. package/build-module/form/input-control/input-control.mjs +25 -0
  220. package/build-module/form/input-control/input-control.mjs.map +7 -0
  221. package/build-module/form/input-control/types.mjs +1 -0
  222. package/build-module/form/input-control/types.mjs.map +7 -0
  223. package/build-module/form/primitives/field/description.mjs +2 -2
  224. package/build-module/form/primitives/field/description.mjs.map +1 -1
  225. package/build-module/form/primitives/field/details.mjs +2 -2
  226. package/build-module/form/primitives/field/details.mjs.map +1 -1
  227. package/build-module/form/primitives/field/label.mjs +2 -2
  228. package/build-module/form/primitives/field/label.mjs.map +1 -1
  229. package/build-module/form/primitives/field/root.mjs +4 -4
  230. package/build-module/form/primitives/field/root.mjs.map +2 -2
  231. package/build-module/form/primitives/fieldset/description.mjs +2 -2
  232. package/build-module/form/primitives/fieldset/description.mjs.map +1 -1
  233. package/build-module/form/primitives/fieldset/details.mjs +2 -2
  234. package/build-module/form/primitives/fieldset/details.mjs.map +1 -1
  235. package/build-module/form/primitives/fieldset/legend.mjs +2 -2
  236. package/build-module/form/primitives/fieldset/legend.mjs.map +1 -1
  237. package/build-module/form/primitives/fieldset/root.mjs +2 -2
  238. package/build-module/form/primitives/fieldset/root.mjs.map +1 -1
  239. package/build-module/form/primitives/input/input.mjs +4 -4
  240. package/build-module/form/primitives/input/input.mjs.map +1 -1
  241. package/build-module/form/primitives/input-layout/input-layout.mjs +4 -4
  242. package/build-module/form/primitives/input-layout/input-layout.mjs.map +1 -1
  243. package/build-module/form/primitives/input-layout/slot.mjs +5 -4
  244. package/build-module/form/primitives/input-layout/slot.mjs.map +2 -2
  245. package/build-module/form/primitives/select/item.mjs +5 -5
  246. package/build-module/form/primitives/select/item.mjs.map +2 -2
  247. package/build-module/form/primitives/select/popup.mjs +7 -7
  248. package/build-module/form/primitives/select/popup.mjs.map +2 -2
  249. package/build-module/form/primitives/select/trigger.mjs +5 -5
  250. package/build-module/form/primitives/select/trigger.mjs.map +2 -2
  251. package/build-module/form/primitives/textarea/textarea.mjs +3 -3
  252. package/build-module/form/primitives/textarea/textarea.mjs.map +2 -2
  253. package/build-module/form/types.mjs +1 -0
  254. package/build-module/form/types.mjs.map +7 -0
  255. package/build-module/icon-button/icon-button.mjs +2 -2
  256. package/build-module/icon-button/icon-button.mjs.map +1 -1
  257. package/build-module/index.mjs +5 -1
  258. package/build-module/index.mjs.map +2 -2
  259. package/build-module/link/link.mjs +6 -6
  260. package/build-module/link/link.mjs.map +1 -1
  261. package/build-module/notice/action-button.mjs +2 -2
  262. package/build-module/notice/action-button.mjs.map +1 -1
  263. package/build-module/notice/action-link.mjs +2 -2
  264. package/build-module/notice/action-link.mjs.map +1 -1
  265. package/build-module/notice/actions.mjs +2 -2
  266. package/build-module/notice/actions.mjs.map +1 -1
  267. package/build-module/notice/close-icon.mjs +2 -2
  268. package/build-module/notice/close-icon.mjs.map +1 -1
  269. package/build-module/notice/description.mjs +2 -2
  270. package/build-module/notice/description.mjs.map +1 -1
  271. package/build-module/notice/index.mjs.map +1 -1
  272. package/build-module/notice/root.mjs +4 -4
  273. package/build-module/notice/root.mjs.map +1 -1
  274. package/build-module/notice/title.mjs +2 -2
  275. package/build-module/notice/title.mjs.map +1 -1
  276. package/build-module/stack/stack.mjs +2 -2
  277. package/build-module/stack/stack.mjs.map +1 -1
  278. package/build-module/tabs/context.mjs +101 -0
  279. package/build-module/tabs/context.mjs.map +7 -0
  280. package/build-module/tabs/list.mjs +3 -3
  281. package/build-module/tabs/list.mjs.map +2 -2
  282. package/build-module/tabs/panel.mjs +5 -3
  283. package/build-module/tabs/panel.mjs.map +2 -2
  284. package/build-module/tabs/root.mjs +2 -1
  285. package/build-module/tabs/root.mjs.map +2 -2
  286. package/build-module/tabs/tab.mjs +5 -3
  287. package/build-module/tabs/tab.mjs.map +2 -2
  288. package/build-module/text/text.mjs +2 -2
  289. package/build-module/text/text.mjs.map +1 -1
  290. package/build-module/tooltip/popup.mjs +4 -4
  291. package/build-module/tooltip/popup.mjs.map +1 -1
  292. package/build-module/tooltip/root.mjs.map +2 -2
  293. package/build-module/utils/use-deprioritized-initial-focus.mjs +39 -0
  294. package/build-module/utils/use-deprioritized-initial-focus.mjs.map +7 -0
  295. package/build-module/visually-hidden/visually-hidden.mjs +2 -2
  296. package/build-module/visually-hidden/visually-hidden.mjs.map +1 -1
  297. package/build-types/alert-dialog/context.d.ts +8 -0
  298. package/build-types/alert-dialog/context.d.ts.map +1 -0
  299. package/build-types/alert-dialog/index.d.ts +4 -0
  300. package/build-types/alert-dialog/index.d.ts.map +1 -0
  301. package/build-types/alert-dialog/popup.d.ts +4 -0
  302. package/build-types/alert-dialog/popup.d.ts.map +1 -0
  303. package/build-types/alert-dialog/root.d.ts +24 -0
  304. package/build-types/alert-dialog/root.d.ts.map +1 -0
  305. package/build-types/alert-dialog/stories/index.story.d.ts +44 -0
  306. package/build-types/alert-dialog/stories/index.story.d.ts.map +1 -0
  307. package/build-types/alert-dialog/test/index.test.d.ts +2 -0
  308. package/build-types/alert-dialog/test/index.test.d.ts.map +1 -0
  309. package/build-types/alert-dialog/trigger.d.ts +6 -0
  310. package/build-types/alert-dialog/trigger.d.ts.map +1 -0
  311. package/build-types/alert-dialog/types.d.ts +70 -0
  312. package/build-types/alert-dialog/types.d.ts.map +1 -0
  313. package/build-types/card/title.d.ts.map +1 -1
  314. package/build-types/collapsible-card/content.d.ts.map +1 -1
  315. package/build-types/collapsible-card/context.d.ts +4 -0
  316. package/build-types/collapsible-card/context.d.ts.map +1 -0
  317. package/build-types/collapsible-card/header-description.d.ts +15 -0
  318. package/build-types/collapsible-card/header-description.d.ts.map +1 -0
  319. package/build-types/collapsible-card/header.d.ts.map +1 -1
  320. package/build-types/collapsible-card/index.d.ts +2 -1
  321. package/build-types/collapsible-card/index.d.ts.map +1 -1
  322. package/build-types/collapsible-card/stories/index.story.d.ts +10 -0
  323. package/build-types/collapsible-card/stories/index.story.d.ts.map +1 -1
  324. package/build-types/collapsible-card/types.d.ts +21 -0
  325. package/build-types/collapsible-card/types.d.ts.map +1 -1
  326. package/build-types/dialog/action.d.ts.map +1 -1
  327. package/build-types/dialog/close-icon.d.ts.map +1 -1
  328. package/build-types/dialog/popup.d.ts.map +1 -1
  329. package/build-types/dialog/stories/index.story.d.ts +0 -6
  330. package/build-types/dialog/stories/index.story.d.ts.map +1 -1
  331. package/build-types/dialog/types.d.ts +5 -5
  332. package/build-types/dialog/types.d.ts.map +1 -1
  333. package/build-types/empty-state/actions.d.ts +7 -0
  334. package/build-types/empty-state/actions.d.ts.map +1 -0
  335. package/build-types/empty-state/description.d.ts +7 -0
  336. package/build-types/empty-state/description.d.ts.map +1 -0
  337. package/build-types/empty-state/icon.d.ts +7 -0
  338. package/build-types/empty-state/icon.d.ts.map +1 -0
  339. package/build-types/empty-state/index.d.ts +8 -0
  340. package/build-types/empty-state/index.d.ts.map +1 -0
  341. package/build-types/empty-state/root.d.ts +6 -0
  342. package/build-types/empty-state/root.d.ts.map +1 -0
  343. package/build-types/empty-state/stories/index.story.d.ts +8 -0
  344. package/build-types/empty-state/stories/index.story.d.ts.map +1 -0
  345. package/build-types/empty-state/test/actions.test.d.ts +2 -0
  346. package/build-types/empty-state/test/actions.test.d.ts.map +1 -0
  347. package/build-types/empty-state/test/description.test.d.ts +2 -0
  348. package/build-types/empty-state/test/description.test.d.ts.map +1 -0
  349. package/build-types/empty-state/test/icon.test.d.ts +2 -0
  350. package/build-types/empty-state/test/icon.test.d.ts.map +1 -0
  351. package/build-types/empty-state/test/root.test.d.ts +2 -0
  352. package/build-types/empty-state/test/root.test.d.ts.map +1 -0
  353. package/build-types/empty-state/test/title.test.d.ts +2 -0
  354. package/build-types/empty-state/test/title.test.d.ts.map +1 -0
  355. package/build-types/empty-state/test/visual.test.d.ts +2 -0
  356. package/build-types/empty-state/test/visual.test.d.ts.map +1 -0
  357. package/build-types/empty-state/title.d.ts +6 -0
  358. package/build-types/empty-state/title.d.ts.map +1 -0
  359. package/build-types/empty-state/types.d.ts +40 -0
  360. package/build-types/empty-state/types.d.ts.map +1 -0
  361. package/build-types/empty-state/visual.d.ts +7 -0
  362. package/build-types/empty-state/visual.d.ts.map +1 -0
  363. package/build-types/form/index.d.ts +3 -0
  364. package/build-types/form/index.d.ts.map +1 -0
  365. package/build-types/form/input-control/index.d.ts +2 -0
  366. package/build-types/form/input-control/index.d.ts.map +1 -0
  367. package/build-types/form/input-control/input-control.d.ts +6 -0
  368. package/build-types/form/input-control/input-control.d.ts.map +1 -0
  369. package/build-types/form/input-control/stories/index.story.d.ts +16 -0
  370. package/build-types/form/input-control/stories/index.story.d.ts.map +1 -0
  371. package/build-types/form/input-control/test/index.test.d.ts +2 -0
  372. package/build-types/form/input-control/test/index.test.d.ts.map +1 -0
  373. package/build-types/form/input-control/types.d.ts +4 -0
  374. package/build-types/form/input-control/types.d.ts.map +1 -0
  375. package/build-types/form/primitives/field/stories/index.story.d.ts.map +1 -1
  376. package/build-types/form/primitives/fieldset/stories/index.story.d.ts.map +1 -1
  377. package/build-types/form/primitives/input/stories/index.story.d.ts +2 -0
  378. package/build-types/form/primitives/input/stories/index.story.d.ts.map +1 -1
  379. package/build-types/form/primitives/input-layout/slot.d.ts.map +1 -1
  380. package/build-types/form/primitives/input-layout/stories/index.story.d.ts +5 -0
  381. package/build-types/form/primitives/input-layout/stories/index.story.d.ts.map +1 -1
  382. package/build-types/form/stories/shared.d.ts +3 -0
  383. package/build-types/form/stories/shared.d.ts.map +1 -0
  384. package/build-types/form/types.d.ts +30 -0
  385. package/build-types/form/types.d.ts.map +1 -0
  386. package/build-types/index.d.ts +3 -1
  387. package/build-types/index.d.ts.map +1 -1
  388. package/build-types/notice/index.d.ts +0 -1
  389. package/build-types/notice/index.d.ts.map +1 -1
  390. package/build-types/tabs/context.d.ts +26 -0
  391. package/build-types/tabs/context.d.ts.map +1 -0
  392. package/build-types/tabs/panel.d.ts.map +1 -1
  393. package/build-types/tabs/root.d.ts.map +1 -1
  394. package/build-types/tabs/tab.d.ts.map +1 -1
  395. package/build-types/tooltip/root.d.ts +12 -0
  396. package/build-types/tooltip/root.d.ts.map +1 -1
  397. package/build-types/tooltip/stories/index.story.d.ts.map +1 -1
  398. package/build-types/utils/test/use-deprioritized-initial-focus.test.d.ts +2 -0
  399. package/build-types/utils/test/use-deprioritized-initial-focus.test.d.ts.map +1 -0
  400. package/build-types/utils/use-deprioritized-initial-focus.d.ts +35 -0
  401. package/build-types/utils/use-deprioritized-initial-focus.d.ts.map +1 -0
  402. package/package.json +17 -16
  403. package/src/alert-dialog/context.tsx +14 -0
  404. package/src/alert-dialog/index.ts +3 -0
  405. package/src/alert-dialog/popup.tsx +58 -0
  406. package/src/alert-dialog/root.tsx +48 -0
  407. package/src/alert-dialog/stories/index.story.tsx +254 -0
  408. package/src/alert-dialog/style.module.css +10 -0
  409. package/src/alert-dialog/test/index.test.tsx +537 -0
  410. package/src/alert-dialog/trigger.tsx +15 -0
  411. package/src/alert-dialog/types.ts +83 -0
  412. package/src/button/style.module.css +2 -0
  413. package/src/card/stories/index.story.tsx +1 -1
  414. package/src/card/style.module.css +3 -5
  415. package/src/card/title.tsx +12 -11
  416. package/src/collapsible-card/content.tsx +16 -3
  417. package/src/collapsible-card/context.ts +7 -0
  418. package/src/collapsible-card/header-description.tsx +43 -0
  419. package/src/collapsible-card/header.tsx +47 -24
  420. package/src/collapsible-card/index.ts +2 -1
  421. package/src/collapsible-card/stories/index.story.tsx +99 -1
  422. package/src/collapsible-card/style.module.css +34 -2
  423. package/src/collapsible-card/test/index.test.tsx +96 -9
  424. package/src/collapsible-card/types.ts +22 -0
  425. package/src/dialog/action.tsx +8 -2
  426. package/src/dialog/close-icon.tsx +1 -0
  427. package/src/dialog/popup.tsx +21 -2
  428. package/src/dialog/stories/index.story.tsx +0 -28
  429. package/src/dialog/style.module.css +5 -5
  430. package/src/dialog/test/index.test.tsx +117 -0
  431. package/src/dialog/types.ts +11 -5
  432. package/src/empty-state/actions.tsx +24 -0
  433. package/src/empty-state/description.tsx +27 -0
  434. package/src/empty-state/icon.tsx +24 -0
  435. package/src/empty-state/index.ts +8 -0
  436. package/src/empty-state/root.tsx +23 -0
  437. package/src/empty-state/stories/index.story.tsx +64 -0
  438. package/src/empty-state/style.module.css +53 -0
  439. package/src/empty-state/test/actions.test.tsx +18 -0
  440. package/src/empty-state/test/description.test.tsx +13 -0
  441. package/src/empty-state/test/icon.test.tsx +13 -0
  442. package/src/empty-state/test/root.test.tsx +13 -0
  443. package/src/empty-state/test/title.test.tsx +13 -0
  444. package/src/empty-state/test/visual.test.tsx +17 -0
  445. package/src/empty-state/title.tsx +23 -0
  446. package/src/empty-state/types.ts +45 -0
  447. package/src/empty-state/visual.tsx +24 -0
  448. package/src/form/index.ts +3 -0
  449. package/src/form/input-control/index.ts +1 -0
  450. package/src/form/input-control/input-control.tsx +33 -0
  451. package/src/form/input-control/stories/index.story.tsx +163 -0
  452. package/src/form/input-control/test/index.test.tsx +53 -0
  453. package/src/form/input-control/types.ts +5 -0
  454. package/src/form/primitives/field/root.tsx +2 -2
  455. package/src/form/primitives/field/stories/index.story.tsx +2 -7
  456. package/src/form/primitives/fieldset/stories/index.story.tsx +2 -7
  457. package/src/form/primitives/input/stories/index.story.tsx +7 -0
  458. package/src/form/primitives/input-layout/slot.tsx +6 -2
  459. package/src/form/primitives/input-layout/stories/index.story.tsx +22 -1
  460. package/src/form/primitives/stories/overview.mdx +15 -0
  461. package/src/form/primitives/textarea/textarea.tsx +1 -1
  462. package/src/form/stories/shared.tsx +19 -0
  463. package/src/form/types.ts +34 -0
  464. package/src/index.ts +3 -1
  465. package/src/notice/index.ts +0 -2
  466. package/src/notice/style.module.css +1 -1
  467. package/src/tabs/context.tsx +170 -0
  468. package/src/tabs/panel.tsx +3 -0
  469. package/src/tabs/root.tsx +6 -1
  470. package/src/tabs/style.module.css +1 -1
  471. package/src/tabs/tab.tsx +3 -0
  472. package/src/tabs/test/index.test.tsx +162 -0
  473. package/src/tooltip/root.tsx +12 -0
  474. package/src/tooltip/stories/index.story.tsx +20 -15
  475. package/src/utils/css/item-popup.module.css +1 -0
  476. package/src/utils/css/select-trigger.module.css +1 -0
  477. package/src/utils/test/use-deprioritized-initial-focus.test.tsx +230 -0
  478. package/src/utils/use-deprioritized-initial-focus.ts +83 -0
@@ -1,6 +1,7 @@
1
1
  import { forwardRef } from '@wordpress/element';
2
2
  import clsx from 'clsx';
3
3
  import { Tabs as _Tabs } from '@base-ui/react/tabs';
4
+ import { useRegisterPanel } from './context';
4
5
  import styles from './style.module.css';
5
6
  import type { TabPanelProps } from './types';
6
7
 
@@ -12,6 +13,8 @@ import type { TabPanelProps } from './types';
12
13
  */
13
14
  export const Panel = forwardRef< HTMLDivElement, TabPanelProps >(
14
15
  function TabPanel( { className, ...otherProps }, forwardedRef ) {
16
+ useRegisterPanel();
17
+
15
18
  return (
16
19
  <_Tabs.Panel
17
20
  ref={ forwardedRef }
package/src/tabs/root.tsx CHANGED
@@ -1,5 +1,6 @@
1
1
  import { forwardRef } from '@wordpress/element';
2
2
  import { Tabs as _Tabs } from '@base-ui/react/tabs';
3
+ import { TabsValidationProvider } from './context';
3
4
  import type { TabRootProps } from './types';
4
5
 
5
6
  /**
@@ -10,6 +11,10 @@ import type { TabRootProps } from './types';
10
11
  */
11
12
  export const Root = forwardRef< HTMLDivElement, TabRootProps >(
12
13
  function TabsRoot( { ...otherProps }, forwardedRef ) {
13
- return <_Tabs.Root ref={ forwardedRef } { ...otherProps } />;
14
+ return (
15
+ <TabsValidationProvider>
16
+ <_Tabs.Root ref={ forwardedRef } { ...otherProps } />
17
+ </TabsValidationProvider>
18
+ );
14
19
  }
15
20
  );
@@ -119,7 +119,7 @@
119
119
  align-items: center;
120
120
 
121
121
  /* Appearance */
122
- cursor: pointer;
122
+ cursor: var(--wpds-cursor-control);
123
123
 
124
124
  /* Typography */
125
125
  font-family: var(--wpds-font-family-body);
package/src/tabs/tab.tsx CHANGED
@@ -3,6 +3,7 @@ import clsx from 'clsx';
3
3
  import { Tabs as _Tabs } from '@base-ui/react/tabs';
4
4
  import { chevronRight } from '@wordpress/icons';
5
5
  import { Icon } from '../icon';
6
+ import { useRegisterTab } from './context';
6
7
  import styles from './style.module.css';
7
8
  import type { TabProps } from './types';
8
9
 
@@ -16,6 +17,8 @@ export const Tab = forwardRef< HTMLButtonElement, TabProps >( function Tab(
16
17
  { className, children, ...otherProps },
17
18
  forwardedRef
18
19
  ) {
20
+ useRegisterTab();
21
+
19
22
  return (
20
23
  <_Tabs.Tab
21
24
  ref={ forwardedRef }
@@ -2256,5 +2256,167 @@ describe( 'Tabs', () => {
2256
2256
  );
2257
2257
  } );
2258
2258
  } );
2259
+
2260
+ describe( 'Development mode validation', () => {
2261
+ function collectUncaughtErrors() {
2262
+ const errors: Error[] = [];
2263
+ const handler = ( event: ErrorEvent ) => {
2264
+ event.preventDefault();
2265
+ errors.push( event.error );
2266
+ };
2267
+ window.addEventListener( 'error', handler );
2268
+ return {
2269
+ errors,
2270
+ cleanup: () => window.removeEventListener( 'error', handler ),
2271
+ };
2272
+ }
2273
+
2274
+ it( 'should throw when there are more Tabs than Panels', async () => {
2275
+ const { errors, cleanup } = collectUncaughtErrors();
2276
+
2277
+ render(
2278
+ <Tabs.Root defaultValue="one">
2279
+ <Tabs.List>
2280
+ <Tabs.Tab value="one">One</Tabs.Tab>
2281
+ <Tabs.Tab value="two">Two</Tabs.Tab>
2282
+ <Tabs.Tab value="three">Three</Tabs.Tab>
2283
+ </Tabs.List>
2284
+ <Tabs.Panel value="one">First panel</Tabs.Panel>
2285
+ <Tabs.Panel value="two">Second panel</Tabs.Panel>
2286
+ </Tabs.Root>
2287
+ );
2288
+
2289
+ await waitForComponentToBeInitializedWithSelectedTab( 'One' );
2290
+
2291
+ await waitFor( () => {
2292
+ expect( errors.length ).toBeGreaterThan( 0 );
2293
+ } );
2294
+
2295
+ expect( errors[ 0 ].message ).toBe(
2296
+ 'Tabs: Tab/Panel count mismatch (3 Tabs, 2 Panels). Each Tab must be associated with exactly one Panel. Mismatched or missing associations can break screen reader navigation and violate WAI-ARIA Tabs pattern requirements.'
2297
+ );
2298
+
2299
+ cleanup();
2300
+ } );
2301
+
2302
+ it( 'should throw when there are more Panels than Tabs', async () => {
2303
+ const { errors, cleanup } = collectUncaughtErrors();
2304
+
2305
+ render(
2306
+ <Tabs.Root defaultValue="one">
2307
+ <Tabs.List>
2308
+ <Tabs.Tab value="one">One</Tabs.Tab>
2309
+ <Tabs.Tab value="two">Two</Tabs.Tab>
2310
+ </Tabs.List>
2311
+ <Tabs.Panel value="one">First panel</Tabs.Panel>
2312
+ <Tabs.Panel value="two">Second panel</Tabs.Panel>
2313
+ <Tabs.Panel value="three">Third panel</Tabs.Panel>
2314
+ </Tabs.Root>
2315
+ );
2316
+
2317
+ await waitForComponentToBeInitializedWithSelectedTab( 'One' );
2318
+
2319
+ await waitFor( () => {
2320
+ expect( errors.length ).toBeGreaterThan( 0 );
2321
+ } );
2322
+
2323
+ expect( errors[ 0 ].message ).toBe(
2324
+ 'Tabs: Tab/Panel count mismatch (2 Tabs, 3 Panels). Each Tab must be associated with exactly one Panel. Mismatched or missing associations can break screen reader navigation and violate WAI-ARIA Tabs pattern requirements.'
2325
+ );
2326
+
2327
+ cleanup();
2328
+ } );
2329
+
2330
+ it( 'should not throw when Tab and Panel counts match', async () => {
2331
+ const { errors, cleanup } = collectUncaughtErrors();
2332
+
2333
+ render(
2334
+ <Tabs.Root defaultValue="one">
2335
+ <Tabs.List>
2336
+ <Tabs.Tab value="one">One</Tabs.Tab>
2337
+ <Tabs.Tab value="two">Two</Tabs.Tab>
2338
+ </Tabs.List>
2339
+ <Tabs.Panel value="one">First panel</Tabs.Panel>
2340
+ <Tabs.Panel value="two">Second panel</Tabs.Panel>
2341
+ </Tabs.Root>
2342
+ );
2343
+
2344
+ await waitForComponentToBeInitializedWithSelectedTab( 'One' );
2345
+
2346
+ // Wait a bit to ensure validation has run
2347
+ await new Promise( ( resolve ) => setTimeout( resolve, 50 ) );
2348
+
2349
+ expect( errors ).toHaveLength( 0 );
2350
+
2351
+ cleanup();
2352
+ } );
2353
+
2354
+ it( 'should throw when tabs are used without any panels', async () => {
2355
+ const { errors, cleanup } = collectUncaughtErrors();
2356
+
2357
+ render(
2358
+ <Tabs.Root>
2359
+ <Tabs.List>
2360
+ <Tabs.Tab value="one">One</Tabs.Tab>
2361
+ <Tabs.Tab value="two">Two</Tabs.Tab>
2362
+ </Tabs.List>
2363
+ </Tabs.Root>
2364
+ );
2365
+
2366
+ await waitFor( () => {
2367
+ expect( errors.length ).toBeGreaterThan( 0 );
2368
+ } );
2369
+
2370
+ expect( errors[ 0 ].message ).toBe(
2371
+ 'Tabs: Tab/Panel count mismatch (2 Tabs, 0 Panels). Each Tab must be associated with exactly one Panel. Mismatched or missing associations can break screen reader navigation and violate WAI-ARIA Tabs pattern requirements.'
2372
+ );
2373
+
2374
+ cleanup();
2375
+ } );
2376
+
2377
+ it( 'should detect count mismatch after dynamic changes', async () => {
2378
+ const { errors, cleanup } = collectUncaughtErrors();
2379
+
2380
+ const { rerender } = render(
2381
+ <Tabs.Root defaultValue="one">
2382
+ <Tabs.List>
2383
+ <Tabs.Tab value="one">One</Tabs.Tab>
2384
+ <Tabs.Tab value="two">Two</Tabs.Tab>
2385
+ </Tabs.List>
2386
+ <Tabs.Panel value="one">First panel</Tabs.Panel>
2387
+ <Tabs.Panel value="two">Second panel</Tabs.Panel>
2388
+ </Tabs.Root>
2389
+ );
2390
+
2391
+ await waitForComponentToBeInitializedWithSelectedTab( 'One' );
2392
+
2393
+ // Wait for validation
2394
+ await new Promise( ( resolve ) => setTimeout( resolve, 50 ) );
2395
+
2396
+ // No errors since counts match
2397
+ expect( errors ).toHaveLength( 0 );
2398
+
2399
+ // Remove a panel to create a mismatch
2400
+ rerender(
2401
+ <Tabs.Root defaultValue="one">
2402
+ <Tabs.List>
2403
+ <Tabs.Tab value="one">One</Tabs.Tab>
2404
+ <Tabs.Tab value="two">Two</Tabs.Tab>
2405
+ </Tabs.List>
2406
+ <Tabs.Panel value="one">First panel</Tabs.Panel>
2407
+ </Tabs.Root>
2408
+ );
2409
+
2410
+ await waitFor( () => {
2411
+ expect( errors.length ).toBeGreaterThan( 0 );
2412
+ } );
2413
+
2414
+ expect( errors[ 0 ].message ).toBe(
2415
+ 'Tabs: Tab/Panel count mismatch (2 Tabs, 1 Panels). Each Tab must be associated with exactly one Panel. Mismatched or missing associations can break screen reader navigation and violate WAI-ARIA Tabs pattern requirements.'
2416
+ );
2417
+
2418
+ cleanup();
2419
+ } );
2420
+ } );
2259
2421
  } );
2260
2422
  /* eslint-enable jest/no-conditional-expect */
@@ -1,6 +1,18 @@
1
1
  import { Tooltip } from '@base-ui/react/tooltip';
2
2
  import type { RootProps } from './types';
3
3
 
4
+ /**
5
+ * `Tooltip` is used to visually show the label of an icon button, or other such interactive controls
6
+ * that don't have a visual text label.
7
+ *
8
+ * Tooltips are not available on touch devices, and thus should not be used for infotips,
9
+ * descriptions, or dynamic status messages.
10
+ *
11
+ * The tooltip itself does not provide any accessible labeling, so when using the
12
+ * `Tooltip` primitive you must ensure that the trigger is accessibly labeled (e.g. with an `aria-label`).
13
+ *
14
+ * See also: [IconButton](https://wordpress.github.io/gutenberg/?path=/docs/design-system-components-iconbutton--docs)
15
+ */
4
16
  function Root( props: RootProps ) {
5
17
  return <Tooltip.Root { ...props } />;
6
18
  }
@@ -1,5 +1,6 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import { Tooltip } from '../..';
2
+ import { formatBold, formatItalic } from '@wordpress/icons';
3
+ import { Icon, Tooltip } from '../..';
3
4
 
4
5
  const meta: Meta< typeof Tooltip.Root > = {
5
6
  title: 'Design System/Components/Tooltip',
@@ -16,8 +17,8 @@ export const Default: StoryObj< typeof Tooltip.Root > = {
16
17
  args: {
17
18
  children: (
18
19
  <>
19
- <Tooltip.Trigger>Hover me</Tooltip.Trigger>
20
- <Tooltip.Popup>Tooltip text</Tooltip.Popup>
20
+ <Tooltip.Trigger aria-label="Save">💾</Tooltip.Trigger>
21
+ <Tooltip.Popup>Save</Tooltip.Popup>
21
22
  </>
22
23
  ),
23
24
  },
@@ -51,23 +52,23 @@ export const Positioning: StoryObj< typeof Tooltip.Root > = {
51
52
  } }
52
53
  >
53
54
  <Tooltip.Root>
54
- <Tooltip.Trigger>Top</Tooltip.Trigger>
55
- <Tooltip.Popup side="top">Tooltip on top</Tooltip.Popup>
55
+ <Tooltip.Trigger aria-label="Up">⬆️</Tooltip.Trigger>
56
+ <Tooltip.Popup side="top">Up</Tooltip.Popup>
56
57
  </Tooltip.Root>
57
58
 
58
59
  <Tooltip.Root>
59
- <Tooltip.Trigger>Right</Tooltip.Trigger>
60
- <Tooltip.Popup side="right">Tooltip on right</Tooltip.Popup>
60
+ <Tooltip.Trigger aria-label="Forward">➡️</Tooltip.Trigger>
61
+ <Tooltip.Popup side="right">Forward</Tooltip.Popup>
61
62
  </Tooltip.Root>
62
63
 
63
64
  <Tooltip.Root>
64
- <Tooltip.Trigger>Bottom</Tooltip.Trigger>
65
- <Tooltip.Popup side="bottom">Tooltip on bottom</Tooltip.Popup>
65
+ <Tooltip.Trigger aria-label="Down">⬇️</Tooltip.Trigger>
66
+ <Tooltip.Popup side="bottom">Down</Tooltip.Popup>
66
67
  </Tooltip.Root>
67
68
 
68
69
  <Tooltip.Root>
69
- <Tooltip.Trigger>Left</Tooltip.Trigger>
70
- <Tooltip.Popup side="left">Tooltip on left</Tooltip.Popup>
70
+ <Tooltip.Trigger aria-label="Back">⬅️</Tooltip.Trigger>
71
+ <Tooltip.Popup side="left">Back</Tooltip.Popup>
71
72
  </Tooltip.Root>
72
73
  </div>
73
74
  ),
@@ -83,13 +84,17 @@ export const WithProvider: StoryObj< typeof Tooltip.Root > = {
83
84
  <Tooltip.Provider delay={ 0 }>
84
85
  <div style={ { display: 'flex', gap: '1rem' } }>
85
86
  <Tooltip.Root>
86
- <Tooltip.Trigger>First</Tooltip.Trigger>
87
- <Tooltip.Popup>First tooltip</Tooltip.Popup>
87
+ <Tooltip.Trigger aria-label="Bold">
88
+ <Icon icon={ formatBold } />
89
+ </Tooltip.Trigger>
90
+ <Tooltip.Popup>Bold</Tooltip.Popup>
88
91
  </Tooltip.Root>
89
92
 
90
93
  <Tooltip.Root>
91
- <Tooltip.Trigger>Second</Tooltip.Trigger>
92
- <Tooltip.Popup>Second tooltip</Tooltip.Popup>
94
+ <Tooltip.Trigger aria-label="Italic">
95
+ <Icon icon={ formatItalic } />
96
+ </Tooltip.Trigger>
97
+ <Tooltip.Popup>Italic</Tooltip.Popup>
93
98
  </Tooltip.Root>
94
99
  </div>
95
100
  </Tooltip.Provider>
@@ -60,6 +60,7 @@
60
60
  align-items: center;
61
61
  justify-content: flex-start;
62
62
  gap: var(--wpds-dimension-gap-xs);
63
+ cursor: var(--wpds-cursor-control);
63
64
  min-height: var(--wp-ui-popup-item-height);
64
65
  border-radius: var(--wpds-border-radius-sm);
65
66
  user-select: none;
@@ -26,6 +26,7 @@
26
26
  font-size: inherit;
27
27
  line-height: 1.4;
28
28
  text-align: start;
29
+ cursor: var(--wpds-cursor-control);
29
30
  user-select: none;
30
31
 
31
32
  &.is-minimal {
@@ -0,0 +1,230 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { createRef, useEffect } from '@wordpress/element';
3
+ import { useDeprioritizedInitialFocus } from '../use-deprioritized-initial-focus';
4
+
5
+ const ATTR = 'data-test-deprioritized';
6
+
7
+ function TestHarness( {
8
+ initialFocus,
9
+ onResolved,
10
+ }: {
11
+ initialFocus?: Parameters<
12
+ typeof useDeprioritizedInitialFocus
13
+ >[ 0 ][ 'initialFocus' ];
14
+ onResolved: (
15
+ result: ReturnType< typeof useDeprioritizedInitialFocus >
16
+ ) => void;
17
+ } ) {
18
+ const result = useDeprioritizedInitialFocus( {
19
+ initialFocus,
20
+ deprioritizedAttribute: ATTR,
21
+ } );
22
+
23
+ useEffect( () => {
24
+ onResolved( result );
25
+ } );
26
+
27
+ return (
28
+ <div ref={ result.popupRef } data-testid="popup">
29
+ <button { ...{ [ ATTR ]: '' } }>Close</button>
30
+ <button>Action</button>
31
+ <input type="text" />
32
+ </div>
33
+ );
34
+ }
35
+
36
+ describe( 'useDeprioritizedInitialFocus', () => {
37
+ describe( 'passthrough', () => {
38
+ it( 'passes through false unchanged', () => {
39
+ let resolved: ReturnType< typeof useDeprioritizedInitialFocus >;
40
+
41
+ render(
42
+ <TestHarness
43
+ initialFocus={ false }
44
+ onResolved={ ( r ) => {
45
+ resolved = r;
46
+ } }
47
+ />
48
+ );
49
+
50
+ expect( resolved!.resolvedInitialFocus ).toBe( false );
51
+ } );
52
+
53
+ it( 'passes through a ref unchanged', () => {
54
+ const ref = createRef< HTMLElement >();
55
+ let resolved: ReturnType< typeof useDeprioritizedInitialFocus >;
56
+
57
+ render(
58
+ <TestHarness
59
+ initialFocus={ ref }
60
+ onResolved={ ( r ) => {
61
+ resolved = r;
62
+ } }
63
+ />
64
+ );
65
+
66
+ expect( resolved!.resolvedInitialFocus ).toBe( ref );
67
+ } );
68
+
69
+ it( 'passes through a custom callback unchanged', () => {
70
+ const cb = () => true as const;
71
+ let resolved: ReturnType< typeof useDeprioritizedInitialFocus >;
72
+
73
+ render(
74
+ <TestHarness
75
+ initialFocus={ cb }
76
+ onResolved={ ( r ) => {
77
+ resolved = r;
78
+ } }
79
+ />
80
+ );
81
+
82
+ expect( resolved!.resolvedInitialFocus ).toBe( cb );
83
+ } );
84
+ } );
85
+
86
+ describe( 'default behavior (initialFocus undefined or true)', () => {
87
+ it.each( [ undefined, true ] )(
88
+ 'returns a callback when initialFocus is %s',
89
+ ( value ) => {
90
+ let resolved: ReturnType< typeof useDeprioritizedInitialFocus >;
91
+
92
+ render(
93
+ <TestHarness
94
+ initialFocus={ value }
95
+ onResolved={ ( r ) => {
96
+ resolved = r;
97
+ } }
98
+ />
99
+ );
100
+
101
+ expect( typeof resolved!.resolvedInitialFocus ).toBe(
102
+ 'function'
103
+ );
104
+ }
105
+ );
106
+
107
+ it( 'returns the popup element on touch interactions', () => {
108
+ let resolved: ReturnType< typeof useDeprioritizedInitialFocus >;
109
+
110
+ render(
111
+ <TestHarness
112
+ onResolved={ ( r ) => {
113
+ resolved = r;
114
+ } }
115
+ />
116
+ );
117
+
118
+ const callback = resolved!.resolvedInitialFocus as (
119
+ type: string
120
+ ) => HTMLElement | boolean | null;
121
+ const result = callback( 'touch' );
122
+
123
+ expect( result ).toBe( screen.getByTestId( 'popup' ) );
124
+ } );
125
+
126
+ it( 'skips the deprioritized element on non-touch interactions', () => {
127
+ let resolved: ReturnType< typeof useDeprioritizedInitialFocus >;
128
+
129
+ render(
130
+ <TestHarness
131
+ onResolved={ ( r ) => {
132
+ resolved = r;
133
+ } }
134
+ />
135
+ );
136
+
137
+ const callback = resolved!.resolvedInitialFocus as (
138
+ type: string
139
+ ) => HTMLElement | boolean | null;
140
+ const result = callback( 'mouse' );
141
+
142
+ // Should return the Action button, skipping Close
143
+ expect( result ).toBeInstanceOf( HTMLButtonElement );
144
+ expect( result as HTMLButtonElement ).toHaveTextContent( 'Action' );
145
+ } );
146
+
147
+ it( 'falls back to default when only deprioritized elements exist', () => {
148
+ let resolved: ReturnType< typeof useDeprioritizedInitialFocus >;
149
+
150
+ function OnlyDeprioritized( {
151
+ onResolved: onResolvedProp,
152
+ }: {
153
+ onResolved: (
154
+ r: ReturnType< typeof useDeprioritizedInitialFocus >
155
+ ) => void;
156
+ } ) {
157
+ const result = useDeprioritizedInitialFocus( {
158
+ initialFocus: undefined,
159
+ deprioritizedAttribute: ATTR,
160
+ } );
161
+
162
+ useEffect( () => {
163
+ onResolvedProp( result );
164
+ } );
165
+
166
+ return (
167
+ <div ref={ result.popupRef } data-testid="popup">
168
+ <button { ...{ [ ATTR ]: '' } }>Close</button>
169
+ <p>No other tabbable elements</p>
170
+ </div>
171
+ );
172
+ }
173
+
174
+ render(
175
+ <OnlyDeprioritized
176
+ onResolved={ ( r ) => {
177
+ resolved = r;
178
+ } }
179
+ />
180
+ );
181
+
182
+ const callback = resolved!.resolvedInitialFocus as (
183
+ type: string
184
+ ) => HTMLElement | boolean | null;
185
+ const result = callback( 'keyboard' );
186
+
187
+ // Falls back to Base UI's default
188
+ expect( result ).toBe( true );
189
+ } );
190
+
191
+ it( 'returns true when popupRef is not attached', () => {
192
+ let resolved: ReturnType< typeof useDeprioritizedInitialFocus >;
193
+
194
+ function NoRef( {
195
+ onResolved: onResolvedProp,
196
+ }: {
197
+ onResolved: (
198
+ r: ReturnType< typeof useDeprioritizedInitialFocus >
199
+ ) => void;
200
+ } ) {
201
+ const result = useDeprioritizedInitialFocus( {
202
+ initialFocus: undefined,
203
+ deprioritizedAttribute: ATTR,
204
+ } );
205
+
206
+ useEffect( () => {
207
+ onResolvedProp( result );
208
+ } );
209
+
210
+ // Intentionally not attaching popupRef to any element
211
+ return <div>Nothing</div>;
212
+ }
213
+
214
+ render(
215
+ <NoRef
216
+ onResolved={ ( r ) => {
217
+ resolved = r;
218
+ } }
219
+ />
220
+ );
221
+
222
+ const callback = resolved!.resolvedInitialFocus as (
223
+ type: string
224
+ ) => HTMLElement | boolean | null;
225
+
226
+ expect( callback( 'touch' ) ).toBe( true );
227
+ expect( callback( 'mouse' ) ).toBe( true );
228
+ } );
229
+ } );
230
+ } );
@@ -0,0 +1,83 @@
1
+ import type { Dialog as _Dialog } from '@base-ui/react/dialog';
2
+ import { useMemo, useRef } from '@wordpress/element';
3
+ import { tabbable } from 'tabbable';
4
+
5
+ /**
6
+ * Derived from Base UI's `Dialog.Popup.Props['initialFocus']`.
7
+ * The same type is shared by all Base UI overlay popups (Dialog, Popover, etc.).
8
+ */
9
+ type InitialFocus = _Dialog.Popup.Props[ 'initialFocus' ];
10
+
11
+ /**
12
+ * Options matching Base UI's internal tabbable configuration.
13
+ * @see https://github.com/floating-ui/floating-ui/blob/master/packages/react/src/utils/tabbable.ts
14
+ */
15
+ const getTabbableOptions = () => ( {
16
+ getShadowRoot: true,
17
+ displayCheck:
18
+ typeof ResizeObserver === 'function' &&
19
+ ResizeObserver.toString().includes( '[native code]' )
20
+ ? ( 'full' as const )
21
+ : ( 'none' as const ),
22
+ } );
23
+
24
+ /**
25
+ * Returns a resolved `initialFocus` value that deprioritizes elements
26
+ * marked with a given data attribute (e.g. a close icon), and an internal
27
+ * ref that must be merged onto the popup element.
28
+ *
29
+ * When `initialFocus` is `undefined` or `true` (the default behavior),
30
+ * the hook replaces it with a callback that:
31
+ * 1. On touch interactions — focuses the popup element itself (preventing
32
+ * the virtual keyboard on Android), matching Base UI's default.
33
+ * 2. On other interactions — returns the first tabbable element that does
34
+ * *not* carry `deprioritizedAttribute`. Falls back to Base UI's default
35
+ * when the deprioritized element is the only tabbable element.
36
+ *
37
+ * All other `initialFocus` values (`false`, `RefObject`, callback) pass
38
+ * through unchanged.
39
+ *
40
+ * @param props
41
+ * @param props.initialFocus The consumer-provided `initialFocus` value.
42
+ * @param props.deprioritizedAttribute The data attribute whose elements should be deprioritized.
43
+ */
44
+ export function useDeprioritizedInitialFocus( {
45
+ initialFocus,
46
+ deprioritizedAttribute,
47
+ }: {
48
+ initialFocus: InitialFocus;
49
+ deprioritizedAttribute: string;
50
+ } ) {
51
+ const popupRef = useRef< HTMLDivElement >( null );
52
+
53
+ const resolvedInitialFocus = useMemo( (): InitialFocus => {
54
+ if ( initialFocus !== undefined && initialFocus !== true ) {
55
+ return initialFocus;
56
+ }
57
+
58
+ return ( interactionType ): HTMLElement | boolean | null => {
59
+ if ( interactionType === 'touch' ) {
60
+ return popupRef.current ?? true;
61
+ }
62
+
63
+ const popup = popupRef.current;
64
+ if ( ! popup ) {
65
+ return true;
66
+ }
67
+
68
+ const tabbables = tabbable( popup, getTabbableOptions() );
69
+ for ( const el of tabbables ) {
70
+ if (
71
+ el instanceof HTMLElement &&
72
+ ! el.hasAttribute( deprioritizedAttribute )
73
+ ) {
74
+ return el;
75
+ }
76
+ }
77
+
78
+ return true;
79
+ };
80
+ }, [ initialFocus, deprioritizedAttribute ] );
81
+
82
+ return { resolvedInitialFocus, popupRef };
83
+ }