@wordpress/components 40.0.0 → 40.1.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 (472) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/CONTRIBUTING.md +2 -0
  3. package/build/border-box-control/border-box-control/component.cjs +37 -5
  4. package/build/border-box-control/border-box-control/component.cjs.map +3 -3
  5. package/build/border-box-control/border-box-control/hook.cjs +24 -3
  6. package/build/border-box-control/border-box-control/hook.cjs.map +3 -3
  7. package/build/border-box-control/border-box-control-linked-button/hook.cjs +2 -2
  8. package/build/border-box-control/border-box-control-linked-button/hook.cjs.map +2 -2
  9. package/build/border-box-control/border-box-control-split-controls/hook.cjs +2 -2
  10. package/build/border-box-control/border-box-control-split-controls/hook.cjs.map +2 -2
  11. package/build/border-box-control/border-box-control-visualizer/hook.cjs +2 -2
  12. package/build/border-box-control/border-box-control-visualizer/hook.cjs.map +2 -2
  13. package/build/color-palette/index.cjs +1 -1
  14. package/build/color-palette/index.cjs.map +2 -2
  15. package/build/composite/context.cjs +9 -2
  16. package/build/composite/context.cjs.map +2 -2
  17. package/build/composite/group-label.cjs +4 -0
  18. package/build/composite/group-label.cjs.map +2 -2
  19. package/build/composite/group.cjs +10 -2
  20. package/build/composite/group.cjs.map +2 -2
  21. package/build/composite/item.cjs +4 -0
  22. package/build/composite/item.cjs.map +3 -3
  23. package/build/context/context-system-provider.cjs +2 -5
  24. package/build/context/context-system-provider.cjs.map +3 -3
  25. package/build/context/use-context-system.cjs +7 -2
  26. package/build/context/use-context-system.cjs.map +2 -2
  27. package/build/disabled/index.cjs +1 -1
  28. package/build/disabled/index.cjs.map +2 -2
  29. package/build/draggable/index.cjs.map +1 -1
  30. package/build/form-token-field/index.cjs +1 -1
  31. package/build/form-token-field/index.cjs.map +2 -2
  32. package/build/icon/index.cjs +30 -12
  33. package/build/icon/index.cjs.map +2 -2
  34. package/build/input-control/styles/input-control-styles.cjs +42 -27
  35. package/build/input-control/styles/input-control-styles.cjs.map +2 -2
  36. package/build/item-group/context.cjs +1 -0
  37. package/build/item-group/context.cjs.map +2 -2
  38. package/build/item-group/item/component.cjs +12 -0
  39. package/build/item-group/item/component.cjs.map +3 -3
  40. package/build/item-group/item/hook.cjs +7 -6
  41. package/build/item-group/item/hook.cjs.map +2 -2
  42. package/build/item-group/item-group/component.cjs +1 -0
  43. package/build/item-group/item-group/component.cjs.map +2 -2
  44. package/build/item-group/item-group/hook.cjs +1 -1
  45. package/build/item-group/item-group/hook.cjs.map +2 -2
  46. package/build/progress-bar/index.cjs +1 -1
  47. package/build/progress-bar/index.cjs.map +2 -2
  48. package/build/resizable-box/index.cjs.map +1 -1
  49. package/build/snackbar/index.cjs +8 -5
  50. package/build/snackbar/index.cjs.map +2 -2
  51. package/build/textarea-control/index.cjs +1 -1
  52. package/build/textarea-control/index.cjs.map +2 -2
  53. package/build/toggle-group-control/context.cjs +8 -2
  54. package/build/toggle-group-control/context.cjs.map +2 -2
  55. package/build/toggle-group-control/toggle-group-control/as-button-group.cjs +5 -2
  56. package/build/toggle-group-control/toggle-group-control/as-button-group.cjs.map +2 -2
  57. package/build/toggle-group-control/toggle-group-control/as-radio-group.cjs +5 -2
  58. package/build/toggle-group-control/toggle-group-control/as-radio-group.cjs.map +2 -2
  59. package/build/toggle-group-control/toggle-group-control/component.cjs +3 -1
  60. package/build/toggle-group-control/toggle-group-control/component.cjs.map +2 -2
  61. package/build/toggle-group-control/toggle-group-control-option/component.cjs +2 -0
  62. package/build/toggle-group-control/toggle-group-control-option/component.cjs.map +2 -2
  63. package/build/toggle-group-control/toggle-group-control-option-base/component.cjs +2 -1
  64. package/build/toggle-group-control/toggle-group-control-option-base/component.cjs.map +2 -2
  65. package/build/toggle-group-control/toggle-group-control-option-icon/component.cjs +2 -0
  66. package/build/toggle-group-control/toggle-group-control-option-icon/component.cjs.map +2 -2
  67. package/build/tools-panel/tools-panel/hook.cjs +102 -106
  68. package/build/tools-panel/tools-panel/hook.cjs.map +2 -2
  69. package/build/tools-panel/tools-panel-item/hook.cjs +33 -33
  70. package/build/tools-panel/tools-panel-item/hook.cjs.map +2 -2
  71. package/build/tree-grid/cell.cjs +4 -0
  72. package/build/tree-grid/cell.cjs.map +2 -2
  73. package/build/tree-grid/roving-tab-index-item.cjs +5 -1
  74. package/build/tree-grid/roving-tab-index-item.cjs.map +2 -2
  75. package/build/tree-grid/row.cjs +6 -1
  76. package/build/tree-grid/row.cjs.map +2 -2
  77. package/build/tree-grid/use-validate-tree-grid-structure.cjs +81 -0
  78. package/build/tree-grid/use-validate-tree-grid-structure.cjs.map +7 -0
  79. package/build/utils/colors-values.cjs +3 -2
  80. package/build/utils/colors-values.cjs.map +2 -2
  81. package/build-module/border-box-control/border-box-control/component.mjs +38 -6
  82. package/build-module/border-box-control/border-box-control/component.mjs.map +2 -2
  83. package/build-module/border-box-control/border-box-control/hook.mjs +14 -3
  84. package/build-module/border-box-control/border-box-control/hook.mjs.map +2 -2
  85. package/build-module/border-box-control/border-box-control-linked-button/hook.mjs +2 -2
  86. package/build-module/border-box-control/border-box-control-linked-button/hook.mjs.map +2 -2
  87. package/build-module/border-box-control/border-box-control-split-controls/hook.mjs +2 -2
  88. package/build-module/border-box-control/border-box-control-split-controls/hook.mjs.map +2 -2
  89. package/build-module/border-box-control/border-box-control-visualizer/hook.mjs +2 -2
  90. package/build-module/border-box-control/border-box-control-visualizer/hook.mjs.map +2 -2
  91. package/build-module/color-palette/index.mjs +1 -1
  92. package/build-module/color-palette/index.mjs.map +2 -2
  93. package/build-module/composite/context.mjs +6 -1
  94. package/build-module/composite/context.mjs.map +2 -2
  95. package/build-module/composite/group-label.mjs +5 -1
  96. package/build-module/composite/group-label.mjs.map +2 -2
  97. package/build-module/composite/group.mjs +11 -3
  98. package/build-module/composite/group.mjs.map +2 -2
  99. package/build-module/composite/item.mjs +4 -0
  100. package/build-module/composite/item.mjs.map +2 -2
  101. package/build-module/context/context-system-provider.mjs +2 -5
  102. package/build-module/context/context-system-provider.mjs.map +3 -3
  103. package/build-module/context/use-context-system.mjs +7 -2
  104. package/build-module/context/use-context-system.mjs.map +2 -2
  105. package/build-module/disabled/index.mjs +1 -1
  106. package/build-module/disabled/index.mjs.map +2 -2
  107. package/build-module/draggable/index.mjs.map +1 -1
  108. package/build-module/form-token-field/index.mjs +1 -1
  109. package/build-module/form-token-field/index.mjs.map +2 -2
  110. package/build-module/icon/index.mjs +30 -12
  111. package/build-module/icon/index.mjs.map +2 -2
  112. package/build-module/input-control/styles/input-control-styles.mjs +42 -27
  113. package/build-module/input-control/styles/input-control-styles.mjs.map +2 -2
  114. package/build-module/item-group/context.mjs +1 -0
  115. package/build-module/item-group/context.mjs.map +2 -2
  116. package/build-module/item-group/item/component.mjs +12 -0
  117. package/build-module/item-group/item/component.mjs.map +2 -2
  118. package/build-module/item-group/item/hook.mjs +7 -6
  119. package/build-module/item-group/item/hook.mjs.map +2 -2
  120. package/build-module/item-group/item-group/component.mjs +1 -0
  121. package/build-module/item-group/item-group/component.mjs.map +2 -2
  122. package/build-module/item-group/item-group/hook.mjs +1 -1
  123. package/build-module/item-group/item-group/hook.mjs.map +2 -2
  124. package/build-module/progress-bar/index.mjs +1 -1
  125. package/build-module/progress-bar/index.mjs.map +2 -2
  126. package/build-module/resizable-box/index.mjs.map +1 -1
  127. package/build-module/snackbar/index.mjs +8 -5
  128. package/build-module/snackbar/index.mjs.map +2 -2
  129. package/build-module/textarea-control/index.mjs +1 -1
  130. package/build-module/textarea-control/index.mjs.map +2 -2
  131. package/build-module/toggle-group-control/context.mjs +8 -2
  132. package/build-module/toggle-group-control/context.mjs.map +2 -2
  133. package/build-module/toggle-group-control/toggle-group-control/as-button-group.mjs +5 -2
  134. package/build-module/toggle-group-control/toggle-group-control/as-button-group.mjs.map +2 -2
  135. package/build-module/toggle-group-control/toggle-group-control/as-radio-group.mjs +5 -2
  136. package/build-module/toggle-group-control/toggle-group-control/as-radio-group.mjs.map +2 -2
  137. package/build-module/toggle-group-control/toggle-group-control/component.mjs +3 -1
  138. package/build-module/toggle-group-control/toggle-group-control/component.mjs.map +2 -2
  139. package/build-module/toggle-group-control/toggle-group-control-option/component.mjs +2 -0
  140. package/build-module/toggle-group-control/toggle-group-control-option/component.mjs.map +2 -2
  141. package/build-module/toggle-group-control/toggle-group-control-option-base/component.mjs +2 -1
  142. package/build-module/toggle-group-control/toggle-group-control-option-base/component.mjs.map +2 -2
  143. package/build-module/toggle-group-control/toggle-group-control-option-icon/component.mjs +2 -0
  144. package/build-module/toggle-group-control/toggle-group-control-option-icon/component.mjs.map +2 -2
  145. package/build-module/tools-panel/tools-panel/hook.mjs +103 -107
  146. package/build-module/tools-panel/tools-panel/hook.mjs.map +2 -2
  147. package/build-module/tools-panel/tools-panel-item/hook.mjs +35 -35
  148. package/build-module/tools-panel/tools-panel-item/hook.mjs.map +2 -2
  149. package/build-module/tree-grid/cell.mjs +5 -1
  150. package/build-module/tree-grid/cell.mjs.map +2 -2
  151. package/build-module/tree-grid/roving-tab-index-item.mjs +5 -1
  152. package/build-module/tree-grid/roving-tab-index-item.mjs.map +2 -2
  153. package/build-module/tree-grid/row.mjs +7 -2
  154. package/build-module/tree-grid/row.mjs.map +2 -2
  155. package/build-module/tree-grid/use-validate-tree-grid-structure.mjs +56 -0
  156. package/build-module/tree-grid/use-validate-tree-grid-structure.mjs.map +7 -0
  157. package/build-module/utils/colors-values.mjs +3 -2
  158. package/build-module/utils/colors-values.mjs.map +2 -2
  159. package/build-style/style-rtl.css +27 -85
  160. package/build-style/style.css +27 -85
  161. package/build-types/border-box-control/border-box-control/component.d.ts +4 -0
  162. package/build-types/border-box-control/border-box-control/component.d.ts.map +1 -1
  163. package/build-types/border-box-control/border-box-control/hook.d.ts +4 -2
  164. package/build-types/border-box-control/border-box-control/hook.d.ts.map +1 -1
  165. package/build-types/border-box-control/types.d.ts +6 -2
  166. package/build-types/border-box-control/types.d.ts.map +1 -1
  167. package/build-types/composite/context.d.ts +2 -0
  168. package/build-types/composite/context.d.ts.map +1 -1
  169. package/build-types/composite/group-label.d.ts.map +1 -1
  170. package/build-types/composite/group.d.ts.map +1 -1
  171. package/build-types/composite/item.d.ts.map +1 -1
  172. package/build-types/composite/types.d.ts +1 -1
  173. package/build-types/context/context-system-provider.d.ts +8 -6
  174. package/build-types/context/context-system-provider.d.ts.map +1 -1
  175. package/build-types/context/use-context-system.d.ts.map +1 -1
  176. package/build-types/icon/index.d.ts.map +1 -1
  177. package/build-types/input-control/styles/input-control-styles.d.ts.map +1 -1
  178. package/build-types/item-group/context.d.ts.map +1 -1
  179. package/build-types/item-group/item/component.d.ts.map +1 -1
  180. package/build-types/item-group/item/hook.d.ts +1 -1
  181. package/build-types/item-group/item/hook.d.ts.map +1 -1
  182. package/build-types/item-group/item-group/component.d.ts.map +1 -1
  183. package/build-types/item-group/types.d.ts +4 -0
  184. package/build-types/item-group/types.d.ts.map +1 -1
  185. package/build-types/popover/types.d.ts +6 -2
  186. package/build-types/popover/types.d.ts.map +1 -1
  187. package/build-types/snackbar/index.d.ts.map +1 -1
  188. package/build-types/toggle-group-control/context.d.ts +2 -2
  189. package/build-types/toggle-group-control/context.d.ts.map +1 -1
  190. package/build-types/toggle-group-control/toggle-group-control/as-button-group.d.ts +1 -1
  191. package/build-types/toggle-group-control/toggle-group-control/as-button-group.d.ts.map +1 -1
  192. package/build-types/toggle-group-control/toggle-group-control/as-radio-group.d.ts +1 -1
  193. package/build-types/toggle-group-control/toggle-group-control/as-radio-group.d.ts.map +1 -1
  194. package/build-types/toggle-group-control/toggle-group-control/component.d.ts.map +1 -1
  195. package/build-types/toggle-group-control/toggle-group-control-option/component.d.ts +2 -2
  196. package/build-types/toggle-group-control/toggle-group-control-option/component.d.ts.map +1 -1
  197. package/build-types/toggle-group-control/toggle-group-control-option-base/component.d.ts.map +1 -1
  198. package/build-types/toggle-group-control/toggle-group-control-option-icon/component.d.ts +2 -2
  199. package/build-types/toggle-group-control/toggle-group-control-option-icon/component.d.ts.map +1 -1
  200. package/build-types/toggle-group-control/types.d.ts +12 -3
  201. package/build-types/toggle-group-control/types.d.ts.map +1 -1
  202. package/build-types/tools-panel/tools-panel/hook.d.ts +4 -4
  203. package/build-types/tools-panel/tools-panel/hook.d.ts.map +1 -1
  204. package/build-types/tools-panel/tools-panel-item/hook.d.ts.map +1 -1
  205. package/build-types/tools-panel/types.d.ts +52 -9
  206. package/build-types/tools-panel/types.d.ts.map +1 -1
  207. package/build-types/tree-grid/cell.d.ts.map +1 -1
  208. package/build-types/tree-grid/roving-tab-index-item.d.ts.map +1 -1
  209. package/build-types/tree-grid/row.d.ts.map +1 -1
  210. package/build-types/tree-grid/use-validate-tree-grid-structure.d.ts +2 -0
  211. package/build-types/tree-grid/use-validate-tree-grid-structure.d.ts.map +1 -0
  212. package/build-types/utils/colors-values.d.ts.map +1 -1
  213. package/emotion-to-scss-modules.md +149 -0
  214. package/package.json +28 -27
  215. package/src/alignment-matrix-control/test/{index.tsx → index.jsdom.test.tsx} +5 -4
  216. package/src/angle-picker-control/style.module.scss +1 -1
  217. package/src/autocomplete/test/get-autocomplete-match.ts +2 -1
  218. package/src/autocomplete/test/{index.tsx → index.jsdom.test.tsx} +2 -1
  219. package/src/badge/test/{index.tsx → index.jsdom.test.tsx} +1 -0
  220. package/src/base-control/stories/index.story.tsx +2 -2
  221. package/src/base-control/test/{index.tsx → index.jsdom.test.tsx} +1 -0
  222. package/src/border-box-control/border-box-control/README.md +5 -3
  223. package/src/border-box-control/border-box-control/component.tsx +83 -15
  224. package/src/border-box-control/border-box-control/hook.ts +19 -1
  225. package/src/border-box-control/stories/index.story.tsx +10 -0
  226. package/src/border-box-control/style.module.scss +34 -3
  227. package/src/border-box-control/test/{index.tsx → index.jsdom.test.tsx} +138 -2
  228. package/src/border-box-control/test/utils.ts +11 -0
  229. package/src/border-box-control/types.ts +6 -2
  230. package/src/border-control/test/{index.js → index.jsdom.test.tsx} +44 -24
  231. package/src/box-control/test/{index.tsx → index.jsdom.test.tsx} +6 -3
  232. package/src/button/style.scss +0 -6
  233. package/src/button/test/{index.tsx → index.jsdom.test.tsx} +6 -2
  234. package/src/checkbox-control/stories/index.story.tsx +1 -1
  235. package/src/checkbox-control/style.scss +2 -2
  236. package/src/checkbox-control/test/__snapshots__/{index.tsx.snap → index.jsdom.test.tsx.snap} +10 -7
  237. package/src/checkbox-control/test/{index.tsx → index.jsdom.test.tsx} +4 -1
  238. package/src/circular-option-picker/test/{index.tsx → index.jsdom.test.tsx} +1 -0
  239. package/src/color-indicator/test/__snapshots__/{index.tsx.snap → index.jsdom.test.tsx.snap} +2 -2
  240. package/src/color-indicator/test/{index.tsx → index.jsdom.test.tsx} +1 -0
  241. package/src/color-palette/index.tsx +1 -1
  242. package/src/color-palette/test/{index.tsx → index.jsdom.test.tsx} +25 -22
  243. package/src/color-palette/test/{utils.ts → utils.jsdom.test.ts} +1 -0
  244. package/src/combobox-control/test/{index.tsx → index.jsdom.test.tsx} +12 -7
  245. package/src/composite/README.md +1 -1
  246. package/src/composite/context.tsx +6 -0
  247. package/src/composite/group-label.tsx +8 -1
  248. package/src/composite/group.tsx +9 -2
  249. package/src/composite/item.tsx +6 -0
  250. package/src/composite/legacy/test/{index.tsx → index.jsdom.test.tsx} +6 -13
  251. package/src/composite/test/{index.tsx → index.jsdom.test.tsx} +60 -17
  252. package/src/composite/types.ts +1 -1
  253. package/src/confirm-dialog/test/{index.tsx → index.jsdom.test.tsx} +16 -14
  254. package/src/content-editable-control/test/{index.tsx → index.jsdom.test.tsx} +4 -3
  255. package/src/context/{context-system-provider.js → context-system-provider.tsx} +19 -5
  256. package/src/context/stories/{ComponentsProvider.stories.jsx → ComponentsProvider.stories.tsx} +2 -2
  257. package/src/context/test/context-connect.tsx +1 -2
  258. package/src/context/test/{context-system-provider.js → context-system-provider.jsdom.test.tsx} +93 -51
  259. package/src/context/test/wordpress-component.tsx +1 -2
  260. package/src/context/use-context-system.js +13 -4
  261. package/src/custom-gradient-picker/gradient-bar/test/{utils.ts → utils.jsdom.test.ts} +6 -5
  262. package/src/custom-gradient-picker/test/serializer.ts +1 -0
  263. package/src/custom-select-control-v2/test/{index.tsx → index.jsdom.test.tsx} +4 -1
  264. package/src/date-time/date-picker/test/{index.tsx → index.jsdom.test.tsx} +6 -3
  265. package/src/date-time/date-picker/test/{use-lilius.ts → use-lilius.jsdom.test.ts} +1 -0
  266. package/src/date-time/date-time/test/{index.tsx → index.jsdom.test.tsx} +20 -11
  267. package/src/date-time/test/utils.test.ts +9 -0
  268. package/src/date-time/time-picker/test/{index.tsx → index.jsdom.test.tsx} +37 -27
  269. package/src/date-time/time-picker/time-input/test/{index.tsx → index.jsdom.test.tsx} +8 -3
  270. package/src/disabled/index.tsx +7 -7
  271. package/src/disabled/test/{index.tsx → index.jsdom.test.tsx} +26 -0
  272. package/src/draggable/index.tsx +1 -1
  273. package/src/draggable/style.module.scss +1 -1
  274. package/src/dropdown/style.module.scss +2 -2
  275. package/src/dropdown/test/{index.tsx → index.jsdom.test.tsx} +1 -0
  276. package/src/dropdown-menu/test/{index.tsx → index.jsdom.test.tsx} +5 -4
  277. package/src/duotone-picker/test/{custom-duotone-bar.tsx → custom-duotone-bar.jsdom.test.tsx} +11 -8
  278. package/src/duotone-picker/test/{index.tsx → index.jsdom.test.tsx} +11 -10
  279. package/src/external-link/test/{index.tsx → index.jsdom.test.tsx} +5 -4
  280. package/src/focal-point-picker/test/{index.tsx → index.jsdom.test.tsx} +12 -9
  281. package/src/focal-point-picker/test/{media.tsx → media.jsdom.test.tsx} +1 -0
  282. package/src/font-size-picker/test/{font-size-picker-select.tsx → font-size-picker-select.jsdom.test.tsx} +19 -16
  283. package/src/font-size-picker/test/{font-size-picker-toggle-group.tsx → font-size-picker-toggle-group.jsdom.test.tsx} +13 -11
  284. package/src/font-size-picker/test/{index.tsx → index.jsdom.test.tsx} +19 -14
  285. package/src/font-size-picker/test/utils.ts +1 -0
  286. package/src/form-toggle/style.scss +1 -1
  287. package/src/form-toggle/test/__snapshots__/{index.tsx.snap → index.jsdom.test.tsx.snap} +4 -4
  288. package/src/form-toggle/test/{index.tsx → index.jsdom.test.tsx} +2 -1
  289. package/src/form-token-field/index.tsx +1 -1
  290. package/src/form-token-field/test/{index.tsx → index.jsdom.test.tsx} +86 -44
  291. package/src/gradient-picker/test/{index.tsx → index.jsdom.test.tsx} +12 -9
  292. package/src/guide/test/{index.tsx → index.jsdom.test.tsx} +4 -2
  293. package/src/h-stack/test/__snapshots__/{index.tsx.snap → index.jsdom.test.tsx.snap} +4 -4
  294. package/src/h-stack/test/{index.tsx → index.jsdom.test.tsx} +3 -0
  295. package/src/higher-order/with-fallback-styles/test/{index.tsx → index.jsdom.test.tsx} +3 -2
  296. package/src/higher-order/with-filters/test/__snapshots__/index.jsdom.test.tsx.snap +87 -0
  297. package/src/higher-order/with-filters/test/{index.tsx → index.jsdom.test.tsx} +6 -5
  298. package/src/higher-order/with-focus-outside/test/{index.tsx → index.jsdom.test.tsx} +2 -1
  299. package/src/higher-order/with-focus-return/test/{index.tsx → index.jsdom.test.tsx} +1 -0
  300. package/src/higher-order/with-notices/test/{index.tsx → index.jsdom.test.tsx} +1 -0
  301. package/src/higher-order/with-spoken-messages/test/{index.tsx → index.jsdom.test.tsx} +3 -2
  302. package/src/icon/index.tsx +28 -12
  303. package/src/icon/test/{index.tsx → index.jsdom.test.tsx} +66 -0
  304. package/src/input-control/styles/input-control-styles.tsx +38 -7
  305. package/src/input-control/test/{index.js → index.jsdom.test.tsx} +16 -10
  306. package/src/isolated-event-container/test/{index.tsx → index.jsdom.test.tsx} +4 -3
  307. package/src/item-group/context.ts +1 -0
  308. package/src/item-group/item/component.tsx +19 -1
  309. package/src/item-group/item/hook.ts +2 -3
  310. package/src/item-group/item-group/component.tsx +1 -0
  311. package/src/item-group/style.module.scss +1 -2
  312. package/src/item-group/test/__snapshots__/{index.js.snap → index.jsdom.test.tsx.snap} +9 -9
  313. package/src/item-group/test/{index.js → index.jsdom.test.tsx} +99 -2
  314. package/src/item-group/types.ts +4 -0
  315. package/src/keyboard-shortcuts/test/{index.tsx → index.jsdom.test.tsx} +5 -4
  316. package/src/menu/test/{index.tsx → index.jsdom.test.tsx} +67 -23
  317. package/src/menu-group/test/{index.tsx → index.jsdom.test.tsx} +1 -0
  318. package/src/menu-item/test/__snapshots__/{index.js.snap → index.jsdom.test.tsx.snap} +5 -5
  319. package/src/menu-item/test/{index.js → index.jsdom.test.tsx} +1 -0
  320. package/src/modal/test/{aria-helper.ts → aria-helper.jsdom.test.ts} +1 -0
  321. package/src/modal/test/{index.tsx → index.jsdom.test.tsx} +9 -5
  322. package/src/navigable-container/test/{navigable-menu.tsx → navigable-menu.jsdom.test.tsx} +7 -5
  323. package/src/navigable-container/test/{tababble-container.tsx → tababble-container.jsdom.test.tsx} +5 -3
  324. package/src/navigator/test/{index.tsx → index.jsdom.test.tsx} +5 -2
  325. package/src/navigator/test/router.ts +1 -0
  326. package/src/notice/test/__snapshots__/{index.tsx.snap → index.jsdom.test.tsx.snap} +7 -4
  327. package/src/notice/test/{index.tsx → index.jsdom.test.tsx} +8 -4
  328. package/src/notice/test/{list.tsx → list.jsdom.test.tsx} +1 -0
  329. package/src/number-control/test/{index.tsx → index.jsdom.test.tsx} +9 -6
  330. package/src/palette-edit/test/{index.tsx → index.jsdom.test.tsx} +16 -13
  331. package/src/panel/test/__snapshots__/body.jsdom.test.tsx.snap +9 -0
  332. package/src/panel/test/__snapshots__/header.jsdom.test.tsx.snap +9 -0
  333. package/src/panel/test/__snapshots__/index.jsdom.test.tsx.snap +17 -0
  334. package/src/panel/test/__snapshots__/row.jsdom.test.tsx.snap +17 -0
  335. package/src/panel/test/{body.tsx → body.jsdom.test.tsx} +2 -1
  336. package/src/panel/test/{header.tsx → header.jsdom.test.tsx} +1 -0
  337. package/src/panel/test/{index.tsx → index.jsdom.test.tsx} +1 -0
  338. package/src/panel/test/{row.tsx → row.jsdom.test.tsx} +1 -0
  339. package/src/placeholder/style.scss +0 -5
  340. package/src/placeholder/test/{index.tsx → index.jsdom.test.tsx} +19 -14
  341. package/src/popover/README.md +2 -2
  342. package/src/popover/test/{index.tsx → index.jsdom.test.tsx} +23 -0
  343. package/src/popover/types.ts +9 -3
  344. package/src/progress-bar/style.module.scss +9 -1
  345. package/src/query-controls/test/terms.ts +1 -0
  346. package/src/radio-control/test/{index.tsx → index.jsdom.test.tsx} +14 -11
  347. package/src/range-control/test/{index.tsx → index.jsdom.test.tsx} +10 -7
  348. package/src/resizable-box/index.tsx +1 -1
  349. package/src/sandbox/test/{index.tsx → index.jsdom.test.tsx} +1 -0
  350. package/src/scroll-lock/test/{index.tsx → index.jsdom.test.tsx} +1 -0
  351. package/src/shortcut/test/__snapshots__/index.jsdom.test.tsx.snap +15 -0
  352. package/src/shortcut/test/{index.tsx → index.jsdom.test.tsx} +1 -0
  353. package/src/slot-fill/test/{index.js → index.jsdom.test.tsx} +4 -3
  354. package/src/slot-fill/test/{slot.js → slot.jsdom.test.tsx} +17 -9
  355. package/src/snackbar/index.tsx +10 -6
  356. package/src/snackbar/test/{index.tsx → index.jsdom.test.tsx} +41 -11
  357. package/src/snackbar/test/list.jsdom.test.tsx +100 -0
  358. package/src/style-provider/test/{index.tsx → index.jsdom.test.tsx} +1 -0
  359. package/src/surface/style.module.scss +2 -2
  360. package/src/surface/test/{index.tsx → index.jsdom.test.tsx} +1 -0
  361. package/src/tab-panel/style.scss +4 -23
  362. package/src/tab-panel/test/{index.tsx → index.jsdom.test.tsx} +22 -21
  363. package/src/tabs/test/{index.tsx → index.jsdom.test.tsx} +21 -19
  364. package/src/text-control/test/{text-control.tsx → text-control.jsdom.test.tsx} +1 -0
  365. package/src/text-highlight/test/{index.tsx → index.jsdom.test.tsx} +1 -0
  366. package/src/textarea-control/style.module.scss +9 -4
  367. package/src/tip/style.scss +1 -1
  368. package/src/toggle-control/test/{index.tsx → index.jsdom.test.tsx} +4 -1
  369. package/src/toggle-group-control/context.ts +14 -5
  370. package/src/toggle-group-control/stories/index.story.tsx +15 -2
  371. package/src/toggle-group-control/test/{index.tsx → index.jsdom.test.tsx} +283 -9
  372. package/src/toggle-group-control/toggle-group-control/README.md +8 -5
  373. package/src/toggle-group-control/toggle-group-control/as-button-group.tsx +4 -0
  374. package/src/toggle-group-control/toggle-group-control/as-radio-group.tsx +4 -0
  375. package/src/toggle-group-control/toggle-group-control/component.tsx +2 -0
  376. package/src/toggle-group-control/toggle-group-control/style.module.scss +8 -2
  377. package/src/toggle-group-control/toggle-group-control-option/README.md +8 -5
  378. package/src/toggle-group-control/toggle-group-control-option/component.tsx +2 -0
  379. package/src/toggle-group-control/toggle-group-control-option-base/component.tsx +4 -1
  380. package/src/toggle-group-control/toggle-group-control-option-icon/README.md +7 -0
  381. package/src/toggle-group-control/toggle-group-control-option-icon/component.tsx +2 -0
  382. package/src/toggle-group-control/types.ts +12 -3
  383. package/src/toolbar/test/{index.tsx → index.jsdom.test.tsx} +1 -0
  384. package/src/toolbar/test/{toolbar-group.tsx → toolbar-group.jsdom.test.tsx} +2 -1
  385. package/src/tools-panel/test/{index.tsx → index.jsdom.test.tsx} +595 -47
  386. package/src/tools-panel/tools-panel/hook.ts +195 -180
  387. package/src/tools-panel/tools-panel-item/README.md +42 -1
  388. package/src/tools-panel/tools-panel-item/hook.ts +97 -69
  389. package/src/tools-panel/types.ts +54 -9
  390. package/src/tooltip/test/{index.tsx → index.jsdom.test.tsx} +5 -3
  391. package/src/tree-grid/cell.tsx +6 -2
  392. package/src/tree-grid/roving-tab-index-item.tsx +7 -3
  393. package/src/tree-grid/row.tsx +8 -2
  394. package/src/tree-grid/test/__snapshots__/{cell.tsx.snap → cell.jsdom.test.tsx.snap} +2 -2
  395. package/src/tree-grid/test/__snapshots__/index.jsdom.test.tsx.snap +3 -0
  396. package/src/tree-grid/test/__snapshots__/roving-tab-index-item.jsdom.test.tsx.snap +27 -0
  397. package/src/tree-grid/test/__snapshots__/roving-tab-index.jsdom.test.tsx.snap +9 -0
  398. package/src/tree-grid/test/__snapshots__/{row.tsx.snap → row.jsdom.test.tsx.snap} +9 -5
  399. package/src/tree-grid/test/cell.jsdom.test.tsx +149 -0
  400. package/src/tree-grid/test/{index.tsx → index.jsdom.test.tsx} +9 -7
  401. package/src/tree-grid/test/{roving-tab-index-item.tsx → roving-tab-index-item.jsdom.test.tsx} +4 -1
  402. package/src/tree-grid/test/{roving-tab-index.tsx → roving-tab-index.jsdom.test.tsx} +1 -0
  403. package/src/tree-grid/test/row.jsdom.test.tsx +132 -0
  404. package/src/tree-grid/use-validate-tree-grid-structure.ts +104 -0
  405. package/src/truncate/style.module.scss +0 -1
  406. package/src/truncate/test/{index.tsx → index.jsdom.test.tsx} +1 -0
  407. package/src/unit-control/test/{index.tsx → index.jsdom.test.tsx} +22 -19
  408. package/src/unit-control/test/utils.ts +1 -0
  409. package/src/utils/colors-values.js +5 -4
  410. package/src/utils/hooks/test/{use-controlled-state.js → use-controlled-state.jsdom.test.tsx} +22 -7
  411. package/src/utils/hooks/test/{use-controlled-value.js → use-controlled-value.jsdom.test.tsx} +6 -3
  412. package/src/utils/hooks/test/{use-cx.js → use-cx.jsdom.test.tsx} +8 -5
  413. package/src/utils/test/colors.js +1 -0
  414. package/src/utils/test/{get-node-text.js → get-node-text.tsx} +1 -0
  415. package/src/utils/test/math.js +1 -0
  416. package/src/utils/test/rtl.js +1 -0
  417. package/src/utils/test/space.js +1 -0
  418. package/src/utils/test/strings.js +1 -0
  419. package/src/utils/test/unit-values.js +1 -0
  420. package/src/v-stack/test/__snapshots__/{index.tsx.snap → index.jsdom.test.tsx.snap} +4 -4
  421. package/src/v-stack/test/{index.tsx → index.jsdom.test.tsx} +3 -0
  422. package/src/validated-form-controls/style.scss +11 -15
  423. package/src/validated-form-controls/test/{content-editable-control.tsx → content-editable-control.jsdom.test.tsx} +1 -1
  424. package/src/validated-form-controls/test/{custom-select-control.tsx → custom-select-control.jsdom.test.tsx} +3 -0
  425. package/src/validated-form-controls/test/{input-control.tsx → input-control.jsdom.test.tsx} +3 -0
  426. package/src/validated-form-controls/test/{range-control.tsx → range-control.jsdom.test.tsx} +3 -0
  427. package/src/validated-form-controls/test/{textarea-control.tsx → textarea-control.jsdom.test.tsx} +1 -0
  428. package/src/view/test/__snapshots__/{index.js.snap → index.jsdom.test.tsx.snap} +7 -7
  429. package/src/view/test/{index.js → index.jsdom.test.tsx} +7 -2
  430. package/src/context/test/__snapshots__/context-system-provider.js.snap +0 -35
  431. package/src/higher-order/with-filters/test/__snapshots__/index.tsx.snap +0 -87
  432. package/src/panel/test/__snapshots__/body.tsx.snap +0 -9
  433. package/src/panel/test/__snapshots__/header.tsx.snap +0 -9
  434. package/src/panel/test/__snapshots__/index.tsx.snap +0 -17
  435. package/src/panel/test/__snapshots__/row.tsx.snap +0 -17
  436. package/src/shortcut/test/__snapshots__/index.tsx.snap +0 -15
  437. package/src/snackbar/test/list.tsx +0 -39
  438. package/src/toggle-group-control/test/__snapshots__/index.tsx.snap +0 -359
  439. package/src/tree-grid/test/__snapshots__/index.tsx.snap +0 -3
  440. package/src/tree-grid/test/__snapshots__/roving-tab-index-item.tsx.snap +0 -27
  441. package/src/tree-grid/test/__snapshots__/roving-tab-index.tsx.snap +0 -9
  442. package/src/tree-grid/test/cell.tsx +0 -46
  443. package/src/tree-grid/test/row.tsx +0 -37
  444. /package/src/card/test/__snapshots__/{index.tsx.snap → index.jsdom.test.tsx.snap} +0 -0
  445. /package/src/card/test/{index.tsx → index.jsdom.test.tsx} +0 -0
  446. /package/src/color-picker/test/{index.tsx → index.jsdom.test.tsx} +0 -0
  447. /package/src/custom-gradient-picker/test/{index.tsx → index.jsdom.test.tsx} +0 -0
  448. /package/src/custom-select-control/test/__snapshots__/{index.tsx.snap → index.jsdom.test.tsx.snap} +0 -0
  449. /package/src/custom-select-control/test/{index.tsx → index.jsdom.test.tsx} +0 -0
  450. /package/src/divider/test/{index.tsx → index.jsdom.test.tsx} +0 -0
  451. /package/src/elevation/test/__snapshots__/{index.tsx.snap → index.jsdom.test.tsx.snap} +0 -0
  452. /package/src/elevation/test/{index.tsx → index.jsdom.test.tsx} +0 -0
  453. /package/src/flex/test/__snapshots__/{index.tsx.snap → index.jsdom.test.tsx.snap} +0 -0
  454. /package/src/flex/test/{index.tsx → index.jsdom.test.tsx} +0 -0
  455. /package/src/form-file-upload/test/{index.tsx → index.jsdom.test.tsx} +0 -0
  456. /package/src/grid/test/{grid.tsx → grid.jsdom.test.tsx} +0 -0
  457. /package/src/heading/test/__snapshots__/{index.tsx.snap → index.jsdom.test.tsx.snap} +0 -0
  458. /package/src/heading/test/{index.tsx → index.jsdom.test.tsx} +0 -0
  459. /package/src/input-control/test/__snapshots__/{index.js.snap → index.jsdom.test.tsx.snap} +0 -0
  460. /package/src/progress-bar/test/{index.tsx → index.jsdom.test.tsx} +0 -0
  461. /package/src/resizable-box/resize-tooltip/test/{index.tsx → index.jsdom.test.tsx} +0 -0
  462. /package/src/resizable-box/resize-tooltip/test/{label.tsx → label.jsdom.test.tsx} +0 -0
  463. /package/src/scrollable/test/{index.tsx → index.jsdom.test.tsx} +0 -0
  464. /package/src/search-control/test/{index.tsx → index.jsdom.test.tsx} +0 -0
  465. /package/src/select-control/test/__snapshots__/{select-control.tsx.snap → select-control.jsdom.test.tsx.snap} +0 -0
  466. /package/src/select-control/test/{select-control.tsx → select-control.jsdom.test.tsx} +0 -0
  467. /package/src/slot-fill/test/__snapshots__/{slot.js.snap → slot.jsdom.test.tsx.snap} +0 -0
  468. /package/src/spacer/test/__snapshots__/{index.tsx.snap → index.jsdom.test.tsx.snap} +0 -0
  469. /package/src/spacer/test/{index.tsx → index.jsdom.test.tsx} +0 -0
  470. /package/src/text/test/__snapshots__/{index.tsx.snap → index.jsdom.test.tsx.snap} +0 -0
  471. /package/src/text/test/{index.tsx → index.jsdom.test.tsx} +0 -0
  472. /package/src/utils/test/{polymorphic-element.tsx → polymorphic-element.jsdom.test.tsx} +0 -0
