@wordpress/block-editor 16.2.0 → 17.0.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 (328) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/build/components/block-inspector/index.cjs +9 -6
  3. package/build/components/block-inspector/index.cjs.map +2 -2
  4. package/build/components/block-keyboard-shortcuts/index.cjs +201 -0
  5. package/build/components/block-keyboard-shortcuts/index.cjs.map +7 -0
  6. package/build/components/block-list/block.cjs +10 -3
  7. package/build/components/block-list/block.cjs.map +2 -2
  8. package/build/components/block-list/index.cjs +6 -1
  9. package/build/components/block-list/index.cjs.map +2 -2
  10. package/build/components/block-list/use-block-props/use-focus-first-element.cjs +4 -0
  11. package/build/components/block-list/use-block-props/use-focus-first-element.cjs.map +2 -2
  12. package/build/components/block-list/use-block-props/use-selected-block-event-handlers.cjs +6 -7
  13. package/build/components/block-list/use-block-props/use-selected-block-event-handlers.cjs.map +2 -2
  14. package/build/components/block-parent-selector/index.cjs +98 -29
  15. package/build/components/block-parent-selector/index.cjs.map +3 -3
  16. package/build/components/block-popover/inbetween.cjs +2 -2
  17. package/build/components/block-popover/inbetween.cjs.map +2 -2
  18. package/build/components/block-preview/index.cjs +0 -4
  19. package/build/components/block-preview/index.cjs.map +2 -2
  20. package/build/components/block-styles/index.cjs +77 -33
  21. package/build/components/block-styles/index.cjs.map +3 -3
  22. package/build/components/block-visibility/constants.cjs.map +2 -2
  23. package/build/components/child-layout-control/index.cjs.map +2 -2
  24. package/build/components/colors/with-colors.cjs +55 -71
  25. package/build/components/colors/with-colors.cjs.map +2 -2
  26. package/build/components/dimensions-tool/aspect-ratio-tool.cjs +4 -2
  27. package/build/components/dimensions-tool/aspect-ratio-tool.cjs.map +2 -2
  28. package/build/components/dimensions-tool/index.cjs +18 -16
  29. package/build/components/dimensions-tool/index.cjs.map +2 -2
  30. package/build/components/dimensions-tool/utils.cjs +61 -0
  31. package/build/components/dimensions-tool/utils.cjs.map +7 -0
  32. package/build/components/duotone-control/index.cjs +2 -0
  33. package/build/components/duotone-control/index.cjs.map +2 -2
  34. package/build/components/global-styles/advanced-panel.cjs.map +3 -3
  35. package/build/components/global-styles/border-panel.cjs +2 -5
  36. package/build/components/global-styles/border-panel.cjs.map +2 -2
  37. package/build/components/global-styles/color-panel.cjs +14 -2
  38. package/build/components/global-styles/color-panel.cjs.map +2 -2
  39. package/build/components/global-styles/dimensions-panel.cjs +9 -5
  40. package/build/components/global-styles/dimensions-panel.cjs.map +2 -2
  41. package/build/components/global-styles/filters-panel.cjs +12 -8
  42. package/build/components/global-styles/filters-panel.cjs.map +2 -2
  43. package/build/components/global-styles/shadow-panel-components.cjs +16 -11
  44. package/build/components/global-styles/shadow-panel-components.cjs.map +2 -2
  45. package/build/components/global-styles/utils.cjs +9 -0
  46. package/build/components/global-styles/utils.cjs.map +2 -2
  47. package/build/components/inner-blocks/index.cjs +5 -3
  48. package/build/components/inner-blocks/index.cjs.map +2 -2
  49. package/build/components/inserter/block-patterns-explorer/index.cjs +39 -22
  50. package/build/components/inserter/block-patterns-explorer/index.cjs.map +2 -2
  51. package/build/components/inserter/block-patterns-explorer/pattern-explorer-sidebar.cjs +21 -55
  52. package/build/components/inserter/block-patterns-explorer/pattern-explorer-sidebar.cjs.map +2 -2
  53. package/build/components/inserter/block-patterns-explorer/pattern-list.cjs +19 -44
  54. package/build/components/inserter/block-patterns-explorer/pattern-list.cjs.map +3 -3
  55. package/build/components/inserter/hooks/use-insertion-point.cjs +4 -4
  56. package/build/components/inserter/hooks/use-insertion-point.cjs.map +2 -2
  57. package/build/components/inserter/menu.cjs +18 -7
  58. package/build/components/inserter/menu.cjs.map +2 -2
  59. package/build/components/inserter/quick-inserter.cjs +30 -27
  60. package/build/components/inserter/quick-inserter.cjs.map +2 -2
  61. package/build/components/inspector-controls-tabs/styles-tab.cjs +1 -1
  62. package/build/components/inspector-controls-tabs/styles-tab.cjs.map +2 -2
  63. package/build/components/list-view/index.cjs +31 -5
  64. package/build/components/list-view/index.cjs.map +3 -3
  65. package/build/components/list-view/utils.cjs +13 -1
  66. package/build/components/list-view/utils.cjs.map +2 -2
  67. package/build/components/media-placeholder/index.cjs +1 -1
  68. package/build/components/media-placeholder/index.cjs.map +3 -3
  69. package/build/components/provider/index.cjs +5 -1
  70. package/build/components/provider/index.cjs.map +3 -3
  71. package/build/components/publish-date-time-picker/index.cjs +3 -2
  72. package/build/components/publish-date-time-picker/index.cjs.map +2 -2
  73. package/build/components/rich-text/event-listeners/enter.cjs +46 -39
  74. package/build/components/rich-text/event-listeners/enter.cjs.map +2 -2
  75. package/build/components/rich-text/index.cjs +7 -5
  76. package/build/components/rich-text/index.cjs.map +2 -2
  77. package/build/components/rich-text/multiline.cjs +105 -70
  78. package/build/components/rich-text/multiline.cjs.map +3 -3
  79. package/build/components/url-input/index.cjs.map +2 -2
  80. package/build/components/writing-flow/use-input.cjs +65 -13
  81. package/build/components/writing-flow/use-input.cjs.map +3 -3
  82. package/build/hooks/border.cjs +25 -3
  83. package/build/hooks/border.cjs.map +2 -2
  84. package/build/hooks/dimensions.cjs +9 -2
  85. package/build/hooks/dimensions.cjs.map +2 -2
  86. package/build/hooks/duotone.cjs +4 -3
  87. package/build/hooks/duotone.cjs.map +2 -2
  88. package/build/hooks/gap.cjs +8 -2
  89. package/build/hooks/gap.cjs.map +2 -2
  90. package/build/hooks/layout-child.cjs +35 -13
  91. package/build/hooks/layout-child.cjs.map +2 -2
  92. package/build/hooks/layout.cjs +23 -1
  93. package/build/hooks/layout.cjs.map +2 -2
  94. package/build/hooks/states.cjs +7 -4
  95. package/build/hooks/states.cjs.map +2 -2
  96. package/build/layouts/constrained.cjs +8 -1
  97. package/build/layouts/constrained.cjs.map +2 -2
  98. package/build/layouts/flex.cjs +9 -0
  99. package/build/layouts/flex.cjs.map +2 -2
  100. package/build/layouts/flow.cjs +4 -1
  101. package/build/layouts/flow.cjs.map +2 -2
  102. package/build/layouts/grid.cjs +5 -1
  103. package/build/layouts/grid.cjs.map +2 -2
  104. package/build/private-apis.cjs +3 -1
  105. package/build/private-apis.cjs.map +2 -2
  106. package/build/store/actions.cjs +1 -1
  107. package/build/store/actions.cjs.map +2 -2
  108. package/build-module/components/block-inspector/index.mjs +9 -6
  109. package/build-module/components/block-inspector/index.mjs.map +2 -2
  110. package/build-module/components/block-keyboard-shortcuts/index.mjs +173 -0
  111. package/build-module/components/block-keyboard-shortcuts/index.mjs.map +7 -0
  112. package/build-module/components/block-list/block.mjs +10 -3
  113. package/build-module/components/block-list/block.mjs.map +2 -2
  114. package/build-module/components/block-list/index.mjs +6 -1
  115. package/build-module/components/block-list/index.mjs.map +2 -2
  116. package/build-module/components/block-list/use-block-props/use-focus-first-element.mjs +4 -0
  117. package/build-module/components/block-list/use-block-props/use-focus-first-element.mjs.map +2 -2
  118. package/build-module/components/block-list/use-block-props/use-selected-block-event-handlers.mjs +6 -7
  119. package/build-module/components/block-list/use-block-props/use-selected-block-event-handlers.mjs.map +2 -2
  120. package/build-module/components/block-parent-selector/index.mjs +101 -32
  121. package/build-module/components/block-parent-selector/index.mjs.map +2 -2
  122. package/build-module/components/block-popover/inbetween.mjs +2 -2
  123. package/build-module/components/block-popover/inbetween.mjs.map +2 -2
  124. package/build-module/components/block-preview/index.mjs +0 -4
  125. package/build-module/components/block-preview/index.mjs.map +2 -2
  126. package/build-module/components/block-styles/index.mjs +79 -35
  127. package/build-module/components/block-styles/index.mjs.map +2 -2
  128. package/build-module/components/block-visibility/constants.mjs.map +2 -2
  129. package/build-module/components/child-layout-control/index.mjs +5 -5
  130. package/build-module/components/child-layout-control/index.mjs.map +1 -1
  131. package/build-module/components/colors/with-colors.mjs +56 -72
  132. package/build-module/components/colors/with-colors.mjs.map +2 -2
  133. package/build-module/components/dimensions-tool/aspect-ratio-tool.mjs +4 -2
  134. package/build-module/components/dimensions-tool/aspect-ratio-tool.mjs.map +2 -2
  135. package/build-module/components/dimensions-tool/index.mjs +19 -17
  136. package/build-module/components/dimensions-tool/index.mjs.map +2 -2
  137. package/build-module/components/dimensions-tool/utils.mjs +35 -0
  138. package/build-module/components/dimensions-tool/utils.mjs.map +7 -0
  139. package/build-module/components/duotone-control/index.mjs +2 -0
  140. package/build-module/components/duotone-control/index.mjs.map +2 -2
  141. package/build-module/components/global-styles/advanced-panel.mjs +5 -2
  142. package/build-module/components/global-styles/advanced-panel.mjs.map +2 -2
  143. package/build-module/components/global-styles/border-panel.mjs +2 -5
  144. package/build-module/components/global-styles/border-panel.mjs.map +2 -2
  145. package/build-module/components/global-styles/color-panel.mjs +14 -2
  146. package/build-module/components/global-styles/color-panel.mjs.map +2 -2
  147. package/build-module/components/global-styles/dimensions-panel.mjs +9 -5
  148. package/build-module/components/global-styles/dimensions-panel.mjs.map +2 -2
  149. package/build-module/components/global-styles/filters-panel.mjs +16 -9
  150. package/build-module/components/global-styles/filters-panel.mjs.map +2 -2
  151. package/build-module/components/global-styles/shadow-panel-components.mjs +16 -11
  152. package/build-module/components/global-styles/shadow-panel-components.mjs.map +2 -2
  153. package/build-module/components/global-styles/utils.mjs +8 -0
  154. package/build-module/components/global-styles/utils.mjs.map +2 -2
  155. package/build-module/components/inner-blocks/index.mjs +5 -3
  156. package/build-module/components/inner-blocks/index.mjs.map +2 -2
  157. package/build-module/components/inserter/block-patterns-explorer/index.mjs +39 -22
  158. package/build-module/components/inserter/block-patterns-explorer/index.mjs.map +2 -2
  159. package/build-module/components/inserter/block-patterns-explorer/pattern-explorer-sidebar.mjs +22 -56
  160. package/build-module/components/inserter/block-patterns-explorer/pattern-explorer-sidebar.mjs.map +2 -2
  161. package/build-module/components/inserter/block-patterns-explorer/pattern-list.mjs +20 -45
  162. package/build-module/components/inserter/block-patterns-explorer/pattern-list.mjs.map +2 -2
  163. package/build-module/components/inserter/hooks/use-insertion-point.mjs +4 -4
  164. package/build-module/components/inserter/hooks/use-insertion-point.mjs.map +2 -2
  165. package/build-module/components/inserter/menu.mjs +19 -8
  166. package/build-module/components/inserter/menu.mjs.map +2 -2
  167. package/build-module/components/inserter/quick-inserter.mjs +30 -27
  168. package/build-module/components/inserter/quick-inserter.mjs.map +2 -2
  169. package/build-module/components/inspector-controls-tabs/styles-tab.mjs +1 -1
  170. package/build-module/components/inspector-controls-tabs/styles-tab.mjs.map +2 -2
  171. package/build-module/components/list-view/index.mjs +32 -5
  172. package/build-module/components/list-view/index.mjs.map +3 -3
  173. package/build-module/components/list-view/utils.mjs +13 -1
  174. package/build-module/components/list-view/utils.mjs.map +2 -2
  175. package/build-module/components/media-placeholder/index.mjs +3 -3
  176. package/build-module/components/media-placeholder/index.mjs.map +2 -2
  177. package/build-module/components/provider/index.mjs +6 -2
  178. package/build-module/components/provider/index.mjs.map +2 -2
  179. package/build-module/components/publish-date-time-picker/index.mjs +3 -2
  180. package/build-module/components/publish-date-time-picker/index.mjs.map +2 -2
  181. package/build-module/components/rich-text/event-listeners/enter.mjs +46 -39
  182. package/build-module/components/rich-text/event-listeners/enter.mjs.map +2 -2
  183. package/build-module/components/rich-text/index.mjs +7 -5
  184. package/build-module/components/rich-text/index.mjs.map +2 -2
  185. package/build-module/components/rich-text/multiline.mjs +112 -72
  186. package/build-module/components/rich-text/multiline.mjs.map +2 -2
  187. package/build-module/components/url-input/index.mjs +2 -2
  188. package/build-module/components/url-input/index.mjs.map +1 -1
  189. package/build-module/components/writing-flow/use-input.mjs +66 -14
  190. package/build-module/components/writing-flow/use-input.mjs.map +3 -3
  191. package/build-module/hooks/border.mjs +26 -8
  192. package/build-module/hooks/border.mjs.map +2 -2
  193. package/build-module/hooks/dimensions.mjs +9 -2
  194. package/build-module/hooks/dimensions.mjs.map +2 -2
  195. package/build-module/hooks/duotone.mjs +8 -4
  196. package/build-module/hooks/duotone.mjs.map +2 -2
  197. package/build-module/hooks/gap.mjs +6 -1
  198. package/build-module/hooks/gap.mjs.map +2 -2
  199. package/build-module/hooks/layout-child.mjs +36 -13
  200. package/build-module/hooks/layout-child.mjs.map +2 -2
  201. package/build-module/hooks/layout.mjs +22 -1
  202. package/build-module/hooks/layout.mjs.map +2 -2
  203. package/build-module/hooks/states.mjs +8 -5
  204. package/build-module/hooks/states.mjs.map +2 -2
  205. package/build-module/layouts/constrained.mjs +8 -1
  206. package/build-module/layouts/constrained.mjs.map +2 -2
  207. package/build-module/layouts/flex.mjs +9 -0
  208. package/build-module/layouts/flex.mjs.map +2 -2
  209. package/build-module/layouts/flow.mjs +4 -1
  210. package/build-module/layouts/flow.mjs.map +2 -2
  211. package/build-module/layouts/grid.mjs +5 -1
  212. package/build-module/layouts/grid.mjs.map +2 -2
  213. package/build-module/private-apis.mjs +3 -1
  214. package/build-module/private-apis.mjs.map +2 -2
  215. package/build-module/store/actions.mjs +1 -1
  216. package/build-module/store/actions.mjs.map +2 -2
  217. package/build-style/style-rtl.css +108 -71
  218. package/build-style/style.css +108 -71
  219. package/build-types/components/block-visibility/constants.d.ts +4 -3
  220. package/build-types/components/block-visibility/constants.d.ts.map +1 -1
  221. package/package.json +43 -43
  222. package/src/components/background-image-control/README.md +122 -0
  223. package/src/components/block-edit-visually-button/README.md +39 -0
  224. package/src/components/block-editing-mode/README.md +72 -0
  225. package/src/components/block-inspector/index.js +6 -6
  226. package/src/components/block-keyboard-shortcuts/index.js +256 -0
  227. package/src/components/block-list/block.js +7 -0
  228. package/src/components/block-list/index.js +7 -1
  229. package/src/components/block-list/use-block-props/use-focus-first-element.js +5 -0
  230. package/src/components/block-list/use-block-props/use-selected-block-event-handlers.js +6 -8
  231. package/src/components/block-parent-selector/index.js +111 -30
  232. package/src/components/block-popover/README.md +10 -3
  233. package/src/components/block-popover/inbetween.js +8 -2
  234. package/src/components/block-popover/stories/index.story.jsx +143 -0
  235. package/src/components/block-preview/index.js +0 -2
  236. package/src/components/block-styles/index.js +95 -43
  237. package/src/components/block-styles/style.scss +5 -34
  238. package/src/components/block-tools/style.scss +94 -0
  239. package/src/components/block-visibility/constants.ts +4 -3
  240. package/src/components/child-layout-control/index.js +5 -5
  241. package/src/components/color-palette/test/__snapshots__/control.js.snap +3 -29
  242. package/src/components/colors/test/with-colors.js +6 -7
  243. package/src/components/colors/with-colors.js +79 -105
  244. package/src/components/dimensions-tool/aspect-ratio-tool.js +16 -4
  245. package/src/components/dimensions-tool/index.js +34 -22
  246. package/src/components/dimensions-tool/test/index.js +166 -2
  247. package/src/components/dimensions-tool/test/utils.js +51 -0
  248. package/src/components/dimensions-tool/utils.js +67 -0
  249. package/src/components/duotone-control/index.js +2 -0
  250. package/src/components/duotone-control/test/index.js +100 -0
  251. package/src/components/global-styles/advanced-panel.js +5 -2
  252. package/src/components/global-styles/border-panel.js +11 -9
  253. package/src/components/global-styles/color-panel.js +13 -1
  254. package/src/components/global-styles/dimensions-panel.js +17 -5
  255. package/src/components/global-styles/filters-panel.js +24 -10
  256. package/src/components/global-styles/shadow-panel-components.js +38 -11
  257. package/src/components/global-styles/test/border-panel.js +230 -0
  258. package/src/components/global-styles/test/color-panel.js +24 -0
  259. package/src/components/global-styles/test/dimensions-panel.js +98 -7
  260. package/src/components/global-styles/test/filters-panel.js +84 -0
  261. package/src/components/global-styles/test/utils.js +30 -0
  262. package/src/components/global-styles/utils.js +23 -0
  263. package/src/components/inner-blocks/index.js +11 -2
  264. package/src/components/inner-blocks/stories/index.story.jsx +246 -0
  265. package/src/components/inserter/block-patterns-explorer/index.js +24 -11
  266. package/src/components/inserter/block-patterns-explorer/pattern-explorer-sidebar.js +18 -56
  267. package/src/components/inserter/block-patterns-explorer/pattern-list.js +22 -47
  268. package/src/components/inserter/hooks/use-insertion-point.js +9 -4
  269. package/src/components/inserter/menu.js +23 -10
  270. package/src/components/inserter/quick-inserter.js +15 -13
  271. package/src/components/inserter/style.scss +44 -62
  272. package/src/components/inspector-controls-tabs/README.md +92 -0
  273. package/src/components/inspector-controls-tabs/styles-tab.js +1 -1
  274. package/src/components/link-control/test/index.js +5 -0
  275. package/src/components/list-view/index.js +50 -7
  276. package/src/components/list-view/utils.js +16 -1
  277. package/src/components/media-placeholder/index.js +9 -4
  278. package/src/components/plain-text/stories/index.story.jsx +3 -0
  279. package/src/components/provider/index.js +7 -1
  280. package/src/components/publish-date-time-picker/README.md +10 -0
  281. package/src/components/publish-date-time-picker/index.js +17 -14
  282. package/src/components/rich-text/event-listeners/enter.js +58 -45
  283. package/src/components/rich-text/index.js +9 -4
  284. package/src/components/rich-text/multiline.js +126 -86
  285. package/src/components/url-input/index.js +2 -2
  286. package/src/components/url-input/test/fixtures/validated-input-control-deprecation.js +27 -0
  287. package/src/components/url-input/test/index.js +5 -0
  288. package/src/components/url-popover/README.md +7 -0
  289. package/src/components/url-popover/stories/index.story.jsx +133 -47
  290. package/src/components/writing-flow/use-input.js +105 -15
  291. package/src/hooks/border.js +27 -8
  292. package/src/hooks/dimensions.js +8 -1
  293. package/src/hooks/duotone.js +23 -7
  294. package/src/hooks/gap.js +16 -0
  295. package/src/hooks/layout-child.js +41 -9
  296. package/src/hooks/layout.js +117 -71
  297. package/src/hooks/states.js +8 -5
  298. package/src/hooks/test/gap.js +31 -1
  299. package/src/hooks/test/layout-child.js +60 -1
  300. package/src/hooks/test/layout.js +27 -0
  301. package/src/layouts/constrained.js +9 -1
  302. package/src/layouts/flex.js +15 -0
  303. package/src/layouts/flow.js +4 -1
  304. package/src/layouts/grid.js +9 -1
  305. package/src/layouts/test/constrained.js +26 -0
  306. package/src/layouts/test/flow.js +26 -0
  307. package/src/layouts/test/grid.js +46 -0
  308. package/src/private-apis.js +2 -0
  309. package/src/store/actions.js +14 -1
  310. package/build/components/floating-toolbar/nav-up-icon.cjs +0 -52
  311. package/build/components/floating-toolbar/nav-up-icon.cjs.map +0 -7
  312. package/build/components/gradients/with-gradient.cjs +0 -36
  313. package/build/components/gradients/with-gradient.cjs.map +0 -7
  314. package/build/components/inserter-button/sparkles.cjs +0 -36
  315. package/build/components/inserter-button/sparkles.cjs.map +0 -7
  316. package/build-module/components/floating-toolbar/nav-up-icon.mjs +0 -31
  317. package/build-module/components/floating-toolbar/nav-up-icon.mjs.map +0 -7
  318. package/build-module/components/gradients/with-gradient.mjs +0 -11
  319. package/build-module/components/gradients/with-gradient.mjs.map +0 -7
  320. package/build-module/components/inserter-button/sparkles.mjs +0 -15
  321. package/build-module/components/inserter-button/sparkles.mjs.map +0 -7
  322. package/build-style/default-editor-styles-rtl.css +0 -68
  323. package/build-style/default-editor-styles.css +0 -68
  324. package/src/components/floating-toolbar/nav-up-icon.js +0 -22
  325. package/src/components/gradients/with-gradient.js +0 -9
  326. package/src/components/inserter-button/README.md +0 -62
  327. package/src/components/inserter-button/sparkles.js +0 -12
  328. package/src/default-editor-styles.scss +0 -25
