@wordpress/edit-site 4.10.0 → 4.11.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 (69) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/components/add-new-template/add-custom-template-modal.js +22 -42
  3. package/build/components/add-new-template/add-custom-template-modal.js.map +1 -1
  4. package/build/components/add-new-template/new-template.js +17 -20
  5. package/build/components/add-new-template/new-template.js.map +1 -1
  6. package/build/components/add-new-template/utils.js +366 -239
  7. package/build/components/add-new-template/utils.js.map +1 -1
  8. package/build/components/block-editor/index.js +1 -3
  9. package/build/components/block-editor/index.js.map +1 -1
  10. package/build/components/global-styles/dimensions-panel.js +183 -13
  11. package/build/components/global-styles/dimensions-panel.js.map +1 -1
  12. package/build/components/global-styles/hooks.js +1 -1
  13. package/build/components/global-styles/hooks.js.map +1 -1
  14. package/build/components/global-styles/use-global-styles-output.js +95 -17
  15. package/build/components/global-styles/use-global-styles-output.js.map +1 -1
  16. package/build/components/global-styles/utils.js +31 -0
  17. package/build/components/global-styles/utils.js.map +1 -1
  18. package/build/components/header/index.js +7 -6
  19. package/build/components/header/index.js.map +1 -1
  20. package/build/hooks/index.js +2 -0
  21. package/build/hooks/index.js.map +1 -1
  22. package/build/hooks/template-part-edit.js +86 -0
  23. package/build/hooks/template-part-edit.js.map +1 -0
  24. package/build-module/components/add-new-template/add-custom-template-modal.js +23 -43
  25. package/build-module/components/add-new-template/add-custom-template-modal.js.map +1 -1
  26. package/build-module/components/add-new-template/new-template.js +18 -21
  27. package/build-module/components/add-new-template/new-template.js.map +1 -1
  28. package/build-module/components/add-new-template/utils.js +365 -227
  29. package/build-module/components/add-new-template/utils.js.map +1 -1
  30. package/build-module/components/block-editor/index.js +1 -2
  31. package/build-module/components/block-editor/index.js.map +1 -1
  32. package/build-module/components/global-styles/dimensions-panel.js +183 -14
  33. package/build-module/components/global-styles/dimensions-panel.js.map +1 -1
  34. package/build-module/components/global-styles/hooks.js +1 -1
  35. package/build-module/components/global-styles/hooks.js.map +1 -1
  36. package/build-module/components/global-styles/use-global-styles-output.js +94 -22
  37. package/build-module/components/global-styles/use-global-styles-output.js.map +1 -1
  38. package/build-module/components/global-styles/utils.js +29 -0
  39. package/build-module/components/global-styles/utils.js.map +1 -1
  40. package/build-module/components/header/index.js +8 -6
  41. package/build-module/components/header/index.js.map +1 -1
  42. package/build-module/hooks/index.js +1 -0
  43. package/build-module/hooks/index.js.map +1 -1
  44. package/build-module/hooks/template-part-edit.js +67 -0
  45. package/build-module/hooks/template-part-edit.js.map +1 -0
  46. package/build-style/style-rtl.css +25 -25
  47. package/build-style/style.css +25 -25
  48. package/package.json +29 -29
  49. package/src/components/add-new-template/add-custom-template-modal.js +27 -45
  50. package/src/components/add-new-template/new-template.js +27 -64
  51. package/src/components/add-new-template/style.scss +20 -8
  52. package/src/components/add-new-template/utils.js +398 -229
  53. package/src/components/block-editor/index.js +0 -2
  54. package/src/components/global-styles/dimensions-panel.js +207 -14
  55. package/src/components/global-styles/hooks.js +2 -0
  56. package/src/components/global-styles/test/use-global-styles-output.js +64 -1
  57. package/src/components/global-styles/use-global-styles-output.js +100 -8
  58. package/src/components/global-styles/utils.js +31 -0
  59. package/src/components/header/index.js +9 -10
  60. package/src/components/header/style.scss +5 -3
  61. package/src/components/sidebar/style.scss +4 -0
  62. package/src/hooks/index.js +1 -0
  63. package/src/hooks/template-part-edit.js +82 -0
  64. package/src/style.scss +0 -1
  65. package/build/components/edit-template-part-menu-button/index.js +0 -90
  66. package/build/components/edit-template-part-menu-button/index.js.map +0 -1
  67. package/build-module/components/edit-template-part-menu-button/index.js +0 -72
  68. package/build-module/components/edit-template-part-menu-button/index.js.map +0 -1
  69. package/src/components/edit-template-part-menu-button/index.js +0 -82
