@wordpress/block-editor 15.7.1-next.2f1c7c01b.0 → 15.8.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 (187) 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 +3 -2
  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/index.js +1 -0
  41. package/build/components/rich-text/index.js.map +2 -2
  42. package/build/hooks/border.js +10 -5
  43. package/build/hooks/border.js.map +3 -3
  44. package/build/hooks/color.js +31 -9
  45. package/build/hooks/color.js.map +3 -3
  46. package/build/hooks/content-lock-ui.js +4 -5
  47. package/build/hooks/content-lock-ui.js.map +2 -2
  48. package/build/hooks/dimensions.js +9 -4
  49. package/build/hooks/dimensions.js.map +2 -2
  50. package/build/hooks/fit-text.js +19 -75
  51. package/build/hooks/fit-text.js.map +3 -3
  52. package/build/hooks/font-size.js +5 -2
  53. package/build/hooks/font-size.js.map +2 -2
  54. package/build/hooks/layout.js +4 -1
  55. package/build/hooks/layout.js.map +2 -2
  56. package/build/hooks/spacing-visualizer.js +5 -0
  57. package/build/hooks/spacing-visualizer.js.map +2 -2
  58. package/build/hooks/typography.js +23 -14
  59. package/build/hooks/typography.js.map +3 -3
  60. package/build/store/private-selectors.js +21 -1
  61. package/build/store/private-selectors.js.map +2 -2
  62. package/build/store/reducer.js +4 -0
  63. package/build/store/reducer.js.map +2 -2
  64. package/build/store/selectors.js +9 -3
  65. package/build/store/selectors.js.map +2 -2
  66. package/build/utils/fit-text-frontend.js +1 -0
  67. package/build/utils/fit-text-frontend.js.map +2 -2
  68. package/build/utils/fit-text-utils.js +1 -1
  69. package/build/utils/fit-text-utils.js.map +1 -1
  70. package/build-module/components/block-card/index.js +82 -32
  71. package/build-module/components/block-card/index.js.map +2 -2
  72. package/build-module/components/block-inspector/edit-contents.js +51 -0
  73. package/build-module/components/block-inspector/edit-contents.js.map +7 -0
  74. package/build-module/components/block-inspector/index.js +32 -9
  75. package/build-module/components/block-inspector/index.js.map +2 -2
  76. package/build-module/components/block-list/index.js +11 -11
  77. package/build-module/components/block-list/index.js.map +2 -2
  78. package/build-module/components/block-switcher/index.js +24 -124
  79. package/build-module/components/block-switcher/index.js.map +2 -2
  80. package/build-module/components/block-toolbar/block-toolbar-icon.js +144 -0
  81. package/build-module/components/block-toolbar/block-toolbar-icon.js.map +7 -0
  82. package/build-module/components/block-toolbar/index.js +51 -53
  83. package/build-module/components/block-toolbar/index.js.map +2 -2
  84. package/build-module/components/block-toolbar/pattern-overrides-dropdown.js +76 -0
  85. package/build-module/components/block-toolbar/pattern-overrides-dropdown.js.map +7 -0
  86. package/build-module/components/block-tools/index.js +10 -3
  87. package/build-module/components/block-tools/index.js.map +2 -2
  88. package/build-module/components/border-radius-control/utils.js +7 -3
  89. package/build-module/components/border-radius-control/utils.js.map +2 -2
  90. package/build-module/components/content-lock/modify-content-lock-menu-item.js +3 -3
  91. package/build-module/components/content-lock/modify-content-lock-menu-item.js.map +2 -2
  92. package/build-module/components/global-styles/border-panel.js +11 -7
  93. package/build-module/components/global-styles/border-panel.js.map +2 -2
  94. package/build-module/components/global-styles/color-panel.js +34 -27
  95. package/build-module/components/global-styles/color-panel.js.map +2 -2
  96. package/build-module/components/global-styles/typography-panel.js +3 -2
  97. package/build-module/components/global-styles/typography-panel.js.map +2 -2
  98. package/build-module/components/inserter/media-tab/media-tab.js +2 -1
  99. package/build-module/components/inserter/media-tab/media-tab.js.map +2 -2
  100. package/build-module/components/inspector-controls-tabs/index.js +2 -1
  101. package/build-module/components/inspector-controls-tabs/index.js.map +2 -2
  102. package/build-module/components/inspector-controls-tabs/styles-tab.js +55 -1
  103. package/build-module/components/inspector-controls-tabs/styles-tab.js.map +2 -2
  104. package/build-module/components/inspector-controls-tabs/use-inspector-controls-tabs.js +1 -1
  105. package/build-module/components/inspector-controls-tabs/use-inspector-controls-tabs.js.map +2 -2
  106. package/build-module/components/keyboard-shortcuts/index.js +8 -0
  107. package/build-module/components/keyboard-shortcuts/index.js.map +2 -2
  108. package/build-module/components/rich-text/index.js +1 -0
  109. package/build-module/components/rich-text/index.js.map +2 -2
  110. package/build-module/hooks/border.js +10 -5
  111. package/build-module/hooks/border.js.map +3 -3
  112. package/build-module/hooks/color.js +31 -9
  113. package/build-module/hooks/color.js.map +3 -3
  114. package/build-module/hooks/content-lock-ui.js +4 -5
  115. package/build-module/hooks/content-lock-ui.js.map +2 -2
  116. package/build-module/hooks/dimensions.js +9 -4
  117. package/build-module/hooks/dimensions.js.map +2 -2
  118. package/build-module/hooks/fit-text.js +18 -66
  119. package/build-module/hooks/fit-text.js.map +2 -2
  120. package/build-module/hooks/font-size.js +5 -2
  121. package/build-module/hooks/font-size.js.map +2 -2
  122. package/build-module/hooks/layout.js +4 -1
  123. package/build-module/hooks/layout.js.map +2 -2
  124. package/build-module/hooks/spacing-visualizer.js +5 -0
  125. package/build-module/hooks/spacing-visualizer.js.map +2 -2
  126. package/build-module/hooks/typography.js +23 -14
  127. package/build-module/hooks/typography.js.map +3 -3
  128. package/build-module/store/private-selectors.js +20 -1
  129. package/build-module/store/private-selectors.js.map +2 -2
  130. package/build-module/store/reducer.js +4 -0
  131. package/build-module/store/reducer.js.map +2 -2
  132. package/build-module/store/selectors.js +9 -3
  133. package/build-module/store/selectors.js.map +2 -2
  134. package/build-module/utils/fit-text-frontend.js +1 -0
  135. package/build-module/utils/fit-text-frontend.js.map +2 -2
  136. package/build-module/utils/fit-text-utils.js +1 -1
  137. package/build-module/utils/fit-text-utils.js.map +1 -1
  138. package/build-style/style-rtl.css +31 -71
  139. package/build-style/style.css +31 -71
  140. package/package.json +37 -37
  141. package/src/components/block-card/index.js +95 -38
  142. package/src/components/block-card/style.scss +17 -1
  143. package/src/components/block-inspector/edit-contents.js +64 -0
  144. package/src/components/block-inspector/index.js +35 -13
  145. package/src/components/block-inspector/style.scss +6 -3
  146. package/src/components/block-list/index.js +11 -9
  147. package/src/components/block-switcher/index.js +51 -180
  148. package/src/components/block-switcher/style.scss +0 -70
  149. package/src/components/block-switcher/test/index.js +17 -18
  150. package/src/components/block-toolbar/block-toolbar-icon.js +173 -0
  151. package/src/components/block-toolbar/index.js +50 -52
  152. package/src/components/block-toolbar/pattern-overrides-dropdown.js +99 -0
  153. package/src/components/block-toolbar/style.scss +21 -21
  154. package/src/components/block-toolbar/test/block-toolbar-icon.js +182 -0
  155. package/src/components/block-tools/index.js +11 -1
  156. package/src/components/border-radius-control/test/utils.js +90 -0
  157. package/src/components/border-radius-control/utils.js +7 -3
  158. package/src/components/content-lock/modify-content-lock-menu-item.js +9 -3
  159. package/src/components/global-styles/border-panel.js +11 -7
  160. package/src/components/global-styles/color-panel.js +32 -26
  161. package/src/components/global-styles/typography-panel.js +2 -1
  162. package/src/components/inserter/media-tab/media-tab.js +7 -1
  163. package/src/components/inspector-controls-tabs/index.js +1 -0
  164. package/src/components/inspector-controls-tabs/styles-tab.js +58 -0
  165. package/src/components/inspector-controls-tabs/use-inspector-controls-tabs.js +5 -1
  166. package/src/components/keyboard-shortcuts/index.js +9 -0
  167. package/src/components/rich-text/index.js +1 -0
  168. package/src/hooks/border.js +12 -6
  169. package/src/hooks/color.js +40 -13
  170. package/src/hooks/content-lock-ui.js +9 -6
  171. package/src/hooks/dimensions.js +25 -17
  172. package/src/hooks/fit-text.js +23 -84
  173. package/src/hooks/font-size.js +7 -2
  174. package/src/hooks/layout.js +11 -7
  175. package/src/hooks/spacing-visualizer.js +9 -1
  176. package/src/hooks/typography.js +24 -18
  177. package/src/store/private-selectors.js +26 -1
  178. package/src/store/reducer.js +6 -0
  179. package/src/store/selectors.js +17 -3
  180. package/src/utils/fit-text-frontend.js +1 -0
  181. package/src/utils/fit-text-utils.js +1 -1
  182. package/tsconfig.tsbuildinfo +1 -1
  183. package/build/components/block-inspector/edit-contents-button.js +0 -61
  184. package/build/components/block-inspector/edit-contents-button.js.map +0 -7
  185. package/build-module/components/block-inspector/edit-contents-button.js +0 -40
  186. package/build-module/components/block-inspector/edit-contents-button.js.map +0 -7
  187. 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
