@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
@@ -1,7 +1,7 @@
1
- import { render, screen } from '@testing-library/react';
1
+ import { act, render, screen } from '@testing-library/react';
2
2
  import userEvent from '@testing-library/user-event';
3
3
  import { __experimentalToolsPanel as ToolsPanel } from '@wordpress/components';
4
- import { useState } from '@wordpress/element';
4
+ import { useEffect, useState } from '@wordpress/element';
5
5
  import DimensionsTool from '../';
6
6
 
7
7
  const EMPTY_OBJECT = {};
@@ -9,6 +9,9 @@ const ASPECT_RATIO_OPTIONS = [
9
9
  { label: 'Original', value: 'auto' },
10
10
  { label: '16/9', value: '16/9' },
11
11
  { label: '4/3', value: '4/3' },
12
+ // Matches the core preset for a square, which is written as a single
13
+ // number rather than a ratio.
14
+ { label: 'Square', value: '1' },
12
15
  {
13
16
  label: 'Custom',
14
17
  value: 'custom',
@@ -41,6 +44,30 @@ function Example( { initialValue, onChange, ...props } ) {
41
44
  );
42
45
  }
43
46
 
47
+ // Holds the value like `Example`, but also lets a test update it from the
48
+ // outside, the way `updateBlockAttributes`, undo, or another client would.
49
+ function ExternallyUpdatedExample( { initialValue, onChange, updateRef } ) {
50
+ const [ value, setValue ] = useState( initialValue );
51
+ useEffect( () => {
52
+ updateRef.current = setValue;
53
+ }, [ updateRef ] );
54
+ return (
55
+ <ToolsPanel label="Dimensions" panelId="panel-id" resetAll={ () => {} }>
56
+ <DimensionsTool
57
+ panelId="panel-id"
58
+ onChange={ ( nextValue ) => {
59
+ setValue( nextValue );
60
+ onChange( nextValue );
61
+ } }
62
+ defaultScale="cover"
63
+ defaultAspectRatio="auto"
64
+ aspectRatioOptions={ ASPECT_RATIO_OPTIONS }
65
+ value={ value }
66
+ />
67
+ </ToolsPanel>
68
+ );
69
+ }
70
+
44
71
  function ControlledExample( { value, onChange, ...props } ) {
45
72
  return (
46
73
  <ToolsPanel
@@ -102,6 +129,143 @@ describe( 'DimensionsTool', () => {
102
129
  );
103
130
  expect( aspectRatioSelect ).toHaveValue( 'auto' );
104
131
  } );
132
+
133
+ it( 'displays an aspect ratio that is written differently to the option with the same ratio', () => {
134
+ const onChange = jest.fn();
135
+ const { rerender } = render(
136
+ <ControlledExample
137
+ value={ { aspectRatio: '1/1' } }
138
+ onChange={ onChange }
139
+ />
140
+ );
141
+ const aspectRatioSelect = screen.getByRole( 'combobox', {
142
+ name: 'Aspect ratio',
143
+ } );
144
+
145
+ expect( aspectRatioSelect ).toHaveValue( '1' );
146
+
147
+ rerender(
148
+ <ControlledExample
149
+ value={ { aspectRatio: '16 / 9' } }
150
+ onChange={ onChange }
151
+ />
152
+ );
153
+ expect( aspectRatioSelect ).toHaveValue( '16/9' );
154
+ } );
155
+
156
+ it( 'displays an aspect ratio without a matching option as custom', () => {
157
+ const onChange = jest.fn();
158
+ render(
159
+ <ControlledExample
160
+ value={ { aspectRatio: '7/5' } }
161
+ onChange={ onChange }
162
+ />
163
+ );
164
+
165
+ expect(
166
+ screen.getByRole( 'combobox', { name: 'Aspect ratio' } )
167
+ ).toHaveValue( 'custom' );
168
+ } );
169
+
170
+ it( 'updates the scale control when the value prop changes', () => {
171
+ const onChange = jest.fn();
172
+ const { rerender } = render(
173
+ <ControlledExample
174
+ value={ { aspectRatio: '16/9', scale: 'cover' } }
175
+ onChange={ onChange }
176
+ />
177
+ );
178
+
179
+ expect(
180
+ screen.getByRole( 'radio', { name: 'Cover' } )
181
+ ).toBeChecked();
182
+
183
+ rerender(
184
+ <ControlledExample
185
+ value={ { aspectRatio: '16/9', scale: 'contain' } }
186
+ onChange={ onChange }
187
+ />
188
+ );
189
+
190
+ expect(
191
+ screen.getByRole( 'radio', { name: 'Contain' } )
192
+ ).toBeChecked();
193
+ } );
194
+ } );
195
+
196
+ describe( 'external updates', () => {
197
+ it( 'restores the scale that was set externally', async () => {
198
+ const user = userEvent.setup();
199
+ const onChange = jest.fn();
200
+ const updateRef = { current: null };
201
+
202
+ render(
203
+ <ExternallyUpdatedExample
204
+ initialValue={ { aspectRatio: '16/9', scale: 'cover' } }
205
+ onChange={ onChange }
206
+ updateRef={ updateRef }
207
+ />
208
+ );
209
+
210
+ // Update the scale from outside the component.
211
+ await act( async () =>
212
+ updateRef.current( { aspectRatio: '16/9', scale: 'contain' } )
213
+ );
214
+
215
+ // Clearing and setting the aspect ratio again restores the scale.
216
+ await user.selectOptions(
217
+ screen.getByRole( 'combobox', { name: 'Aspect ratio' } ),
218
+ 'auto'
219
+ );
220
+ await user.selectOptions(
221
+ screen.getByRole( 'combobox', { name: 'Aspect ratio' } ),
222
+ '4/3'
223
+ );
224
+
225
+ expect( onChange ).toHaveBeenLastCalledWith( {
226
+ aspectRatio: '4/3',
227
+ scale: 'contain',
228
+ } );
229
+ } );
230
+
231
+ it( 'restores the aspect ratio that was set externally', async () => {
232
+ const user = userEvent.setup();
233
+ const onChange = jest.fn();
234
+ const updateRef = { current: null };
235
+
236
+ render(
237
+ <ExternallyUpdatedExample
238
+ initialValue={ { aspectRatio: '16/9', scale: 'cover' } }
239
+ onChange={ onChange }
240
+ updateRef={ updateRef }
241
+ />
242
+ );
243
+
244
+ // Update the aspect ratio from outside the component.
245
+ await act( async () =>
246
+ updateRef.current( { aspectRatio: '4/3', scale: 'cover' } )
247
+ );
248
+
249
+ // Setting both a width and a height replaces the aspect ratio with
250
+ // a custom one, and clearing the width restores the previous one.
251
+ await user.type(
252
+ screen.getByRole( 'spinbutton', { name: 'Width' } ),
253
+ '100'
254
+ );
255
+ await user.type(
256
+ screen.getByRole( 'spinbutton', { name: 'Height' } ),
257
+ '100'
258
+ );
259
+ await user.clear(
260
+ screen.getByRole( 'spinbutton', { name: 'Width' } )
261
+ );
262
+
263
+ expect( onChange ).toHaveBeenLastCalledWith( {
264
+ aspectRatio: '4/3',
265
+ scale: 'cover',
266
+ height: '100px',
267
+ } );
268
+ } );
105
269
  } );