@@ -25,13 +25,27 @@ const EMPTY_ARRAY = [];
25
25
 
26
26
  export function ShadowPopoverContainer( { shadow, onShadowChange, settings } ) {
27
27
  const shadows = useShadowPresets( settings );
28
+ const presets = useMemo( () => {
29
+ if ( ! shadows.length ) {
30
+ return shadows;
31
+ }
32
+ // The entry that clears the shadow is a display-only affordance with no
33
+ // counterpart in `theme.json`. It is added here rather than in
34
+ // `useShadowPresets` so that callers mapping a value back to a preset
35
+ // never mistake it for one and persist a reference to a CSS variable
36
+ // that is never output.
37
+ return [
38
+ { name: __( 'Unset' ), slug: 'unset', shadow: 'none' },
39
+ ...shadows,
40
+ ];
41
+ }, [ shadows ] );
28
42
 
29
43
  return (
30
44
  <div className="block-editor-global-styles__shadow-popover-container">
31
45
  <VStack spacing={ 4 }>
32
46
  <Heading level={ 5 }>{ __( 'Drop shadow' ) }</Heading>
33
47
  <ShadowPresets
34
- presets={ shadows }
48
+ presets={ presets }
35
49
  activeShadow={ shadow }
36
50
  onSelect={ onShadowChange }
37
51
  />
@@ -209,6 +223,18 @@ function renderShadowToggle( shadow, onShadowChange, resetConfig ) {
209
223
  };
210
224
  }
211
225
 
226
+ /**
227
+ * Returns the available shadow presets, from every origin, in the order they
228
+ * are presented to the user.
229
+ *
230
+ * This is the single source of truth for which presets exist: it backs both the
231
+ * popover's preset list and the mapping of a chosen shadow back to the preset
232
+ * it came from, so what is shown and what is stored cannot drift apart.
233
+ *
234
+ * @param {Object} settings Theme.json settings for the current context.
235
+ *
236
+ * @return {Array} The shadow presets.
237
+ */
212
238
  export function useShadowPresets( settings ) {
213
239
  return useMemo( () => {
214
240
  if ( ! settings?.shadow ) {
@@ -221,21 +247,22 @@ export function useShadowPresets( settings ) {
221
247
  theme: themeShadows,
222
248
  custom: customShadows,
223
249
  } = settings?.shadow?.presets ?? {};
224
- const unsetShadow = {
225
- name: __( 'Unset' ),
226
- slug: 'unset',
227
- shadow: 'none',
228
- };
229
250
 
230
- const shadowPresets = [
251
+ const allPresets = [
231
252
  ...( ( defaultPresetsEnabled && defaultShadows ) || EMPTY_ARRAY ),
232
253
  ...( themeShadows || EMPTY_ARRAY ),
233
254
  ...( customShadows || EMPTY_ARRAY ),
234
255
  ];
235
- if ( shadowPresets.length ) {
236
- shadowPresets.unshift( unsetShadow );
237
- }
238
256
 
239
- return shadowPresets;
257
+ // More than one origin can define the same slug, but only the most
258
+ // specific definition is output as a CSS variable. Keeping the ones it
259
+ // overrides would offer swatches that apply a different shadow than
260
+ // the one they show.
261
+ return allPresets.filter(
262
+ ( preset, index ) =>
263
+ allPresets.findLastIndex(
264
+ ( { slug } ) => slug === preset.slug
265
+ ) === index
266
+ );
240
267
  }, [ settings ] );
241
268
  }
@@ -432,3 +432,233 @@ describe( 'BorderPanel — inherited Global Styles label treatment', () => {
432
432
  } );
433
433
  } );
434
434
  } );
