@wordpress/edit-site 4.3.0 → 4.4.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 (47) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/components/block-editor/resizable-editor.js +2 -1
  3. package/build/components/block-editor/resizable-editor.js.map +1 -1
  4. package/build/components/editor/global-styles-renderer.js +2 -1
  5. package/build/components/editor/global-styles-renderer.js.map +1 -1
  6. package/build/components/editor/index.js +9 -5
  7. package/build/components/editor/index.js.map +1 -1
  8. package/build/components/global-styles/border-panel.js +100 -79
  9. package/build/components/global-styles/border-panel.js.map +1 -1
  10. package/build/components/global-styles/screen-block-list.js +48 -4
  11. package/build/components/global-styles/screen-block-list.js.map +1 -1
  12. package/build/components/global-styles/use-global-styles-output.js +91 -15
  13. package/build/components/global-styles/use-global-styles-output.js.map +1 -1
  14. package/build/components/global-styles/utils.js +12 -2
  15. package/build/components/global-styles/utils.js.map +1 -1
  16. package/build/components/sidebar/navigation-menu-sidebar/index.js +4 -2
  17. package/build/components/sidebar/navigation-menu-sidebar/index.js.map +1 -1
  18. package/build-module/components/block-editor/resizable-editor.js +2 -1
  19. package/build-module/components/block-editor/resizable-editor.js.map +1 -1
  20. package/build-module/components/editor/global-styles-renderer.js +2 -1
  21. package/build-module/components/editor/global-styles-renderer.js.map +1 -1
  22. package/build-module/components/editor/index.js +10 -6
  23. package/build-module/components/editor/index.js.map +1 -1
  24. package/build-module/components/global-styles/border-panel.js +103 -80
  25. package/build-module/components/global-styles/border-panel.js.map +1 -1
  26. package/build-module/components/global-styles/screen-block-list.js +47 -4
  27. package/build-module/components/global-styles/screen-block-list.js.map +1 -1
  28. package/build-module/components/global-styles/use-global-styles-output.js +87 -13
  29. package/build-module/components/global-styles/use-global-styles-output.js.map +1 -1
  30. package/build-module/components/global-styles/utils.js +12 -2
  31. package/build-module/components/global-styles/utils.js.map +1 -1
  32. package/build-module/components/sidebar/navigation-menu-sidebar/index.js +4 -2
  33. package/build-module/components/sidebar/navigation-menu-sidebar/index.js.map +1 -1
  34. package/build-style/style-rtl.css +41 -1
  35. package/build-style/style.css +41 -1
  36. package/package.json +29 -29
  37. package/src/components/block-editor/resizable-editor.js +6 -2
  38. package/src/components/editor/global-styles-renderer.js +2 -1
  39. package/src/components/editor/index.js +16 -5
  40. package/src/components/global-styles/border-panel.js +112 -109
  41. package/src/components/global-styles/screen-block-list.js +63 -8
  42. package/src/components/global-styles/style.scss +2 -1
  43. package/src/components/global-styles/test/use-global-styles-output.js +4 -1
  44. package/src/components/global-styles/use-global-styles-output.js +112 -17
  45. package/src/components/global-styles/utils.js +6 -0
  46. package/src/components/sidebar/navigation-menu-sidebar/index.js +4 -1
  47. package/src/components/sidebar/style.scss +50 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/edit-site",
3
- "version": "4.3.0",
3
+ "version": "4.4.0",
4
4
  "description": "Edit Site Page module for WordPress.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -27,33 +27,33 @@
27
27
  "react-native": "src/index",
