@wordpress/block-editor 16.0.0 → 16.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 (368) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +8 -0
  3. package/build/components/alignment-control/ui.cjs +2 -4
  4. package/build/components/alignment-control/ui.cjs.map +2 -2
  5. package/build/components/background-image-control/index.cjs +40 -10
  6. package/build/components/background-image-control/index.cjs.map +2 -2
  7. package/build/components/block-alignment-control/ui.cjs +5 -3
  8. package/build/components/block-alignment-control/ui.cjs.map +2 -2
  9. package/build/components/block-inspector/index.cjs +73 -46
  10. package/build/components/block-inspector/index.cjs.map +2 -2
  11. package/build/components/block-list/block-html.cjs +4 -1
  12. package/build/components/block-list/block-html.cjs.map +2 -2
  13. package/build/components/block-list/use-block-props/index.cjs +5 -2
  14. package/build/components/block-list/use-block-props/index.cjs.map +2 -2
  15. package/build/components/block-list/use-block-props/use-focus-handler.cjs +2 -3
  16. package/build/components/block-list/use-block-props/use-focus-handler.cjs.map +2 -2
  17. package/build/components/block-list/use-block-props/use-register-block-event-handlers.cjs +48 -0
  18. package/build/components/block-list/use-block-props/use-register-block-event-handlers.cjs.map +7 -0
  19. package/build/components/block-parent-selector/index.cjs +2 -2
  20. package/build/components/block-parent-selector/index.cjs.map +2 -2
  21. package/build/components/block-toolbar/switch-section-style.cjs +4 -2
  22. package/build/components/block-toolbar/switch-section-style.cjs.map +3 -3
  23. package/build/components/block-tools/empty-block-inserter.cjs +2 -7
  24. package/build/components/block-tools/empty-block-inserter.cjs.map +2 -2
  25. package/build/components/block-vertical-alignment-control/ui.cjs +3 -5
  26. package/build/components/block-vertical-alignment-control/ui.cjs.map +2 -2
  27. package/build/components/border-radius-control/utils.cjs +8 -58
  28. package/build/components/border-radius-control/utils.cjs.map +2 -2
  29. package/build/components/colors-gradients/control.cjs +5 -3
  30. package/build/components/colors-gradients/control.cjs.map +2 -2
  31. package/build/components/dimension-control/index.cjs +30 -20
  32. package/build/components/dimension-control/index.cjs.map +3 -3
  33. package/build/components/dimensions-tool/aspect-ratio-tool.cjs +9 -2
  34. package/build/components/dimensions-tool/aspect-ratio-tool.cjs.map +3 -3
  35. package/build/components/editor-styles/index.cjs +9 -1
  36. package/build/components/editor-styles/index.cjs.map +2 -2
  37. package/build/components/global-styles/background-panel.cjs +66 -25
  38. package/build/components/global-styles/background-panel.cjs.map +3 -3
  39. package/build/components/global-styles/border-panel.cjs +153 -55
  40. package/build/components/global-styles/border-panel.cjs.map +3 -3
  41. package/build/components/global-styles/color-gradient-dropdown-item.cjs +46 -14
  42. package/build/components/global-styles/color-gradient-dropdown-item.cjs.map +3 -3
  43. package/build/components/global-styles/color-panel.cjs +63 -17
  44. package/build/components/global-styles/color-panel.cjs.map +2 -2
  45. package/build/components/global-styles/dimensions-panel.cjs +242 -64
  46. package/build/components/global-styles/dimensions-panel.cjs.map +3 -3
  47. package/build/components/global-styles/filters-panel.cjs +59 -20
  48. package/build/components/global-styles/filters-panel.cjs.map +3 -3
  49. package/build/components/global-styles/hooks.cjs +4 -1
  50. package/build/components/global-styles/hooks.cjs.map +2 -2
  51. package/build/components/global-styles/inheritance/index.cjs +142 -0
  52. package/build/components/global-styles/inheritance/index.cjs.map +7 -0
  53. package/build/components/global-styles/inherited-value-context.cjs +173 -0
  54. package/build/components/global-styles/inherited-value-context.cjs.map +7 -0
  55. package/build/components/global-styles/shadow-panel-components.cjs +38 -20
  56. package/build/components/global-styles/shadow-panel-components.cjs.map +2 -2
  57. package/build/components/global-styles/state-control.cjs +1 -1
  58. package/build/components/global-styles/state-control.cjs.map +2 -2
  59. package/build/components/global-styles/typography-panel.cjs +271 -78
  60. package/build/components/global-styles/typography-panel.cjs.map +3 -3
  61. package/build/components/inspector-controls-tabs/styles-tab.cjs +10 -1
  62. package/build/components/inspector-controls-tabs/styles-tab.cjs.map +2 -2
  63. package/build/components/line-height-control/index.cjs +1 -1
  64. package/build/components/line-height-control/index.cjs.map +2 -2
  65. package/build/components/link-control/index.cjs +3 -1
  66. package/build/components/link-control/index.cjs.map +2 -2
  67. package/build/components/preset-input-control/index.cjs +2 -1
  68. package/build/components/preset-input-control/index.cjs.map +2 -2
  69. package/build/components/provider/block-refs-provider.cjs +8 -2
  70. package/build/components/provider/block-refs-provider.cjs.map +2 -2
  71. package/build/components/provider/index.cjs +1 -1
  72. package/build/components/provider/index.cjs.map +2 -2
  73. package/build/components/provider/use-block-sync.cjs +18 -6
  74. package/build/components/provider/use-block-sync.cjs.map +2 -2
  75. package/build/components/provider/use-media-upload-settings.cjs +3 -1
  76. package/build/components/provider/use-media-upload-settings.cjs.map +2 -2
  77. package/build/components/rich-text/index.cjs +23 -13
  78. package/build/components/rich-text/index.cjs.map +2 -2
  79. package/build/components/spacing-sizes-control/utils.cjs +6 -19
  80. package/build/components/spacing-sizes-control/utils.cjs.map +2 -2
  81. package/build/components/text-indent-control/index.cjs +8 -2
  82. package/build/components/text-indent-control/index.cjs.map +2 -2
  83. package/build/components/url-input/index.cjs +303 -344
  84. package/build/components/url-input/index.cjs.map +3 -3
  85. package/build/components/writing-flow/index.cjs +2 -0
  86. package/build/components/writing-flow/index.cjs.map +3 -3
  87. package/build/components/writing-flow/use-arrow-nav.cjs +24 -1
  88. package/build/components/writing-flow/use-arrow-nav.cjs.map +2 -2
  89. package/build/components/writing-flow/use-click-selection.cjs +28 -2
  90. package/build/components/writing-flow/use-click-selection.cjs.map +2 -2
  91. package/build/components/writing-flow/use-editable-root-event-handlers.cjs +179 -0
  92. package/build/components/writing-flow/use-editable-root-event-handlers.cjs.map +7 -0
  93. package/build/components/writing-flow/use-editable-root.cjs +5 -42
  94. package/build/components/writing-flow/use-editable-root.cjs.map +2 -2
  95. package/build/components/writing-flow/use-home-end.cjs +1 -1
  96. package/build/components/writing-flow/use-home-end.cjs.map +2 -2
  97. package/build/components/writing-flow/use-selection-observer.cjs +17 -13
  98. package/build/components/writing-flow/use-selection-observer.cjs.map +2 -2
  99. package/build/components/writing-flow/utils.cjs +3 -14
  100. package/build/components/writing-flow/utils.cjs.map +2 -2
  101. package/build/hooks/background.cjs +13 -10
  102. package/build/hooks/background.cjs.map +2 -2
  103. package/build/hooks/block-style-variation.cjs +4 -45
  104. package/build/hooks/block-style-variation.cjs.map +3 -3
  105. package/build/hooks/border.cjs +19 -4
  106. package/build/hooks/border.cjs.map +2 -2
  107. package/build/hooks/custom-css.cjs +6 -2
  108. package/build/hooks/custom-css.cjs.map +2 -2
  109. package/build/hooks/dimensions.cjs +15 -4
  110. package/build/hooks/dimensions.cjs.map +2 -2
  111. package/build/hooks/duotone.cjs +26 -14
  112. package/build/hooks/duotone.cjs.map +3 -3
  113. package/build/hooks/elements.cjs +14 -4
  114. package/build/hooks/elements.cjs.map +2 -2
  115. package/build/hooks/line-height.cjs +1 -2
  116. package/build/hooks/line-height.cjs.map +2 -2
  117. package/build/hooks/typography.cjs +13 -4
  118. package/build/hooks/typography.cjs.map +2 -2
  119. package/build/private-apis.cjs +9 -5
  120. package/build/private-apis.cjs.map +3 -3
  121. package/build/store/actions.cjs +9 -4
  122. package/build/store/actions.cjs.map +2 -2
  123. package/build/store/private-selectors.cjs +28 -1
  124. package/build/store/private-selectors.cjs.map +2 -2
  125. package/build/store/reducer.cjs +6 -6
  126. package/build/store/reducer.cjs.map +2 -2
  127. package/build/store/selectors.cjs +23 -0
  128. package/build/store/selectors.cjs.map +2 -2
  129. package/build/utils/native-undo.cjs +55 -0
  130. package/build/utils/native-undo.cjs.map +7 -0
  131. package/build-module/components/alignment-control/ui.mjs +2 -4
  132. package/build-module/components/alignment-control/ui.mjs.map +2 -2
  133. package/build-module/components/background-image-control/index.mjs +43 -10
  134. package/build-module/components/background-image-control/index.mjs.map +2 -2
  135. package/build-module/components/block-alignment-control/ui.mjs +5 -3
  136. package/build-module/components/block-alignment-control/ui.mjs.map +2 -2
  137. package/build-module/components/block-inspector/index.mjs +74 -46
  138. package/build-module/components/block-inspector/index.mjs.map +2 -2
  139. package/build-module/components/block-list/block-html.mjs +4 -1
  140. package/build-module/components/block-list/block-html.mjs.map +2 -2
  141. package/build-module/components/block-list/use-block-props/index.mjs +5 -2
  142. package/build-module/components/block-list/use-block-props/index.mjs.map +2 -2
  143. package/build-module/components/block-list/use-block-props/use-focus-handler.mjs +2 -3
  144. package/build-module/components/block-list/use-block-props/use-focus-handler.mjs.map +2 -2
  145. package/build-module/components/block-list/use-block-props/use-register-block-event-handlers.mjs +28 -0
  146. package/build-module/components/block-list/use-block-props/use-register-block-event-handlers.mjs.map +7 -0
  147. package/build-module/components/block-parent-selector/index.mjs +2 -2
  148. package/build-module/components/block-parent-selector/index.mjs.map +2 -2
  149. package/build-module/components/block-toolbar/switch-section-style.mjs +4 -2
  150. package/build-module/components/block-toolbar/switch-section-style.mjs.map +2 -2
  151. package/build-module/components/block-tools/empty-block-inserter.mjs +2 -7
  152. package/build-module/components/block-tools/empty-block-inserter.mjs.map +2 -2
  153. package/build-module/components/block-vertical-alignment-control/ui.mjs +4 -6
  154. package/build-module/components/block-vertical-alignment-control/ui.mjs.map +2 -2
  155. package/build-module/components/border-radius-control/utils.mjs +11 -53
  156. package/build-module/components/border-radius-control/utils.mjs.map +2 -2
  157. package/build-module/components/colors-gradients/control.mjs +5 -3
  158. package/build-module/components/colors-gradients/control.mjs.map +2 -2
  159. package/build-module/components/dimension-control/index.mjs +30 -20
  160. package/build-module/components/dimension-control/index.mjs.map +2 -2
  161. package/build-module/components/dimensions-tool/aspect-ratio-tool.mjs +10 -6
  162. package/build-module/components/dimensions-tool/aspect-ratio-tool.mjs.map +2 -2
  163. package/build-module/components/editor-styles/index.mjs +9 -1
  164. package/build-module/components/editor-styles/index.mjs.map +2 -2
  165. package/build-module/components/global-styles/background-panel.mjs +71 -29
  166. package/build-module/components/global-styles/background-panel.mjs.map +2 -2
  167. package/build-module/components/global-styles/border-panel.mjs +157 -56
  168. package/build-module/components/global-styles/border-panel.mjs.map +2 -2
  169. package/build-module/components/global-styles/color-gradient-dropdown-item.mjs +51 -15
  170. package/build-module/components/global-styles/color-gradient-dropdown-item.mjs.map +2 -2
  171. package/build-module/components/global-styles/color-panel.mjs +63 -17
  172. package/build-module/components/global-styles/color-panel.mjs.map +2 -2
  173. package/build-module/components/global-styles/dimensions-panel.mjs +248 -66
  174. package/build-module/components/global-styles/dimensions-panel.mjs.map +2 -2
  175. package/build-module/components/global-styles/filters-panel.mjs +64 -21
  176. package/build-module/components/global-styles/filters-panel.mjs.map +2 -2
  177. package/build-module/components/global-styles/hooks.mjs +4 -1
  178. package/build-module/components/global-styles/hooks.mjs.map +2 -2
  179. package/build-module/components/global-styles/inheritance/index.mjs +108 -0
  180. package/build-module/components/global-styles/inheritance/index.mjs.map +7 -0
  181. package/build-module/components/global-styles/inherited-value-context.mjs +141 -0
  182. package/build-module/components/global-styles/inherited-value-context.mjs.map +7 -0
  183. package/build-module/components/global-styles/shadow-panel-components.mjs +38 -20
  184. package/build-module/components/global-styles/shadow-panel-components.mjs.map +2 -2
  185. package/build-module/components/global-styles/state-control.mjs +1 -1
  186. package/build-module/components/global-styles/state-control.mjs.map +2 -2
  187. package/build-module/components/global-styles/typography-panel.mjs +276 -79
  188. package/build-module/components/global-styles/typography-panel.mjs.map +2 -2
  189. package/build-module/components/inspector-controls-tabs/styles-tab.mjs +6 -1
  190. package/build-module/components/inspector-controls-tabs/styles-tab.mjs.map +2 -2
  191. package/build-module/components/line-height-control/index.mjs +1 -1
  192. package/build-module/components/line-height-control/index.mjs.map +2 -2
  193. package/build-module/components/link-control/index.mjs +4 -2
  194. package/build-module/components/link-control/index.mjs.map +2 -2
  195. package/build-module/components/preset-input-control/index.mjs +2 -1
  196. package/build-module/components/preset-input-control/index.mjs.map +2 -2
  197. package/build-module/components/provider/block-refs-provider.mjs +8 -2
  198. package/build-module/components/provider/block-refs-provider.mjs.map +2 -2
  199. package/build-module/components/provider/index.mjs +1 -1
  200. package/build-module/components/provider/index.mjs.map +2 -2
  201. package/build-module/components/provider/use-block-sync.mjs +18 -6
  202. package/build-module/components/provider/use-block-sync.mjs.map +2 -2
  203. package/build-module/components/provider/use-media-upload-settings.mjs +3 -1
  204. package/build-module/components/provider/use-media-upload-settings.mjs.map +2 -2
  205. package/build-module/components/rich-text/index.mjs +28 -14
  206. package/build-module/components/rich-text/index.mjs.map +2 -2
  207. package/build-module/components/spacing-sizes-control/utils.mjs +6 -18
  208. package/build-module/components/spacing-sizes-control/utils.mjs.map +2 -2
  209. package/build-module/components/text-indent-control/index.mjs +8 -2
  210. package/build-module/components/text-indent-control/index.mjs.map +2 -2
  211. package/build-module/components/url-input/index.mjs +306 -353
  212. package/build-module/components/url-input/index.mjs.map +2 -2
  213. package/build-module/components/writing-flow/index.mjs +2 -0
  214. package/build-module/components/writing-flow/index.mjs.map +2 -2
  215. package/build-module/components/writing-flow/use-arrow-nav.mjs +24 -1
  216. package/build-module/components/writing-flow/use-arrow-nav.mjs.map +2 -2
  217. package/build-module/components/writing-flow/use-click-selection.mjs +28 -2
  218. package/build-module/components/writing-flow/use-click-selection.mjs.map +2 -2
  219. package/build-module/components/writing-flow/use-editable-root-event-handlers.mjs +154 -0
  220. package/build-module/components/writing-flow/use-editable-root-event-handlers.mjs.map +7 -0
  221. package/build-module/components/writing-flow/use-editable-root.mjs +5 -37
  222. package/build-module/components/writing-flow/use-editable-root.mjs.map +2 -2
  223. package/build-module/components/writing-flow/use-home-end.mjs +1 -1
  224. package/build-module/components/writing-flow/use-home-end.mjs.map +2 -2
  225. package/build-module/components/writing-flow/use-selection-observer.mjs +18 -14
  226. package/build-module/components/writing-flow/use-selection-observer.mjs.map +2 -2
  227. package/build-module/components/writing-flow/utils.mjs +2 -11
  228. package/build-module/components/writing-flow/utils.mjs.map +2 -2
  229. package/build-module/hooks/background.mjs +13 -10
  230. package/build-module/hooks/background.mjs.map +2 -2
  231. package/build-module/hooks/block-style-variation.mjs +8 -44
  232. package/build-module/hooks/block-style-variation.mjs.map +2 -2
  233. package/build-module/hooks/border.mjs +19 -4
  234. package/build-module/hooks/border.mjs.map +2 -2
  235. package/build-module/hooks/custom-css.mjs +7 -3
  236. package/build-module/hooks/custom-css.mjs.map +2 -2
  237. package/build-module/hooks/dimensions.mjs +15 -4
  238. package/build-module/hooks/dimensions.mjs.map +2 -2
  239. package/build-module/hooks/duotone.mjs +20 -9
  240. package/build-module/hooks/duotone.mjs.map +2 -2
  241. package/build-module/hooks/elements.mjs +14 -4
  242. package/build-module/hooks/elements.mjs.map +2 -2
  243. package/build-module/hooks/line-height.mjs +1 -2
  244. package/build-module/hooks/line-height.mjs.map +2 -2
  245. package/build-module/hooks/typography.mjs +13 -4
  246. package/build-module/hooks/typography.mjs.map +2 -2
  247. package/build-module/private-apis.mjs +7 -3
  248. package/build-module/private-apis.mjs.map +2 -2
  249. package/build-module/store/actions.mjs +9 -4
  250. package/build-module/store/actions.mjs.map +2 -2
  251. package/build-module/store/private-selectors.mjs +29 -1
  252. package/build-module/store/private-selectors.mjs.map +2 -2
  253. package/build-module/store/reducer.mjs +6 -6
  254. package/build-module/store/reducer.mjs.map +2 -2
  255. package/build-module/store/selectors.mjs +23 -0
  256. package/build-module/store/selectors.mjs.map +2 -2
  257. package/build-module/utils/native-undo.mjs +29 -0
  258. package/build-module/utils/native-undo.mjs.map +7 -0
  259. package/build-style/content-rtl.css +10 -18
  260. package/build-style/content.css +10 -18
  261. package/build-style/style-rtl.css +150 -7
  262. package/build-style/style.css +150 -7
  263. package/package.json +40 -41
  264. package/src/components/alignment-control/test/__snapshots__/index.js.snap +1 -1
  265. package/src/components/alignment-control/ui.js +2 -4
  266. package/src/components/background-image-control/index.js +74 -37
  267. package/src/components/background-image-control/style.scss +13 -4
  268. package/src/components/block-alignment-control/test/__snapshots__/index.js.snap +3 -3
  269. package/src/components/block-alignment-control/test/index.js +1 -1
  270. package/src/components/block-alignment-control/ui.js +4 -2
  271. package/src/components/block-breadcrumb/test/__snapshots__/index.js.snap +1 -1
  272. package/src/components/block-compare/test/__snapshots__/block-view.js.snap +1 -1
  273. package/src/components/block-icon/test/__snapshots__/index.js.snap +1 -1
  274. package/src/components/block-inspector/index.js +60 -31
  275. package/src/components/block-list/block-html.js +5 -0
  276. package/src/components/block-list/content.scss +1 -11
  277. package/src/components/block-list/use-block-props/index.js +5 -1
  278. package/src/components/block-list/use-block-props/use-focus-handler.js +9 -8
  279. package/src/components/block-list/use-block-props/use-register-block-event-handlers.js +45 -0
  280. package/src/components/block-parent-selector/index.js +5 -2
  281. package/src/components/block-toolbar/switch-section-style.js +5 -2
  282. package/src/components/block-tools/empty-block-inserter.js +3 -7
  283. package/src/components/block-vertical-alignment-control/test/__snapshots__/index.js.snap +3 -3
  284. package/src/components/block-vertical-alignment-control/test/index.js +18 -2
  285. package/src/components/block-vertical-alignment-control/ui.js +3 -5
  286. package/src/components/border-radius-control/test/utils.js +0 -41
  287. package/src/components/border-radius-control/utils.js +16 -107
  288. package/src/components/button-block-appender/content.scss +6 -2
  289. package/src/components/color-palette/test/__snapshots__/control.js.snap +1 -1
  290. package/src/components/colors-gradients/control.js +6 -3
  291. package/src/components/default-block-appender/test/__snapshots__/index.js.snap +1 -1
  292. package/src/components/dimension-control/index.js +13 -1
  293. package/src/components/dimensions-tool/aspect-ratio-tool.js +13 -6
  294. package/src/components/editor-styles/index.js +16 -1
  295. package/src/components/global-styles/background-panel.js +107 -34
  296. package/src/components/global-styles/border-panel.js +219 -48
  297. package/src/components/global-styles/color-gradient-dropdown-item.js +93 -28
  298. package/src/components/global-styles/color-panel.js +92 -11
  299. package/src/components/global-styles/dimensions-panel.js +422 -71
  300. package/src/components/global-styles/filters-panel.js +88 -28
  301. package/src/components/global-styles/hooks.js +8 -1
  302. package/src/components/global-styles/inheritance/index.js +204 -0
  303. package/src/components/global-styles/inheritance/style.scss +184 -0
  304. package/src/components/global-styles/inheritance/test/index.js +281 -0
  305. package/src/components/global-styles/inherited-value-context.js +271 -0
  306. package/src/components/global-styles/shadow-panel-components.js +48 -26
  307. package/src/components/global-styles/state-control.js +2 -2
  308. package/src/components/global-styles/test/background-panel.js +338 -1
  309. package/src/components/global-styles/test/border-panel.js +441 -0
  310. package/src/components/global-styles/test/color-panel.js +182 -2
  311. package/src/components/global-styles/test/dimensions-panel.js +753 -0
  312. package/src/components/global-styles/test/filters-panel.js +194 -0
  313. package/src/components/global-styles/test/inherited-value-context-core.js +98 -0
  314. package/src/components/global-styles/test/inherited-value-context.js +454 -0
  315. package/src/components/global-styles/test/typography-panel-core.js +185 -0
  316. package/src/components/global-styles/test/typography-panel.js +849 -12
  317. package/src/components/global-styles/typography-panel.js +449 -100
  318. package/src/components/image-editor/style.scss +33 -0
  319. package/src/components/inner-blocks/test/__snapshots__/index.js.snap +1 -1
  320. package/src/components/inspector-controls-tabs/styles-tab.js +5 -1
  321. package/src/components/line-height-control/index.js +1 -1
  322. package/src/components/link-control/index.js +7 -2
  323. package/src/components/preset-input-control/index.js +4 -1
  324. package/src/components/provider/block-refs-provider.js +12 -2
  325. package/src/components/provider/index.js +2 -2
  326. package/src/components/provider/use-block-sync.js +18 -6
  327. package/src/components/provider/use-media-upload-settings.js +2 -0
  328. package/src/components/rich-text/index.js +37 -16
  329. package/src/components/spacing-sizes-control/test/utils.js +16 -5
  330. package/src/components/spacing-sizes-control/utils.js +11 -28
  331. package/src/components/text-indent-control/index.js +8 -0
  332. package/src/components/url-input/index.js +363 -431
  333. package/src/components/url-input/test/index.js +592 -0
  334. package/src/components/warning/test/__snapshots__/index.js.snap +1 -1
  335. package/src/components/writing-flow/index.js +2 -0
  336. package/src/components/writing-flow/use-arrow-nav.js +51 -1
  337. package/src/components/writing-flow/use-click-selection.js +54 -3
  338. package/src/components/writing-flow/use-editable-root-event-handlers.js +266 -0
  339. package/src/components/writing-flow/use-editable-root.js +7 -77
  340. package/src/components/writing-flow/use-home-end.js +17 -9
  341. package/src/components/writing-flow/use-selection-observer.js +32 -15
  342. package/src/components/writing-flow/utils.js +3 -24
  343. package/src/hooks/background.js +26 -21
  344. package/src/hooks/block-fields/rich-text/styles.scss +1 -1
  345. package/src/hooks/block-style-variation.js +8 -74
  346. package/src/hooks/border.js +19 -4
  347. package/src/hooks/custom-css.js +15 -6
  348. package/src/hooks/dimensions.js +17 -4
  349. package/src/hooks/duotone.js +26 -8
  350. package/src/hooks/elements.js +14 -3
  351. package/src/hooks/line-height.js +0 -1
  352. package/src/hooks/test/inherited-value-wiring.js +411 -0
  353. package/src/hooks/test/preset-round-trip.js +456 -0
  354. package/src/hooks/typography.js +35 -23
  355. package/src/private-apis.js +11 -2
  356. package/src/store/actions.js +10 -4
  357. package/src/store/private-selectors.js +57 -1
  358. package/src/store/reducer.js +9 -9
  359. package/src/store/selectors.js +56 -8
  360. package/src/store/test/private-selectors.js +160 -1
  361. package/src/style.scss +2 -0
  362. package/src/utils/native-undo.js +56 -0
  363. package/build/components/duotone/utils.cjs +0 -87
  364. package/build/components/duotone/utils.cjs.map +0 -7
  365. package/build-module/components/duotone/utils.mjs +0 -59
  366. package/build-module/components/duotone/utils.mjs.map +0 -7
  367. package/src/components/duotone/utils.js +0 -90
  368. package/src/hooks/test/get-variation-styles-with-ref-values.js +0 -91