106
270
 
107
271
  describe( 'updating aspectRatio', () => {
@@ -0,0 +1,51 @@
1
+ import { findAspectRatioOption, parseAspectRatio } from '../utils';
2
+
3
+ describe( 'parseAspectRatio', () => {
4
+ it.each( [
5
+ [ '1', 1 ],
6
+ [ '1/1', 1 ],
7
+ [ '16/9', 16 / 9 ],
8
+ [ '16 / 9', 16 / 9 ],
9
+ [ '1.5', 1.5 ],
10
+ [ '3/2', 1.5 ],
11
+ ] )( 'parses %s', ( value, expected ) => {
12
+ expect( parseAspectRatio( value ) ).toBe( expected );
13
+ } );
14
+
15
+ it.each( [
16
+ [ 'auto' ],
17
+ [ '' ],
18
+ [ '16/9/2' ],
19
+ [ '0' ],
20
+ [ '1/0' ],
21
+ [ '-16/9' ],
22
+ [ undefined ],
23
+ [ null ],
24
+ ] )( 'returns null for %s', ( value ) => {
25
+ expect( parseAspectRatio( value ) ).toBeNull();
26
+ } );
27
+ } );
28
+
29
+ describe( 'findAspectRatioOption', () => {
30
+ const options = [
31
+ { label: 'Original', value: 'auto' },
32
+ { label: 'Square', value: '1' },
33
+ { label: 'Wide', value: '16/9' },
34
+ ];
35
+
36
+ it( 'matches an option by value', () => {
37
+ expect( findAspectRatioOption( '16/9', options ) ).toBe( options[ 2 ] );
38
+ } );
39
+
40
+ it( 'matches an option describing the same ratio', () => {
41
+ expect( findAspectRatioOption( '1/1', options ) ).toBe( options[ 1 ] );
42
+ expect( findAspectRatioOption( '16 / 9', options ) ).toBe(
43
+ options[ 2 ]
44
+ );
45
+ } );
46
+
47
+ it( 'returns null when no option describes the ratio', () => {
48
+ expect( findAspectRatioOption( '7/5', options ) ).toBeNull();
49
+ expect( findAspectRatioOption( 'nonsense', options ) ).toBeNull();
50
+ } );
51
+ } );
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Parses a CSS `aspect-ratio` value into a number so that values that are
3
+ * written differently but describe the same ratio, e.g. `1` and `1/1`, can be
4
+ * compared.
5
+ *
6
+ * @param {string} [value] CSS aspect-ratio value, e.g. '16/9' or '1.5'.
7
+ *
8
+ * @return {?number} The ratio as a number, or null when it cannot be parsed.
9
+ */
10
+ export function parseAspectRatio( value ) {
11
+ if ( typeof value !== 'string' ) {
12
+ return null;
13
+ }
14
+
15
+ const parts = value.split( '/' );
16
+
17
+ if ( parts.length > 2 ) {
18
+ return null;
19
+ }
20
+
21
+ const [ width, height = '1' ] = parts;
22
+ const numericWidth = Number( width.trim() );
23
+ const numericHeight = Number( height.trim() );
24
+
25
+ if (
26
+ ! Number.isFinite( numericWidth ) ||
27
+ ! Number.isFinite( numericHeight ) ||
28
+ numericWidth <= 0 ||
29
+ numericHeight <= 0
30
+ ) {
31
+ return null;
32
+ }
33
+
34
+ return numericWidth / numericHeight;
35
+ }
36
+
37
+ /**
38
+ * Finds the option that represents the given CSS `aspect-ratio` value.
39
+ *
40
+ * Aspect ratio values are free-form CSS, while the options are a fixed list of
41
+ * presets, so an exact string match isn't enough: `1/1` and `1` describe the
42
+ * same ratio but only one of them is a preset value.
43
+ *
44
+ * @param {string} value CSS aspect-ratio value.
45
+ * @param {Object[]} options Aspect ratio options.
46
+ *
47
+ * @return {?Object} The matching option, or null when there is none.
48
+ */
49
+ export function findAspectRatioOption( value, options ) {
50
+ const exactMatch = options.find( ( option ) => option.value === value );
51
+
52
+ if ( exactMatch ) {
53
+ return exactMatch;
54
+ }
55
+
56
+ const ratio = parseAspectRatio( value );
57
+
58
+ if ( ratio === null ) {
59
+ return null;
60
+ }
61
+
62
+ return (
63
+ options.find(
64
+ ( option ) => parseAspectRatio( option.value ) === ratio
65
+ ) ?? null
66
+ );
67
+ }
@@ -18,6 +18,7 @@ function DuotoneControl( {
18
18
  disableCustomColors,
19
19
  disableCustomDuotone,
20
20
  value,
21
+ selectedSlug,
21
22
  onChange,
22
23
  } ) {
23
24
  let toolbarIcon;
@@ -75,6 +76,7 @@ function DuotoneControl( {
75
76
  disableCustomColors={ disableCustomColors }
76
77
  disableCustomDuotone={ disableCustomDuotone }
77
78
  value={ value }
79
+ selectedSlug={ selectedSlug }
78
80
  onChange={ onChange }
79
81
  />
80
82
  </MenuGroup>
@@ -0,0 +1,100 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import DuotoneControl from '../';
4
+
5
+ // Adding two custom duotones in Global Styles seeds both from the palette's
6
+ // darkest and lightest colors, so two presets holding the same pair is the
7
+ // ordinary case. Only the slug tells them apart.
8
+ const DUPLICATE_PALETTE = [
9
+ {
10
+ name: 'Duotone 1',
11
+ slug: 'custom-duotone-1',
12
+ colors: [ '#000000', '#ffffff' ],
13
+ },
14
+ {
15
+ name: 'Duotone 2',
16
+ slug: 'custom-duotone-2',
17
+ colors: [ '#000000', '#ffffff' ],
18
+ },
19
+ ];
20
+
21
+ const COLOR_PALETTE = [
22
+ { color: '#000000', name: 'Black', slug: 'black' },
23
+ { color: '#ffffff', name: 'White', slug: 'white' },
24
+ ];
25
+
26
+ async function openControl( user ) {
27
+ await user.click(
28
+ screen.getByRole( 'button', { name: 'Apply duotone filter' } )
29
+ );
30
+ }
31
+
32
+ describe( 'DuotoneControl', () => {
33
+ it( 'reports the slug of the duplicate that was picked', async () => {
34
+ const user = userEvent.setup();
35
+ const onChange = jest.fn();
36
+
37
+ render(
38
+ <DuotoneControl
39
+ duotonePalette={ DUPLICATE_PALETTE }
40
+ colorPalette={ COLOR_PALETTE }
41
+ value={ undefined }
42
+ onChange={ onChange }
43
+ />
44
+ );
45
+ await openControl( user );
46
+
47
+ await user.click(
48
+ screen.getByRole( 'option', { name: 'Duotone: Duotone 2' } )
49
+ );
50
+
51
+ expect( onChange ).toHaveBeenCalledWith(
52
+ [ '#000000', '#ffffff' ],
53
+ 1,
54
+ 'custom-duotone-2'
55
+ );
56
+ } );
57
+
58
+ it( 'marks only the stored preset as selected when two share colors', async () => {
59
+ const user = userEvent.setup();
60
+
61
+ render(
62
+ <DuotoneControl
63
+ duotonePalette={ DUPLICATE_PALETTE }
64
+ colorPalette={ COLOR_PALETTE }
65
+ value={ [ '#000000', '#ffffff' ] }
66
+ selectedSlug="custom-duotone-2"
67
+ onChange={ jest.fn() }
68
+ />
69
+ );
70
+ await openControl( user );
71
+
72
+ expect(
73
+ screen.getByRole( 'option', { name: 'Duotone: Duotone 1' } )
74
+ ).toHaveAttribute( 'aria-selected', 'false' );
75
+ expect(
76
+ screen.getByRole( 'option', { name: 'Duotone: Duotone 2' } )
77
+ ).toHaveAttribute( 'aria-selected', 'true' );
78
+ } );
79
+
80
+ it( 'falls back to color matching when no slug is stored', async () => {
81
+ const user = userEvent.setup();
82
+
83
+ render(
84
+ <DuotoneControl
85
+ duotonePalette={ DUPLICATE_PALETTE }
86
+ colorPalette={ COLOR_PALETTE }
87
+ value={ [ '#000000', '#ffffff' ] }
88
+ onChange={ jest.fn() }
89
+ />
90
+ );
91
+ await openControl( user );
92
+
93
+ expect(
94
+ screen.getByRole( 'option', { name: 'Duotone: Duotone 1' } )
95
+ ).toHaveAttribute( 'aria-selected', 'true' );
96
+ expect(
97
+ screen.getByRole( 'option', { name: 'Duotone: Duotone 2' } )
98
+ ).toHaveAttribute( 'aria-selected', 'true' );
99
+ } );
100
+ } );
@@ -1,4 +1,7 @@
1
- import { TextareaControl, Notice } from '@wordpress/components';
1
+ import {
2
+ TextareaControl as WCTextareaControl,
3
+ Notice,
4
+ } from '@wordpress/components';
2
5
  import { Stack } from '@wordpress/ui';
3
6
  import { useState } from '@wordpress/element';
4
7
  import { __ } from '@wordpress/i18n';
@@ -88,7 +91,7 @@ export default function AdvancedPanel( {
88
91
  { cssError }
89
92
  </Notice>
90
93
  ) }
91
- <TextareaControl
94
+ <WCTextareaControl
92
95
  label={ __( 'Additional CSS' ) }
93
96
  value={ customCSS }
94
97
  onChange={ ( newValue ) => handleOnChange( newValue ) }
@@ -98,7 +98,6 @@ export default function BorderPanel( {
98
98
  inheritedValue = value,
99
99
  settings,
100
100
  panelId,
101
- name,
102
101
  defaultControls = DEFAULT_CONTROLS,
103
102
  showInheritanceLabelIndicators = isGlobalStylesInheritanceEnabled(),
104
103
  } ) {
@@ -262,14 +261,18 @@ export default function BorderPanel( {
262
261
  inheritedShadow !== undefined &&
263
262
  inheritedShadow !== '';
264
263
  const shadow = isShadowPlaceholder ? inheritedShadow : localShadow;
265
- const shadowPresets = settings?.shadow?.presets ?? {};
266
- const mergedShadowPresets =
267
- shadowPresets.custom ??
268
- shadowPresets.theme ??
269
- shadowPresets.default ??
270
- [];
264
+ // The presets the popover offers, from every origin. A preset the user
265
+ // picked has to be stored as a reference to its CSS variable rather than as
266
+ // the value it resolves to today, otherwise it stops tracking later edits
267
+ // to the preset. Reading the same list the popover renders is what keeps
268
+ // the two in step.
269
+ const shadowPresets = useShadowPresets( settings );
271
270
  const setShadow = ( newValue ) => {
272
- const slug = mergedShadowPresets?.find(
271
+ // The list runs default, theme, custom, so searching backwards picks
272
+ // the most specific origin when more than one defines the same shadow
273
+ // value — a newly added custom preset starts out identical to the
274
+ // `natural` default one, and it is the custom slug the user means.
275
+ const slug = shadowPresets.findLast(
273
276
  ( { shadow: shadowName } ) => shadowName === newValue
274
277
  )?.slug;
275
278
 
@@ -369,7 +372,6 @@ export default function BorderPanel( {
369
372
  showBorderRadius;
370
373
 
371
374
  const label = useBorderPanelLabel( {
372
- blockName: name,
373
375
  hasShadowControl,
374
376
  hasBorderControl,
375
377
  } );
@@ -142,6 +142,7 @@ export default function ColorPanel( {
142
142
  label,
143
143
  children,
144
144
  contrastWarning,
145
+ additionalElements,
145
146
  showInheritanceLabelIndicators = isGlobalStylesInheritanceEnabled(),
146
147
  } ) {
147
148
  const {
@@ -259,8 +260,19 @@ export default function ColorPanel( {
259
260
  label: __( 'H6' ),
260
261
  showPanel: showHeadingPanel,
261
262
  },
263
+ ...( additionalElements?.map( ( element ) => ( {
264
+ ...element,
265
+ showPanel: hasSolidColors || hasGradientColors,
266
+ } ) ) ?? [] ),
262
267
  ],
263
- [ showCaptionPanel, showButtonPanel, showHeadingPanel ]
268
+ [
269
+ additionalElements,
270
+ hasGradientColors,
271
+ hasSolidColors,
272
+ showCaptionPanel,
273
+ showButtonPanel,
274
+ showHeadingPanel,
275
+ ]
264
276
  );
265
277
 
266
278
  const resetAllFilter = useCallback(
@@ -347,6 +347,7 @@ export default function DimensionsPanel( {
347
347
  // Special case because the layout controls are not part of the dimensions panel
348
348
  // in global styles but not in block inspector.
349
349
  includeLayoutControls = false,
350
+ allowAxialBlockGap = true,
350
351
  styleState = DEFAULT_BLOCK_STYLE_STATE,
351
352
  showInheritanceLabelIndicators = isGlobalStylesInheritanceEnabled(),
352
353
  } ) {
@@ -556,9 +557,19 @@ export default function DimensionsPanel( {
556
557
  ? settings?.spacing?.blockGap
557
558
  : settings?.spacing?.blockGap?.sides;
558
559
  const isAxialGap =
559
- gapSides && gapSides.some( ( side ) => AXIAL_SIDES.includes( side ) );
560
+ allowAxialBlockGap &&
561
+ gapSides &&
562
+ gapSides.some( ( side ) => AXIAL_SIDES.includes( side ) );
560
563
  const localGapRaw = decodeValue( value?.spacing?.blockGap );
561
564
  const inheritedGapRaw = decodeValue( inheritedValue?.spacing?.blockGap );
565
+ const localGapForSingleInput =
566
+ ! isAxialGap && typeof localGapRaw === 'object'
567
+ ? localGapRaw?.top
568
+ : localGapRaw;
569
+ const inheritedGapForSingleInput =
570
+ ! isAxialGap && typeof inheritedGapRaw === 'object'
571
+ ? inheritedGapRaw?.top
572
+ : inheritedGapRaw;
562
573
  // Merge local-then-inherited so SpacingSizesControl's chip slider and
563
574
  // the axial-gap BoxControl reflect the local value when set and fall
564
575
  // back to the inherited value at rest.
@@ -572,8 +583,8 @@ export default function DimensionsPanel( {
572
583
  // at-rest cue for those.
573
584
  const isGapPlaceholder =
574
585
  ! hasValue( value?.spacing?.blockGap ) &&
575
- typeof inheritedGapRaw === 'string' &&
576
- inheritedGapRaw !== '';
586
+ typeof inheritedGapForSingleInput === 'string' &&
587
+ inheritedGapForSingleInput !== '';
577
588
  const setGapValue = ( newGapValue ) => {
578
589
  onChange(
579
590
  setImmutably( value, [ 'spacing', 'blockGap' ], newGapValue )
@@ -993,16 +1004,17 @@ export default function DimensionsPanel( {
993
1004
  min={ 0 }
994
1005
  onChange={ setGapValue }
995
1006
  units={ units }
996
- value={ localGapRaw ?? undefined }
1007
+ value={ localGapForSingleInput ?? undefined }
997
1008
  placeholder={
998
1009
  isGapPlaceholder
999
- ? inheritedGapRaw
1010
+ ? inheritedGapForSingleInput
1000
1011
  : undefined
1001
1012
  }
1002
1013
  />
1003
1014
  ) ) }
1004
1015
  { showSpacingPresetsControl && (
1005
1016
  <SpacingSizesControl
1017
+ key={ isAxialGap ? 'axial-gap' : 'single-gap' }
1006
1018
  label={ __( 'Block spacing' ) }
1007
1019
  min={ 0 }
1008
1020
  onChange={ setGapValues }
@@ -17,7 +17,10 @@ import { __, _x } from '@wordpress/i18n';
17
17
  import { useCallback, useMemo, useRef } from '@wordpress/element';
18
18
  import { reset as resetIcon } from '@wordpress/icons';
19
19
  import { getValueFromVariable } from '@wordpress/global-styles-engine';
20
- import { useToolsPanelDropdownMenuProps } from './utils';
20
+ import {
21
+ useToolsPanelDropdownMenuProps,
22
+ getDuotoneSlugFromPreset,
23
+ } from './utils';
21
24
  import { setImmutably } from '../../utils/object';
22
25
  import {
23
26
  getInheritanceProps,
@@ -202,14 +205,24 @@ export default function FiltersPanel( {
202
205
  const localDuotone = decodeValue( value?.filter?.duotone );
203
206
  const inheritedDuotone = decodeValue( inheritedValue?.filter?.duotone );
204
207
  const duotone = localDuotone ?? inheritedDuotone;
208
+ // Read from the raw value, since decoding resolves a preset reference to
209
+ // its colors and two presets can hold the same pair.
210
+ const localDuotoneSlug = getDuotoneSlugFromPreset( value?.filter?.duotone );
211
+ const inheritedDuotoneSlug = getDuotoneSlugFromPreset(
212
+ inheritedValue?.filter?.duotone
213
+ );
214
+ const duotoneSlug =
215
+ localDuotone !== undefined ? localDuotoneSlug : inheritedDuotoneSlug;
205
216
  const isDuotonePlaceholder =
206
217
  localDuotone === undefined && inheritedDuotone !== undefined;
207
- const setDuotone = ( newValue ) => {
208
- const duotonePreset = duotonePalette.find( ( { colors } ) => {
209
- return colors === newValue;
210
- } );
211
- const duotoneValue = duotonePreset
212
- ? `var:preset|duotone|${ duotonePreset.slug }`
218
+ const setDuotone = ( newValue, slug ) => {
219
+ // A slug identifies the picked preset exactly. Falling back to a value
220
+ // match cannot tell two presets holding the same colors apart.
221
+ const presetSlug =
222
+ slug ??
223
+ duotonePalette.find( ( { colors } ) => colors === newValue )?.slug;
224
+ const duotoneValue = presetSlug
225
+ ? `var:preset|duotone|${ presetSlug }`
213
226
  : newValue;
214
227
  onChange(
215
228
  setImmutably( value, [ 'filter', 'duotone' ], duotoneValue )
@@ -217,16 +230,16 @@ export default function FiltersPanel( {
217
230
  };
218
231
  // Commit the inherited value when the user clicks the active preset
219
232
  // while the picker is showing inherited duotone at rest.
220
- const setDuotoneWithInheritedCommit = ( newValue ) => {
233
+ const setDuotoneWithInheritedCommit = ( newValue, index, slug ) => {
221
234
  if (
222
235
  newValue === undefined &&
223
236
  isDuotonePlaceholder &&
224
237
  inheritedDuotone !== undefined
225
238
  ) {
226
- setDuotone( inheritedDuotone );
239
+ setDuotone( inheritedDuotone, inheritedDuotoneSlug );
227
240
  return;
228
241
  }
229
- setDuotone( newValue );
242
+ setDuotone( newValue, slug );
230
243
  };
231
244
  const hasDuotone = () => !! value?.filter?.duotone;
232
245
  const resetDuotone = () => setDuotone( undefined );
@@ -292,6 +305,7 @@ export default function FiltersPanel( {
292
305
  disableCustomColors
293
306
  disableCustomDuotone
294
307
  value={ duotone }
308
+ selectedSlug={ duotoneSlug }
295
309
  onChange={
296
310
  setDuotoneWithInheritedCommit
297
311
  }