@wordpress/ui 0.13.1-next.v.202605131032.0 → 0.14.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 (282) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/CONTRIBUTING.md +34 -0
  3. package/README.md +15 -0
  4. package/build/alert-dialog/portal.cjs.map +2 -2
  5. package/build/alert-dialog/types.cjs.map +1 -1
  6. package/build/button/button.cjs +1 -1
  7. package/build/button/button.cjs.map +2 -2
  8. package/build/card/content.cjs +1 -1
  9. package/build/card/content.cjs.map +2 -2
  10. package/build/card/full-bleed.cjs +1 -1
  11. package/build/card/full-bleed.cjs.map +2 -2
  12. package/build/card/header.cjs +1 -1
  13. package/build/card/header.cjs.map +2 -2
  14. package/build/card/root.cjs +1 -1
  15. package/build/card/root.cjs.map +2 -2
  16. package/build/collapsible-card/header.cjs.map +2 -2
  17. package/build/dialog/portal.cjs.map +2 -2
  18. package/build/dialog/types.cjs.map +1 -1
  19. package/build/drawer/portal.cjs.map +2 -2
  20. package/build/drawer/types.cjs.map +1 -1
  21. package/build/form/primitives/autocomplete/clear.cjs +4 -1
  22. package/build/form/primitives/autocomplete/clear.cjs.map +2 -2
  23. package/build/form/primitives/autocomplete/empty.cjs +1 -1
  24. package/build/form/primitives/autocomplete/empty.cjs.map +2 -2
  25. package/build/form/primitives/autocomplete/index.cjs +4 -1
  26. package/build/form/primitives/autocomplete/index.cjs.map +2 -2
  27. package/build/form/primitives/autocomplete/item.cjs +1 -1
  28. package/build/form/primitives/autocomplete/item.cjs.map +2 -2
  29. package/build/form/primitives/autocomplete/list-body.cjs +1 -1
  30. package/build/form/primitives/autocomplete/list-body.cjs.map +2 -2
  31. package/build/form/primitives/autocomplete/list.cjs +1 -1
  32. package/build/form/primitives/autocomplete/list.cjs.map +2 -2
  33. package/build/form/primitives/autocomplete/popup.cjs +14 -31
  34. package/build/form/primitives/autocomplete/popup.cjs.map +3 -3
  35. package/build/form/primitives/autocomplete/portal.cjs +10 -2
  36. package/build/form/primitives/autocomplete/portal.cjs.map +2 -2
  37. package/build/form/primitives/autocomplete/positioner.cjs +158 -0
  38. package/build/form/primitives/autocomplete/positioner.cjs.map +7 -0
  39. package/build/form/primitives/autocomplete/types.cjs.map +1 -1
  40. package/build/form/primitives/constants.cjs.map +2 -2
  41. package/build/form/primitives/select/index.cjs +4 -1
  42. package/build/form/primitives/select/index.cjs.map +2 -2
  43. package/build/form/primitives/select/item.cjs +1 -1
  44. package/build/form/primitives/select/item.cjs.map +2 -2
  45. package/build/form/primitives/select/popup.cjs +18 -36
  46. package/build/form/primitives/select/popup.cjs.map +3 -3
  47. package/build/form/primitives/select/portal.cjs +11 -5
  48. package/build/form/primitives/select/portal.cjs.map +2 -2
  49. package/build/form/primitives/select/positioner.cjs +159 -0
  50. package/build/form/primitives/select/positioner.cjs.map +7 -0
  51. package/build/form/primitives/select/types.cjs.map +1 -1
  52. package/build/icon-button/icon-button.cjs +1 -1
  53. package/build/icon-button/icon-button.cjs.map +2 -2
  54. package/build/index.cjs +7 -1
  55. package/build/index.cjs.map +2 -2
  56. package/build/popover/index.cjs +3 -0
  57. package/build/popover/index.cjs.map +2 -2
  58. package/build/popover/popup.cjs +23 -51
  59. package/build/popover/popup.cjs.map +3 -3
  60. package/build/popover/portal.cjs.map +2 -2
  61. package/build/popover/positioner.cjs +168 -0
  62. package/build/popover/positioner.cjs.map +7 -0
  63. package/build/popover/root.cjs.map +2 -2
  64. package/build/popover/types.cjs.map +1 -1
  65. package/build/tooltip/portal.cjs +10 -2
  66. package/build/tooltip/portal.cjs.map +2 -2
  67. package/build/tooltip/positioner.cjs.map +2 -2
  68. package/build/tooltip/types.cjs.map +1 -1
  69. package/build/utils/create-overlay-title-validation.cjs.map +2 -2
  70. package/build/utils/render-slot-with-children.cjs +4 -1
  71. package/build/utils/render-slot-with-children.cjs.map +2 -2
  72. package/build/utils/use-enable-wp-compat-overlay-slot.cjs +39 -0
  73. package/build/utils/use-enable-wp-compat-overlay-slot.cjs.map +7 -0
  74. package/build/utils/wp-compat-overlay-slot.cjs +177 -0
  75. package/build/utils/wp-compat-overlay-slot.cjs.map +7 -0
  76. package/build-module/alert-dialog/portal.mjs.map +2 -2
  77. package/build-module/button/button.mjs +1 -1
  78. package/build-module/button/button.mjs.map +2 -2
  79. package/build-module/card/content.mjs +1 -1
  80. package/build-module/card/content.mjs.map +2 -2
  81. package/build-module/card/full-bleed.mjs +1 -1
  82. package/build-module/card/full-bleed.mjs.map +2 -2
  83. package/build-module/card/header.mjs +1 -1
  84. package/build-module/card/header.mjs.map +2 -2
  85. package/build-module/card/root.mjs +1 -1
  86. package/build-module/card/root.mjs.map +2 -2
  87. package/build-module/collapsible-card/header.mjs.map +2 -2
  88. package/build-module/dialog/portal.mjs.map +2 -2
  89. package/build-module/drawer/portal.mjs.map +2 -2
  90. package/build-module/form/primitives/autocomplete/clear.mjs +4 -1
  91. package/build-module/form/primitives/autocomplete/clear.mjs.map +2 -2
  92. package/build-module/form/primitives/autocomplete/empty.mjs +1 -1
  93. package/build-module/form/primitives/autocomplete/empty.mjs.map +2 -2
  94. package/build-module/form/primitives/autocomplete/index.mjs +3 -1
  95. package/build-module/form/primitives/autocomplete/index.mjs.map +2 -2
  96. package/build-module/form/primitives/autocomplete/item.mjs +1 -1
  97. package/build-module/form/primitives/autocomplete/item.mjs.map +2 -2
  98. package/build-module/form/primitives/autocomplete/list-body.mjs +1 -1
  99. package/build-module/form/primitives/autocomplete/list-body.mjs.map +2 -2
  100. package/build-module/form/primitives/autocomplete/list.mjs +1 -1
  101. package/build-module/form/primitives/autocomplete/list.mjs.map +2 -2
  102. package/build-module/form/primitives/autocomplete/popup.mjs +14 -31
  103. package/build-module/form/primitives/autocomplete/popup.mjs.map +3 -3
  104. package/build-module/form/primitives/autocomplete/portal.mjs +10 -2
  105. package/build-module/form/primitives/autocomplete/portal.mjs.map +2 -2
  106. package/build-module/form/primitives/autocomplete/positioner.mjs +123 -0
  107. package/build-module/form/primitives/autocomplete/positioner.mjs.map +7 -0
  108. package/build-module/form/primitives/constants.mjs.map +2 -2
  109. package/build-module/form/primitives/select/index.mjs +3 -1
  110. package/build-module/form/primitives/select/index.mjs.map +2 -2
  111. package/build-module/form/primitives/select/item.mjs +1 -1
  112. package/build-module/form/primitives/select/item.mjs.map +2 -2
  113. package/build-module/form/primitives/select/popup.mjs +18 -36
  114. package/build-module/form/primitives/select/popup.mjs.map +3 -3
  115. package/build-module/form/primitives/select/portal.mjs +11 -5
  116. package/build-module/form/primitives/select/portal.mjs.map +2 -2
  117. package/build-module/form/primitives/select/positioner.mjs +124 -0
  118. package/build-module/form/primitives/select/positioner.mjs.map +7 -0
  119. package/build-module/icon-button/icon-button.mjs +1 -1
  120. package/build-module/icon-button/icon-button.mjs.map +2 -2
  121. package/build-module/index.mjs +5 -1
  122. package/build-module/index.mjs.map +2 -2
  123. package/build-module/popover/index.mjs +2 -0
  124. package/build-module/popover/index.mjs.map +2 -2
  125. package/build-module/popover/popup.mjs +23 -51
  126. package/build-module/popover/popup.mjs.map +3 -3
  127. package/build-module/popover/portal.mjs.map +2 -2
  128. package/build-module/popover/positioner.mjs +133 -0
  129. package/build-module/popover/positioner.mjs.map +7 -0
  130. package/build-module/popover/root.mjs.map +2 -2
  131. package/build-module/tooltip/portal.mjs +10 -2
  132. package/build-module/tooltip/portal.mjs.map +2 -2
  133. package/build-module/tooltip/positioner.mjs.map +2 -2
  134. package/build-module/utils/create-overlay-title-validation.mjs.map +2 -2
  135. package/build-module/utils/render-slot-with-children.mjs +4 -1
  136. package/build-module/utils/render-slot-with-children.mjs.map +2 -2
  137. package/build-module/utils/use-enable-wp-compat-overlay-slot.mjs +14 -0
  138. package/build-module/utils/use-enable-wp-compat-overlay-slot.mjs.map +7 -0
  139. package/build-module/utils/wp-compat-overlay-slot.mjs +148 -0
  140. package/build-module/utils/wp-compat-overlay-slot.mjs.map +7 -0
  141. package/build-types/alert-dialog/portal.d.ts +8 -5
  142. package/build-types/alert-dialog/portal.d.ts.map +1 -1
  143. package/build-types/alert-dialog/types.d.ts +2 -2
  144. package/build-types/alert-dialog/types.d.ts.map +1 -1
  145. package/build-types/badge/stories/e2e/index.story.d.ts +7 -0
  146. package/build-types/badge/stories/e2e/index.story.d.ts.map +1 -0
  147. package/build-types/button/stories/e2e/index.story.d.ts +8 -0
  148. package/build-types/button/stories/e2e/index.story.d.ts.map +1 -0
  149. package/build-types/card/full-bleed.d.ts +18 -0
  150. package/build-types/card/full-bleed.d.ts.map +1 -1
  151. package/build-types/card/stories/index.story.d.ts +23 -0
  152. package/build-types/card/stories/index.story.d.ts.map +1 -1
  153. package/build-types/collapsible-card/header.d.ts +2 -0
  154. package/build-types/collapsible-card/header.d.ts.map +1 -1
  155. package/build-types/collapsible-card/stories/index.story.d.ts +14 -0
  156. package/build-types/collapsible-card/stories/index.story.d.ts.map +1 -1
  157. package/build-types/dialog/portal.d.ts +8 -6
  158. package/build-types/dialog/portal.d.ts.map +1 -1
  159. package/build-types/dialog/types.d.ts +2 -2
  160. package/build-types/dialog/types.d.ts.map +1 -1
  161. package/build-types/drawer/portal.d.ts +8 -6
  162. package/build-types/drawer/portal.d.ts.map +1 -1
  163. package/build-types/drawer/types.d.ts +2 -2
  164. package/build-types/drawer/types.d.ts.map +1 -1
  165. package/build-types/form/primitives/autocomplete/clear.d.ts.map +1 -1
  166. package/build-types/form/primitives/autocomplete/index.d.ts +2 -1
  167. package/build-types/form/primitives/autocomplete/index.d.ts.map +1 -1
  168. package/build-types/form/primitives/autocomplete/popup.d.ts +1 -0
  169. package/build-types/form/primitives/autocomplete/popup.d.ts.map +1 -1
  170. package/build-types/form/primitives/autocomplete/portal.d.ts +9 -4
  171. package/build-types/form/primitives/autocomplete/portal.d.ts.map +1 -1
  172. package/build-types/form/primitives/autocomplete/positioner.d.ts +12 -0
  173. package/build-types/form/primitives/autocomplete/positioner.d.ts.map +1 -0
  174. package/build-types/form/primitives/autocomplete/stories/index.story.d.ts.map +1 -1
  175. package/build-types/form/primitives/autocomplete/types.d.ts +11 -9
  176. package/build-types/form/primitives/autocomplete/types.d.ts.map +1 -1
  177. package/build-types/form/primitives/constants.d.ts +9 -4
  178. package/build-types/form/primitives/constants.d.ts.map +1 -1
  179. package/build-types/form/primitives/select/index.d.ts +2 -1
  180. package/build-types/form/primitives/select/index.d.ts.map +1 -1
  181. package/build-types/form/primitives/select/popup.d.ts +1 -0
  182. package/build-types/form/primitives/select/popup.d.ts.map +1 -1
  183. package/build-types/form/primitives/select/portal.d.ts +9 -4
  184. package/build-types/form/primitives/select/portal.d.ts.map +1 -1
  185. package/build-types/form/primitives/select/positioner.d.ts +12 -0
  186. package/build-types/form/primitives/select/positioner.d.ts.map +1 -0
  187. package/build-types/form/primitives/select/stories/index.story.d.ts.map +1 -1
  188. package/build-types/form/primitives/select/types.d.ts +11 -2
  189. package/build-types/form/primitives/select/types.d.ts.map +1 -1
  190. package/build-types/icon/stories/index.story.d.ts.map +1 -1
  191. package/build-types/index.d.ts +2 -0
  192. package/build-types/index.d.ts.map +1 -1
  193. package/build-types/popover/index.d.ts +2 -1
  194. package/build-types/popover/index.d.ts.map +1 -1
  195. package/build-types/popover/popup.d.ts +2 -1
  196. package/build-types/popover/popup.d.ts.map +1 -1
  197. package/build-types/popover/portal.d.ts +8 -5
  198. package/build-types/popover/portal.d.ts.map +1 -1
  199. package/build-types/popover/positioner.d.ts +12 -0
  200. package/build-types/popover/positioner.d.ts.map +1 -0
  201. package/build-types/popover/root.d.ts +5 -2
  202. package/build-types/popover/root.d.ts.map +1 -1
  203. package/build-types/popover/stories/index.story.d.ts +10 -21
  204. package/build-types/popover/stories/index.story.d.ts.map +1 -1
  205. package/build-types/popover/types.d.ts +12 -3
  206. package/build-types/popover/types.d.ts.map +1 -1
  207. package/build-types/tooltip/portal.d.ts +9 -4
  208. package/build-types/tooltip/portal.d.ts.map +1 -1
  209. package/build-types/tooltip/positioner.d.ts +8 -5
  210. package/build-types/tooltip/positioner.d.ts.map +1 -1
  211. package/build-types/tooltip/types.d.ts +3 -3
  212. package/build-types/tooltip/types.d.ts.map +1 -1
  213. package/build-types/utils/render-slot-with-children.d.ts.map +1 -1
  214. package/build-types/utils/test/use-enable-wp-compat-overlay-slot.test.d.ts +2 -0
  215. package/build-types/utils/test/use-enable-wp-compat-overlay-slot.test.d.ts.map +1 -0
  216. package/build-types/utils/test/wp-compat-overlay-slot.test.d.ts +2 -0
  217. package/build-types/utils/test/wp-compat-overlay-slot.test.d.ts.map +1 -0
  218. package/build-types/utils/use-deprioritized-initial-focus.d.ts +1 -1
  219. package/build-types/utils/use-enable-wp-compat-overlay-slot.d.ts +17 -0
  220. package/build-types/utils/use-enable-wp-compat-overlay-slot.d.ts.map +1 -0
  221. package/build-types/utils/wp-compat-overlay-slot.d.ts +30 -0
  222. package/build-types/utils/wp-compat-overlay-slot.d.ts.map +1 -0
  223. package/package.json +14 -14
  224. package/src/alert-dialog/portal.tsx +1 -4
  225. package/src/alert-dialog/types.ts +2 -4
  226. package/src/badge/stories/e2e/index.story.tsx +20 -0
  227. package/src/button/stories/e2e/index.story.tsx +130 -0
  228. package/src/button/stories/index.story.tsx +1 -1
  229. package/src/button/style.module.css +17 -12
  230. package/src/card/full-bleed.tsx +18 -0
  231. package/src/card/stories/index.story.tsx +115 -1
  232. package/src/card/style.module.css +16 -0
  233. package/src/card/test/index.test.tsx +18 -1
  234. package/src/collapsible-card/header.tsx +2 -0
  235. package/src/collapsible-card/stories/index.story.tsx +66 -0
  236. package/src/dialog/portal.tsx +1 -5
  237. package/src/dialog/types.ts +2 -2
  238. package/src/drawer/portal.tsx +1 -5
  239. package/src/drawer/types.ts +2 -2
  240. package/src/form/primitives/autocomplete/clear.tsx +10 -4
  241. package/src/form/primitives/autocomplete/index.ts +2 -1
  242. package/src/form/primitives/autocomplete/popup.tsx +17 -21
  243. package/src/form/primitives/autocomplete/portal.tsx +11 -5
  244. package/src/form/primitives/autocomplete/positioner.tsx +29 -0
  245. package/src/form/primitives/autocomplete/stories/index.story.tsx +1 -0
  246. package/src/form/primitives/autocomplete/test/index.test.tsx +219 -0
  247. package/src/form/primitives/autocomplete/types.ts +15 -15
  248. package/src/form/primitives/constants.ts +7 -4
  249. package/src/form/primitives/select/index.ts +2 -1
  250. package/src/form/primitives/select/popup.tsx +30 -34
  251. package/src/form/primitives/select/portal.tsx +15 -8
  252. package/src/form/primitives/select/positioner.tsx +33 -0
  253. package/src/form/primitives/select/stories/index.story.tsx +1 -0
  254. package/src/form/primitives/select/test/index.test.tsx +134 -0
  255. package/src/form/primitives/select/types.ts +12 -2
  256. package/src/form/select-control/test/index.test.tsx +64 -0
  257. package/src/icon/stories/index.story.tsx +3 -2
  258. package/src/icon-button/icon-button.tsx +1 -1
  259. package/src/icon-button/test/index.test.tsx +10 -10
  260. package/src/index.ts +2 -0
  261. package/src/popover/index.ts +12 -1
  262. package/src/popover/popup.tsx +28 -45
  263. package/src/popover/portal.tsx +1 -4
  264. package/src/popover/positioner.tsx +42 -0
  265. package/src/popover/root.tsx +5 -2
  266. package/src/popover/stories/index.story.tsx +85 -138
  267. package/src/popover/test/index.test.tsx +36 -1
  268. package/src/popover/types.ts +13 -15
  269. package/src/tabs/stories/index.story.tsx +2 -2
  270. package/src/tooltip/portal.tsx +11 -5
  271. package/src/tooltip/positioner.tsx +1 -4
  272. package/src/tooltip/style.module.css +1 -1
  273. package/src/tooltip/test/index.test.tsx +110 -0
  274. package/src/tooltip/types.ts +3 -5
  275. package/src/utils/create-overlay-title-validation.tsx +1 -1
  276. package/src/utils/css/item-popup.module.css +7 -4
  277. package/src/utils/css/wp-compat-overlay-slot.module.css +35 -0
  278. package/src/utils/render-slot-with-children.ts +4 -1
  279. package/src/utils/test/use-enable-wp-compat-overlay-slot.test.tsx +74 -0
  280. package/src/utils/test/wp-compat-overlay-slot.test.ts +300 -0
  281. package/src/utils/use-enable-wp-compat-overlay-slot.ts +32 -0
  282. package/src/utils/wp-compat-overlay-slot.ts +129 -0
