@wordpress/block-editor 15.7.1-next.2f1c7c01b.0 → 15.8.1-next.16d95556a.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 (195) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/components/block-card/index.js +76 -34
  3. package/build/components/block-card/index.js.map +2 -2
  4. package/build/components/block-inspector/edit-contents.js +72 -0
  5. package/build/components/block-inspector/edit-contents.js.map +7 -0
  6. package/build/components/block-inspector/index.js +32 -9
  7. package/build/components/block-inspector/index.js.map +3 -3
  8. package/build/components/block-list/index.js +9 -9
  9. package/build/components/block-list/index.js.map +2 -2
  10. package/build/components/block-switcher/index.js +24 -123
  11. package/build/components/block-switcher/index.js.map +3 -3
  12. package/build/components/block-toolbar/block-toolbar-icon.js +175 -0
  13. package/build/components/block-toolbar/block-toolbar-icon.js.map +7 -0
  14. package/build/components/block-toolbar/index.js +51 -53
  15. package/build/components/block-toolbar/index.js.map +3 -3
  16. package/build/components/block-toolbar/pattern-overrides-dropdown.js +93 -0
  17. package/build/components/block-toolbar/pattern-overrides-dropdown.js.map +7 -0
  18. package/build/components/block-tools/index.js +10 -3
  19. package/build/components/block-tools/index.js.map +2 -2
  20. package/build/components/border-radius-control/utils.js +7 -3
  21. package/build/components/border-radius-control/utils.js.map +2 -2
  22. package/build/components/content-lock/modify-content-lock-menu-item.js +3 -3
  23. package/build/components/content-lock/modify-content-lock-menu-item.js.map +2 -2
  24. package/build/components/global-styles/border-panel.js +11 -7
  25. package/build/components/global-styles/border-panel.js.map +2 -2
  26. package/build/components/global-styles/color-panel.js +35 -27
  27. package/build/components/global-styles/color-panel.js.map +2 -2
  28. package/build/components/global-styles/typography-panel.js +19 -12
  29. package/build/components/global-styles/typography-panel.js.map +2 -2
  30. package/build/components/inserter/media-tab/media-tab.js +2 -1
  31. package/build/components/inserter/media-tab/media-tab.js.map +2 -2
  32. package/build/components/inspector-controls-tabs/index.js +2 -1
  33. package/build/components/inspector-controls-tabs/index.js.map +2 -2
  34. package/build/components/inspector-controls-tabs/styles-tab.js +55 -1
  35. package/build/components/inspector-controls-tabs/styles-tab.js.map +3 -3
  36. package/build/components/inspector-controls-tabs/use-inspector-controls-tabs.js +1 -1
  37. package/build/components/inspector-controls-tabs/use-inspector-controls-tabs.js.map +2 -2
  38. package/build/components/keyboard-shortcuts/index.js +8 -0
  39. package/build/components/keyboard-shortcuts/index.js.map +2 -2
  40. package/build/components/rich-text/format-edit.js +9 -1
  41. package/build/components/rich-text/format-edit.js.map +2 -2
  42. package/build/components/rich-text/index.js +1 -0
  43. package/build/components/rich-text/index.js.map +2 -2
  44. package/build/hooks/border.js +10 -5
  45. package/build/hooks/border.js.map +3 -3
  46. package/build/hooks/color.js +31 -9
  47. package/build/hooks/color.js.map +3 -3
  48. package/build/hooks/content-lock-ui.js +4 -5
  49. package/build/hooks/content-lock-ui.js.map +2 -2
  50. package/build/hooks/dimensions.js +9 -4
  51. package/build/hooks/dimensions.js.map +2 -2
  52. package/build/hooks/fit-text.js +19 -75
  53. package/build/hooks/fit-text.js.map +3 -3
  54. package/build/hooks/font-size.js +5 -2
  55. package/build/hooks/font-size.js.map +2 -2
  56. package/build/hooks/layout.js +4 -1
  57. package/build/hooks/layout.js.map +2 -2
  58. package/build/hooks/spacing-visualizer.js +5 -0
  59. package/build/hooks/spacing-visualizer.js.map +2 -2
  60. package/build/hooks/typography.js +23 -14
  61. package/build/hooks/typography.js.map +3 -3
  62. package/build/store/private-selectors.js +21 -1
  63. package/build/store/private-selectors.js.map +2 -2
  64. package/build/store/reducer.js +4 -0
  65. package/build/store/reducer.js.map +2 -2
  66. package/build/store/selectors.js +12 -3
  67. package/build/store/selectors.js.map +2 -2
  68. package/build/utils/fit-text-frontend.js +1 -0
  69. package/build/utils/fit-text-frontend.js.map +2 -2
  70. package/build/utils/fit-text-utils.js +1 -1
  71. package/build/utils/fit-text-utils.js.map +1 -1
  72. package/build-module/components/block-card/index.js +82 -32
  73. package/build-module/components/block-card/index.js.map +2 -2
  74. package/build-module/components/block-inspector/edit-contents.js +51 -0
  75. package/build-module/components/block-inspector/edit-contents.js.map +7 -0
  76. package/build-module/components/block-inspector/index.js +32 -9
  77. package/build-module/components/block-inspector/index.js.map +2 -2
  78. package/build-module/components/block-list/index.js +11 -11
  79. package/build-module/components/block-list/index.js.map +2 -2
  80. package/build-module/components/block-switcher/index.js +24 -124
  81. package/build-module/components/block-switcher/index.js.map +2 -2
  82. package/build-module/components/block-toolbar/block-toolbar-icon.js +144 -0
  83. package/build-module/components/block-toolbar/block-toolbar-icon.js.map +7 -0
  84. package/build-module/components/block-toolbar/index.js +51 -53
  85. package/build-module/components/block-toolbar/index.js.map +2 -2
  86. package/build-module/components/block-toolbar/pattern-overrides-dropdown.js +76 -0
  87. package/build-module/components/block-toolbar/pattern-overrides-dropdown.js.map +7 -0
  88. package/build-module/components/block-tools/index.js +10 -3
  89. package/build-module/components/block-tools/index.js.map +2 -2
  90. package/build-module/components/border-radius-control/utils.js +7 -3
  91. package/build-module/components/border-radius-control/utils.js.map +2 -2
  92. package/build-module/components/content-lock/modify-content-lock-menu-item.js +3 -3
  93. package/build-module/components/content-lock/modify-content-lock-menu-item.js.map +2 -2
  94. package/build-module/components/global-styles/border-panel.js +11 -7
  95. package/build-module/components/global-styles/border-panel.js.map +2 -2
  96. package/build-module/components/global-styles/color-panel.js +34 -27
  97. package/build-module/components/global-styles/color-panel.js.map +2 -2
  98. package/build-module/components/global-styles/typography-panel.js +21 -13
  99. package/build-module/components/global-styles/typography-panel.js.map +2 -2
  100. package/build-module/components/inserter/media-tab/media-tab.js +2 -1
  101. package/build-module/components/inserter/media-tab/media-tab.js.map +2 -2
  102. package/build-module/components/inspector-controls-tabs/index.js +2 -1
  103. package/build-module/components/inspector-controls-tabs/index.js.map +2 -2
  104. package/build-module/components/inspector-controls-tabs/styles-tab.js +55 -1
  105. package/build-module/components/inspector-controls-tabs/styles-tab.js.map +2 -2
  106. package/build-module/components/inspector-controls-tabs/use-inspector-controls-tabs.js +1 -1
  107. package/build-module/components/inspector-controls-tabs/use-inspector-controls-tabs.js.map +2 -2
  108. package/build-module/components/keyboard-shortcuts/index.js +8 -0
  109. package/build-module/components/keyboard-shortcuts/index.js.map +2 -2
  110. package/build-module/components/rich-text/format-edit.js +9 -1
  111. package/build-module/components/rich-text/format-edit.js.map +2 -2
  112. package/build-module/components/rich-text/index.js +1 -0
  113. package/build-module/components/rich-text/index.js.map +2 -2
  114. package/build-module/hooks/border.js +10 -5
  115. package/build-module/hooks/border.js.map +3 -3
  116. package/build-module/hooks/color.js +31 -9
  117. package/build-module/hooks/color.js.map +3 -3
  118. package/build-module/hooks/content-lock-ui.js +4 -5
  119. package/build-module/hooks/content-lock-ui.js.map +2 -2
  120. package/build-module/hooks/dimensions.js +9 -4
  121. package/build-module/hooks/dimensions.js.map +2 -2
  122. package/build-module/hooks/fit-text.js +18 -66
  123. package/build-module/hooks/fit-text.js.map +2 -2
  124. package/build-module/hooks/font-size.js +5 -2
  125. package/build-module/hooks/font-size.js.map +2 -2
  126. package/build-module/hooks/layout.js +4 -1
  127. package/build-module/hooks/layout.js.map +2 -2
  128. package/build-module/hooks/spacing-visualizer.js +5 -0
  129. package/build-module/hooks/spacing-visualizer.js.map +2 -2
  130. package/build-module/hooks/typography.js +23 -14
  131. package/build-module/hooks/typography.js.map +3 -3
  132. package/build-module/store/private-selectors.js +20 -1
  133. package/build-module/store/private-selectors.js.map +2 -2
  134. package/build-module/store/reducer.js +4 -0
  135. package/build-module/store/reducer.js.map +2 -2
  136. package/build-module/store/selectors.js +12 -3
  137. package/build-module/store/selectors.js.map +2 -2
  138. package/build-module/utils/fit-text-frontend.js +1 -0
  139. package/build-module/utils/fit-text-frontend.js.map +2 -2
  140. package/build-module/utils/fit-text-utils.js +1 -1
  141. package/build-module/utils/fit-text-utils.js.map +1 -1
  142. package/build-style/style-rtl.css +31 -71
  143. package/build-style/style.css +31 -71
  144. package/package.json +37 -37
  145. package/src/components/block-card/index.js +95 -38
  146. package/src/components/block-card/style.scss +17 -1
  147. package/src/components/block-inspector/edit-contents.js +64 -0
  148. package/src/components/block-inspector/index.js +35 -13
  149. package/src/components/block-inspector/style.scss +6 -3
  150. package/src/components/block-list/index.js +11 -9
  151. package/src/components/block-switcher/block-transformations-menu.native.js +0 -1
  152. package/src/components/block-switcher/index.js +51 -180
  153. package/src/components/block-switcher/style.scss +0 -70
  154. package/src/components/block-switcher/test/index.js +17 -18
  155. package/src/components/block-toolbar/block-toolbar-icon.js +173 -0
  156. package/src/components/block-toolbar/index.js +50 -52
  157. package/src/components/block-toolbar/pattern-overrides-dropdown.js +99 -0
  158. package/src/components/block-toolbar/style.scss +21 -21
  159. package/src/components/block-toolbar/test/__snapshots__/block-toolbar-menu.native.js.snap +6 -4
  160. package/src/components/block-toolbar/test/block-toolbar-icon.js +182 -0
  161. package/src/components/block-toolbar/test/block-toolbar-menu.native.js +2 -2
  162. package/src/components/block-tools/index.js +11 -1
  163. package/src/components/border-radius-control/test/utils.js +90 -0
  164. package/src/components/border-radius-control/utils.js +7 -3
  165. package/src/components/content-lock/modify-content-lock-menu-item.js +9 -3
  166. package/src/components/global-styles/border-panel.js +11 -7
  167. package/src/components/global-styles/color-panel.js +32 -26
  168. package/src/components/global-styles/typography-panel.js +14 -1
  169. package/src/components/inserter/media-tab/media-tab.js +7 -1
  170. package/src/components/inspector-controls-tabs/index.js +1 -0
  171. package/src/components/inspector-controls-tabs/styles-tab.js +58 -0
  172. package/src/components/inspector-controls-tabs/use-inspector-controls-tabs.js +5 -1
  173. package/src/components/keyboard-shortcuts/index.js +9 -0
  174. package/src/components/rich-text/format-edit.js +9 -1
  175. package/src/components/rich-text/index.js +1 -0
  176. package/src/hooks/border.js +12 -6
  177. package/src/hooks/color.js +40 -13
  178. package/src/hooks/content-lock-ui.js +9 -6
  179. package/src/hooks/dimensions.js +25 -17
  180. package/src/hooks/fit-text.js +23 -84
  181. package/src/hooks/font-size.js +7 -2
  182. package/src/hooks/layout.js +11 -7
  183. package/src/hooks/spacing-visualizer.js +9 -1
  184. package/src/hooks/typography.js +24 -18
  185. package/src/store/private-selectors.js +26 -1
  186. package/src/store/reducer.js +6 -0
  187. package/src/store/selectors.js +24 -3
  188. package/src/utils/fit-text-frontend.js +1 -0
  189. package/src/utils/fit-text-utils.js +1 -1
  190. package/tsconfig.tsbuildinfo +1 -1
  191. package/build/components/block-inspector/edit-contents-button.js +0 -61
  192. package/build/components/block-inspector/edit-contents-button.js.map +0 -7
  193. package/build-module/components/block-inspector/edit-contents-button.js +0 -40
  194. package/build-module/components/block-inspector/edit-contents-button.js.map +0 -7
  195. package/src/components/block-inspector/edit-contents-button.js +0 -46