@@ -38,7 +38,6 @@ import NavigateToLink from '../navigate-to-link';
38
38
  import { SidebarInspectorFill } from '../sidebar';
39
39
  import { store as editSiteStore } from '../../store';
40
40
  import BlockInspectorButton from './block-inspector-button';
41
- import EditTemplatePartMenuButton from '../edit-template-part-menu-button';
42
41
  import BackButton from './back-button';
43
42
  import ResizableEditor from './resizable-editor';
44
43
 
@@ -155,7 +154,6 @@ export default function BlockEditor( { setIsInserterOpen } ) {
155
154
  onChange={ onChange }
156
155
  useSubRegistry={ false }
157
156
  >
158
- <EditTemplatePartMenuButton />
159
157
  <TemplatePartConverter />
160
158
  <__experimentalLinkControl.ViewerFill>
161
159
  { useCallback(
@@ -6,10 +6,13 @@ import {
6
6
  __experimentalToolsPanel as ToolsPanel,
7
7
  __experimentalToolsPanelItem as ToolsPanelItem,
8
8
  __experimentalBoxControl as BoxControl,
9
+ __experimentalHStack as HStack,
9
10
  __experimentalUnitControl as UnitControl,
10
11
  __experimentalUseCustomUnits as useCustomUnits,
12
+ __experimentalView as View,
11
13
  } from '@wordpress/components';
12
14
  import { __experimentalUseCustomSides as useCustomSides } from '@wordpress/block-editor';
15
+ import { Icon, positionCenter, stretchWide } from '@wordpress/icons';
13
16
 
14
17
  /**
15
18
  * Internal dependencies
@@ -19,11 +22,27 @@ import { getSupportedGlobalStylesPanels, useSetting, useStyle } from './hooks';
19
22
  const AXIAL_SIDES = [ 'horizontal', 'vertical' ];
20
23
 
21
24
  export function useHasDimensionsPanel( name ) {
25
+ const hasContentSize = useHasContentSize( name );
26
+ const hasWideSize = useHasWideSize( name );
22
27
  const hasPadding = useHasPadding( name );
23
28
  const hasMargin = useHasMargin( name );
24
29
  const hasGap = useHasGap( name );
25
30
 
26
- return hasPadding || hasMargin || hasGap;
31
+ return hasContentSize || hasWideSize || hasPadding || hasMargin || hasGap;
32
+ }
33
+
34
+ function useHasContentSize( name ) {
35
+ const supports = getSupportedGlobalStylesPanels( name );
36
+ const [ settings ] = useSetting( 'layout.contentSize', name );
37
+
38
+ return settings && supports.includes( 'contentSize' );
39
+ }
40
+
41
+ function useHasWideSize( name ) {
42
+ const supports = getSupportedGlobalStylesPanels( name );
43
+ const [ settings ] = useSetting( 'layout.wideSize', name );
44
+
45
+ return settings && supports.includes( 'wideSize' );
27
46
  }
28
47
 
29
48
  function useHasPadding( name ) {
@@ -85,20 +104,50 @@ function splitStyleValue( value ) {
85
104
  return value;
86
105
  }
87
106
 
88
- export default function DimensionsPanel( { name } ) {
89
- const showPaddingControl = useHasPadding( name );
90
- const showMarginControl = useHasMargin( name );
91
- const showGapControl = useHasGap( name );
92
- const units = useCustomUnits( {
93
- availableUnits: useSetting( 'spacing.units', name )[ 0 ] || [
94
- '%',
95
- 'px',
96
- 'em',
97
- 'rem',
98
- 'vw',
99
- ],
100
- } );
107
+ // Props for managing `layout.contentSize`.
108
+ function useContentSizeProps( name ) {
109
+ const [ contentSizeValue, setContentSizeValue ] = useSetting(
110
+ 'layout.contentSize',
111
+ name
112
+ );
113
+ const [ userSetContentSizeValue ] = useSetting(
114
+ 'layout.contentSize',
115
+ name,
116
+ 'user'
117
+ );
118
+ const hasUserSetContentSizeValue = () => !! userSetContentSizeValue;
119
+ const resetContentSizeValue = () => setContentSizeValue( '' );
120
+ return {
121
+ contentSizeValue,
122
+ setContentSizeValue,
123
+ hasUserSetContentSizeValue,
124
+ resetContentSizeValue,
125
+ };
126
+ }
101
127
 
128
+ // Props for managing `layout.wideSize`.
129
+ function useWideSizeProps( name ) {
130
+ const [ wideSizeValue, setWideSizeValue ] = useSetting(
131
+ 'layout.wideSize',
132
+ name
133
+ );
134
+ const [ userSetWideSizeValue ] = useSetting(
135
+ 'layout.wideSize',
136
+ name,
137
+ 'user'
138
+ );
139
+ const hasUserSetWideSizeValue = () => !! userSetWideSizeValue;
140
+ const resetWideSizeValue = () => setWideSizeValue( '' );
141
+ return {
142
+ wideSizeValue,
143
+ setWideSizeValue,
144
+ hasUserSetWideSizeValue,
145
+ resetWideSizeValue,
146
+ };
147
+ }
148
+
149
+ // Props for managing `spacing.padding`.
150
+ function usePaddingProps( name ) {
102
151
  const [ rawPadding, setRawPadding ] = useStyle( 'spacing.padding', name );
103
152
  const paddingValues = splitStyleValue( rawPadding );
104
153
  const paddingSides = useCustomSides( name, 'padding' );
@@ -114,6 +163,18 @@ export default function DimensionsPanel( { name } ) {
114
163
  const hasPaddingValue = () =>
115
164
  !! paddingValues && Object.keys( paddingValues ).length;
116
165
 
166
+ return {
167
+ paddingValues,
168
+ paddingSides,
169
+ isAxialPadding,
170
+ setPaddingValues,
171
+ resetPaddingValue,
172
+ hasPaddingValue,
173
+ };
174
+ }
175
+
176
+ // Props for managing `spacing.margin`.
177
+ function useMarginProps( name ) {
117
178
  const [ rawMargin, setRawMargin ] = useStyle( 'spacing.margin', name );
118
179
  const marginValues = splitStyleValue( rawMargin );
119
180
  const marginSides = useCustomSides( name, 'margin' );
@@ -129,18 +190,150 @@ export default function DimensionsPanel( { name } ) {
129
190
  const hasMarginValue = () =>
130
191
  !! marginValues && Object.keys( marginValues ).length;
131
192
 
193
+ return {
194
+ marginValues,
195
+ marginSides,
196
+ isAxialMargin,
197
+ setMarginValues,
198
+ resetMarginValue,
199
+ hasMarginValue,
200
+ };
201
+ }
202
+
203
+ // Props for managing `spacing.blockGap`.
204
+ function useBlockGapProps( name ) {
132
205
  const [ gapValue, setGapValue ] = useStyle( 'spacing.blockGap', name );
133
206
  const resetGapValue = () => setGapValue( undefined );
134
207
  const hasGapValue = () => !! gapValue;
208
+ return {
209
+ gapValue,
210
+ setGapValue,
211
+ resetGapValue,
212
+ hasGapValue,
213
+ };
214
+ }
215
+
216
+ export default function DimensionsPanel( { name } ) {
217
+ const showContentSizeControl = useHasContentSize( name );
218
+ const showWideSizeControl = useHasWideSize( name );
219
+ const showPaddingControl = useHasPadding( name );
220
+ const showMarginControl = useHasMargin( name );
221
+ const showGapControl = useHasGap( name );
222
+ const units = useCustomUnits( {
223
+ availableUnits: useSetting( 'spacing.units', name )[ 0 ] || [
224
+ '%',
225
+ 'px',
226
+ 'em',
227
+ 'rem',
228
+ 'vw',
229
+ ],
230
+ } );
231
+
232
+ // Props for managing `layout.contentSize`.
233
+ const {
234
+ contentSizeValue,
235
+ setContentSizeValue,
236
+ hasUserSetContentSizeValue,
237
+ resetContentSizeValue,
238
+ } = useContentSizeProps( name );
239
+
240
+ // Props for managing `layout.wideSize`.
241
+ const {
242
+ wideSizeValue,
243
+ setWideSizeValue,
244
+ hasUserSetWideSizeValue,
245
+ resetWideSizeValue,
246
+ } = useWideSizeProps( name );
247
+
248
+ // Props for managing `spacing.padding`.
249
+ const {
250
+ paddingValues,
251
+ paddingSides,
252
+ isAxialPadding,
253
+ setPaddingValues,
254
+ resetPaddingValue,
255
+ hasPaddingValue,
256
+ } = usePaddingProps( name );
257
+
258
+ // Props for managing `spacing.margin`.
259
+ const {
260
+ marginValues,
261
+ marginSides,
262
+ isAxialMargin,
263
+ setMarginValues,
264
+ resetMarginValue,
265
+ hasMarginValue,
266
+ } = useMarginProps( name );
267
+
268
+ // Props for managing `spacing.blockGap`.
269
+ const { gapValue, setGapValue, resetGapValue, hasGapValue } =
270
+ useBlockGapProps( name );
135
271
 
136
272
  const resetAll = () => {
137
273
  resetPaddingValue();
138
274
  resetMarginValue();
139
275
  resetGapValue();
276
+ resetContentSizeValue();
277
+ resetWideSizeValue();
140
278
  };
141
279
 
142
280
  return (
143
281
  <ToolsPanel label={ __( 'Dimensions' ) } resetAll={ resetAll }>
282
+ { ( showContentSizeControl || showWideSizeControl ) && (
283
+ <span className="span-columns">
284
+ { __( 'Set the width of the main content area.' ) }
285
+ </span>
286
+ ) }
287
+ { showContentSizeControl && (
288
+ <ToolsPanelItem
289
+ className="single-column"
290
+ label={ __( 'Content size' ) }
291
+ hasValue={ hasUserSetContentSizeValue }
292
+ onDeselect={ resetContentSizeValue }
293
+ isShownByDefault={ true }
294
+ >
295
+ <HStack alignment="flex-end" justify="flex-start">
296
+ <UnitControl
297
+ label={ __( 'Content' ) }
298
+ labelPosition="top"
299
+ __unstableInputWidth="80px"
300
+ value={ contentSizeValue || '' }
301
+ onChange={ ( nextContentSize ) => {
302
+ setContentSizeValue( nextContentSize );
303
+ } }
304
+ units={ units }
305
+ />
306
+ <View>
307
+ <Icon icon={ positionCenter } />
308
+ </View>
309
+ </HStack>
310
+ </ToolsPanelItem>
311
+ ) }
312
+ { showWideSizeControl && (
313
+ <ToolsPanelItem
314
+ className="single-column"
315
+ label={ __( 'Wide size' ) }
316
+ hasValue={ hasUserSetWideSizeValue }
317
+ onDeselect={ resetWideSizeValue }
318
+ isShownByDefault={ true }
319
+ >
320
+ <HStack alignment="flex-end" justify="flex-start">
321
+ <UnitControl
322
+ label={ __( 'Wide' ) }
323
+ labelPosition="top"
324
+ __unstableInputWidth="80px"
325
+ value={ wideSizeValue || '' }
326
+ onChange={ ( nextWideSize ) => {
327
+ setWideSizeValue( nextWideSize );
328
+ } }
329
+ units={ units }
330
+ />
331
+ <View>
332
+ <Icon icon={ stretchWide } />
333
+ </View>
334
+ </HStack>
335
+ </ToolsPanelItem>
336
+ ) }
144
337
  { showPaddingControl && (
145
338
  <ToolsPanelItem
146
339
  hasValue={ hasPaddingValue }
@@ -168,6 +168,8 @@ const ROOT_BLOCK_SUPPORTS = [
168
168
  'textDecoration',
169
169
  'textTransform',
170
170
  'padding',
171
+ 'contentSize',
172
+ 'wideSize',
171
173
  ];
172
174
 
173
175
  export function getSupportedGlobalStylesPanels( name ) {
@@ -10,6 +10,7 @@ import {
10
10
  getLayoutStyles,
11
11
  getNodesWithSettings,
12
12
  getNodesWithStyles,
13
+ getBlockSelectors,
13
14
  toCustomProperties,
14
15
  toStyles,
15
16
  } from '../use-global-styles-output';
@@ -57,6 +58,11 @@ describe( 'global styles renderer', () => {
57
58
  },
58
59
  },
59
60
  },
61
+ 'core/image': {
62
+ border: {
63
+ radius: '9999px',
64
+ },
65
+ },
60
66
  },
61
67
  elements: {
62
68
  link: {
@@ -84,6 +90,10 @@ describe( 'global styles renderer', () => {
84
90
  'core/heading': {
85
91
  selector: '.my-heading1, .my-heading2',
86
92
  },
93
+ 'core/image': {
94
+ selector: '.my-image',
95
+ featureSelectors: '.my-image img, .my-image .crop-area',
96
+ },
87
97
  };
88
98
 
89
99
  expect( getNodesWithStyles( tree, blockSelectors ) ).toEqual( [
@@ -159,6 +169,15 @@ describe( 'global styles renderer', () => {
159
169
  },
160
170
  selector: '.my-heading1 a, .my-heading2 a',
161
171
  },
172
+ {
173
+ styles: {
174
+ border: {
175
+ radius: '9999px',
176
+ },
177
+ },
178
+ selector: '.my-image',
179
+ featureSelectors: '.my-image img, .my-image .crop-area',
180
+ },
162
181
  ] );
163
182
  } );
164
183
  } );
@@ -430,6 +449,14 @@ describe( 'global styles renderer', () => {
430
449
  },
431
450
  },
432
451
  },
452
+ 'core/image': {
453
+ color: {
454
+ text: 'red',
455
+ },
456
+ border: {
457
+ radius: '9999px',
458
+ },
459
+ },
433
460
  },
434
461
  },
435
462
  };
@@ -441,12 +468,18 @@ describe( 'global styles renderer', () => {
441
468
  'core/heading': {
442
469
  selector: 'h1,h2,h3,h4,h5,h6',
443
470
  },
471
+ 'core/image': {
472
+ selector: '.wp-block-image',
473
+ featureSelectors: {
474
+ border: '.wp-block-image img, .wp-block-image .wp-crop-area',
475
+ },
476
+ },
444
477
  };
445
478
 
446
479
  expect( toStyles( tree, blockSelectors ) ).toEqual(
447
480
  'body {margin: 0;}' +
448
481
  'body{background-color: red;margin: 10px;padding: 10px;}h1{font-size: 42px;}a{color: blue;}a:hover{color: orange;}a:focus{color: orange;}.wp-block-group{margin-top: 10px;margin-right: 20px;margin-bottom: 30px;margin-left: 40px;padding-top: 11px;padding-right: 22px;padding-bottom: 33px;padding-left: 44px;}h1,h2,h3,h4,h5,h6{color: orange;}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{color: hotpink;}h1 a:hover,h2 a:hover,h3 a:hover,h4 a:hover,h5 a:hover,h6 a:hover{color: red;}h1 a:focus,h2 a:focus,h3 a:focus,h4 a:focus,h5 a:focus,h6 a:focus{color: red;}' +
449
- '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }' +
482
+ '.wp-block-image img, .wp-block-image .wp-crop-area{border-radius: 9999px }.wp-block-image{color: red;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }' +
450
483
  '.has-white-color{color: var(--wp--preset--color--white) !important;}.has-white-background-color{background-color: var(--wp--preset--color--white) !important;}.has-white-border-color{border-color: var(--wp--preset--color--white) !important;}.has-black-color{color: var(--wp--preset--color--black) !important;}.has-black-background-color{background-color: var(--wp--preset--color--black) !important;}.has-black-border-color{border-color: var(--wp--preset--color--black) !important;}h1.has-blue-color,h2.has-blue-color,h3.has-blue-color,h4.has-blue-color,h5.has-blue-color,h6.has-blue-color{color: var(--wp--preset--color--blue) !important;}h1.has-blue-background-color,h2.has-blue-background-color,h3.has-blue-background-color,h4.has-blue-background-color,h5.has-blue-background-color,h6.has-blue-background-color{background-color: var(--wp--preset--color--blue) !important;}h1.has-blue-border-color,h2.has-blue-border-color,h3.has-blue-border-color,h4.has-blue-border-color,h5.has-blue-border-color,h6.has-blue-border-color{border-color: var(--wp--preset--color--blue) !important;}'
451
484
  );
452
485
  } );
@@ -618,4 +651,34 @@ describe( 'global styles renderer', () => {
618
651
  );
619
652
  } );
620
653
  } );
654
+
655
+ describe( 'getBlockSelectors', () => {
656
+ it( 'should return block selectors data', () => {
657
+ const imageSupports = {
658
+ __experimentalBorder: {
659
+ radius: true,
660
+ __experimentalSelector: 'img, .crop-area',
661
+ },
662
+ color: {
663
+ __experimentalDuotone: 'img',
664
+ },
665
+ __experimentalSelector: '.my-image',
666
+ };
667
+ const imageBlock = { name: 'core/image', supports: imageSupports };
668
+ const blockTypes = [ imageBlock ];
669
+
670
+ expect( getBlockSelectors( blockTypes ) ).toEqual( {
671
+ 'core/image': {
672
+ name: imageBlock.name,
673
+ selector: imageSupports.__experimentalSelector,
674
+ duotoneSelector: imageSupports.color.__experimentalDuotone,
675
+ fallbackGapValue: undefined,
676
+ featureSelectors: {
677
+ border: '.my-image img, .my-image .crop-area',
678
+ },
679
+ hasLayoutSupport: false,
680
+ },
681
+ } );
682
+ } );
683
+ } );
621
684
  } );
@@ -31,10 +31,19 @@ import {
31
31
  /**
32
32
  * Internal dependencies
33
33
  */
34
- import { PRESET_METADATA, ROOT_BLOCK_SELECTOR } from './utils';
34
+ import { PRESET_METADATA, ROOT_BLOCK_SELECTOR, scopeSelector } from './utils';
35
35
  import { GlobalStylesContext } from './context';
36
36
  import { useSetting } from './hooks';
37
37
 
38
+ // List of block support features that can have their related styles
39
+ // generated under their own feature level selector rather than the block's.
40
+ const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = {
41
+ __experimentalBorder: 'border',
42
+ color: 'color',
43
+ spacing: 'spacing',
44
+ typography: 'typography',
45
+ };
46
+
38
47
  function compileStyleValue( uncompiledValue ) {
39
48
  const VARIABLE_REFERENCE_PREFIX = 'var:';
40
49
  const VARIABLE_PATH_SEPARATOR_TOKEN_ATTRIBUTE = '|';
@@ -172,14 +181,26 @@ function flattenTree( input = {}, prefix, token ) {
172
181
  /**
173
182
  * Transform given style tree into a set of style declarations.
174
183
  *
175
- * @param {Object} blockStyles Block styles.
184
+ * @param {Object} blockStyles Block styles.
185
+ *
186
+ * @param {string} selector The selector these declarations should attach to.
187
+ *
188
+ * @param {boolean} useRootPaddingAlign Whether to use CSS custom properties in root selector.
176
189
  *
177
190
  * @return {Array} An array of style declarations.
178
191
  */
179
- function getStylesDeclarations( blockStyles = {} ) {
192
+ function getStylesDeclarations(
193
+ blockStyles = {},
194
+ selector = '',
195
+ useRootPaddingAlign
196
+ ) {
197
+ const isRoot = ROOT_BLOCK_SELECTOR === selector;
180
198
  const output = reduce(
181
199
  STYLE_PROPERTY,
182
- ( declarations, { value, properties, useEngine }, key ) => {
200
+ ( declarations, { value, properties, useEngine, rootOnly }, key ) => {
201
+ if ( rootOnly && ! isRoot ) {
202
+ return declarations;
203
+ }
183
204
  const pathToValue = value;
184
205
  if ( first( pathToValue ) === 'elements' || useEngine ) {
185
206
  return declarations;
@@ -197,13 +218,22 @@ function getStylesDeclarations( blockStyles = {} ) {
197
218
  return;
198
219
  }
199
220
 
200
- const cssProperty = kebabCase( name );
221
+ const cssProperty = name.startsWith( '--' )
222
+ ? name
223
+ : kebabCase( name );
201
224
  declarations.push(
202
225
  `${ cssProperty }: ${ compileStyleValue(
203
226
  get( styleValue, [ prop ] )
204
227
  ) }`
205
228
  );
206
229
  } );
230
+ } else if (
231
+ key === '--wp--style--root--padding' &&
232
+ typeof styleValue === 'string'
233
+ ) {
234
+ // Root-level padding styles don't currently support strings with CSS shorthand values.
235
+ // This may change: https://github.com/WordPress/gutenberg/issues/40132.
236
+ return declarations;
207
237
  } else if ( get( blockStyles, pathToValue, false ) ) {
208
238
  const cssProperty = key.startsWith( '--' )
209
239
  ? key
@@ -220,6 +250,10 @@ function getStylesDeclarations( blockStyles = {} ) {
220
250
  []
221
251
  );
222
252
 
253
+ if ( isRoot && useRootPaddingAlign ) {
254
+ return output;
255
+ }
256
+
223
257
  // The goal is to move everything to server side generated engine styles
224
258
  // This is temporary as we absorb more and more styles into the engine.
225
259
  const extraRules = getCSSRules( blockStyles );
@@ -403,6 +437,7 @@ export const getNodesWithStyles = ( tree, blockSelectors ) => {
403
437
  hasLayoutSupport: blockSelectors[ blockName ].hasLayoutSupport,
404
438
  selector: blockSelectors[ blockName ].selector,
405
439
  styles: blockStyles,
440
+ featureSelectors: blockSelectors[ blockName ].featureSelectors,
406
441
  } );
407
442
  }
408
443
 
@@ -505,6 +540,7 @@ export const toStyles = (
505
540
  ) => {
506
541
  const nodesWithStyles = getNodesWithStyles( tree, blockSelectors );
507
542
  const nodesWithSettings = getNodesWithSettings( tree, blockSelectors );
543
+ const useRootPaddingAlign = tree?.settings?.useRootPaddingAwareAlignments;
508
544
 
509
545
  /*
510
546
  * Reset default browser margin on the root body element.
@@ -515,6 +551,12 @@ export const toStyles = (
515
551
  * @link https://github.com/WordPress/gutenberg/issues/36147.
516
552
  */
517
553
  let ruleset = 'body {margin: 0;}';
554
+
555
+ if ( useRootPaddingAlign ) {
556
+ ruleset =
557
+ 'body { margin: 0; padding-right: 0; padding-left: 0; padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom) } .has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); } .has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); } .has-global-padding > .alignfull > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }';
558
+ }
559
+
518
560
  nodesWithStyles.forEach(
519
561
  ( {
520
562
  selector,
@@ -522,7 +564,33 @@ export const toStyles = (
522
564
  styles,
523
565
  fallbackGapValue,
524
566
  hasLayoutSupport,
567
+ featureSelectors,
525
568
  } ) => {
569
+ // Process styles for block support features with custom feature level
570
+ // CSS selectors set.
571
+ if ( featureSelectors ) {
572
+ Object.entries( featureSelectors ).forEach(
573
+ ( [ featureName, featureSelector ] ) => {
574
+ if ( styles?.[ featureName ] ) {
575
+ const featureStyles = {
576
+ [ featureName ]: styles[ featureName ],
577
+ };
578
+ const featureDeclarations =
579
+ getStylesDeclarations( featureStyles );
580
+ delete styles[ featureName ];
581
+
582
+ if ( !! featureDeclarations.length ) {
583
+ ruleset =
584
+ ruleset +
585
+ `${ featureSelector }{${ featureDeclarations.join(
586
+ ';'
587
+ ) } }`;
588
+ }
589
+ }
590
+ }
591
+ );
592
+ }
593
+
526
594
  const duotoneStyles = {};
527
595
  if ( styles?.filter ) {
528
596
  duotoneStyles.filter = styles.filter;
@@ -556,7 +624,11 @@ export const toStyles = (
556
624
  }
557
625
 
558
626
  // Process the remaining block styles (they use either normal block class or __experimentalSelector).
559
- const declarations = getStylesDeclarations( styles );
627
+ const declarations = getStylesDeclarations(
628
+ styles,
629
+ selector,
630
+ useRootPaddingAlign
631
+ );
560
632
  if ( declarations?.length ) {
561
633
  ruleset =
562
634
  ruleset + `${ selector }{${ declarations.join( ';' ) };}`;
@@ -579,7 +651,7 @@ export const toStyles = (
579
651
 
580
652
  // `selector` maybe provided in a form
581
653
  // where block level selectors have sub element
582
- // selectors appended to them as a comma seperated
654
+ // selectors appended to them as a comma separated
583
655
  // string.
584
656
  // e.g. `h1 a,h2 a,h3 a,h4 a,h5 a,h6 a`;
585
657
  // Split and append pseudo selector to create
@@ -645,7 +717,7 @@ export function toSvgFilters( tree, blockSelectors ) {
645
717
  } );
646
718
  }
647
719
 
648
- const getBlockSelectors = ( blockTypes ) => {
720
+ export const getBlockSelectors = ( blockTypes ) => {
649
721
  const result = {};
650
722
  blockTypes.forEach( ( blockType ) => {
651
723
  const name = blockType.name;
@@ -657,9 +729,29 @@ const getBlockSelectors = ( blockTypes ) => {
657
729
  const hasLayoutSupport = !! blockType?.supports?.__experimentalLayout;
658
730
  const fallbackGapValue =
659
731
  blockType?.supports?.spacing?.blockGap?.__experimentalDefault;
732
+
733
+ // For each block support feature add any custom selectors.
734
+ const featureSelectors = {};
735
+ Object.entries( BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS ).forEach(
736
+ ( [ featureKey, featureName ] ) => {
737
+ const featureSelector =
738
+ blockType?.supports?.[ featureKey ]?.__experimentalSelector;
739
+
740
+ if ( featureSelector ) {
741
+ featureSelectors[ featureName ] = scopeSelector(
742
+ selector,
743
+ featureSelector
744
+ );
745
+ }
746
+ }
747
+ );
748
+
660
749
  result[ name ] = {
661
750
  duotoneSelector,
662
751
  fallbackGapValue,
752
+ featureSelectors: Object.keys( featureSelectors ).length
753
+ ? featureSelectors
754
+ : undefined,
663
755
  hasLayoutSupport,
664
756
  name,
665
757
  selector,
@@ -255,3 +255,34 @@ export function getValueFromVariable( features, blockName, variable ) {
255
255
  }
256
256
  return variable;
257
257
  }
258
+
259
+ /**
260
+ * Function that scopes a selector with another one. This works a bit like
261
+ * SCSS nesting except the `&` operator isn't supported.
262
+ *
263
+ * @example
264
+ * ```js
265
+ * const scope = '.a, .b .c';
266
+ * const selector = '> .x, .y';
267
+ * const merged = scopeSelector( scope, selector );
268
+ * // merged is '.a > .x, .a .y, .b .c > .x, .b .c .y'
269
+ * ```
270
+ *
271
+ * @param {string} scope Selector to scope to.
272
+ * @param {string} selector Original selector.
273
+ *
274
+ * @return {string} Scoped selector.
275
+ */
276
+ export function scopeSelector( scope, selector ) {
277
+ const scopes = scope.split( ',' );
278
+ const selectors = selector.split( ',' );
279
+
280
+ const selectorsScoped = [];
281
+ scopes.forEach( ( outer ) => {
282
+ selectors.forEach( ( inner ) => {
283
+ selectorsScoped.push( `${ outer.trim() } ${ inner.trim() }` );
284
+ } );
285
+ } );
286
+
287
+ return selectorsScoped.join( ', ' );
288
+ }
@@ -106,6 +106,13 @@ export default function Header( {
106
106
 
107
107
  const isFocusMode = templateType === 'wp_template_part';
108
108
 
109
+ /* translators: button label text should, if possible, be under 16 characters. */
110
+ const longLabel = _x(
111
+ 'Toggle block inserter',
112
+ 'Generic label for block inserter button'
113
+ );
114
+ const shortLabel = ! isInserterOpen ? __( 'Add' ) : __( 'Close' );
115
+
109
116
  return (
110
117
  <div className="edit-site-header">
111
118
  <NavigableToolbar
@@ -123,17 +130,9 @@ export default function Header( {
123
130
  onClick={ openInserter }
124
131
  disabled={ ! isVisualMode }
125
132
  icon={ plus }
126
- /* translators: button label text should, if possible, be under 16
127
- characters. */
128
- label={ _x(
129
- 'Toggle block inserter',
130
- 'Generic label for block inserter button'
131
- ) }
133
+ label={ showIconLabels ? shortLabel : longLabel }
132
134
  showTooltip={ ! showIconLabels }
133
- >
134
- { showIconLabels &&
135
- ( ! isInserterOpen ? __( 'Add' ) : __( 'Close' ) ) }
136
- </ToolbarItem>
135
+ />
137
136
  { isLargeViewport && (
138
137
  <>
139
138
  <ToolbarItem