@@ -0,0 +1,149 @@
1
+ # Migrating components from Emotion to SCSS Modules
2
+
3
+ An Emotion migration should preserve the component's public API, rendered elements, interactions, and resulting styles. Use the existing SCSS Modules and `@wordpress/style-runtime` pipeline. A component migration does not require a new CSS extraction pipeline.
4
+
5
+ This temporary guide collects the [migration guidelines in #66806](https://github.com/WordPress/gutenberg/issues/66806) and lessons from its merged PRs. The [reference migrations](#reference-migrations) identify useful examples and their limitations. Read the current implementation before copying an older diff. Some merged PRs include explicitly agreed visual or behavior changes that are not part of a mechanical migration.
6
+
7
+ ## Establish the component and consumer contract
8
+
9
+ Before editing, read the component's types, README, stories, style hooks, and composed primitives. Search for consumers of its exported wrappers, style fragments, and public classes, including outside `packages/components`. A shared style file can serve several components, as with `ColorPalette` and `GradientPicker`.
10
+
11
+ Record the behavior that needs comparison:
12
+
13
+ - Default rendered element, supported `as` targets, refs, prop forwarding, and intrinsic HTML/SVG prop filtering.
14
+ - Existing public classes and which element adds each one. `useContextSystem` may already supply the root class. Keep legacy spellings, including singular `component-*` names.
15
+ - Consumer `className` and `style` handling, including spread order and conditional forwarding.
16
+ - Defaults, variants, nested instances, focus and disabled states, directional styles, and animation state changes.
17
+ - Selectors that override the component and selectors the component uses to override its children.
18
+ - Portal and iframe paths, including the document that receives styles.
19
+
20
+ Capture a focused baseline in Storybook or the editor for the states and consumers at risk. If a regression motivates the migration, reproduce it before changing the implementation. An unchanged isolated story cannot establish compatibility for a consumer that adds its own styles.
21
+
22
+ ### Replace wrappers without changing their role
23
+
24
+ For `styled( ExistingComponent )`, apply the module class to that same component when it already supplies the needed behavior. Do not replace `Flex`, `Heading`, `Text`, or an input control with a native element just because its wrapper only contained CSS.
25
+
26
+ For a wrapper that relied directly on Emotion's `as` support, inspect and reuse [`PolymorphicElement`](/packages/components/src/utils/polymorphic-element.tsx). Preserve the original default tag and the component's existing ref and prop contract. `View` already delegates to this utility. A fixed internal element with no exposed polymorphic path can remain a plain element.
27
+
28
+ Check the current [`WordPressComponent` types](/packages/components/src/context/wordpress-component.ts) instead of introducing another polymorphic type system. [#80705](https://github.com/WordPress/gutenberg/pull/80705) deliberately reduced intrinsic-prop type complexity to control compilation cost. Removing Emotion does not remove the compatibility obligation for `as`.
29
+
30
+ Keep shared wrappers' private module classes separate from public classes added by callers. In [#80001](https://github.com/WordPress/gutenberg/pull/80001), adding BaseControl label/help classes inside shared wrappers made unrelated consumers match BaseControl selectors; adding classes on both sides also produced duplicates. Test a real `as="legend"` consumer when migrating shared labels, including its visible and visually hidden branches.
31
+
32
+ `View`'s legacy `css` prop is an explicit exception established by [#79443](https://github.com/WordPress/gutenberg/pull/79443). It remains accepted as a no-op and is consumed before rendering. Do not restore its styling behavior, forward it to the DOM, or treat that decision as permission to remove other props.
33
+
34
+ ## Translate the styles
35
+
36
+ Follow the package's [styling conventions](/packages/components/CONTRIBUTING.md#styling). Put static declarations in a colocated `style.module.scss`, use kebab-case classes, and compose variants with `clsx`. Use existing semantic attributes and pseudo-classes where appropriate; do not add data attributes solely to select styles.
37
+
38
+ Name modifier classes for their state and make their owning element clear. When scoping a modifier under a base class, account for the extra specificity. A standalone `.is-active` and `.root.is-active` do not have equal specificity.
39
+
40
+ Keep fixed values and calculations in Sass when JavaScript does not need them. `ProgressBar` moved its constant indeterminate indicator width into Sass and shared it with the keyframes. CSS Module keyframes can have descriptive local names without component prefixes.
41
+
42
+ ### Preserve dynamic values and overrides
43
+
44
+ Prefer inline custom properties for genuinely dynamic values, with the actual property declared in the stylesheet. Emotion adds units to many numeric CSS values; custom properties do not. Preserve the original conversion, such as `space( value )` or an explicit `px` suffix, and keep zero distinct from an absent value.
45
+
46
+ Check these differences before choosing the representation:
47
+
48
+ - **Cascade.** Moving `z-index` from a CSS rule to `style.zIndex` prevents ordinary consumer CSS from overriding it. [ZStack's review](https://github.com/WordPress/gutenberg/pull/80514#discussion_r3845479235) led to passing the number through a custom property and keeping `z-index` in the module rule.
49
+ - **Precedence.** Preserve consumer `style` values and the component's existing override rules. Do not copy a universal spread order from another migration. `Spacer` lets consumer custom properties win; `Flex` supplies its internal variables after the consumer style object. Ordinary inline CSS properties still override stylesheet declarations.
50
+ - **Inheritance.** Internal custom properties inherit unless reset. Reset them on each instance when nested components must not inherit a parent's values, as in `Spacer`. Do not reset intentionally inherited theme variables.
51
+ - **Shorthands.** A later longhand with an absent custom property can undo the shorthand result. Preserve the fallback and precedence of all-sides, axis, and individual-side values. Check shorthand-only, longhand-only, and combined inputs, including supported CSS strings. The `Spacer` review caught missing longhand fallbacks.
52
+ - **Accepted CSS values.** CSS-wide keywords apply to a custom property itself. `--style: inherit; border-style: var(--style, solid)` does not mean `border-style: inherit`. In [#80437](https://github.com/WordPress/gutenberg/pull/80437#discussion_r3608442254), the indicator retained direct `borderStyle` and `borderColor` values and the original `color || fallback` behavior. Check empty strings and CSS-wide values when the API accepts arbitrary CSS. Direct inline properties can be appropriate, but assess their effect on consumer overrides explicitly.
53
+
54
+ ### Preserve direction, tokens, and state changes
55
+
56
+ Use logical properties where they preserve the intended behavior. Compare LTR and RTL, and relevant writing modes, before changing a physical property. `Surface` exposes physical border props; `TextareaControl` retains `resize: vertical`; `DropdownContentWrapper` offsets the Popover's physical padding. These are reasons for narrow lint suppressions, not a blanket ban on logical properties. `padding: 0` also resets more than `padding-inline: 0`.
57
+
58
+ Preserve necessary browser declarations and pseudo-elements. Examples include Truncate's `-webkit-box-orient: vertical`, SearchControl's WebKit search decorations, and TextareaControl's placeholder selectors and global dark-theme ancestor.
59
+
60
+ Prefer equivalent WPDS tokens and existing Sass variables or mixins. Check what they expand to, including fallback values and validated-control accent overrides. A similarly named token can change the color or geometry. Keep intentional visual changes explicit, as in `Surface` and `ToggleGroupControl`, instead of presenting them as style parity.
61
+
62
+ Compare transitions between states as well as static states. [ProgressBar's review](https://github.com/WordPress/gutenberg/pull/80512#discussion_r3830324726) caught a width transition newly applying when switching to indeterminate mode. Preserve the original animation direction, duration, transition conditions, reduced-motion rules, and forced-colors treatment where present.
63
+
64
+ ## Preserve the winning declarations
65
+
66
+ CSS Module scoping prevents class-name collisions. It does not control specificity or stylesheet insertion order. The order of class names passed to `clsx` does not control the cascade either.
67
+
68
+ Compare matched declarations on the component and its consumers:
69
+
70
+ - Keep low-specificity defaults overridable. The Divider migration added attribute-selector specificity and broke `CardDivider` and complementary-area border overrides. [#79534](https://github.com/WordPress/gutenberg/pull/79534) wrapped orientation attributes in `:where()` to remove that extra specificity.
71
+ - Preserve deliberate Emotion `&&` or `&&&` increases. Sass can express these as `.wrapper.wrapper` or `.label.label.label`; raw `&&&` is not valid Dart Sass. ConfirmDialog's doubled selector keeps its overlay above the parent Popover.
72
+ - Protect every declaration that must beat a still-Emotion-based primitive. ColorPalette initially protected `font-size` and margin but missed `line-height`; Heading's later rule won in the root document. ToolsPanel had a similar heading conflict.
73
+ - Keep selector relationships and scope. Nesting `.layered > .child` under `.z-stack` adds specificity. Replacing Emotion component interpolation requires checking which real elements the new selector matches.
74
+ - Mark existing external classes with `:global(...)` inside a CSS Module, for example `:global(.components-panel__row) &`. Do not accidentally hash a legacy class, broaden its matching elements, or add a public class solely to replace an internal selector.
75
+
76
+ Use the smallest selector change that preserves the required winner. Do not put every rule in `:where()`, increase every selector, or add `!important` to hide an unexplained conflict. Record why a deliberate specificity adjustment is needed.
77
+
78
+ ### Audit remaining Emotion consumers
79
+
80
+ Removing an Emotion wrapper can expose source-order dependencies in callers that still use `useCx`. Where fragments overlap on a property, shorthand/longhand pair, or nested selector, compose them in one `css()` call before passing them to `cx()`:
81
+
82
+ ```js
83
+ const classes = cx(
84
+ css( baseStyles, condition && overrideStyles ),
85
+ className
86
+ );
87
+ ```
88
+
89
+ This is an interim compatibility fix for existing Emotion consumers, not a pattern for newly migrated styles. Do not mechanically combine unrelated fragments. Audit callers outside the migrated directory: [#79443](https://github.com/WordPress/gutenberg/pull/79443) updated several consumers but missed the BorderBoxControl gutter regression fixed in [#79967](https://github.com/WordPress/gutenberg/pull/79967).
90
+
91
+ Test mixed Emotion/Module composition in both the root and iframe documents when used there. [Scrollable's review](https://github.com/WordPress/gutenberg/pull/80694#discussion_r3845389532) found that `CardBody isScrollable` kept `height: 100%` in an iframe but resolved to `auto` in the root document because insertion order differed.
92
+
93
+ ## Verify CSS delivery and behavior
94
+
95
+ Reuse existing tests and stories first. Add or extend a test only for a concrete migration risk that existing coverage does not protect. Choose the smallest check that observes the behavior and would fail if it regressed. A behavior-preserving migration may need no new tests. When automated tests cannot observe the real styling path, document a focused before/after browser check instead of adding a test that cannot detect the regression.
96
+
97
+ [`StyleProvider`](/packages/components/src/style-provider/index.tsx) both supplies an Emotion cache and registers a document with `@wordpress/style-runtime`. Keep the relevant provider and registration path while either responsibility is needed. Removing a component's Emotion import does not make this infrastructure redundant.
98
+
99
+ For SlotFill or portals, use an attached iframe with a real `contentDocument` and `defaultView`. Assert a computed style in the destination document. A class on the element or a style tag in the parent document does not prove delivery. [#80384](https://github.com/WordPress/gutenberg/pull/80384) pairs a registration test with a Storybook example that renders migrated Spacer styles in an iframe.
100
+
101
+ Match the evidence to the behavior at risk:
102
+
103
+ | What needs proof | Suitable evidence |
104
+ | --- | --- |
105
+ | DOM, public classes, refs, props, variants, and interactions | Existing component tests; extend them only for an uncovered integration risk. Assert the actual styled node, including the outer Grid child when an inner wrapper has the same class. |
106
+ | Real SCSS declarations, specificity, custom-property resolution, dimensions, and animation | Storybook or editor comparison using production styles, computed styles, and relevant interactions. Include a consumer override and the affected document/RTL states. |
107
+ | Cross-document registration or a particular mixed-style composition mechanism | Reuse shared registration coverage. Add a component-specific test only for an uncovered registration or composition risk. A small fixture stylesheet can test that mechanism; pair it with a real-style check. |
108
+
109
+ Jest mocks stylesheet imports. Public-class assertions can protect a compatibility contract; private module-class assertions establish wiring only. Neither proves that the real CSS loads or wins. Do not add class assertions or snapshots solely to mirror the implementation. Do not inject the desired production selector into a test and claim it protects that selector: the test would still pass if the SCSS rule were deleted. This distinction led to removing tests in [#81792](https://github.com/WordPress/gutenberg/pull/81792#discussion_r3811746653).
110
+
111
+ Keep new tests focused on the migrated component's integration. A wrapper test can protect its `as`, ref, or prop forwarding even when the shared helper is already tested. Do not copy PolymorphicElement's entire filtering suite into every wrapper, duplicate a full editor setup for a small assertion, or introduce a new Storybook testing convention just for the migration. Check the relevant states and combinations from the contract audit; do not generate every prop combination or supported tag. When a real-style assertion is unavailable, state the gap and give reproducible manual steps.
112
+
113
+ ## Complete the migration
114
+
115
+ Remove obsolete Emotion imports, files, lint/type suppressions, and utilities that become unused because of this migration. Search all consumers before deleting shared constants. Preserve useful comments explaining layout calculations or compatibility workarounds. Leave unrelated cleanup and global Emotion dependencies, Babel wiring, serializers, `useCx`, and the `rtl` utility until their remaining users are gone.
116
+
117
+ Follow [worktree setup](/docs/contributors/code/getting-started-with-code-contribution.md#set-up-each-worktree) and read the [testing overview](/docs/contributors/code/testing-overview.md) before adding tests. New tests use Vitest; the [migration manifest](/test/unit/test-migration.json) identifies legacy tests still owned by Jest. From the repository root, use the appropriate focused command below, then run lint and the repository gates:
118
+
119
+ ```sh
120
+ npm run test:unit -- <legacy-jest-test-path> --runInBand
121
+ npm run test:unit:vitest -- <vitest-test-path>
122
+ npm run lint:js -- <changed-js-files>
123
+ npm run lint:css
124
+ npm run typecheck
125
+ npm run build
126
+ npm run test:unit:update
127
+ npm run test:unit:vitest:update
128
+ git diff --check
129
+ ```
130
+
131
+ The snapshot updates are for the whole repository because downstream consumers can change. Jest and Vitest own separate suites, so neither command covers both. Review every snapshot change for removed or duplicated public classes, changed elements, forwarded props, and consumer output. Do not accept a snapshot solely because Emotion classes disappeared. Rerun the exact failing file or shard before attributing a broad failure to the migration.
132
+
133
+ For generated-file verification, start from a clean committed worktree and run the applicable generators and suppression updates. `npm run other:check-local-changes` runs the documentation and theme generators through its npm prehook, then checks the unstaged diff. It does not run every generator or suppression update; consult the current [static-checks workflow](/.github/workflows/static-checks.yml) for those required by the migration. Inspect generated changes before staging them, since staged changes are invisible to the checker. Commit required updates, then repeat the applicable generation and check commands from the clean worktree.
134
+
135
+ Recheck the final selectors and consumers after the last edit or rebase; a previous visual check does not cover a later specificity change. Preserve newer trunk changes, such as focus-ring updates, when moving declarations out of an old style file.
136
+
137
+ Follow [package changelog guidance](/docs/contributors/code/managing-packages.md). For production migrations, use the current unreleased section and this PR's link. The merged migrations record the `cx()` composition limitation under **Breaking Changes**, even when normal component usage is visually unchanged. Do not copy an early pilot's `Internal` entry as the only release note. Record any separately agreed behavior change and its verification independently, as #80715 did for Reset focus retention.
138
+
139
+ ## Reference migrations
140
+
141
+ Choose an example for the risk in the current migration, then check it against current source. The full migration history is maintained in [#66806](https://github.com/WordPress/gutenberg/issues/66806).
142
+
143
+ | Migration risk | Merged example | What to inspect |
144
+ | --- | --- | --- |
145
+ | Polymorphic wrappers | View, [#79443](https://github.com/WordPress/gutenberg/pull/79443) | Shared polymorphic replacement, legacy `css` no-op, and downstream consumers. |
146
+ | Dynamic spacing | Spacer, [#79449](https://github.com/WordPress/gutenberg/pull/79449) | Nested custom-property resets and shorthand/longhand fallback. |
147
+ | Selector specificity | Divider, [#79444](https://github.com/WordPress/gutenberg/pull/79444), and [#79534](https://github.com/WordPress/gutenberg/pull/79534) | Orientation selectors and the correction that restored consumer overrides. |
148
+ | Mixed Emotion composition | Border controls, [#80437](https://github.com/WordPress/gutenberg/pull/80437) | Overrides, CSS-wide values, and the linked-control gutter. |
149
+ | Root and iframe delivery | Scrollable, [#80694](https://github.com/WordPress/gutenberg/pull/80694) | CardBody height and stylesheet insertion order in each document. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/components",
3
- "version": "40.0.0",
3
+ "version": "40.1.0",
4
4
  "description": "UI components for WordPress.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -62,27 +62,27 @@
62
62
  "@types/gradient-parser": "^1.1.0",
63
63
  "@types/highlight-words-core": "^1.2.1",
64
64
  "@use-gesture/react": "^10.3.1",
65
- "@wordpress/a11y": "^4.54.0",
66
- "@wordpress/base-styles": "^13.0.0",
67
- "@wordpress/compose": "^8.7.0",
68
- "@wordpress/date": "^5.54.0",
69
- "@wordpress/deprecated": "^4.54.0",
70
- "@wordpress/dom": "^4.54.0",
71
- "@wordpress/element": "^8.6.0",
72
- "@wordpress/escape-html": "^3.54.0",
73
- "@wordpress/hooks": "^4.54.0",
74
- "@wordpress/html-entities": "^4.54.0",
75
- "@wordpress/i18n": "^6.27.0",
76
- "@wordpress/icons": "^15.5.0",
77
- "@wordpress/is-shallow-equal": "^5.54.0",
78
- "@wordpress/kebab-case": "^1.1.0",
79
- "@wordpress/keycodes": "^4.54.0",
80
- "@wordpress/primitives": "^4.54.0",
81
- "@wordpress/private-apis": "^1.54.0",
82
- "@wordpress/rich-text": "^7.54.0",
83
- "@wordpress/style-runtime": "^0.10.0",
84
- "@wordpress/ui": "^0.21.0",
85
- "@wordpress/warning": "^3.54.0",
65
+ "@wordpress/a11y": "^4.55.0",
66
+ "@wordpress/base-styles": "^13.1.0",
67
+ "@wordpress/compose": "^8.8.0",
68
+ "@wordpress/date": "^5.55.0",
69
+ "@wordpress/deprecated": "^4.55.0",
70
+ "@wordpress/dom": "^4.55.0",
71
+ "@wordpress/element": "^8.7.0",
72
+ "@wordpress/escape-html": "^3.55.0",
73
+ "@wordpress/hooks": "^4.55.0",
74
+ "@wordpress/html-entities": "^4.55.0",
75
+ "@wordpress/i18n": "^6.28.0",
76
+ "@wordpress/icons": "^16.0.0",
77
+ "@wordpress/is-shallow-equal": "^5.55.0",
78
+ "@wordpress/kebab-case": "^1.2.0",
79
+ "@wordpress/keycodes": "^4.55.0",
80
+ "@wordpress/primitives": "^4.55.0",
81
+ "@wordpress/private-apis": "^1.55.0",
82
+ "@wordpress/rich-text": "^7.55.0",
83
+ "@wordpress/style-runtime": "^0.11.0",
84
+ "@wordpress/ui": "^0.22.0",
85
+ "@wordpress/warning": "^3.55.0",
86
86
  "change-case": "^4.1.2",
87
87
  "clsx": "^2.1.1",
88
88
  "colord": "^2.9.3",
@@ -106,16 +106,17 @@
106
106
  "@storybook/addon-docs": "^10.5.3",
107
107
  "@storybook/react-vite": "^10.5.3",
108
108
  "@testing-library/dom": "^10.4.1",
109
- "@testing-library/jest-dom": "^6.9.1",
110
- "@testing-library/react": "^16.3.2",
109
+ "@testing-library/jest-dom": "^7.0.1",
110
+ "@testing-library/react": "^16.3.3",
111
111
  "@testing-library/user-event": "^14.6.1",
112
112
  "@types/jest": "^30.0.0",
113
113
  "@types/react-dom": "^18.3.1",
114
- "@wordpress/jest-console": "^9.2.0",
114
+ "@wordpress/jest-console": "^9.3.0",
115
115
  "@wordpress/stylelint-tools": "file:../../tools/stylelint",
116
116
  "snapshot-diff": "^0.10.0",
117
117
  "storybook": "^10.5.3",
118
- "timezone-mock": "^1.3.6"
118
+ "timezone-mock": "^1.3.6",
119
+ "vitest": "^5.0.0"
119
120
  },
120
121
  "peerDependencies": {
121
122
  "@types/react": "^18 || ^19",
@@ -130,5 +131,5 @@
130
131
  "publishConfig": {
131
132
  "access": "public"
132
133
  },
133
- "gitHead": "5ef24f60261976bb198ad035db24bf81c1fb92b4"
134
+ "gitHead": "485f42ae8a1c58ceea18371a507fd4acfa86fbd8"
134
135
  }
@@ -1,3 +1,4 @@
1
+ import { describe, expect, it, vi } from 'vitest';
1
2
  import { render, screen, waitFor, within } from '@testing-library/react';
2
3
  import { press, click } from '@ariakit/test';
3
4
  import AlignmentMatrixControl from '..';
@@ -51,7 +52,7 @@ describe( 'AlignmentMatrixControl', () => {
51
52
  'bottom center',
52
53
  'bottom right',
53
54
  ] )( '%s', async ( alignment ) => {
54
- const spy = jest.fn();
55
+ const spy = vi.fn();
55
56
 
56
57
  await renderAndInitCompositeStore(
57
58
  <AlignmentMatrixControl
@@ -69,7 +70,7 @@ describe( 'AlignmentMatrixControl', () => {
69
70
  } );
70
71
 
71
72
  it( 'unless already focused', async () => {
72
- const spy = jest.fn();
73
+ const spy = vi.fn();
73
74
 
74
75
  await renderAndInitCompositeStore(
75
76
  <AlignmentMatrixControl
@@ -96,7 +97,7 @@ describe( 'AlignmentMatrixControl', () => {
96
97
  [ 'ArrowDown', 'bottom center' ],
97
98
  [ 'ArrowRight', 'center right' ],
98
99
  ] as const )( '%s', async ( keyRef, cellRef ) => {
99
- const spy = jest.fn();
100
+ const spy = vi.fn();
100
101
 
101
102
  await renderAndInitCompositeStore(
102
103
  <AlignmentMatrixControl onChange={ spy } />
@@ -117,7 +118,7 @@ describe( 'AlignmentMatrixControl', () => {
117
118
  [ 'ArrowDown', 'bottom right' ],
118
119
  [ 'ArrowRight', 'bottom right' ],
119
120
  ] as const )( '%s', async ( keyRef, cellRef ) => {
120
- const spy = jest.fn();
121
+ const spy = vi.fn();
121
122
 
122
123
  await renderAndInitCompositeStore(
123
124
  <AlignmentMatrixControl onChange={ spy } />
@@ -31,7 +31,7 @@
31
31
  border-radius: $radius-round;
32
32
  box-sizing: border-box;
33
33
  display: block;
34
- /* stylelint-disable-next-line plugin/use-logical-properties-and-values -- Geometric centering */
34
+ /* stylelint-disable-next-line logical-css/require-logical-properties -- Geometric centering */
35
35
  left: 50%;
36
36
  top: 4px;
37
37
  transform: translateX(-50%);
@@ -1,3 +1,4 @@
1
+ import { describe, expect, it, vi } from 'vitest';
1
2
  import { getAutocompleteMatch } from '../get-autocomplete-match';
2
3
  import type { WPCompleter } from '../types';
3
4
 
@@ -174,7 +175,7 @@ describe( 'getAutocompleteMatch', () => {
174
175
  } );
175
176
 
176
177
  it( 'should pass correct before/after text to allowContext', () => {
177
- const allowContext = jest.fn().mockReturnValue( true );
178
+ const allowContext = vi.fn().mockReturnValue( true );
178
179
  const completers = [
179
180
  createCompleter( {
180
181
  triggerPrefix: '@',
@@ -1,3 +1,4 @@
1
+ import { describe, expect, it, vi } from 'vitest';
1
2
  import { render, screen, renderHook } from '@testing-library/react';
2
3
  import userEvent from '@testing-library/user-event';
3
4
  import { useRef } from '@wordpress/element';
@@ -119,7 +120,7 @@ describe( 'AutocompleterUI', () => {
119
120
  it( 'should call reset function when a click on another element occurs', async () => {
120
121
  const user = userEvent.setup();
121
122
 
122
- const resetSpy = jest.fn();
123
+ const resetSpy = vi.fn();
123
124
 
124
125
  const autocompleter = {
125
126
  name: 'fruit',
@@ -1,3 +1,4 @@
1
+ import { describe, expect, it } from 'vitest';
1
2
  import { render, screen } from '@testing-library/react';
2
3
  import _Badge from '..';
3
4
 
@@ -19,9 +19,9 @@ const meta: Meta< typeof BaseControl > = {
19
19
  controls: { expanded: true },
20
20
  docs: { canvas: { sourceState: 'shown' } },
21
21
  componentStatus: {
22
- status: 'recommended',
22
+ status: 'not-recommended',
23
23
  whereUsed: 'global',
24
- notes: '`BaseControl.VisualLabel` can continue to be used for now. To add labels and descriptions semantically, use [`Field`](?path=/docs/design-system-components-form-primitives-field--docs) in `@wordpress/ui`.',
24
+ notes: 'Use [`Field`](?path=/docs/design-system-components-form-primitives-field--docs) from `@wordpress/ui` instead. For a purely visual label outside `Field.Root`, use `Field.VisualLabel`. For a group legend, use [`Fieldset`](?path=/docs/design-system-components-form-primitives-fieldset--docs) and `Fieldset.Legend`.',
25
25
  },
26
26
  },
27
27
  };
@@ -1,3 +1,4 @@
1
+ import { describe, expect, it } from 'vitest';
1
2
  import { render, screen } from '@testing-library/react';
2
3
  import BaseControl from '..';
3
4
  import { useBaseControlProps } from '../hooks';
@@ -103,7 +103,9 @@ Provides control over whether the label will only be visible to screen readers.
103
103
 
104
104
  ### `label`: `string`
105
105
 
106
- If provided, a label will be generated using this as the content.
106
+ If provided, a label will be generated using this as the content. A visible
107
+ label is rendered in a row alongside the linked/unlinked toggle; when there is
108
+ no visible label the toggle is rendered beside the border inputs instead.
107
109
 
108
110
  _Whether it is visible only to screen readers is controlled via
109
111
  `hideLabelFromVision`._
@@ -130,9 +132,9 @@ The available base placements are 'top', 'right', 'bottom', 'left'. Each of thes
130
132
 
131
133
  - Required: No
132
134
 
133
- ### `popoverOffset`: `number`
135
+ ### `popoverOffset`: `number | { mainAxis?: number; crossAxis?: number }`
134
136
 
135
- The space between the popover and the control wrapper.
137
+ The space between the popover and the control wrapper. A number displaces the popover along its main axis; pass an object to also displace it along its cross axis.
136
138
 
137
139
  - Required: No
138
140
 
@@ -1,10 +1,11 @@
1
1
  import { __ } from '@wordpress/i18n';
2
2
  import { useMemo, useState } from '@wordpress/element';
3
- import { useMergeRefs } from '@wordpress/compose';
3
+ import { useInstanceId, useMergeRefs } from '@wordpress/compose';
4
4
  import BorderBoxControlLinkedButton from '../border-box-control-linked-button';
5
5
  import BorderBoxControlSplitControls from '../border-box-control-split-controls';
6
6
  import { BorderControl } from '../../border-control';
7
- import { StyledLabel } from '../../base-control/styles/base-control-styles';
7
+ import BaseControl from '../../base-control';
8
+ import { Grid } from '../../grid';
8
9
  import { View } from '../../view';
9
10
  import { VisuallyHidden } from '../../visually-hidden';
10
11
  import type { WordPressComponentProps } from '../../context';
@@ -16,17 +17,22 @@ import type {
16
17
  BorderControlProps,
17
18
  } from '../../border-control/types';
18
19
 
19
- const BorderLabel = ( props: LabelProps ) => {
20
- const { label, hideLabelFromVision } = props;
20
+ const BorderLabel = ( props: LabelProps & { id?: string } ) => {
21
+ const { id, label, hideLabelFromVision } = props;
21
22
 
22
23
  if ( ! label ) {
23
24
  return null;
24
25
  }
25
26
 
27
+ // The visible label is rendered as `BaseControl.VisualLabel` so it carries
28
+ // the stable `.components-base-control__label` className consumers style
29
+ // against; `StyledLabel` is an emotion component with a generated one.
26
30
  return hideLabelFromVision ? (
27
- <VisuallyHidden as="label">{ label }</VisuallyHidden>
31
+ <VisuallyHidden as="span" id={ id }>
32
+ { label }
33
+ </VisuallyHidden>
28
34
  ) : (
29
- <StyledLabel>{ label }</StyledLabel>
35
+ <BaseControl.VisualLabel id={ id }>{ label }</BaseControl.VisualLabel>
30
36
  );
31
37
  };
32
38
 
@@ -42,6 +48,8 @@ const UnconnectedBorderBoxControl = (
42
48
  enableAlpha,
43
49
  enableStyle,
44
50
  hasMixedBorders,
51
+ hasVisibleLabel,
52
+ headerClassName,
45
53
  hideLabelFromVision,
46
54
  isLinked,
47
55
  label,
@@ -55,9 +63,27 @@ const UnconnectedBorderBoxControl = (
55
63
  toggleLinked,
56
64
  wrapperClassName,
57
65
  __experimentalIsRenderedInSidebar,
66
+ 'aria-label': ariaLabel,
67
+ 'aria-labelledby': ariaLabelledBy,
58
68
  ...otherProps
59
69
  } = useBorderBoxControl( props );
60
70
 
71
+ // The label names the group of border controls rather than a single input,
72
+ // so it is associated via `aria-labelledby` instead of `htmlFor`.
73
+ const generatedLabelId = useInstanceId(
74
+ BorderBoxControl,
75
+ 'border-box-control-label'
76
+ );
77
+
78
+ // A consumer-provided accessible name takes precedence, so the generated
79
+ // relationship is only used as a fallback. Where an external name wins,
80
+ // the unused ID is left off the internal label. Empty values are treated
81
+ // as absent, matching the accessible name computation, which skips an
82
+ // empty `aria-label` or `aria-labelledby` rather than resolving a name
83
+ // from it.
84
+ const labelId =
85
+ label && ! ariaLabel && ! ariaLabelledBy ? generatedLabelId : undefined;
86
+
61
87
  // Use internal state instead of a ref to make sure that the component
62
88
  // re-renders when the popover's anchor updates.
63
89
  const [ popoverAnchor, setPopoverAnchor ] = useState< Element | null >(
@@ -80,12 +106,46 @@ const UnconnectedBorderBoxControl = (
80
106
  );
81
107
 
82
108
  const mergedRef = useMergeRefs( [ setPopoverAnchor, forwardedRef ] );
109
+
83
110
  return (
84
- <View className={ className } { ...otherProps } ref={ mergedRef }>
85
- <BorderLabel
86
- label={ label }
87
- hideLabelFromVision={ hideLabelFromVision }
88
- />
111
+ <View
112
+ className={ className }
113
+ // Whichever naming source wins, it describes the border controls
114
+ // as a whole, so the wrapper needs a role for that accessible
115
+ // name to attach to. Without a name there is no group to expose.
116
+ role={ label || ariaLabel || ariaLabelledBy ? 'group' : undefined }
117
+ aria-label={ ariaLabel }
118
+ aria-labelledby={ ariaLabelledBy ?? labelId }
119
+ { ...otherProps }
120
+ ref={ mergedRef }
121
+ >
122
+ { hasVisibleLabel ? (
123
+ // The toggle shares the label's row so that it lines up with
124
+ // the equivalent toggle on sibling controls, e.g. the border
125
+ // radius one.
126
+ <Grid
127
+ className={ headerClassName }
128
+ columns={ 2 }
129
+ templateColumns="1fr min-content"
130
+ alignment="center"
131
+ >
132
+ <BorderLabel
133
+ id={ labelId }
134
+ label={ label }
135
+ hideLabelFromVision={ hideLabelFromVision }
136
+ />
137
+ <BorderBoxControlLinkedButton
138
+ onClick={ toggleLinked }
139
+ isLinked={ isLinked }
140
+ />
141
+ </Grid>
142
+ ) : (
143
+ <BorderLabel
144
+ id={ labelId }
145
+ label={ label }
146
+ hideLabelFromVision={ hideLabelFromVision }
147
+ />
148
+ ) }
89
149
  <View className={ wrapperClassName }>
90
150
  { isLinked ? (
91
151
  <BorderControl
@@ -123,10 +183,14 @@ const UnconnectedBorderBoxControl = (
123
183
  }
124
184
  />
125
185
  ) }
126
- <BorderBoxControlLinkedButton
127
- onClick={ toggleLinked }
128
- isLinked={ isLinked }
129
- />
186
+ { /* With no label row to join, the toggle sits alongside the
187
+ inputs instead. */ }
188
+ { ! hasVisibleLabel && (
189
+ <BorderBoxControlLinkedButton
190
+ onClick={ toggleLinked }
191
+ isLinked={ isLinked }
192
+ />
193
+ ) }
130
194
  </View>
131
195
  </View>
132
196
  );
@@ -136,6 +200,10 @@ const UnconnectedBorderBoxControl = (
136
200
  * An input control for the color, style, and width of the border of a box. The
137
201
  * border can be customized as a whole, or individually for each side of the box.
138
202
  *
203
+ * The controls are exposed as a group named by the `label` prop. Passing
204
+ * `aria-labelledby` or `aria-label` names the group instead, taking precedence
205
+ * over `label`; with none of the three, the wrapper is not exposed as a group.
206
+ *
139
207
  * ```jsx
140
208
  * import { BorderBoxControl } from '@wordpress/components';
141
209
  * import { __ } from '@wordpress/i18n';
@@ -1,3 +1,4 @@
1
+ import clsx from 'clsx';
1
2
  import { useState } from '@wordpress/element';
2
3
  import styles from '../style.module.scss';
3
4
  import {
@@ -23,6 +24,8 @@ export function useBorderBoxControl(
23
24
  onChange,
24
25
  enableAlpha = false,
25
26
  enableStyle = true,
27
+ hideLabelFromVision,
28
+ label,
26
29
  value,
27
30
  __experimentalIsRenderedInSidebar = false,
28
31
  // Deprecated props, no longer used.
@@ -31,6 +34,11 @@ export function useBorderBoxControl(
31
34
  ...otherProps
32
35
  } = useContextSystem( props, 'BorderBoxControl' );
33
36
 
37
+ // A visible label gives the control a header row to place the linked/
38
+ // unlinked toggle in, alongside that label. Without one the toggle stays
39
+ // inside the input wrapper, positioned absolutely.
40
+ const hasVisibleLabel = !! label && ! hideLabelFromVision;
41
+
34
42
  const mixedBorders = hasMixedBorders( value );
35
43
  const splitBorders = hasSplitBorders( value );
36
44
 
@@ -99,13 +107,23 @@ export function useBorderBoxControl(
99
107
  }
100
108
  };
101
109
 
102
- const linkedControlClassName = styles[ 'linked-border-control' ];
110
+ // The linked control reserves an inline-end gutter for the absolutely
111
+ // positioned toggle. Once the toggle moves up into the header row that
112
+ // gutter only shortens the input, so drop it.
113
+ const linkedControlClassName = clsx( styles[ 'linked-border-control' ], {
114
+ [ styles[ 'linked-border-control-full-width' ] ]: hasVisibleLabel,
115
+ } );
103
116
  const wrapperClassName = styles.wrapper;
117
+ const headerClassName = styles.header;
104
118
 
105
119
  return {
106
120
  ...otherProps,
107
121
  className,
108
122
  colors,
123
+ hasVisibleLabel,
124
+ headerClassName,
125
+ hideLabelFromVision,
126
+ label,
109
127
  disableUnits: mixedBorders && ! hasWidthValue,
110
128
  enableAlpha,
111
129
  enableStyle,
@@ -79,3 +79,13 @@ Default.args = {
79
79
  label: 'Borders',
80
80
  enableStyle: true,
81
81
  };
82
+
83
+ /**
84
+ * With no visible label there is no label row for the linked/unlinked toggle to
85
+ * join, so it is rendered alongside the border inputs instead.
86
+ */
87
+ export const WithoutLabel = Template.bind( {} );
88
+ WithoutLabel.args = {
89
+ ...Default.args,
90
+ label: undefined,
91
+ };