@@ -0,0 +1,182 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { render, screen } from '@testing-library/react';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { useSelect } from '@wordpress/data';
10
+ import { paragraph } from '@wordpress/icons';
11
+
12
+ /**
13
+ * Internal dependencies
14
+ */
15
+ import BlockToolbarIcon from '../block-toolbar-icon';
16
+
17
+ jest.mock( '@wordpress/data/src/components/use-select', () => jest.fn() );
18
+ jest.mock( '../../block-title/use-block-display-title', () =>
19
+ jest.fn().mockReturnValue( 'Block Name' )
20
+ );
21
+ jest.mock( '../../block-switcher', () =>
22
+ jest.fn( ( { children } ) => (
23
+ <div data-testid="block-switcher">{ children }</div>
24
+ ) )
25
+ );
26
+ jest.mock( '../pattern-overrides-dropdown', () =>
27
+ jest.fn( ( { clientIds } ) => (
28
+ <div data-testid="pattern-overrides-dropdown">
29
+ { clientIds.length === 1
30
+ ? 'Block Name'
31
+ : 'Multiple blocks selected' }
32
+ </div>
33
+ ) )
34
+ );
35
+
36
+ describe( 'BlockToolbarIcon', () => {
37
+ const defaultProps = {
38
+ clientIds: [ 'test-client-id' ],
39
+ isSynced: false,
40
+ };
41
+
42
+ beforeEach( () => {
43
+ jest.clearAllMocks();
44
+ } );
45
+
46
+ describe( 'when variant is "switcher"', () => {
47
+ it( 'should render BlockSwitcher with icon', () => {
48
+ useSelect.mockImplementation( () => ( {
49
+ icon: paragraph,
50
+ showIconLabels: false,
51
+ variant: 'switcher',
52
+ } ) );
53
+
54
+ render( <BlockToolbarIcon { ...defaultProps } /> );
55
+
56
+ expect(
57
+ screen.getByTestId( 'block-switcher' )
58
+ ).toBeInTheDocument();
59
+ expect(
60
+ screen.queryByTestId( 'pattern-overrides-dropdown' )
61
+ ).not.toBeInTheDocument();
62
+ } );
63
+ } );
64
+
65
+ describe( 'when variant is "pattern-overrides"', () => {
66
+ it( 'should render PatternOverridesDropdown for single block', () => {
67
+ useSelect.mockImplementation( () => ( {
68
+ icon: paragraph,
69
+ showIconLabels: false,
70
+ variant: 'pattern-overrides',
71
+ } ) );
72
+
73
+ render( <BlockToolbarIcon { ...defaultProps } /> );
74
+
75
+ const dropdown = screen.getByTestId( 'pattern-overrides-dropdown' );
76
+ expect( dropdown ).toBeInTheDocument();
77
+ expect( dropdown ).toHaveTextContent( 'Block Name' );
78
+ expect(
79
+ screen.queryByTestId( 'block-switcher' )
80
+ ).not.toBeInTheDocument();
81
+ } );
82
+
83
+ it( 'should render PatternOverridesDropdown for multiple blocks', () => {
84
+ useSelect.mockImplementation( () => ( {
85
+ icon: paragraph,
86
+ showIconLabels: false,
87
+ variant: 'pattern-overrides',
88
+ } ) );
89
+
90
+ render(
91
+ <BlockToolbarIcon
92
+ { ...defaultProps }
93
+ clientIds={ [ 'test-1', 'test-2' ] }
94
+ />
95
+ );
96
+
97
+ const dropdown = screen.getByTestId( 'pattern-overrides-dropdown' );
98
+ expect( dropdown ).toBeInTheDocument();
99
+ expect( dropdown ).toHaveTextContent( 'Multiple blocks selected' );
100
+ } );
101
+ } );
102
+
103
+ describe( 'when variant is "default"', () => {
104
+ it( 'should render disabled ToolbarButton for single block', () => {
105
+ useSelect.mockImplementation( () => ( {
106
+ icon: paragraph,
107
+ showIconLabels: false,
108
+ variant: 'default',
109
+ } ) );
110
+
111
+ render( <BlockToolbarIcon { ...defaultProps } /> );
112
+
113
+ const button = screen.getByRole( 'button' );
114
+ expect( button ).toHaveAttribute( 'aria-disabled', 'true' );
115
+ expect( button ).toHaveAttribute( 'aria-label', 'Block Name' );
116
+ expect(
117
+ screen.queryByTestId( 'block-switcher' )
118
+ ).not.toBeInTheDocument();
119
+ expect(
120
+ screen.queryByTestId( 'pattern-overrides-dropdown' )
121
+ ).not.toBeInTheDocument();
122
+ } );
123
+
124
+ it( 'should render disabled ToolbarButton for multiple blocks', () => {
125
+ useSelect.mockImplementation( () => ( {
126
+ icon: paragraph,
127
+ showIconLabels: false,
128
+ variant: 'default',
129
+ } ) );
130
+
131
+ render(
132
+ <BlockToolbarIcon
133
+ { ...defaultProps }
134
+ clientIds={ [ 'test-1', 'test-2', 'test-3' ] }
135
+ />
136
+ );
137
+
138
+ const button = screen.getByRole( 'button' );
139
+ expect( button ).toHaveAttribute( 'aria-disabled', 'true' );
140
+ expect( button ).toHaveAttribute(
141
+ 'aria-label',
142
+ 'Multiple blocks selected'
143
+ );
144
+ } );
145
+ } );
146
+
147
+ describe( 'label calculation', () => {
148
+ it( 'should use block title for single block', () => {
149
+ useSelect.mockImplementation( () => ( {
150
+ icon: paragraph,
151
+ showIconLabels: false,
152
+ variant: 'default',
153
+ } ) );
154
+
155
+ render( <BlockToolbarIcon { ...defaultProps } /> );
156
+
157
+ const button = screen.getByRole( 'button' );
158
+ expect( button ).toHaveAttribute( 'aria-label', 'Block Name' );
159
+ } );
160
+
161
+ it( 'should use "Multiple blocks selected" for multiple blocks', () => {
162
+ useSelect.mockImplementation( () => ( {
163
+ icon: paragraph,
164
+ showIconLabels: false,
165
+ variant: 'default',
166
+ } ) );
167
+
168
+ render(
169
+ <BlockToolbarIcon
170
+ { ...defaultProps }
171
+ clientIds={ [ 'test-1', 'test-2' ] }
172
+ />
173
+ );
174
+
175
+ const button = screen.getByRole( 'button' );
176
+ expect( button ).toHaveAttribute(
177
+ 'aria-label',
178
+ 'Multiple blocks selected'
179
+ );
180
+ } );
181
+ } );
182
+ } );
@@ -357,7 +357,7 @@ describe( 'Block Actions Menu', () => {
357
357
  expect( getEditorHtml() ).toMatchSnapshot();
358
358
  } );