@@ -1,7 +1,9 @@
1
1
  import { act, render, screen } from '@testing-library/react';
2
2
  import userEvent from '@testing-library/user-event';
3
3
  import { createRef } from '@wordpress/element';
4
+ import type { ReactNode } from 'react';
4
5
  import { SelectControl } from '../index';
6
+ import { useEnableWpCompatOverlaySlot } from '../../../utils/use-enable-wp-compat-overlay-slot';
5
7
 
6
8
  describe( 'SelectControl', () => {
7
9
  const mockItems = [
@@ -193,4 +195,66 @@ describe( 'SelectControl', () => {
193
195
  expect( formData.get( 'country' ) ).toBe( '' );
194
196
  } );
195
197
  } );
198
+
199
+ // Slot is identified by a data attribute, not a user-facing role/text.
200
+ /* eslint-disable testing-library/no-node-access */
201
+ describe( 'wp compat overlay slot', () => {
202
+ const SLOT_SELECTOR = '[data-wp-compat-overlay-slot]';
203
+
204
+ // Exercises the public opt-in path rather than poking the flag.
205
+ function WithSlotEnabled( { children }: { children: ReactNode } ) {
206
+ useEnableWpCompatOverlaySlot();
207
+ return <>{ children }</>;
208
+ }
209
+
210
+ afterEach( () => {
211
+ // The hook is one-way at runtime; reset explicitly between tests.
212
+ delete ( window as { __wpUiCompatOverlaySlotEnabled?: boolean } )
213
+ .__wpUiCompatOverlaySlotEnabled;
214
+ document
215
+ .querySelectorAll( SLOT_SELECTOR )
216
+ .forEach( ( el ) => el.remove() );
217
+ } );
218
+
219
+ it( 'portals the popup into the slot when the consumer opts in', async () => {
220
+ const user = userEvent.setup();
221
+
222
+ render(
223
+ <WithSlotEnabled>
224
+ <SelectControl label="Country" items={ mockItems } />
225
+ </WithSlotEnabled>
226
+ );
227
+
228
+ await user.click(
229
+ screen.getByRole( 'combobox', { name: 'Country' } )
230
+ );
231
+
232
+ const option = await screen.findByRole( 'option', {
233
+ name: 'Option 1',
234
+ } );
235
+ expect( option ).toBeVisible();
236
+
237
+ const slot = document.querySelector( SLOT_SELECTOR );
238
+ expect( slot ).not.toBeNull();
239
+ expect( slot ).toContainElement( option );
240
+ } );
241
+
242
+ it( 'does not create a slot when the consumer has not opted in (dormant default)', async () => {
243
+ const user = userEvent.setup();
244
+
245
+ render( <SelectControl label="Country" items={ mockItems } /> );
246
+
247
+ await user.click(
248
+ screen.getByRole( 'combobox', { name: 'Country' } )
249
+ );
250
+
251
+ const option = await screen.findByRole( 'option', {
252
+ name: 'Option 1',
253
+ } );
254
+ expect( option ).toBeVisible();
255
+
256
+ expect( document.querySelector( SLOT_SELECTOR ) ).toBeNull();
257
+ } );
258
+ } );
259
+ /* eslint-enable testing-library/no-node-access */
196
260
  } );