+ } );
@@ -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;
@@ -179,6 +179,7 @@ export default function TypographyPanel( {
179
179
  settings,
180
180
  panelId,
181
181
  defaultControls = DEFAULT_CONTROLS,
182
+ fitText = false,
182
183
  } ) {
183
184
  const decodeValue = ( rawValue ) =>
184
185
  getValueFromVariable( { settings }, '', rawValue );
@@ -448,7 +449,7 @@ export default function TypographyPanel( {
448
449
  />
449
450
  </ToolsPanelItem>
450
451
  ) }
451
- { hasFontSizeEnabled && (
452
+ { hasFontSizeEnabled && ! fitText && (
452
453
  <ToolsPanelItem
453
454
  label={ __( 'Size' ) }
454
455
  hasValue={ hasFontSize }
@@ -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
 
@@ -114,6 +114,15 @@ function KeyboardShortcutsRegister() {
114
114
  ],
115
115
  } );
116
116
 
117
+ registerShortcut( {
118
+ name: 'core/block-editor/stop-editing-as-blocks',
119
+ category: 'block',
120
+ description: __( 'Finish editing a design.' ),
121
+ keyCombination: {
122
+ character: 'escape',
123
+ },
124
+ } );
125
+
117
126
  registerShortcut( {
118
127
  name: 'core/block-editor/select-all',
119
128
  category: 'selection',
@@ -569,6 +569,7 @@ const PublicForwardedRichTextContainer = forwardRef( ( props, ref ) => {
569
569
  } = removeNativeProps( props );
570
570
  return (
571
571
  <Tag
572
+ ref={ ref }
572
573
  { ...contentProps }
573
574
  dangerouslySetInnerHTML={ {
574
575
  __html: valueToHTMLString( value, multiline ),
@@ -142,12 +142,18 @@ function BordersInspectorControl( { label, children, resetAllFilter } ) {
142
142
 
143
143
  export function BorderPanel( { clientId, name, setAttributes, settings } ) {
144
144
  const isEnabled = useHasBorderPanel( settings );
145
- function selector( select ) {
146
- const { style, borderColor } =
147
- select( blockEditorStore ).getBlockAttributes( clientId ) || {};
148
- return { style, borderColor };
149
- }
150
- const { style, borderColor } = useSelect( selector, [ clientId ] );
145
+ const { style, borderColor } = useSelect(
146
+ ( select ) => {
147
+ // Early return to avoid subscription when disabled
148
+ if ( ! isEnabled ) {
149
+ return {};
150
+ }
151
+ const { style: _style, borderColor: _borderColor } =
152
+ select( blockEditorStore ).getBlockAttributes( clientId ) || {};
153
+ return { style: _style, borderColor: _borderColor };
154
+ },
155
+ [ clientId, isEnabled ]
156
+ );
151
157
  const value = useMemo( () => {
152
158
  return attributesToStyle( { style, borderColor } );
153
159
  }, [ style, borderColor ] );