359
359
 
360
- it( 'transforms a Paragraph block into a Pullquote block', async () => {
360
+ it( 'transforms a Paragraph block into a Quote block', async () => {
361
361
  const screen = await initializeEditor();
362
362
  const { getByLabelText, getByRole } = screen;
363
363
 
@@ -397,7 +397,7 @@ describe( 'Block Actions Menu', () => {
397
397
  expect( headerTitle ).toBeVisible();
398
398
 
399
399
  // Tap on the Transform block button
400
- fireEvent.press( getByLabelText( /Pullquote/ ) );
400
+ fireEvent.press( getByLabelText( /Quote/ ) );
401
401
 
402
402
  expect( getEditorHtml() ).toMatchSnapshot();
403
403
  } );
@@ -77,7 +77,8 @@ export default function BlockTools( {
77
77
  getBlockRootClientId,
78
78
  isGroupable,
79
79
  getBlockName,
80
- } = useSelect( blockEditorStore );
80
+ getEditedContentOnlySection,
81
+ } = unlock( useSelect( blockEditorStore ) );
81
82
  const { getGroupingBlockName } = useSelect( blocksStore );
82
83
  const { showEmptyBlockSideInserter, showBlockToolbarPopover } =
83
84
  useShowBlockTools();
@@ -94,6 +95,7 @@ export default function BlockTools( {
94
95
  moveBlocksDown,
95
96
  expandBlock,
96
97
  updateBlockAttributes,
98
+ stopEditingContentOnlySection,
97
99
  } = unlock( useDispatch( blockEditorStore ) );
98
100
 
99
101
  function onKeyDown( event ) {
@@ -246,6 +248,14 @@ export default function BlockTools( {
246
248
  } );
247
249
  }
248
250
  }
251
+
252
+ // Has the same keyboard shortcut as 'unselect', so can't be within the
253
+ // if/else chain above.
254
+ if ( isMatch( 'core/block-editor/stop-editing-as-blocks', event ) ) {
255
+ if ( getEditedContentOnlySection() ) {
256
+ stopEditingContentOnlySection();
257
+ }
258
+ }
249
259
  }
250
260
  const blockToolbarRef = usePopoverScroll( __unstableContentRef );
251
261
  const blockToolbarAfterRef = usePopoverScroll( __unstableContentRef );
@@ -107,6 +107,96 @@ describe( 'getAllValue', () => {
107
107
  expect( getAllValue( undefined ) ).toBe( undefined );
108
108
  } );
109
109
  } );
110
+
111
+ describe( 'when provided complex CSS values (clamp, min, max, calc)', () => {
112
+ it( 'should preserve clamp values when all corners have the same clamp value', () => {
113
+ const clampValue = 'clamp(1rem, 2vw, 3rem)';
114
+ const values = {
115
+ bottomLeft: clampValue,
116
+ bottomRight: clampValue,
117
+ topLeft: clampValue,
118
+ topRight: clampValue,
119
+ };
120
+ expect( getAllValue( values ) ).toBe( clampValue );
121
+ } );
122
+
123
+ it( 'should preserve min() values when all corners have the same min value', () => {
124
+ const minValue = 'min(10px, 5vw)';
125
+ const values = {
126
+ bottomLeft: minValue,
127
+ bottomRight: minValue,
128
+ topLeft: minValue,
129
+ topRight: minValue,
130
+ };
131
+ expect( getAllValue( values ) ).toBe( minValue );
132
+ } );
133
+
134
+ it( 'should preserve max() values when all corners have the same max value', () => {
135
+ const maxValue = 'max(20px, 10vw)';
136
+ const values = {
137
+ bottomLeft: maxValue,
138
+ bottomRight: maxValue,
139
+ topLeft: maxValue,
140
+ topRight: maxValue,
141
+ };
142
+ expect( getAllValue( values ) ).toBe( maxValue );
143
+ } );
144
+
145
+ it( 'should preserve calc() values when all corners have the same calc value', () => {
146
+ const calcValue = 'calc(100% - 20px)';
147
+ const values = {
148
+ bottomLeft: calcValue,
149
+ bottomRight: calcValue,
150
+ topLeft: calcValue,
151
+ topRight: calcValue,
152
+ };
153
+ expect( getAllValue( values ) ).toBe( calcValue );
154
+ } );
155
+
156
+ it( 'should return undefined when complex CSS values are mixed', () => {
157
+ const values = {
158
+ bottomLeft: 'clamp(1rem, 2vw, 3rem)',
159
+ bottomRight: 'clamp(1rem, 2vw, 3rem)',
160
+ topLeft: 'min(10px, 5vw)',
161
+ topRight: 'clamp(1rem, 2vw, 3rem)',
162
+ };
163
+ expect( getAllValue( values ) ).toBe( undefined );
164
+ } );
165
+
166
+ it( 'should return undefined when complex CSS values are mixed with simple values', () => {
167
+ const values = {
168
+ bottomLeft: 'clamp(1rem, 2vw, 3rem)',
169
+ bottomRight: 'clamp(1rem, 2vw, 3rem)',
170
+ topLeft: '2px',
171
+ topRight: 'clamp(1rem, 2vw, 3rem)',
172
+ };
173
+ expect( getAllValue( values ) ).toBe( undefined );
174
+ } );
175
+
176
+ it( 'should preserve string values that cannot be parsed at all (no numeric prefix)', () => {
177
+ // Values with no numeric prefix cannot be parsed, so they should be preserved
178
+ const unparseableValue = 'apples';
179
+ const values = {
180
+ bottomLeft: unparseableValue,
181
+ bottomRight: unparseableValue,
182
+ topLeft: unparseableValue,
183
+ topRight: unparseableValue,
184
+ };
185
+ expect( getAllValue( values ) ).toBe( unparseableValue );
186
+ } );
187
+
188
+ it( 'should parse numeric prefix from partially parseable values', () => {
189
+ // Values with numeric prefix get parsed, so "32apples" becomes "32"
190
+ const partiallyParseableValue = '32apples';
191
+ const values = {
192
+ bottomLeft: partiallyParseableValue,
193
+ bottomRight: partiallyParseableValue,
194
+ topLeft: partiallyParseableValue,
195
+ topRight: partiallyParseableValue,
196
+ };
197
+ expect( getAllValue( values ) ).toBe( '32' );
198
+ } );
199
+ } );
110
200
  } );