435
+
436
+ describe( 'BorderPanel — shadow preset persistence', () => {
437
+ const selectShadowPreset = async ( user, name ) => {
438
+ await user.click(
439
+ screen.getByRole( 'button', { name: 'Drop shadow' } )
440
+ );
441
+ await user.click( screen.getByRole( 'option', { name } ) );
442
+ };
443
+
444
+ const shadowSettings = ( presets, defaultPresets = true ) => ( {
445
+ shadow: { defaultPresets, presets },
446
+ } );
447
+
448
+ const defaultPreset = {
449
+ name: 'Natural',
450
+ slug: 'natural',
451
+ shadow: '6px 6px 9px rgba(0, 0, 0, 0.2)',
452
+ };
453
+ const themePreset = {
454
+ name: 'Outlined',
455
+ slug: 'outlined',
456
+ shadow: '6px 6px 0px -3px rgba(255, 255, 255, 1), 6px 6px rgba(0, 0, 0, 1)',
457
+ };
458
+ const customPreset = {
459
+ name: 'My shadow',
460
+ slug: 'my-shadow',
461
+ shadow: '0 0 10px rgba(255, 0, 0, 1)',
462
+ };
463
+
464
+ it( 'persists a theme preset as a preset reference when custom presets also exist', async () => {
465
+ const user = userEvent.setup();
466
+ const onChange = jest.fn();
467
+
468
+ render(
469
+ <BorderPanel
470
+ value={ {} }
471
+ settings={ shadowSettings( {
472
+ default: [ defaultPreset ],
473
+ theme: [ themePreset ],
474
+ custom: [ customPreset ],
475
+ } ) }
476
+ onChange={ onChange }
477
+ panelId="test-panel"
478
+ />
479
+ );
480
+
481
+ await selectShadowPreset( user, 'Outlined' );
482
+
483
+ expect( onChange ).toHaveBeenCalledWith( {
484
+ shadow: 'var:preset|shadow|outlined',
485
+ } );
486
+ } );
487
+
488
+ it( 'persists a default preset as a preset reference when custom presets also exist', async () => {
489
+ const user = userEvent.setup();
490
+ const onChange = jest.fn();
491
+
492
+ render(
493
+ <BorderPanel
494
+ value={ {} }
495
+ settings={ shadowSettings( {
496
+ default: [ defaultPreset ],
497
+ theme: [ themePreset ],
498
+ custom: [ customPreset ],
499
+ } ) }
500
+ onChange={ onChange }
501
+ panelId="test-panel"
502
+ />
503
+ );
504
+
505
+ await selectShadowPreset( user, 'Natural' );
506
+
507
+ expect( onChange ).toHaveBeenCalledWith( {
508
+ shadow: 'var:preset|shadow|natural',
509
+ } );
510
+ } );
511
+
512
+ it( 'persists a custom preset as a preset reference', async () => {
513
+ const user = userEvent.setup();
514
+ const onChange = jest.fn();
515
+
516
+ render(
517
+ <BorderPanel
518
+ value={ {} }
519
+ settings={ shadowSettings( {
520
+ default: [ defaultPreset ],
521
+ theme: [ themePreset ],
522
+ custom: [ customPreset ],
523
+ } ) }
524
+ onChange={ onChange }
525
+ panelId="test-panel"
526
+ />
527
+ );
528
+
529
+ await selectShadowPreset( user, 'My shadow' );
530
+
531
+ expect( onChange ).toHaveBeenCalledWith( {
532
+ shadow: 'var:preset|shadow|my-shadow',
533
+ } );
534
+ } );
535
+
536
+ it( 'persists the unset entry as a literal value, not a preset reference', async () => {
537
+ const user = userEvent.setup();
538
+ const onChange = jest.fn();
539
+
540
+ render(
541
+ <BorderPanel
542
+ value={ {} }
543
+ settings={ shadowSettings( {
544
+ theme: [ themePreset ],
545
+ custom: [ customPreset ],
546
+ } ) }
547
+ onChange={ onChange }
548
+ panelId="test-panel"
549
+ />
550
+ );
551
+
552
+ await selectShadowPreset( user, 'Unset' );
553
+
554
+ expect( onChange ).toHaveBeenCalledWith( { shadow: 'none' } );
555
+ } );
556
+
557
+ it( 'does not reference default presets the theme has opted out of', async () => {
558
+ const user = userEvent.setup();
559
+ const onChange = jest.fn();
560
+
561
+ render(
562
+ <BorderPanel
563
+ value={ {} }
564
+ settings={ shadowSettings(
565
+ {
566
+ // Same shadow value as the theme preset below, so the
567
+ // lookup has to skip it rather than match it first.
568
+ default: [
569
+ { ...defaultPreset, shadow: themePreset.shadow },
570
+ ],
571
+ theme: [ themePreset ],
572
+ },
573
+ false
574
+ ) }
575
+ onChange={ onChange }
576
+ panelId="test-panel"
577
+ />
578
+ );
579
+
580
+ await selectShadowPreset( user, 'Outlined' );
581
+
582
+ // The opted-out preset is not offered, so it is not something the
583
+ // user can have meant.
584
+ expect(
585
+ screen.queryByRole( 'option', { name: 'Natural' } )
586
+ ).not.toBeInTheDocument();
587
+ expect( onChange ).toHaveBeenCalledWith( {
588
+ shadow: 'var:preset|shadow|outlined',
589
+ } );
590
+ } );
591
+
592
+ it( 'offers only the most specific preset when a slug is defined twice', async () => {
593
+ const user = userEvent.setup();
594
+ const onChange = jest.fn();
595
+
596
+ // Custom preset slugs are generated as `shadow-<n>` from the custom
597
+ // presets alone, so they can collide with a theme preset's slug.
598
+ const themeShadowOne = {
599
+ name: 'Theme shadow',
600
+ slug: 'shadow-1',
601
+ shadow: '0 0 10px rgba(0, 0, 255, 1)',
602
+ };
603
+ const customShadowOne = {
604
+ name: 'Shadow 1',
605
+ slug: 'shadow-1',
606
+ shadow: '0 0 10px rgba(255, 0, 0, 1)',
607
+ };
608
+
609
+ render(
610
+ <BorderPanel
611
+ value={ {} }
612
+ settings={ shadowSettings( {
613
+ theme: [ themeShadowOne ],
614
+ custom: [ customShadowOne ],
615
+ } ) }
616
+ onChange={ onChange }
617
+ panelId="test-panel"
618
+ />
619
+ );
620
+
621
+ await user.click(
622
+ screen.getByRole( 'button', { name: 'Drop shadow' } )
623
+ );
624
+
625
+ // `--wp--preset--shadow--shadow-1` holds the custom preset's value, so
626
+ // offering the theme one would show a shadow it cannot apply.
627
+ expect(
628
+ screen.queryByRole( 'option', { name: 'Theme shadow' } )
629
+ ).not.toBeInTheDocument();
630
+
631
+ await user.click( screen.getByRole( 'option', { name: 'Shadow 1' } ) );
632
+
633
+ expect( onChange ).toHaveBeenCalledWith( {
634
+ shadow: 'var:preset|shadow|shadow-1',
635
+ } );
636
+ } );
637
+
638
+ it( 'references the most specific origin when two presets share a value', async () => {
639
+ const user = userEvent.setup();
640
+ const onChange = jest.fn();
641
+
642
+ // A custom preset starts out with the same value as the `natural`
643
+ // default one, so this is the state right after adding one.
644
+ render(
645
+ <BorderPanel
646
+ value={ {} }
647
+ settings={ shadowSettings( {
648
+ default: [ defaultPreset ],
649
+ custom: [
650
+ { ...customPreset, shadow: defaultPreset.shadow },
651
+ ],
652
+ } ) }
653
+ onChange={ onChange }
654
+ panelId="test-panel"
655
+ />
656
+ );
657
+
658
+ await selectShadowPreset( user, 'My shadow' );
659
+
660
+ expect( onChange ).toHaveBeenCalledWith( {
661
+ shadow: 'var:preset|shadow|my-shadow',
662
+ } );
663
+ } );
664
+ } );
@@ -258,6 +258,30 @@ describe( 'ColorPanel — duplicate-hex preset slug identity', () => {
258
258
  } );