@@ -5,6 +5,7 @@ import { Icon } from '../index';
5
5
  const meta: Meta< typeof Icon > = {
6
6
  title: 'Design System/Components/Icon',
7
7
  component: Icon,
8
+ tags: [ 'manifest' ],
8
9
  decorators: [
9
10
  ( Story ) => {
10
11
  return (
@@ -20,9 +21,9 @@ const meta: Meta< typeof Icon > = {
20
21
  ],
21
22
  parameters: {
22
23
  componentStatus: {
23
- status: 'use-with-caution',
24
+ status: 'recommended',
24
25
  whereUsed: 'global',
25
- notes: 'Not yet recommended for use alongside components from `@wordpress/components`, pending a general readiness review. See [WordPress/gutenberg#76135](https://github.com/WordPress/gutenberg/issues/76135).',
26
+ notes: 'Prefer this component over the `Icon` component from `@wordpress/components` or `@wordpress/icons`.',
26
27
  },
27
28
  },
28
29
  };
@@ -18,7 +18,7 @@ export const IconButton = forwardRef< HTMLButtonElement, IconButtonProps >(
18
18
  // Prevent accidental forwarding of `children`
19
19
  children: _children,
20
20
  disabled,
21
- focusableWhenDisabled,
21
+ focusableWhenDisabled = true,
22
22
  icon,
23
23
  size,
24
24
  shortcut,
@@ -37,7 +37,14 @@ describe( 'IconButton', () => {
37
37
  it( 'does not show tooltip when truly disabled', async () => {
38
38
  const user = userEvent.setup();
39
39
 
40
- render( <IconButton label="Save" icon={ <svg /> } disabled /> );
40
+ render(
41
+ <IconButton
42
+ label="Save"
43
+ icon={ <svg /> }
44
+ disabled
45
+ focusableWhenDisabled={ false }
46
+ />
47
+ );
41
48
 
42
49
  const button = screen.getByRole( 'button', { name: 'Save' } );
43
50
  await user.hover( button );
@@ -45,17 +52,10 @@ describe( 'IconButton', () => {
45
52
  expect( screen.queryByText( 'Save' ) ).not.toBeInTheDocument();
46
53
  } );
47
54
 
48
- it( 'shows tooltip when focusably disabled', async () => {
55
+ it( 'shows tooltip when disabled by default', async () => {
49
56
  const user = userEvent.setup();
50
57
 
51
- render(
52
- <IconButton
53
- label="Save"
54
- icon={ <svg /> }
55
- disabled
56
- focusableWhenDisabled
57
- />
58
- );
58
+ render( <IconButton label="Save" icon={ <svg /> } disabled /> );
59
59
 
60
60
  const button = screen.getByRole( 'button', { name: 'Save' } );
61
61
  await user.hover( button );
package/src/index.ts CHANGED
@@ -17,4 +17,6 @@ export * from './stack';
17
17
  export * as Tabs from './tabs';
18
18
  export * from './text';
19
19
  export * as Tooltip from './tooltip';
20
+ export { getWpCompatOverlaySlot } from './utils/wp-compat-overlay-slot';
21
+ export { useEnableWpCompatOverlaySlot } from './utils/use-enable-wp-compat-overlay-slot';
20
22
  export * from './visually-hidden';
@@ -3,8 +3,19 @@ import { Close } from './close';
3
3
  import { Description } from './description';
4
4
  import { Popup } from './popup';
5
5
  import { Portal } from './portal';
6
+ import { Positioner } from './positioner';
6
7
  import { Root } from './root';
7
8
  import { Title } from './title';
8
9
  import { Trigger } from './trigger';
9
10
 
10
- export { Arrow, Close, Description, Portal, Popup, Root, Title, Trigger };
11
+ export {
12
+ Arrow,
13
+ Close,
14
+ Description,
15
+ Popup,
16
+ Portal,
17
+ Positioner,
18
+ Root,
19
+ Title,
20
+ Trigger,
21
+ };
@@ -7,11 +7,11 @@ import {
7
7
  privateApis as themePrivateApis,
8
8
  } from '@wordpress/theme';
9
9
  import { unlock } from '../lock-unlock';
10
- import resetStyles from '../utils/css/resets.module.css';
11
10
  import { useDeprioritizedInitialFocus } from '../utils/use-deprioritized-initial-focus';
12
11
  import { renderSlotWithChildren } from '../utils/render-slot-with-children';
13
12
  import { PopoverValidationProvider } from './context';
14
13
  import { Portal } from './portal';
14
+ import { Positioner } from './positioner';
15
15
  import styles from './style.module.css';
16
16
  import type { PopupProps } from './types';
17
17
 
@@ -26,27 +26,18 @@ const CLOSE_ATTR = 'data-wp-ui-popover-close';
26
26
  * Handles portal rendering, positioning relative to the anchor, collision
27
27
  * avoidance, focus management, and optional backdrop. Use
28
28
  * `portal={ <Popover.Portal container={ ... } /> }` for cross-document
29
- * scenarios such as iframes.
29
+ * scenarios such as iframes, and `positioner={ <Popover.Positioner … /> }`
30
+ * to customize placement.
30
31
  */
31
32
  const Popup = forwardRef< HTMLDivElement, PopupProps >( function PopoverPopup(
32
33
  {
33
- align = 'center',
34
- alignOffset,
35
- anchor,
36
- // Matches the popup's border-radius (--wpds-border-radius-md).
37
- arrowPadding = 8,
38
34
  backdrop = false,
39
35
  children,
40
36
  className,
41
- collisionAvoidance,
42
- collisionBoundary,
43
- collisionPadding,
44
37
  portal,
38
+ positioner,
45
39
  finalFocus,
46
40
  initialFocus,
47
- side = 'bottom',
48
- sideOffset = 8,
49
- sticky,
50
41
  variant = 'default',
51
42
  ...props
52
43
  },
@@ -62,43 +53,35 @@ const Popup = forwardRef< HTMLDivElement, PopupProps >( function PopoverPopup(
62
53
  <_Popover.Backdrop className={ styles.backdrop } />
63
54
  ) : null;
64
55
 
65
- const positioner = (
66
- <_Popover.Positioner
67
- align={ align }
68
- alignOffset={ alignOffset }
69
- anchor={ anchor }
70
- arrowPadding={ arrowPadding }
71
- collisionAvoidance={ collisionAvoidance }
72
- collisionBoundary={ collisionBoundary }
73
- collisionPadding={ collisionPadding }
74
- side={ side }
75
- sideOffset={ sideOffset }
76
- sticky={ sticky }
77
- className={ clsx( resetStyles[ 'box-sizing' ], styles.positioner ) }
78
- >
79
- <ThemeProvider>
80
- <_Popover.Popup
81
- ref={ mergedPopupRef }
82
- initialFocus={ resolvedInitialFocus }
83
- finalFocus={ finalFocus }
84
- className={ clsx(
85
- variant !== 'unstyled' && styles.popup,
86
- className
87
- ) }
88
- { ...props }
89
- >
90
- <PopoverValidationProvider>
91
- { children }
92
- </PopoverValidationProvider>
93
- </_Popover.Popup>
94
- </ThemeProvider>
95
- </_Popover.Positioner>
56
+ const popupContent = (
57
+ <ThemeProvider>
58
+ <_Popover.Popup
59
+ ref={ mergedPopupRef }
60
+ initialFocus={ resolvedInitialFocus }
61
+ finalFocus={ finalFocus }
62
+ className={ clsx(
63
+ variant !== 'unstyled' && styles.popup,
64
+ className
65
+ ) }
66
+ { ...props }
67
+ >
68
+ <PopoverValidationProvider>
69
+ { children }
70
+ </PopoverValidationProvider>
71
+ </_Popover.Popup>
72
+ </ThemeProvider>
73
+ );
74
+
75
+ const positionedPopup = renderSlotWithChildren(
76
+ positioner,
77
+ <Positioner />,
78
+ popupContent
96
79
  );
97
80
 
98
81
  const portalChildren = (
99
82
  <>
100
83
  { backdropElement }
101
- { positioner }
84
+ { positionedPopup }
102
85
  </>
103
86
  );
104
87
 
@@ -3,10 +3,7 @@ import { forwardRef } from '@wordpress/element';
3
3
  import type { PortalProps } from './types';
4
4
 
5
5
  /**
6
- * Root element that portals `Popover` floating content. Pass to
7
- * `Popover.Popup`'s `portal` prop (for example `container` for
8
- * cross-document rendering). When `portal` is omitted, `Popover.Popup` uses
9
- * this component with default props.
6
+ * Used to apply custom portal behavior to `Popover`'s floating content.
10
7
  */
11
8
  const Portal = forwardRef< HTMLDivElement, PortalProps >(
12
9
  function PopoverPortal( props, ref ) {
@@ -0,0 +1,42 @@
1
+ import clsx from 'clsx';
2
+ import { Popover as _Popover } from '@base-ui/react/popover';
3
+ import { forwardRef } from '@wordpress/element';
4
+ import type { PositionerProps } from './types';
5
+ import resetStyles from '../utils/css/resets.module.css';
6
+ import styles from './style.module.css';
7
+
8
+ /**
9
+ * Used to apply custom positioning to `Popover`'s floating content.
10
+ */
11
+ const Positioner = forwardRef< HTMLDivElement, PositionerProps >(
12
+ function PopoverPositioner(
13
+ {
14
+ align = 'center',
15
+ // Matches the popup's border-radius (--wpds-border-radius-md).
16
+ arrowPadding = 8,
17
+ className,
18
+ side = 'bottom',
19
+ sideOffset = 8,
20
+ ...props
21
+ },
22
+ ref
23
+ ) {
24
+ return (
25
+ <_Popover.Positioner
26
+ ref={ ref }
27
+ align={ align }
28
+ arrowPadding={ arrowPadding }
29
+ side={ side }
30
+ sideOffset={ sideOffset }
31
+ { ...props }
32
+ className={ clsx(
33
+ resetStyles[ 'box-sizing' ],
34
+ styles.positioner,
35
+ className
36
+ ) }
37
+ />
38
+ );
39
+ }
40
+ );
41
+
42
+ export { Positioner };
@@ -12,8 +12,11 @@ import type { RootProps } from './types';
12
12
  *
13
13
  * - `Popover.Root` — provides open state and context to all sub-components.
14
14
  * - `Popover.Trigger` — the button that toggles the popup.
15
- * - `Popover.Popup` — the floating container (positioning, collision
16
- * avoidance); portals by default or via `portal={ <Popover.Portal /> }`.
15
+ * - `Popover.Popup` — the floating container. Portals by default or via
16
+ * `portal={ <Popover.Portal /> }`, and is positioned by default or via
17
+ * `positioner={ <Popover.Positioner /> }`.
18
+ * - `Popover.Positioner` — controls placement, alignment, offset, collision
19
+ * behavior, and anchor for the floating content.
17
20
  * - `Popover.Arrow` — an optional arrow pointing toward the anchor.
18
21
  * - `Popover.Title` — **required** heading that labels the popover for
19
22
  * accessibility (can be visually hidden).