28
28
  "dependencies": {
29
29
  "@babel/runtime": "^7.16.0",
30
- "@wordpress/a11y": "^3.6.0",
31
- "@wordpress/api-fetch": "^6.3.0",
32
- "@wordpress/block-editor": "^8.5.0",
33
- "@wordpress/block-library": "^7.3.0",
34
- "@wordpress/blocks": "^11.5.0",
35
- "@wordpress/components": "^19.8.0",
36
- "@wordpress/compose": "^5.4.0",
37
- "@wordpress/core-data": "^4.4.0",
38
- "@wordpress/data": "^6.6.0",
39
- "@wordpress/deprecated": "^3.6.0",
40
- "@wordpress/editor": "^12.5.0",
41
- "@wordpress/element": "^4.4.0",
42
- "@wordpress/hooks": "^3.6.0",
43
- "@wordpress/html-entities": "^3.6.0",
44
- "@wordpress/i18n": "^4.6.0",
45
- "@wordpress/icons": "^8.2.0",
46
- "@wordpress/interface": "^4.5.0",
47
- "@wordpress/keyboard-shortcuts": "^3.4.0",
48
- "@wordpress/keycodes": "^3.6.0",
49
- "@wordpress/media-utils": "^3.4.0",
50
- "@wordpress/notices": "^3.6.0",
51
- "@wordpress/plugins": "^4.4.0",
52
- "@wordpress/preferences": "^1.2.0",
53
- "@wordpress/reusable-blocks": "^3.4.0",
54
- "@wordpress/style-engine": "^0.5.0",
55
- "@wordpress/url": "^3.7.0",
56
- "@wordpress/viewport": "^4.4.0",
30
+ "@wordpress/a11y": "^3.7.0",
31
+ "@wordpress/api-fetch": "^6.4.0",
32
+ "@wordpress/block-editor": "^8.6.0",
33
+ "@wordpress/block-library": "^7.4.0",
34
+ "@wordpress/blocks": "^11.6.0",
35
+ "@wordpress/components": "^19.9.0",
36
+ "@wordpress/compose": "^5.5.0",
37
+ "@wordpress/core-data": "^4.5.0",
38
+ "@wordpress/data": "^6.7.0",
39
+ "@wordpress/deprecated": "^3.7.0",
40
+ "@wordpress/editor": "^12.6.0",
41
+ "@wordpress/element": "^4.5.0",
42
+ "@wordpress/hooks": "^3.7.0",
43
+ "@wordpress/html-entities": "^3.7.0",
44
+ "@wordpress/i18n": "^4.7.0",
45
+ "@wordpress/icons": "^8.3.0",
46
+ "@wordpress/interface": "^4.6.0",
47
+ "@wordpress/keyboard-shortcuts": "^3.5.0",
48
+ "@wordpress/keycodes": "^3.7.0",
49
+ "@wordpress/media-utils": "^3.5.0",
50
+ "@wordpress/notices": "^3.7.0",
51
+ "@wordpress/plugins": "^4.5.0",
52
+ "@wordpress/preferences": "^1.3.0",
53
+ "@wordpress/reusable-blocks": "^3.5.0",
54
+ "@wordpress/style-engine": "^0.6.0",
55
+ "@wordpress/url": "^3.8.0",
56
+ "@wordpress/viewport": "^4.5.0",
57
57
  "classnames": "^2.3.1",
58
58
  "downloadjs": "^1.4.7",
59
59
  "history": "^5.1.0",
@@ -68,5 +68,5 @@
68
68
  "publishConfig": {
69
69
  "access": "public"
70
70
  },
71
- "gitHead": "11eb1241e63c9240018323551c6753f8a5fa96f9"
71
+ "gitHead": "1ba52312b56db563df2d8d4fba5b00613fb46d8c"
72
72
  }
@@ -36,7 +36,7 @@ const HANDLE_STYLES_OVERRIDE = {
36
36
  left: undefined,
37
37
  };
38
38
 