259
259
  } );
260
260
 
261
+ describe( 'ColorPanel — additional elements', () => {
262
+ it( 'renders additional element color controls', () => {
263
+ render(
264
+ <ColorPanel
265
+ value={ {} }
266
+ settings={ settingsWithColors( {
267
+ text: true,
268
+ background: true,
269
+ } ) }
270
+ onChange={ () => {} }
271
+ panelId="test-panel"
272
+ additionalElements={ [
273
+ { name: 'textInput', label: 'Inputs' },
274
+ ] }
275
+ defaultControls={ { textInput: true } }
276
+ />
277
+ );
278
+
279
+ expect(
280
+ screen.getByRole( 'button', { name: /^Inputs/ } )
281
+ ).toBeVisible();
282
+ } );
283
+ } );
284
+
261
285
  describe( 'ColorPanel — inherited Global Styles label treatment', () => {
262
286
  describe( 'Link color', () => {
263
287
  it( 'exposes an accessible reset when local link.text overrides the inherited value', () => {
@@ -90,6 +90,14 @@ const baseSettings = {
90
90
  },
91
91
  };
92
92
 
93
+ const settingsWithAxialGap = {
94
+ ...baseSettings,
95
+ spacing: {
96
+ ...baseSettings.spacing,
97
+ blockGap: [ 'horizontal', 'vertical' ],
98
+ },
99
+ };
100
+
93
101
  const settingsWithDimensions = {
94
102
  ...baseSettings,
95
103
  dimensions: {
@@ -280,12 +288,7 @@ describe( 'DimensionsPanel — per-control placeholder pattern', () => {
280
288
  expect( gapInput ).toHaveAttribute( 'placeholder', '1.5rem' );
281
289
  } );
282
290
 
283
- it( 'renders no placeholder for blockGap when the inherited value is the compound axial shape (single-input path can only display strings)', () => {
284
- // When blockGap is an object (axial split), the panel still
285
- // renders the single-input UnitControl path because the
286
- // settings here do not enable axial gap sides. The placeholder
287
- // must NOT surface a non-string value into a control that
288
- // cannot display it.
291
+ it( 'renders the vertical blockGap as a placeholder when the inherited value has an axial shape', () => {
289
292
  renderPanel( {
290
293
  value: {},
291
294
  inheritedValue: {
@@ -295,7 +298,95 @@ describe( 'DimensionsPanel — per-control placeholder pattern', () => {
295
298
  } );
296
299
 
297
300
  const gapInput = screen.getByLabelText( /block spacing/i );
298
- expect( gapInput ).not.toHaveAttribute( 'placeholder' );
301
+ expect( gapInput ).toHaveAttribute( 'placeholder', '1rem' );
302
+ } );
303
+
304
+ it( 'renders the vertical blockGap when a local axial value is shown in a single input', () => {
305
+ renderPanel( {
306
+ value: {
307
+ spacing: { blockGap: { top: '1rem', left: '0.5rem' } },
308
+ },
309
+ settings: baseSettings,
310
+ } );
311
+
312
+ const gapInput = screen.getByLabelText( /block spacing/i );
313
+ expect( gapInput ).toHaveValue( 1 );
314
+ } );
315
+ } );
316
+
317
+ describe( 'block spacing (axial gap path)', () => {
318
+ it( 'renders separate horizontal and vertical controls when axial block gap is allowed', async () => {
319
+ const user = userEvent.setup();
320
+ renderPanel( {
321
+ value: {},
322
+ settings: settingsWithAxialGap,
323
+ } );
324
+
325
+ const blockSpacingControl = screen.getByRole( 'group', {
326
+ name: 'Block spacing',
327
+ } );
328
+ await user.click(
329
+ within( blockSpacingControl ).getByRole( 'button', {
330
+ name: 'Unlink sides',
331
+ } )
332
+ );
333
+
334
+ expect(
335
+ within( blockSpacingControl ).getByRole( 'textbox', {
336
+ name: 'Top and bottom sides',
337
+ } )
338
+ ).toBeInTheDocument();
339
+ expect(
340
+ within( blockSpacingControl ).getByRole( 'textbox', {
341
+ name: 'Left and right sides',
342
+ } )
343
+ ).toBeInTheDocument();
344
+ } );
345
+
346
+ it( 'renders a single control when axial block gap is not allowed', () => {
347
+ renderPanel( {
348
+ value: {},
349
+ settings: settingsWithAxialGap,
350
+ allowAxialBlockGap: false,
351
+ } );
352
+
353
+ expect(
354
+ screen.getByLabelText( /block spacing/i )
355
+ ).toBeInTheDocument();
356
+ expect(
357
+ screen.queryByRole( 'group', { name: 'Block spacing' } )
358
+ ).not.toBeInTheDocument();
359
+ } );
360
+
361
+ it( 'keeps the block spacing control visible when axial support changes', () => {
362
+ const settings = {
363
+ ...settingsWithSpacingPresets,
364
+ spacing: {
365
+ ...settingsWithSpacingPresets.spacing,
366
+ blockGap: [ 'horizontal', 'vertical' ],
367
+ },
368
+ };
369
+ const props = {
370
+ value: {},
371
+ settings,
372
+ onChange: () => {},
373
+ panelId: 'test-panel',
374
+ };
375
+ const { rerender } = render(
376
+ <DimensionsPanel { ...props } allowAxialBlockGap />
377
+ );
378
+
379
+ expect(
380
+ screen.getAllByRole( 'slider', { name: /block spacing/i } )
381
+ ).toHaveLength( 2 );
382
+
383
+ rerender(
384
+ <DimensionsPanel { ...props } allowAxialBlockGap={ false } />
385
+ );
386
+
387
+ expect(
388
+ screen.getAllByRole( 'slider', { name: /block spacing/i } )
389
+ ).toHaveLength( 1 );
299
390
  } );
300
391
  } );
301
392
 
@@ -185,3 +185,87 @@ describe( 'FiltersPanel — visual treatment and display-without-commit', () =>
185
185
  expect( onChange ).not.toHaveBeenCalled();
186
186
  } );
187
187
  } );
188
+
189
+ // Adding two custom duotones in Global Styles seeds both from the palette's
190
+ // darkest and lightest colors, so duplicates are the ordinary case. Only the
191
+ // slug tells them apart.
192
+ const duplicateSettings = {
193
+ color: {
194
+ customDuotone: true,
195
+ defaultDuotone: true,
196
+ duotone: {
197
+ custom: [
198
+ {
199
+ name: 'Duotone 1',
200
+ slug: 'custom-duotone-1',
201
+ colors: [ '#000000', '#ffffff' ],
202
+ },
203
+ {
204
+ name: 'Duotone 2',
205
+ slug: 'custom-duotone-2',
206
+ colors: [ '#000000', '#ffffff' ],
207
+ },
208
+ ],
209
+ },
210
+ palette: {
211
+ default: [
212
+ { name: 'Black', slug: 'black', color: '#000000' },
213
+ { name: 'White', slug: 'white', color: '#ffffff' },
214
+ ],
215
+ },
216
+ },
217
+ };
218
+
219
+ async function openDuotone( user ) {
220
+ await user.click( screen.getByRole( 'button', { name: /duotone/i } ) );
221
+ }
222
+
223
+ describe( 'FiltersPanel — duplicate duotone presets', () => {
224
+ it( 'marks only the stored preset as selected', async () => {
225
+ const user = userEvent.setup();
226
+
227
+ render(
228
+ <FiltersPanel
229
+ value={ {
230
+ filter: { duotone: 'var:preset|duotone|custom-duotone-2' },
231
+ } }
232
+ inheritedValue={ {} }
233
+ settings={ duplicateSettings }
234
+ onChange={ () => {} }
235
+ panelId="test-panel"
236
+ />
237
+ );
238
+ await openDuotone( user );
239
+
240
+ expect(
241
+ screen.getByRole( 'option', { name: 'Duotone: Duotone 1' } )
242
+ ).toHaveAttribute( 'aria-selected', 'false' );
243
+ expect(
244
+ screen.getByRole( 'option', { name: 'Duotone: Duotone 2' } )
245
+ ).toHaveAttribute( 'aria-selected', 'true' );
246
+ } );
247
+
248
+ it( 'saves the slug of the preset that was picked', async () => {
249
+ const user = userEvent.setup();
250
+ const onChange = jest.fn();
251
+
252
+ render(
253
+ <FiltersPanel
254
+ value={ {} }
255
+ inheritedValue={ {} }
256
+ settings={ duplicateSettings }
257
+ onChange={ onChange }
258
+ panelId="test-panel"
259
+ />
260
+ );
261
+ await openDuotone( user );
262
+
263
+ await user.click(
264
+ screen.getByRole( 'option', { name: 'Duotone: Duotone 2' } )
265
+ );
266
+
267
+ expect( onChange ).toHaveBeenCalledWith( {
268
+ filter: { duotone: 'var:preset|duotone|custom-duotone-2' },
269
+ } );
270
+ } );
271
+ } );
@@ -0,0 +1,30 @@
1
+ import { getDuotoneSlugFromPreset } from '../utils';
2
+
3
+ describe( 'global styles utils', () => {
4
+ describe( 'getDuotoneSlugFromPreset', () => {
5
+ it( 'should return the slug of a preset reference', () => {
6
+ expect(
7
+ getDuotoneSlugFromPreset( 'var:preset|duotone|grayscale' )
8
+ ).toBe( 'grayscale' );
9
+ } );
10
+
11
+ it( 'should return the slug of a custom preset', () => {
12
+ expect(
13
+ getDuotoneSlugFromPreset(
14
+ 'var:preset|duotone|custom-duotone-2'
15
+ )
16
+ ).toBe( 'custom-duotone-2' );
17
+ } );
18
+
19
+ it( 'should return undefined for custom colors', () => {
20
+ expect(
21
+ getDuotoneSlugFromPreset( [ '#000000', '#ffffff' ] )
22
+ ).toBeUndefined();
23
+ } );
24
+
25
+ it( 'should return undefined for unset and for no value', () => {
26
+ expect( getDuotoneSlugFromPreset( 'unset' ) ).toBeUndefined();
27
+ expect( getDuotoneSlugFromPreset( undefined ) ).toBeUndefined();
28
+ } );
29
+ } );
30
+ } );
@@ -47,3 +47,26 @@ export function scopeSelector( scope, selector ) {
47
47
 
48
48
  return selectorsScoped.join( ', ' );
49
49
  }
50
+
51
+ /**
52
+ * Reads the preset slug out of a duotone style value.
53
+ *
54
+ * Two presets can hold the same pair of colors, so the slug is the only thing
55
+ * that identifies which one is applied. The colors alone do not.
56
+ *
57
+ * Lives here rather than beside its siblings in `hooks/duotone` because that
58
+ * module imports the filters panel, so the panel cannot import back from it.
59
+ *
60
+ * @param {string|string[]|undefined} duotone A duotone style value.
61
+ *
62
+ * @return {string|undefined} The preset slug, if the value references one.
63
+ */
64
+ export function getDuotoneSlugFromPreset( duotone ) {
65
+ if ( typeof duotone !== 'string' ) {
66
+ return undefined;
67
+ }
68
+
69
+ const [ , slug ] = duotone.match( /^var:preset\|duotone\|(.+)$/ ) ?? [];
70
+
71
+ return slug;
72
+ }
@@ -94,7 +94,11 @@ function UncontrolledInnerBlocks( props ) {
94
94
  EMPTY_OBJECT;
95
95
 
96
96
  const { allowSizingOnChildren = false } = defaultLayoutBlockSupport;
97
- const usedLayout = layout || defaultLayoutBlockSupport;
97
+ // The support is a config object, e.g. `{ allowSwitching: false, default: { type: 'flex' } }`,
98
+ // so the layout itself lives under `default`. Passing the config through would leave the
99
+ // context without a `type`, and consumers would resolve it to the flow layout.
100
+ const usedLayout =
101
+ layout || defaultLayoutBlockSupport.default || EMPTY_OBJECT;
98
102
 
99
103
  const memoedLayout = useMemo(
100
104
  () => ( {
@@ -174,12 +178,17 @@ export function useInnerBlocksProps( props = {}, options = {} ) {
174
178
  __unstableDisableLayoutClassNames,
175
179
  __unstableDisableDropZone,
176
180
  dropZoneElement,
181
+ layout: layoutOverride,
177
182
  } = options;
178
183
  const {
179
184
  clientId,
180
- layout = null,
185
+ layout: contextLayout = null,
181
186
  __unstableLayoutClassNames: layoutClassNames = '',
182
187
  } = useBlockEditContext();
188
+ // An options layout takes precedence over the context layout, as it does
189
+ // for the inner blocks settings, so containers whose layout attribute
190
+ // doesn't apply to their inner blocks (e.g. Post Template) can opt out.
191
+ const layout = layoutOverride ?? contextLayout;
183
192
  const selected = useSelect(
184
193
  ( select ) => {
185
194
  const {