@@ -0,0 +1,753 @@
1
+ /* eslint jest/expect-expect: ["warn", { "assertFunctionNames": ["expect", "expectLocalOverride", "expectPlaceholderState"] }] */
2
+ /**
3
+ * External dependencies
4
+ */
5
+ import { render, screen, within } from '@testing-library/react';
6
+ import userEvent from '@testing-library/user-event';
7
+
8
+ /**
9
+ * Internal dependencies
10
+ */
11
+ import DimensionsPanel from '../dimensions-panel';
12
+
13
+ // The inheritance treatment sits behind the
14
+ // `gutenberg-global-styles-inheritance-ui` experiment. Turn it on so these
15
+ // tests exercise the inheriting path.
16
+ beforeEach( () => {
17
+ window.__experimentalGlobalStylesInheritanceUI = true;
18
+ } );
19
+
20
+ afterEach( () => {
21
+ delete window.__experimentalGlobalStylesInheritanceUI;
22
+ } );
23
+
24
+ // `AspectRatioTool` reads its option list from the block-editor data
25
+ // store via `useSettings`. Mock that hook so the tests can render the
26
+ // full set of inherited and selectable ratios without spinning up a
27
+ // store registry. Per-call resolution lets the same mock answer all
28
+ // of the paths the tool requests on a single render.
29
+ jest.mock( '../../use-settings', () => {
30
+ const actual = jest.requireActual( '../../use-settings' );
31
+ return {
32
+ ...actual,
33
+ useSettings: jest.fn( ( ...paths ) =>
34
+ paths.map( ( path ) => {
35
+ switch ( path ) {
36
+ case 'dimensions.aspectRatios.default':
37
+ return [
38
+ { name: 'Square', slug: 'square', ratio: '1' },
39
+ {
40
+ name: 'Standard',
41
+ slug: 'standard',
42
+ ratio: '4/3',
43
+ },
44
+ { name: 'Wide', slug: 'wide', ratio: '16/9' },
45
+ ];
46
+ case 'dimensions.aspectRatios.theme':
47
+ return [];
48
+ case 'dimensions.defaultAspectRatios':
49
+ return true;
50
+ case 'dimensions.dimensionSizes':
51
+ return undefined;
52
+ case 'spacing.units':
53
+ return [ '%', 'px', 'em', 'rem', 'vh', 'vw' ];
54
+ // `useSpacingSizes` reads these for SpacingSizesControl.
55
+ // The spacing slider tests rely on them being populated;
56
+ // other tests don't render SpacingSizesControl, so the
57
+ // slider hook doesn't reach this path for them.
58
+ case 'spacing.spacingSizes.custom':
59
+ return [];
60
+ case 'spacing.spacingSizes.theme':
61
+ return [];
62
+ case 'spacing.spacingSizes.default':
63
+ return [
64
+ { name: 'Small', slug: '20', size: '12px' },
65
+ { name: 'Medium', slug: '40', size: '24px' },
66
+ { name: 'Large', slug: '60', size: '48px' },
67
+ ];
68
+ case 'spacing.defaultSpacingSizes':
69
+ return true;
70
+ default:
71
+ return undefined;
72
+ }
73
+ } )
74
+ ),
75
+ };
76
+ } );
77
+
78
+ /**
79
+ * Tests for inherited values in `DimensionsPanel`.
80
+ *
81
+ * The control's `value` prop reads from local `value` only; inherited values
82
+ * are surfaced through placeholders or at-rest selected options when local
83
+ * paths are empty. The wrapping `ToolsPanelItem` carries the inherited or
84
+ * local-override class hooks for label-level treatment.
85
+ */
86
+
87
+ const baseSettings = {
88
+ layout: {
89
+ contentSize: '600px',
90
+ wideSize: '1100px',
91
+ },
92
+ spacing: {
93
+ blockGap: true,
94
+ padding: true,
95
+ margin: true,
96
+ units: [ 'px', 'em', 'rem' ],
97
+ },
98
+ };
99
+
100
+ const settingsWithDimensions = {
101
+ ...baseSettings,
102
+ dimensions: {
103
+ minHeight: true,
104
+ height: true,
105
+ minWidth: true,
106
+ width: true,
107
+ aspectRatio: true,
108
+ aspectRatios: {
109
+ default: [
110
+ { name: 'Square', slug: 'square', ratio: '1' },
111
+ { name: 'Standard', slug: 'standard', ratio: '4/3' },
112
+ { name: 'Wide', slug: 'wide', ratio: '16/9' },
113
+ ],
114
+ },
115
+ defaultAspectRatios: true,
116
+ },
117
+ };
118
+
119
+ const settingsWithSpacingPresets = {
120
+ ...baseSettings,
121
+ spacing: {
122
+ ...baseSettings.spacing,
123
+ spacingSizes: {
124
+ default: [
125
+ { name: 'Small', slug: '20', size: '12px' },
126
+ { name: 'Medium', slug: '40', size: '24px' },
127
+ { name: 'Large', slug: '60', size: '48px' },
128
+ ],
129
+ },
130
+ defaultSpacingSizes: true,
131
+ },
132
+ };
133
+
134
+ // Render helper that fills in the boilerplate props every test repeats.
135
+ function renderPanel( props ) {
136
+ return render(
137
+ <DimensionsPanel
138
+ onChange={ () => {} }
139
+ panelId="test-panel"
140
+ { ...props }
141
+ />
142
+ );
143
+ }
144
+
145
+ // A local override surfaces the accessible reset-to-inherited affordance, so
146
+ // assert on that rather than the label's CSS class hook.
147
+ function expectLocalOverride() {
148
+ expect(
149
+ screen.getByRole( 'button', {
150
+ name: /reset to inherited value/i,
151
+ } )
152
+ ).toBeInTheDocument();
153
+ }
154
+
155
+ // Asserts the placeholder treatment for a control. A control can inherit a
156
+ // value while showing no placeholder when the inherited sides differ
157
+ // (BoxControl cannot render a per-side placeholder). Kept as a helper so the
158
+ // branch isn't a conditional `expect` in the test body (which
159
+ // `jest/no-conditional-expect` disallows).
160
+ function expectPlaceholderState( el, placeholder ) {
161
+ if ( placeholder ) {
162
+ expect( el ).toHaveAttribute( 'placeholder', placeholder );
163
+ } else {
164
+ expect( el ).not.toHaveAttribute( 'placeholder' );
165
+ }
166
+ }
167
+
168
+ // `getByLabelText` for the DimensionControl archetype matches both the inner
169
+ // UnitControl input and the sibling RangeControl slider, so filter to the
170
+ // text input element by class.
171
+ const getDimensionInput = ( label ) =>
172
+ screen
173
+ .getAllByLabelText( label )
174
+ .find( ( el ) =>
175
+ el.classList.contains( 'components-input-control__input' )
176
+ );
177
+
178
+ const getPaddingAllSides = () =>
179
+ within( screen.getByRole( 'group', { name: /Padding/ } ) ).getByRole(
180
+ 'textbox',
181
+ { name: 'All sides' }
182
+ );
183
+
184
+ const getPaddingSliders = () =>
185
+ screen
186
+ .getAllByRole( 'slider' )
187
+ .filter( ( s ) =>
188
+ /padding/i.test( s.getAttribute( 'aria-label' ) || '' )
189
+ );
190
+
191
+ describe( 'DimensionsPanel — per-control placeholder pattern', () => {
192
+ describe( 'layout content width', () => {
193
+ it( 'renders an inherited contentSize as the control value when `value` is empty', () => {
194
+ renderPanel( {
195
+ value: {},
196
+ inheritedValue: { layout: { contentSize: '720px' } },
197
+ settings: baseSettings,
198
+ includeLayoutControls: true,
199
+ } );
200
+
201
+ const contentInput = screen.getByLabelText( /content width/i );
202
+ // The inherited value is rendered as the control value (so the
203
+ // unit parses from it) and marked at-rest, with only the numeric
204
+ // portion echoed as the placeholder; the raw unit must not leak in.
205
+ expect( contentInput ).toHaveValue( 720 );
206
+ expect( contentInput ).toHaveAttribute( 'placeholder', '720' );
207
+ expect( contentInput ).not.toHaveAttribute(
208
+ 'placeholder',
209
+ '720px'
210
+ );
211
+ } );
212
+
213
+ it( 'renders a locally-set contentSize as the value with no placeholder', () => {
214
+ renderPanel( {
215
+ value: { layout: { contentSize: '900px' } },
216
+ inheritedValue: { layout: { contentSize: '720px' } },
217
+ settings: baseSettings,
218
+ includeLayoutControls: true,
219
+ } );
220
+
221
+ const contentInput = screen.getByLabelText( /content width/i );
222
+ expect( contentInput ).toHaveValue( 900 );
223
+ expect( contentInput ).not.toHaveAttribute( 'placeholder' );
224
+ } );
225
+
226
+ it( 'renders an inherited wideSize as the control value independently of contentSize state', () => {
227
+ renderPanel( {
228
+ value: { layout: { contentSize: '900px' } },
229
+ inheritedValue: {
230
+ layout: { contentSize: '720px', wideSize: '1280px' },
231
+ },
232
+ settings: baseSettings,
233
+ includeLayoutControls: true,
234
+ } );
235
+
236
+ // contentSize: locally-set, no placeholder.
237
+ const contentInput = screen.getByLabelText( /content width/i );
238
+ expect( contentInput ).toHaveValue( 900 );
239
+
240
+ // wideSize: not locally set, rendered as the at-rest control value.
241
+ const wideInput = screen.getByLabelText( /wide width/i );
242
+ expect( wideInput ).toHaveValue( 1280 );
243
+ expect( wideInput ).toHaveAttribute( 'placeholder', '1280' );
244
+ expect( wideInput ).not.toHaveAttribute( 'placeholder', '1280px' );
245
+ } );
246
+
247
+ it( 'commits a local contentSize override on user input without copying any inherited value into other paths (strip-not-copy)', async () => {
248
+ const user = userEvent.setup();
249
+ const onChange = jest.fn();
250
+
251
+ renderPanel( {
252
+ value: {},
253
+ inheritedValue: {
254
+ layout: { contentSize: '720px', wideSize: '1280px' },
255
+ spacing: { blockGap: '1.5rem' },
256
+ },
257
+ settings: baseSettings,
258
+ onChange,
259
+ includeLayoutControls: true,
260
+ } );
261
+
262
+ const contentInput = screen.getByLabelText( /content width/i );
263
+ await user.type( contentInput, '800px' );
264
+
265
+ expect( onChange ).toHaveBeenCalled();
266
+ const lastCall = onChange.mock.calls.at( -1 )[ 0 ];
267
+
268
+ // The committed change is local-only at the touched leaf path;
269
+ // no inherited value (wideSize, blockGap) is copied into the
270
+ // committed `value`.
271
+ expect( lastCall?.layout?.contentSize ).toBeDefined();
272
+ expect( lastCall?.layout?.wideSize ).toBeUndefined();
273
+ expect( lastCall?.spacing?.blockGap ).toBeUndefined();
274
+ } );
275
+ } );
276
+
277
+ describe( 'block spacing (single-input gap path)', () => {
278
+ it( 'renders an inherited blockGap as placeholder when `value` is empty', () => {
279
+ renderPanel( {
280
+ value: {},
281
+ inheritedValue: { spacing: { blockGap: '1.5rem' } },
282
+ settings: baseSettings,
283
+ } );
284
+
285
+ const gapInput = screen.getByLabelText( /block spacing/i );
286
+ expect( gapInput ).toHaveValue( null );
287
+ expect( gapInput ).toHaveAttribute( 'placeholder', '1.5rem' );
288
+ } );
289
+
290
+ it( 'renders no placeholder for blockGap when the inherited value is the compound axial shape (single-input path can only display strings)', () => {
291
+ // When blockGap is an object (axial split), the panel still
292
+ // renders the single-input UnitControl path because the
293
+ // settings here do not enable axial gap sides. The placeholder
294
+ // must NOT surface a non-string value into a control that
295
+ // cannot display it.
296
+ renderPanel( {
297
+ value: {},
298
+ inheritedValue: {
299
+ spacing: { blockGap: { top: '1rem', left: '0.5rem' } },
300
+ },
301
+ settings: baseSettings,
302
+ } );
303
+
304
+ const gapInput = screen.getByLabelText( /block spacing/i );
305
+ expect( gapInput ).not.toHaveAttribute( 'placeholder' );
306
+ } );
307
+ } );
308
+
309
+ describe( 'padding (BoxControl archetype)', () => {
310
+ // In linked mode (the default when there are no local values), the
311
+ // BoxControl renders a single "All sides" input; the placeholder
312
+ // reaches the inner UnitControl via inputProps and the className
313
+ // lands on the inner input wrapper for SCSS targeting.
314
+ it.each( [
315
+ {
316
+ name: 'shorthand string',
317
+ padding: '16px',
318
+ placeholder: '16px',
319
+ },
320
+ {
321
+ name: 'object with all sides equal',
322
+ padding: {
323
+ top: '20px',
324
+ right: '20px',
325
+ bottom: '20px',
326
+ left: '20px',
327
+ },
328
+ placeholder: '20px',
329
+ },
330
+ {
331
+ // Per-side mismatch: the single "All sides" input cannot
332
+ // represent differing sides, so no placeholder is shown.
333
+ name: 'object with sides that differ',
334
+ padding: {
335
+ top: '16px',
336
+ right: '8px',
337
+ bottom: '16px',
338
+ left: '8px',
339
+ },
340
+ placeholder: null,
341
+ },
342
+ {
343
+ // Only some sides defined (e.g. the top/bottom-only margin a
344
+ // theme may set). No common single-string placeholder.
345
+ name: 'object with only some sides defined',
346
+ padding: {
347
+ top: '16px',
348
+ bottom: '16px',
349
+ },
350
+ placeholder: null,
351
+ },
352
+ ] )(
353
+ 'renders an inherited padding $name as placeholder $placeholder when `value` is empty',
354
+ ( { padding, placeholder } ) => {
355
+ renderPanel( {
356
+ value: {},
357
+ inheritedValue: { spacing: { padding } },
358
+ settings: baseSettings,
359
+ } );
360
+
361
+ const paddingAllSides = getPaddingAllSides();
362
+ expectPlaceholderState( paddingAllSides, placeholder );
363
+ }
364
+ );
365
+
366
+ it( 'renders a locally-set padding shorthand as the value with no placeholder, even when `inheritedValue` also defines it', () => {
367
+ renderPanel( {
368
+ value: { spacing: { padding: '24px' } },
369
+ inheritedValue: { spacing: { padding: '16px' } },
370
+ settings: baseSettings,
371
+ } );
372
+
373
+ const paddingAllSides = getPaddingAllSides();
374
+ expect( paddingAllSides ).toHaveValue( '24' );
375
+ expect( paddingAllSides ).not.toHaveAttribute( 'placeholder' );
376
+ } );
377
+ } );
378
+
379
+ // The min-dimension DimensionControls (minHeight, minWidth) use the
380
+ // local-then-inherited pattern: the inherited value is rendered as the
381
+ // control's value (so the unit parses from it) and marked at-rest, rather
382
+ // than shown as a placeholder. It is only committed to local on user
383
+ // change.
384
+ describe.each( [
385
+ {
386
+ name: 'minHeight',
387
+ leaf: 'minHeight',
388
+ label: /minimum height/i,
389
+ inherited: '320px',
390
+ expected: 320,
391
+ },
392
+ {
393
+ name: 'minWidth',
394
+ leaf: 'minWidth',
395
+ label: /minimum width/i,
396
+ inherited: '200px',
397
+ expected: 200,
398
+ },
399
+ ] )(
400
+ 'DimensionControl ($name)',
401
+ ( { leaf, label, inherited, expected } ) => {
402
+ it( 'renders an inherited value as the control value when `value` is empty', () => {
403
+ renderPanel( {
404
+ value: {},
405
+ inheritedValue: { dimensions: { [ leaf ]: inherited } },
406
+ settings: settingsWithDimensions,
407
+ } );
408
+
409
+ const input = getDimensionInput( label );
410
+ expect( input ).toBeDefined();
411
+ expect( input ).toHaveValue( expected );
412
+ // The placeholder carries only the numeric portion of the
413
+ // inherited value; the unit selector reflects the inherited
414
+ // unit separately, so the raw unit string must not leak in.
415
+ expect( input ).toHaveAttribute(
416
+ 'placeholder',
417
+ `${ expected }`
418
+ );
419
+ expect( input ).not.toHaveAttribute( 'placeholder', inherited );
420
+ } );
421
+ }
422
+ );
423
+
424
+ // The plain `width` DimensionControl still uses the native-placeholder
425
+ // pattern for its inherited value.
426
+ describe( 'DimensionControl (width)', () => {
427
+ it( 'renders an inherited value as placeholder when `value` is empty', () => {
428
+ renderPanel( {
429
+ value: {},
430
+ inheritedValue: { dimensions: { width: '640px' } },
431
+ settings: settingsWithDimensions,
432
+ } );
433
+
434
+ const input = getDimensionInput( /^width$/i );
435
+ expect( input ).toBeDefined();
436
+ expect( input ).toHaveValue( null );
437
+ expect( input ).toHaveAttribute( 'placeholder', '640px' );
438
+ } );
439
+ } );
440
+
441
+ describe( 'minHeight (DimensionControl)', () => {
442
+ it( 'renders a locally-set minHeight as the value with no placeholder, even when `inheritedValue` also defines it', () => {
443
+ renderPanel( {
444
+ value: { dimensions: { minHeight: '480px' } },
445
+ inheritedValue: { dimensions: { minHeight: '320px' } },
446
+ settings: settingsWithDimensions,
447
+ } );
448
+
449
+ const minHeightInput = getDimensionInput( /minimum height/i );
450
+ expect( minHeightInput ).toBeDefined();
451
+ // PresetInputControl parses '480px' into the numeric input
452
+ // + a unit selector; the visible input value is just the
453
+ // numeric portion.
454
+ expect( minHeightInput ).toHaveValue( 480 );
455
+ expect( minHeightInput ).not.toHaveAttribute( 'placeholder' );
456
+ } );
457
+
458
+ it( 'does not call `onChange` on mount of an at-rest minHeight', () => {
459
+ const onChange = jest.fn();
460
+ renderPanel( {
461
+ value: {},
462
+ inheritedValue: { dimensions: { minHeight: '320px' } },
463
+ settings: settingsWithDimensions,
464
+ onChange,
465
+ } );
466
+
467
+ expect( onChange ).not.toHaveBeenCalled();
468
+ } );
469
+
470
+ it( 'commits a local minHeight override on user input without copying any inherited value into other paths', async () => {
471
+ const user = userEvent.setup();
472
+ const onChange = jest.fn();
473
+
474
+ renderPanel( {
475
+ value: {},
476
+ inheritedValue: {
477
+ dimensions: { minHeight: '320px', width: '640px' },
478
+ },
479
+ settings: settingsWithDimensions,
480
+ onChange,
481
+ } );
482
+
483
+ const minHeightInput = getDimensionInput( /minimum height/i );
484
+ expect( minHeightInput ).toBeDefined();
485
+ await user.type( minHeightInput, '500' );
486
+
487
+ expect( onChange ).toHaveBeenCalled();
488
+ const lastCall = onChange.mock.calls.at( -1 )[ 0 ];
489
+
490
+ // The committed change is local-only at the touched leaf path;
491
+ // no other inherited dimension (width) is copied into the
492
+ // committed `value`.
493
+ expect( lastCall?.dimensions?.minHeight ).toBeDefined();
494
+ expect( lastCall?.dimensions?.width ).toBeUndefined();
495
+ } );
496
+ } );
497
+
498
+ describe( 'aspectRatio (native <select> archetype)', () => {
499
+ it( 'preselects the inherited aspectRatio option as at-rest with inherited-value treatment', () => {
500
+ renderPanel( {
501
+ value: {},
502
+ inheritedValue: { dimensions: { aspectRatio: '16/9' } },
503
+ settings: settingsWithDimensions,
504
+ } );
505
+
506
+ const aspectSelect = screen.getByRole( 'combobox', {
507
+ name: /aspect ratio/i,
508
+ } );
509
+ expect( aspectSelect ).toHaveValue( '16/9' );
510
+ } );
511
+
512
+ it( 'renders a locally-set aspectRatio as the selected value with the local-override hook', () => {
513
+ renderPanel( {
514
+ value: { dimensions: { aspectRatio: '1' } },
515
+ inheritedValue: { dimensions: { aspectRatio: '16/9' } },
516
+ settings: settingsWithDimensions,
517
+ } );
518
+
519
+ const aspectSelect = screen.getByRole( 'combobox', {
520
+ name: /aspect ratio/i,
521
+ } );
522
+ expect( aspectSelect ).toHaveValue( '1' );
523
+ // A local override now gets the same local-override flag as
524
+ // every other control, so the inline reset button can attach.
525
+ // The button itself is portaled and covered by the
526
+ // InheritanceResetButton unit tests.
527
+ expectLocalOverride();
528
+ } );
529
+
530
+ it( 'does not call `onChange` on mount of an at-rest aspectRatio', () => {
531
+ const onChange = jest.fn();
532
+ renderPanel( {
533
+ value: {},
534
+ inheritedValue: { dimensions: { aspectRatio: '16/9' } },
535
+ settings: settingsWithDimensions,
536
+ onChange,
537
+ } );
538
+
539
+ expect( onChange ).not.toHaveBeenCalled();
540
+ } );
541
+
542
+ it( 'commits a local aspectRatio override on user selection of a different option without copying any inherited value into other paths', async () => {
543
+ const user = userEvent.setup();
544
+ const onChange = jest.fn();
545
+
546
+ renderPanel( {
547
+ value: {},
548
+ inheritedValue: {
549
+ dimensions: { aspectRatio: '16/9', minHeight: '320px' },
550
+ },
551
+ settings: settingsWithDimensions,
552
+ onChange,
553
+ } );
554
+
555
+ const aspectSelect = screen.getByRole( 'combobox', {
556
+ name: /aspect ratio/i,
557
+ } );
558
+ await user.selectOptions( aspectSelect, '1' );
559
+
560
+ expect( onChange ).toHaveBeenCalled();
561
+ const lastCall = onChange.mock.calls.at( -1 )[ 0 ];
562
+ expect( lastCall?.dimensions?.aspectRatio ).toBe( '1' );
563
+ // minHeight from inheritedValue must not be copied into the
564
+ // committed local value (strip-not-copy).
565
+ expect( lastCall?.dimensions?.minHeight ).toBeUndefined();
566
+ } );
567
+ } );
568
+
569
+ /*
570
+ * Regression: spacing slider stuck on inherited value
571
+ * ----------------------------------------------------
572
+ * `SpacingSizesControl` (the preset chip slider) and the axial-gap
573
+ * `BoxControl` have no native `placeholder` slot — the at-rest cue
574
+ * is the displayed `values` themselves. Previously these reads
575
+ * came from `inheritedValue` only, so when the user dragged the
576
+ * slider, `setPaddingValues` / `setMarginValues` / `setGapValues`
577
+ * wrote to local state but the `values` prop never updated and the
578
+ * slider appeared 'stuck'. The fix merges local-then-inherited
579
+ * before passing to the chip slider / axial BoxControl.
580
+ */
581
+ describe( 'SpacingSizesControl (preset chip slider)', () => {
582
+ // Each `SpacingSizesControl` slider reports its current preset
583
+ // position via the slider's `value` attribute. The numbering
584
+ // corresponds to the index of the active preset in the merged size
585
+ // list (the slider's `None` zero position precedes the presets).
586
+ it.each( [
587
+ {
588
+ name: 'padding',
589
+ leaf: 'padding',
590
+ match: /padding/i,
591
+ // Local Medium (slug 40) → index 2.
592
+ local: 'var:preset|spacing|40',
593
+ sliderValue: '2',
594
+ valueText: 'Medium',
595
+ },
596
+ {
597
+ name: 'margin',
598
+ leaf: 'margin',
599
+ match: /margin/i,
600
+ // Local Large (slug 60) → index 3.
601
+ local: 'var:preset|spacing|60',
602
+ sliderValue: '3',
603
+ valueText: 'Large',
604
+ },
605
+ ] )(
606
+ '($name) reflects the local value when set, ignoring the inherited fallback',
607
+ ( { leaf, match, local, sliderValue, valueText } ) => {
608
+ renderPanel( {
609
+ value: { spacing: { [ leaf ]: local } },
610
+ inheritedValue: {
611
+ spacing: {
612
+ [ leaf ]: leaf === 'padding' ? '10px' : '8px',
613
+ },
614
+ },
615
+ settings: settingsWithSpacingPresets,
616
+ } );
617
+
618
+ const sliders = screen
619
+ .getAllByRole( 'slider' )
620
+ .filter( ( s ) =>
621
+ match.test( s.getAttribute( 'aria-label' ) || '' )
622
+ );
623
+ expect( sliders.length ).toBeGreaterThan( 0 );
624
+ expect(
625
+ sliders.every( ( s ) => s.value === sliderValue )
626
+ ).toBe( true );
627
+ expect(
628
+ sliders.every(
629
+ ( s ) =>
630
+ s.getAttribute( 'aria-valuetext' ) === valueText
631
+ )
632
+ ).toBe( true );
633
+ }
634
+ );
635
+
636
+ it( '(padding) renders the inherited value at-rest when the local path is empty', () => {
637
+ renderPanel( {
638
+ value: {},
639
+ inheritedValue: {
640
+ spacing: { padding: 'var:preset|spacing|20' },
641
+ },
642
+ settings: settingsWithSpacingPresets,
643
+ } );
644
+
645
+ // At-rest, slider tracks inherited 'Small' (slug 20) → index 1.
646
+ const paddingSliders = getPaddingSliders();
647
+ expect( paddingSliders.length ).toBeGreaterThan( 0 );
648
+ expect( paddingSliders.every( ( s ) => s.value === '1' ) ).toBe(
649
+ true
650
+ );
651
+ } );
652
+
653
+ it( '(padding) commits to local on slider activation and the displayed value follows the new local value', () => {
654
+ const { fireEvent } = require( '@testing-library/react' );
655
+
656
+ // Controlled wrapper so we can verify the slider follows local
657
+ // value updates after onChange writes through.
658
+ function Wrapper() {
659
+ const [ value, setValue ] =
660
+ require( '@wordpress/element' ).useState( {} );
661
+ return (
662
+ <DimensionsPanel
663
+ value={ value }
664
+ inheritedValue={ {
665
+ spacing: { padding: 'var:preset|spacing|20' },
666
+ } }
667
+ settings={ settingsWithSpacingPresets }
668
+ onChange={ setValue }
669
+ panelId="test-panel"
670
+ />
671
+ );
672
+ }
673
+
674
+ render( <Wrapper /> );
675
+
676
+ // At-rest the padding slider reads the inherited Small (index 1).
677
+ const slidersBefore = getPaddingSliders();
678
+ expect( slidersBefore[ 0 ].value ).toBe( '1' );
679
+
680
+ // `RangeControl` listens to native `change`/`input` events on
681
+ // its slider; setting the value and firing change is the most
682
+ // reliable way to drive an update through RTL in jsdom.
683
+ fireEvent.change( slidersBefore[ 0 ], { target: { value: '2' } } );
684
+
685
+ const slidersAfter = getPaddingSliders();
686
+ // The activated padding slider advanced from 1 → 2.
687
+ expect( slidersAfter[ 0 ].value ).toBe( '2' );
688
+ expect( slidersAfter[ 0 ] ).toHaveAttribute(
689
+ 'aria-valuetext',
690
+ 'Medium'
691
+ );
692
+ } );
693
+
694
+ it( 'still surfaces block-sourced inherited padding on a block panel', () => {
695
+ renderPanel( {
696
+ value: {},
697
+ inheritedValue: {
698
+ spacing: {
699
+ padding: {
700
+ top: 'var:preset|spacing|40',
701
+ right: 'var:preset|spacing|40',
702
+ bottom: 'var:preset|spacing|40',
703
+ left: 'var:preset|spacing|40',
704
+ },
705
+ },
706
+ },
707
+ settings: settingsWithSpacingPresets,
708
+ } );
709
+
710
+ const paddingSliders = getPaddingSliders();
711
+ expect( paddingSliders.length ).toBeGreaterThan( 0 );
712
+ // Inherited Medium (slug 40) lands at preset index 2 (None=0,
713
+ // Small=1, Medium=2). All sliders should reflect that.
714
+ expect( paddingSliders.every( ( s ) => s.value === '2' ) ).toBe(
715
+ true
716
+ );
717
+ } );
718
+ } );
719
+
720
+ describe( 'local zero values (override, not inherited placeholder)', () => {
721
+ it( 'treats a local numeric zero minHeight as a local override instead of inherited placeholder', () => {
722
+ renderPanel( {
723
+ value: { dimensions: { minHeight: 0 } },
724
+ inheritedValue: { dimensions: { minHeight: '200px' } },
725
+ settings: settingsWithDimensions,
726
+ } );
727
+
728
+ const minHeightInput = screen
729
+ .getAllByLabelText( /minimum height/i )
730
+ .find(
731
+ ( input ) =>
732
+ input.tagName === 'INPUT' && input.value === '0'
733
+ );
734
+ expect( minHeightInput ).toBeDefined();
735
+ expect( minHeightInput ).toHaveValue( 0 );
736
+ expect( minHeightInput ).not.toHaveAttribute( 'placeholder' );
737
+ expectLocalOverride();
738
+ } );
739
+
740
+ it( 'treats a local zero block gap as a local value instead of inherited placeholder', () => {
741
+ renderPanel( {
742
+ value: { spacing: { blockGap: 0 } },
743
+ inheritedValue: { spacing: { blockGap: '2rem' } },
744
+ settings: baseSettings,
745
+ } );
746
+
747
+ const gapInput = screen.getByLabelText( /block spacing/i );
748
+ expect( gapInput ).toHaveValue( 0 );
749
+ expect( gapInput ).not.toHaveAttribute( 'placeholder' );
750
+ expectLocalOverride();
751
+ } );
752
+ } );
753
+ } );