39
- function ResizableEditor( { enableResizing, settings, ...props } ) {
39
+ function ResizableEditor( { enableResizing, settings, children, ...props } ) {
40
40
  const deviceType = useSelect(
41
41
  ( select ) =>
42
42
  select( editSiteStore ).__experimentalGetPreviewDeviceType(),
@@ -182,7 +182,11 @@ function ResizableEditor( { enableResizing, settings, ...props } ) {
182
182
  name="editor-canvas"
183
183
  className="edit-site-visual-editor__editor-canvas"
184
184
  { ...props }
185
- />
185
+ >
186
+ { /* Filters need to be rendered before children to avoid Safari rendering issues. */ }
187
+ { settings.svgFilters }
188
+ { children }
189
+ </Iframe>
186
190
  </ResizableBox>
187
191
  );
188
192
  }
@@ -20,7 +20,7 @@ import { store as editSiteStore } from '../../store';
20
20
  import { useGlobalStylesOutput } from '../global-styles/use-global-styles-output';
21
21
 
22
22
  function useGlobalStylesRenderer() {
23
- const [ styles, settings ] = useGlobalStylesOutput();
23
+ const [ styles, settings, svgFilters ] = useGlobalStylesOutput();
24
24
  const { getSettings } = useSelect( editSiteStore );
25
25
  const { updateSettings } = useDispatch( editSiteStore );
26
26
 
@@ -37,6 +37,7 @@ function useGlobalStylesRenderer() {
37
37
  updateSettings( {
38
38
  ...currentStoreSettings,
39
39
  styles: [ ...nonGlobalStyles, ...styles ],
40
+ svgFilters,
40
41
  __experimentalFeatures: settings,
41
42
  } );
42
43
  }, [ styles, settings ] );
@@ -5,7 +5,11 @@ import { useEffect, useState, useMemo, useCallback } from '@wordpress/element';
5
5
  import { useSelect, useDispatch } from '@wordpress/data';
6
6
  import { Popover, Button, Notice } from '@wordpress/components';
7
7
  import { EntityProvider, store as coreStore } from '@wordpress/core-data';
8
- import { BlockContextProvider, BlockBreadcrumb } from '@wordpress/block-editor';
8
+ import {
9
+ BlockContextProvider,
10
+ BlockBreadcrumb,
11
+ BlockStyles,
12
+ } from '@wordpress/block-editor';
9
13
  import {
10
14
  InterfaceSkeleton,
11
15
  ComplementaryArea,
@@ -43,7 +47,6 @@ import { GlobalStylesProvider } from '../global-styles/global-styles-provider';
43
47
  import useTitle from '../routes/use-title';
44
48
 
45
49
  const interfaceLabels = {
46
- secondarySidebar: __( 'Block Library' ),
47
50
  drawer: __( 'Navigation Sidebar' ),
48
51
  };
49
52
 
@@ -176,11 +179,15 @@ function Editor( { onError } ) {
176
179
  templateType !== undefined &&
177
180
  entityId !== undefined;
178
181
 
182
+ const secondarySidebarLabel = isListViewOpen
183
+ ? __( 'List View' )
184
+ : __( 'Block Library' );
185
+
179
186
  const secondarySidebar = () => {
180
- if ( isInserterOpen ) {
187
+ if ( editorMode === 'visual' && isInserterOpen ) {
181
188
  return <InserterSidebar />;
182
189
  }
183
- if ( isListViewOpen ) {
190
+ if ( editorMode === 'visual' && isListViewOpen ) {
184
191
  return <ListViewSidebar />;
185
192
  }
186
193
  return null;
@@ -208,7 +215,10 @@ function Editor( { onError } ) {
208
215
  <KeyboardShortcuts.Register />
209
216
  <SidebarComplementaryAreaFills />
210
217
  <InterfaceSkeleton
211
- labels={ interfaceLabels }
218
+ labels={ {
219
+ ...interfaceLabels,
220
+ secondarySidebar: secondarySidebarLabel,
221
+ } }
212
222
  className={
213
223
  showIconLabels &&
214
224
  'show-icon-labels'
@@ -236,6 +246,7 @@ function Editor( { onError } ) {
236
246
  content={
237
247
  <>
238
248
  <EditorNotices />
249
+ <BlockStyles.Slot scope="core/block-inspector" />
239
250
  { editorMode === 'visual' &&
240
251
  template && (
241
252
  <BlockEditor
@@ -1,17 +1,15 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
+ import { __experimentalBorderRadiusControl as BorderRadiusControl } from '@wordpress/block-editor';
4
5
  import {
5
- __experimentalBorderRadiusControl as BorderRadiusControl,
6
- __experimentalBorderStyleControl as BorderStyleControl,
7
- __experimentalColorGradientSettingsDropdown as ColorGradientSettingsDropdown,
8
- } from '@wordpress/block-editor';
9
- import {
6
+ __experimentalBorderBoxControl as BorderBoxControl,
7
+ __experimentalHasSplitBorders as hasSplitBorders,
8
+ __experimentalIsDefinedBorder as isDefinedBorder,
10
9
  __experimentalToolsPanel as ToolsPanel,
11
10
  __experimentalToolsPanelItem as ToolsPanelItem,
12
- __experimentalUnitControl as UnitControl,
13
- __experimentalUseCustomUnits as useCustomUnits,
14
11
  } from '@wordpress/components';
12
+ import { useCallback } from '@wordpress/element';
15
13
  import { __ } from '@wordpress/i18n';
16
14
 
17
15
  /**
@@ -24,8 +22,6 @@ import {
24
22
  useStyle,
25
23
  } from './hooks';
26
24
 
27
- const MIN_BORDER_WIDTH = 0;
28
-
29
25
  export function useHasBorderPanel( name ) {
30
26
  const controls = [
31
27
  useHasBorderColorControl( name ),
@@ -69,63 +65,45 @@ function useHasBorderWidthControl( name ) {
69
65
  );
70
66
  }
71
67
 
72
- export default function BorderPanel( { name } ) {
73
- // To better reflect if the user has customized a value we need to
74
- // ensure the style value being checked is from the `user` origin.
75
- const [ userBorderStyles ] = useStyle( 'border', name, 'user' );
76
- const createHasValueCallback = ( feature ) => () =>
77
- !! userBorderStyles?.[ feature ];
68
+ function applyFallbackStyle( border ) {
69
+ if ( ! border ) {
70
+ return border;
71
+ }
78
72
 
79
- const createResetCallback = ( setStyle ) => () => setStyle( undefined );
80
-
81
- const handleOnChange = ( setStyle ) => ( value ) => {
82
- setStyle( value || undefined );
83
- };
84
-
85
- const units = useCustomUnits( {
86
- availableUnits: useSetting( 'spacing.units' )[ 0 ] || [
87
- 'px',
88
- 'em',
89
- 'rem',
90
- ],
91
- } );
92
-
93
- // Border width.
94
- const showBorderWidth = useHasBorderWidthControl( name );
95
- const [ borderWidthValue, setBorderWidth ] = useStyle(
96
- 'border.width',
97
- name
98
- );
73
+ if ( ! border.style && ( border.color || border.width ) ) {
74
+ return { ...border, style: 'solid' };
75
+ }
99
76
 
100
- // Border style.
101
- const showBorderStyle = useHasBorderStyleControl( name );
102
- const [ borderStyle, setBorderStyle ] = useStyle( 'border.style', name );
77
+ return border;
78
+ }
103
79
 
104
- // When we set a border color or width ensure we have a style so the user
105
- // can see a visible border.
106
- const handleOnChangeWithStyle = ( setStyle ) => ( value ) => {
107
- if ( !! value && ! borderStyle ) {
108
- setBorderStyle( 'solid' );
109
- }
80
+ function applyAllFallbackStyles( border ) {
81
+ if ( ! border ) {
82
+ return border;
83
+ }
84
+
85
+ if ( hasSplitBorders( border ) ) {
86
+ return {
87
+ top: applyFallbackStyle( border.top ),
88
+ right: applyFallbackStyle( border.right ),
89
+ bottom: applyFallbackStyle( border.bottom ),
90
+ left: applyFallbackStyle( border.left ),
91
+ };
92
+ }
93
+
94
+ return applyFallbackStyle( border );
95
+ }
110
96
 
111
- setStyle( value || undefined );
112
- };
97
+ export default function BorderPanel( { name } ) {
98
+ // To better reflect if the user has customized a value we need to
99
+ // ensure the style value being checked is from the `user` origin.
100
+ const [ userBorderStyles ] = useStyle( 'border', name, 'user' );
101
+ const [ border, setBorder ] = useStyle( 'border', name );
102
+ const colors = useColorsPerOrigin( name );
113
103
 
114
- // Border color.
115
104
  const showBorderColor = useHasBorderColorControl( name );
116
- const [ borderColor, setBorderColor ] = useStyle( 'border.color', name );
117
- const disableCustomColors = ! useSetting( 'color.custom' )[ 0 ];
118
- const disableCustomGradients = ! useSetting( 'color.customGradient' )[ 0 ];
119
-
120
- const borderColorSettings = [
121
- {
122
- label: __( 'Color' ),
123
- colors: useColorsPerOrigin( name ),
124
- colorValue: borderColor,
125
- onColorChange: handleOnChangeWithStyle( setBorderColor ),
126
- clearable: false,
127
- },
128
- ];
105
+ const showBorderStyle = useHasBorderStyleControl( name );
106
+ const showBorderWidth = useHasBorderWidthControl( name );
129
107
 
130
108
  // Border radius.
131
109
  const showBorderRadius = useHasBorderRadiusControl( name );
@@ -141,60 +119,83 @@ export default function BorderPanel( { name } ) {
141
119
  return !! borderValues;
142
120
  };
143
121
 
144
- const resetAll = () => {
145
- setBorderColor( undefined );
146
- setBorderRadius( undefined );
147
- setBorderStyle( undefined );
148
- setBorderWidth( undefined );
122
+ const resetBorder = () => {
123
+ if ( hasBorderRadius() ) {
124
+ return setBorder( { radius: userBorderStyles.radius } );
125
+ }
126
+
127
+ setBorder( undefined );
149
128
  };
150
129
 
130
+ const resetAll = useCallback( () => setBorder( undefined ), [ setBorder ] );
131
+ const onBorderChange = useCallback(
132
+ ( newBorder ) => {
133
+ // Ensure we have a visible border style when a border width or
134
+ // color is being selected.
135
+ const newBorderWithStyle = applyAllFallbackStyles( newBorder );
136
+
137
+ // As we can't conditionally generate styles based on if other
138
+ // style properties have been set we need to force split border
139
+ // definitions for user set border styles. Border radius is derived
140
+ // from the same property i.e. `border.radius` if it is a string
141
+ // that is used. The longhand border radii styles are only generated
142
+ // if that property is an object.
143
+ //
144
+ // For borders (color, style, and width) those are all properties on
145
+ // the `border` style property. This means if the theme.json defined
146
+ // split borders and the user condenses them into a flat border or
147
+ // vice-versa we'd get both sets of styles which would conflict.
148
+ const updatedBorder = ! hasSplitBorders( newBorderWithStyle )
149
+ ? {
150
+ top: newBorderWithStyle,
151
+ right: newBorderWithStyle,
152
+ bottom: newBorderWithStyle,
153
+ left: newBorderWithStyle,
154
+ }
155
+ : {
156
+ color: null,
157
+ style: null,
158
+ width: null,
159
+ ...newBorderWithStyle,
160
+ };
161
+
162
+ // As radius is maintained separately to color, style, and width
163
+ // maintain its value. Undefined values here will be cleaned when
164
+ // global styles are saved.
165
+ setBorder( { radius: border?.radius, ...updatedBorder } );
166
+ },
167
+ [ setBorder ]
168
+ );
169
+
151
170
  return (
152
171
  <ToolsPanel label={ __( 'Border' ) } resetAll={ resetAll }>
153
- { showBorderWidth && (
154
- <ToolsPanelItem
155
- className="single-column"
156
- hasValue={ createHasValueCallback( 'width' ) }
157
- label={ __( 'Width' ) }
158
- onDeselect={ createResetCallback( setBorderWidth ) }
159
- isShownByDefault={ true }
160
- >
161
- <UnitControl
162
- value={ borderWidthValue }
163
- label={ __( 'Width' ) }
164
- min={ MIN_BORDER_WIDTH }
165
- onChange={ handleOnChangeWithStyle( setBorderWidth ) }
166
- units={ units }
167
- />
168
- </ToolsPanelItem>
169
- ) }
170
- { showBorderStyle && (
171
- <ToolsPanelItem
172
- className="single-column"
173
- hasValue={ createHasValueCallback( 'style' ) }
174
- label={ __( 'Style' ) }
175
- onDeselect={ createResetCallback( setBorderStyle ) }
176
- isShownByDefault={ true }
177
- >
178
- <BorderStyleControl
179
- value={ borderStyle }
180
- onChange={ handleOnChange( setBorderStyle ) }
181
- />
182
- </ToolsPanelItem>
183
- ) }
184
- { showBorderColor && (
172
+ { ( showBorderWidth || showBorderColor ) && (
185
173
  <ToolsPanelItem
186
- hasValue={ createHasValueCallback( 'color' ) }
187
- label={ __( 'Color' ) }
188
- onDeselect={ createResetCallback( setBorderColor ) }
174
+ hasValue={ () => isDefinedBorder( userBorderStyles ) }
175
+ label={ __( 'Border' ) }
176
+ onDeselect={ () => resetBorder() }
189
177
  isShownByDefault={ true }
190
178
  >
191
- <ColorGradientSettingsDropdown
192
- __experimentalHasMultipleOrigins
193
- __experimentalIsRenderedInSidebar
194
- disableCustomColors={ disableCustomColors }
195
- disableCustomGradients={ disableCustomGradients }
196
- enableAlpha
197
- settings={ borderColorSettings }
179
+ <BorderBoxControl
180
+ colors={ colors }
181
+ enableAlpha={ true }
182
+ onChange={ onBorderChange }
183
+ showStyle={ showBorderStyle }
184
+ value={ border }
185
+ popoverClassNames={ {
186
+ linked:
187
+ 'edit-site-global-styles-sidebar__border-box-control__popover',
188
+ top:
189
+ 'edit-site-global-styles-sidebar__border-box-control__popover-top',
190
+ right:
191
+ 'edit-site-global-styles-sidebar__border-box-control__popover-right',
192
+ bottom:
193
+ 'edit-site-global-styles-sidebar__border-box-control__popover-bottom',
194
+ left:
195
+ 'edit-site-global-styles-sidebar__border-box-control__popover-left',
196
+ } }
197
+ __experimentalHasMultipleOrigins={ true }
198
+ __experimentalIsRenderedInSidebar={ true }
198
199
  />
199
200
  </ToolsPanelItem>
200
201
  ) }
@@ -202,12 +203,14 @@ export default function BorderPanel( { name } ) {
202
203
  <ToolsPanelItem
203
204
  hasValue={ hasBorderRadius }
204
205
  label={ __( 'Radius' ) }
205
- onDeselect={ createResetCallback( setBorderRadius ) }
206
+ onDeselect={ () => setBorderRadius( undefined ) }
206
207
  isShownByDefault={ true }
207
208
  >
208
209
  <BorderRadiusControl
209
210
  values={ borderRadiusValues }
210
- onChange={ handleOnChange( setBorderRadius ) }
211
+ onChange={ ( value ) => {
212
+ setBorderRadius( value || undefined );
213
+ } }
211
214
  />
212
215
  </ToolsPanelItem>
213
216
  ) }
@@ -2,13 +2,17 @@
2
2
  * WordPress dependencies
3
3
  */
4
4
  import { store as blocksStore } from '@wordpress/blocks';
5
- import { useSelect } from '@wordpress/data';
6
- import { __ } from '@wordpress/i18n';
5
+ import { __, sprintf, _n } from '@wordpress/i18n';
7
6
  import {
8
7
  FlexItem,
8
+ SearchControl,
9
9
  __experimentalHStack as HStack,
10
10
  } from '@wordpress/components';
11
+ import { useSelect } from '@wordpress/data';
12
+ import { useState, useMemo, useEffect, useRef } from '@wordpress/element';
11
13
  import { BlockIcon } from '@wordpress/block-editor';
14
+ import { useDebounce } from '@wordpress/compose';
15
+ import { speak } from '@wordpress/a11y';
12
16
 
13
17
  /**
14
18
  * Internal dependencies
@@ -68,6 +72,45 @@ function BlockMenuItem( { block } ) {
68
72
 
69
73
  function ScreenBlockList() {
70
74
  const sortedBlockTypes = useSortedBlockTypes();
75
+ const [ filterValue, setFilterValue ] = useState( '' );
76
+ const debouncedSpeak = useDebounce( speak, 500 );
77
+ const isMatchingSearchTerm = useSelect(
78
+ ( select ) => select( blocksStore ).isMatchingSearchTerm,
79
+ []
80
+ );
81
+ const filteredBlockTypes = useMemo( () => {
82
+ if ( ! filterValue ) {
83
+ return sortedBlockTypes;
84
+ }
85
+ return sortedBlockTypes.filter( ( blockType ) =>
86
+ isMatchingSearchTerm( blockType, filterValue )
87
+ );
88
+ }, [ filterValue, sortedBlockTypes, isMatchingSearchTerm ] );
89
+
90
+ const blockTypesListRef = useRef();
91
+
92
+ // Announce search results on change
93
+ useEffect( () => {
94
+ if ( ! filterValue ) {
95
+ return;
96
+ }
97
+ // We extract the results from the wrapper div's `ref` because
98
+ // filtered items can contain items that will eventually not
99
+ // render and there is no reliable way to detect when a child
100
+ // will return `null`.
101
+ // TODO: We should find a better way of handling this as it's
102
+ // fragile and depends on the number of rendered elements of `BlockMenuItem`,
103
+ // which is now one.
104
+ // @see https://github.com/WordPress/gutenberg/pull/39117#discussion_r816022116
105
+ const count = blockTypesListRef.current.childElementCount;
106
+ const resultsFoundMessage = sprintf(
107
+ /* translators: %d: number of results. */
108
+ _n( '%d result found.', '%d results found.', count ),
109
+ count
110
+ );
111
+ debouncedSpeak( resultsFoundMessage, count );
112
+ }, [ filterValue, debouncedSpeak ] );
113
+
71
114
  return (
72
115
  <>
73
116
  <ScreenHeader
@@ -76,12 +119,24 @@ function ScreenBlockList() {
76
119
  'Customize the appearance of specific blocks and for the whole site.'
77
120
  ) }
78
121
  />
79
- { sortedBlockTypes.map( ( block ) => (
80
- <BlockMenuItem
81
- block={ block }
82
- key={ 'menu-itemblock-' + block.name }
83
- />
84
- ) ) }
122
+ <SearchControl
123
+ className="edit-site-block-types-search"
124
+ onChange={ setFilterValue }
125
+ value={ filterValue }
126
+ label={ __( 'Search for blocks' ) }
127
+ placeholder={ __( 'Search' ) }
128
+ />
129
+ <div
130
+ ref={ blockTypesListRef }
131
+ className="edit-site-block-types-item-list"
132
+ >
133
+ { filteredBlockTypes.map( ( block ) => (
134
+ <BlockMenuItem
135
+ block={ block }
136
+ key={ 'menu-itemblock-' + block.name }
137
+ />
138
+ ) ) }
139
+ </div>
85
140
  </>
86
141
  );
87
142
  }
@@ -44,7 +44,8 @@
44
44
  }
45
45
  }
46
46
 
47
- .edit-site-global-styles-header__description {
47
+ .edit-site-global-styles-header__description,
48
+ .edit-site-block-types-search {
48
49
  padding: 0 $grid-unit-20;
49
50
  }
50
51
 
@@ -377,7 +377,10 @@ describe( 'global styles renderer', () => {
377
377
  };
378
378
 
379
379
  expect( toStyles( tree, blockSelectors ) ).toEqual(
380
- '.wp-site-blocks > * { margin-top: 0; margin-bottom: 0; }.wp-site-blocks > * + * { margin-top: var( --wp--style--block-gap ); }body{background-color: red;margin: 10px;padding: 10px;}h1{font-size: 42px;}.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;}.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;}'
380
+ 'body {margin: 0;}' +
381
+ 'body{background-color: red;margin: 10px;padding: 10px;}h1{font-size: 42px;}.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;}' +
382
+ '.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; }' +
383
+ '.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;}'
381
384
  );
382
385
  } );
383
386
  } );