111
201
 
112
202
  describe( 'hasMixedValues', () => {
@@ -60,9 +60,13 @@ export function getAllValue( values = {} ) {
60
60
  return values;
61
61
  }
62
62
 
63
- const parsedQuantitiesAndUnits = Object.values( values ).map( ( value ) =>
64
- parseQuantityAndUnitFromRawValue( value )
65
- );
63
+ const parsedQuantitiesAndUnits = Object.values( values ).map( ( value ) => {
64
+ const newValue = parseQuantityAndUnitFromRawValue( value );
65
+ if ( typeof value === 'string' && newValue[ 0 ] === undefined ) {
66
+ return [ value, '' ];
67
+ }
68
+ return newValue;
69
+ } );
66
70
 
67
71
  const allValues = parsedQuantitiesAndUnits.map(
68
72
  ( value ) => value[ 0 ] ?? ''
@@ -38,16 +38,22 @@ export function ModifyContentOnlySectionMenuItem( { clientId, onClose } ) {
38
38
  const blockEditorActions = useDispatch( blockEditorStore );
39
39
  const isContentLocked =
40
40
  ! isLockedByParent && templateLock === 'contentOnly';
41
- if ( ! isContentLocked && ! isEditingContentOnlySection ) {
41
+
42
+ // Hide the Modify button when the content only pattern insertion experiment is active.
43
+ // This is replaced by an alternative UI in the experiment.
44
+ if (
45
+ window?.__experimentalContentOnlyPatternInsertion ||
46
+ ( ! isContentLocked && ! isEditingContentOnlySection )
47
+ ) {
42
48
  return null;
43
49
  }
44
50
 
45
51
  const { editContentOnlySection } = unlock( blockEditorActions );
46
- const showStartEditingAsBlocks =
52
+ const showContentOnlyModifyButton =
47
53
  ! isEditingContentOnlySection && isContentLocked;
48
54
 
49
55
  return (
50
- showStartEditingAsBlocks && (
56
+ showContentOnlyModifyButton && (
51
57
  <MenuItem
52
58
  onClick={ () => {
53
59
  editContentOnlySection( clientId );
@@ -147,17 +147,21 @@ export default function BorderPanel( {
147
147
  // Border radius.
148
148
  const showBorderRadius = useHasBorderRadiusControl( settings );
149
149
  const borderRadiusValues = useMemo( () => {
150
- if ( typeof border?.radius !== 'object' ) {
151
- return border?.radius;
150
+ if ( typeof inheritedValue?.border?.radius !== 'object' ) {
151
+ return decodeValue( inheritedValue?.border?.radius );
152
152
  }
153
153
 
154
154
  return {
155
- topLeft: border?.radius?.topLeft,
156
- topRight: border?.radius?.topRight,
157
- bottomLeft: border?.radius?.bottomLeft,
158
- bottomRight: border?.radius?.bottomRight,
155
+ topLeft: decodeValue( inheritedValue?.border?.radius?.topLeft ),
156
+ topRight: decodeValue( inheritedValue?.border?.radius?.topRight ),
157
+ bottomLeft: decodeValue(
158
+ inheritedValue?.border?.radius?.bottomLeft
159
+ ),
160
+ bottomRight: decodeValue(
161
+ inheritedValue?.border?.radius?.bottomRight
162
+ ),
159
163
  };
160
- }, [ border?.radius ] );
164
+ }, [ inheritedValue?.border?.radius, decodeValue ] );
161
165
  const setBorderRadius = ( newBorderRadius ) =>
162
166
  setBorder( { ...border, radius: newBorderRadius } );
163
167
  const hasBorderRadius = () => {
@@ -111,12 +111,13 @@ export function useHasBackgroundColorPanel( settings ) {
111
111
  );
112
112
  }
113
113
 
114
- function ColorToolsPanel( {
114
+ export function ColorToolsPanel( {
115
115
  resetAllFilter,
116
116
  onChange,
117
117
  value,
118
118
  panelId,
119
119
  children,
120
+ label,
120
121
  } ) {
121
122
  const dropdownMenuProps = useToolsPanelDropdownMenuProps();
122
123
  const resetAll = () => {
@@ -126,7 +127,7 @@ function ColorToolsPanel( {
126
127
 
127
128
  return (
128
129
  <ToolsPanel
129
- label={ __( 'Elements' ) }
130
+ label={ label || __( 'Elements' ) }
130
131
  resetAll={ resetAll }
131
132
  panelId={ panelId }
132
133
  hasInnerWrapper
@@ -326,6 +327,7 @@ export default function ColorPanel( {
326
327
  settings,
327
328
  panelId,
328
329
  defaultControls = DEFAULT_CONTROLS,
330
+ label,
329
331
  children,
330
332
  } ) {
331
333
  const colors = useColorsPerOrigin( settings );
@@ -511,31 +513,34 @@ export default function ColorPanel( {
511
513
  },
512
514
  ];
513
515
 
514
- const resetAllFilter = useCallback( ( previousValue ) => {
515
- return {
516
- ...previousValue,
517
- color: undefined,
518
- elements: {
519
- ...previousValue?.elements,
520
- link: {
521
- ...previousValue?.elements?.link,
522
- color: undefined,
523
- ':hover': {
516
+ const resetAllFilter = useCallback(
517
+ ( previousValue ) => {
518
+ return {
519
+ ...previousValue,
520
+ color: undefined,
521
+ elements: {
522
+ ...previousValue?.elements,
523
+ link: {
524
+ ...previousValue?.elements?.link,
524
525
  color: undefined,
525
- },
526
- },
527
- ...elements.reduce( ( acc, element ) => {
528
- return {
529
- ...acc,
530
- [ element.name ]: {
531
- ...previousValue?.elements?.[ element.name ],
526
+ ':hover': {
532
527
  color: undefined,
533
528
  },
534
- };
535
- }, {} ),
536
- },
537
- };
538
- }, [] );
529
+ },
530
+ ...elements.reduce( ( acc, element ) => {
531
+ return {
532
+ ...acc,
533
+ [ element.name ]: {
534
+ ...previousValue?.elements?.[ element.name ],
535
+ color: undefined,
536
+ },
537
+ };
538
+ }, {} ),
539
+ },
540
+ };
541
+ },
542
+ [ elements ]
543
+ );
539
544
 
540
545
  const items = [
541
546
  showTextPanel && {
@@ -606,7 +611,7 @@ export default function ColorPanel( {
606
611
  },
607
612
  ].filter( Boolean );
608
613
 
609
- elements.forEach( ( { name, label, showPanel } ) => {
614
+ elements.forEach( ( { name, label: elementLabel, showPanel } ) => {
610
615
  if ( ! showPanel ) {
611
616
  return;
612
617
  }
@@ -680,7 +685,7 @@ export default function ColorPanel( {
680
685
 
681
686
  items.push( {
682
687
  key: name,
683
- label,
688
+ label: elementLabel,
684
689
  hasValue: hasElement,
685
690
  resetValue: resetElement,
686
691
  isShownByDefault: defaultControls[ name ],
@@ -731,6 +736,7 @@ export default function ColorPanel( {
731
736
  value={ value }
732
737
  onChange={ onChange }
733
738
  panelId={ panelId }
739
+ label={ label }
734
740
  >
735
741
  { items.map( ( item ) => {
736
742
  const { key, ...restItem } = item;
@@ -6,6 +6,7 @@ import {
6
6
  __experimentalNumberControl as NumberControl,
7
7
  __experimentalToolsPanel as ToolsPanel,
8
8
  __experimentalToolsPanelItem as ToolsPanelItem,
9
+ Notice,
9
10
  } from '@wordpress/components';
10
11
  import { __ } from '@wordpress/i18n';
11
12
  import { useCallback, useMemo, useEffect } from '@wordpress/element';
@@ -179,6 +180,7 @@ export default function TypographyPanel( {
179
180
  settings,
180
181
  panelId,
181
182
  defaultControls = DEFAULT_CONTROLS,
183
+ fitText = false,
182
184
  } ) {
183
185
  const decodeValue = ( rawValue ) =>
184
186
  getValueFromVariable( { settings }, '', rawValue );
@@ -448,7 +450,7 @@ export default function TypographyPanel( {
448
450
  />
449
451
  </ToolsPanelItem>
450
452
  ) }
451
- { hasFontSizeEnabled && (
453
+ { hasFontSizeEnabled && ! fitText && (
452
454
  <ToolsPanelItem
453
455
  label={ __( 'Size' ) }
454
456
  hasValue={ hasFontSize }
@@ -608,9 +610,20 @@ export default function TypographyPanel( {
608
610
  <TextAlignmentControl
609
611
  value={ textAlign }
610
612
  onChange={ setTextAlign }
613
+ options={ [ 'left', 'center', 'right', 'justify' ] }
611
614
  size="__unstable-large"
612
615
  __nextHasNoMarginBottom
613
616
  />
617
+
618
+ { textAlign === 'justify' && (
619
+ <div>
620
+ <Notice status="warning" isDismissible={ false }>
621
+ { __(
622
+ 'Justified text can reduce readability. For better accessibility, use left-aligned text instead.'
623
+ ) }
624
+ </Notice>
625
+ </div>
626
+ ) }
614
627
  </ToolsPanelItem>
615
628
  ) }
616
629
  </Wrapper>
@@ -77,7 +77,13 @@ function MediaTab( {
77
77
  if ( ! media?.url ) {
78
78
  return;
79
79
  }
80
- const [ block ] = getBlockAndPreviewFromMedia( media, media.type );
80
+ // When the experimental DataViews media modal is enabled,
81
+ // we need to extract the media type from mime_type (e.g., 'image/jpeg' -> 'image')
82
+ const mediaType =
83
+ window.__experimentalDataViewsMediaModal && media.mime_type
84
+ ? media.mime_type.split( '/' )[ 0 ]
85
+ : media.type;
86
+ const [ block ] = getBlockAndPreviewFromMedia( media, mediaType );
81
87
  onInsert( block );
82
88
  },
83
89
  [ onInsert ]
@@ -105,6 +105,7 @@ export default function InspectorControlsTabs( {
105
105
  clientId={ clientId }
106
106
  hasBlockStyles={ hasBlockStyles }
107
107
  isSectionBlock={ isSectionBlock }
108
+ contentClientIds={ contentClientIds }
108
109
  />
109
110
  </Tabs.TabPanel>
110
111
  <Tabs.TabPanel tabId={ TAB_CONTENT.name } focusable={ false }>
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { PanelBody } from '@wordpress/components';
5
5
  import { __ } from '@wordpress/i18n';
6
+ import { useDispatch, useSelect } from '@wordpress/data';
6
7
 
7
8
  /**
8
9
  * Internal dependencies
@@ -10,12 +11,61 @@ import { __ } from '@wordpress/i18n';
10
11
  import BlockStyles from '../block-styles';
11
12
  import InspectorControls from '../inspector-controls';
12
13
  import { useBorderPanelLabel } from '../../hooks/border';
14
+ import { useBlockSettings } from '../../hooks/utils';
15
+ import { store as blockEditorStore } from '../../store';
16
+ import { ColorEdit } from '../../hooks/color';
17
+ import { ColorToolsPanel } from '../global-styles/color-panel';
18
+
19
+ function SectionBlockColorControls( {
20
+ blockName,
21
+ clientId,
22
+ contentClientIds,
23
+ } ) {
24
+ const settings = useBlockSettings( blockName );
25
+ const { updateBlockAttributes } = useDispatch( blockEditorStore );
26
+
27
+ const { hasButton, hasHeading } = useSelect(
28
+ ( select ) => {
29
+ const blockNames =
30
+ select( blockEditorStore ).getBlockNamesByClientId(
31
+ contentClientIds
32
+ );
33
+ return {
34
+ hasButton: blockNames.includes( 'core/button' ),
35
+ hasHeading: blockNames.includes( 'core/heading' ),
36
+ };
37
+ },
38
+ [ contentClientIds ]
39
+ );
40
+
41
+ const setAttributes = ( newAttributes ) => {
42
+ updateBlockAttributes( clientId, newAttributes );
43
+ };
44
+
45
+ return (
46
+ <ColorEdit
47
+ clientId={ clientId }
48
+ name={ blockName }
49
+ settings={ settings }
50
+ setAttributes={ setAttributes }
51
+ asWrapper={ ColorToolsPanel }
52
+ label={ __( 'Color' ) }
53
+ defaultControls={ {
54
+ text: true,
55
+ background: true,
56
+ button: hasButton,
57
+ heading: hasHeading,
58
+ } }
59
+ />
60
+ );
61
+ }
13
62
 
14
63
  const StylesTab = ( {
15
64
  blockName,
16
65
  clientId,
17
66
  hasBlockStyles,
18
67
  isSectionBlock,
68
+ contentClientIds,
19
69
  } ) => {
20
70
  const borderPanelLabel = useBorderPanelLabel( { blockName } );
21
71
 
@@ -28,6 +78,14 @@ const StylesTab = ( {
28
78
  </PanelBody>
29
79
  </div>
30
80
  ) }
81
+ { isSectionBlock &&
82
+ window?.__experimentalContentOnlyPatternInsertion && (
83
+ <SectionBlockColorControls
84
+ blockName={ blockName }
85
+ clientId={ clientId }
86
+ contentClientIds={ contentClientIds }
87
+ />
88
+ ) }
31
89
  { ! isSectionBlock && (
32
90
  <>
33
91
  <InspectorControls.Slot
@@ -99,7 +99,11 @@ export default function useInspectorControlsTabs(
99
99
  tabs.push( TAB_SETTINGS );
100
100
  }
101
101
 
102
- if ( isSectionBlock ? hasBlockStyles : hasStyleFills ) {
102
+ if (
103
+ hasBlockStyles ||
104
+ hasStyleFills ||
105
+ window?.__experimentalContentOnlyPatternInsertion
106
+ ) {
103
107
  tabs.push( TAB_STYLES );
104
108
  }
105
109