@wordpress/block-editor 12.8.0 → 12.9.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 (173) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +30 -12
  3. package/build/components/block-controls/slot.js +1 -1
  4. package/build/components/block-controls/slot.js.map +1 -1
  5. package/build/components/block-controls/slot.native.js +1 -1
  6. package/build/components/block-controls/slot.native.js.map +1 -1
  7. package/build/components/block-controls/use-has-block-controls.js +1 -1
  8. package/build/components/block-controls/use-has-block-controls.js.map +1 -1
  9. package/build/components/block-list/block-outline.native.js +4 -3
  10. package/build/components/block-list/block-outline.native.js.map +1 -1
  11. package/build/components/block-list/index.js +4 -3
  12. package/build/components/block-list/index.js.map +1 -1
  13. package/build/components/block-list/use-block-props/index.js +27 -1
  14. package/build/components/block-list/use-block-props/index.js.map +1 -1
  15. package/build/components/block-list/use-block-props/use-nav-mode-exit.js +1 -1
  16. package/build/components/block-list/use-block-props/use-nav-mode-exit.js.map +1 -1
  17. package/build/components/block-lock/menu-item.js +3 -1
  18. package/build/components/block-lock/menu-item.js.map +1 -1
  19. package/build/components/block-lock/toolbar.js +3 -1
  20. package/build/components/block-lock/toolbar.js.map +1 -1
  21. package/build/components/block-popover/inbetween.js +4 -5
  22. package/build/components/block-popover/inbetween.js.map +1 -1
  23. package/build/components/block-popover/index.js +3 -2
  24. package/build/components/block-popover/index.js.map +1 -1
  25. package/build/components/color-style-selector/index.js +1 -1
  26. package/build/components/color-style-selector/index.js.map +1 -1
  27. package/build/components/iframe/index.js +1 -0
  28. package/build/components/iframe/index.js.map +1 -1
  29. package/build/components/inserter/reusable-blocks-tab.native.js +7 -4
  30. package/build/components/inserter/reusable-blocks-tab.native.js.map +1 -1
  31. package/build/components/inserter/search-results.native.js +10 -8
  32. package/build/components/inserter/search-results.native.js.map +1 -1
  33. package/build/components/inspector-controls/fill.js +1 -1
  34. package/build/components/inspector-controls/fill.js.map +1 -1
  35. package/build/components/inspector-controls/fill.native.js +1 -1
  36. package/build/components/inspector-controls/fill.native.js.map +1 -1
  37. package/build/components/inspector-controls/slot.js +1 -1
  38. package/build/components/inspector-controls/slot.js.map +1 -1
  39. package/build/components/inspector-controls/slot.native.js +1 -1
  40. package/build/components/inspector-controls/slot.native.js.map +1 -1
  41. package/build/components/link-control/index.js +1 -7
  42. package/build/components/link-control/index.js.map +1 -1
  43. package/build/components/list-view/block-select-button.js +48 -7
  44. package/build/components/list-view/block-select-button.js.map +1 -1
  45. package/build/components/list-view/drop-indicator.js +3 -3
  46. package/build/components/list-view/drop-indicator.js.map +1 -1
  47. package/build/components/list-view/index.js +14 -8
  48. package/build/components/list-view/index.js.map +1 -1
  49. package/build/components/list-view/use-list-view-images.js +5 -4
  50. package/build/components/list-view/use-list-view-images.js.map +1 -1
  51. package/build/components/preview-options/index.js +3 -1
  52. package/build/components/preview-options/index.js.map +1 -1
  53. package/build/components/provider/index.js +3 -1
  54. package/build/components/provider/index.js.map +1 -1
  55. package/build/components/rich-text/use-remove-browser-shortcuts.js +1 -1
  56. package/build/components/rich-text/use-remove-browser-shortcuts.js.map +1 -1
  57. package/build/components/use-block-commands/index.js +74 -63
  58. package/build/components/use-block-commands/index.js.map +1 -1
  59. package/build/components/warning/index.js +1 -1
  60. package/build/components/warning/index.js.map +1 -1
  61. package/build/hooks/auto-inserting-blocks.js +174 -0
  62. package/build/hooks/auto-inserting-blocks.js.map +1 -0
  63. package/build/hooks/index.js +1 -0
  64. package/build/hooks/index.js.map +1 -1
  65. package/build/store/selectors.js +1 -1
  66. package/build/store/selectors.js.map +1 -1
  67. package/build-module/components/block-controls/slot.js +1 -1
  68. package/build-module/components/block-controls/slot.js.map +1 -1
  69. package/build-module/components/block-controls/slot.native.js +1 -1
  70. package/build-module/components/block-controls/slot.native.js.map +1 -1
  71. package/build-module/components/block-controls/use-has-block-controls.js +1 -1
  72. package/build-module/components/block-controls/use-has-block-controls.js.map +1 -1
  73. package/build-module/components/block-list/block-outline.native.js +4 -3
  74. package/build-module/components/block-list/block-outline.native.js.map +1 -1
  75. package/build-module/components/block-list/index.js +4 -3
  76. package/build-module/components/block-list/index.js.map +1 -1
  77. package/build-module/components/block-list/use-block-props/index.js +27 -1
  78. package/build-module/components/block-list/use-block-props/index.js.map +1 -1
  79. package/build-module/components/block-list/use-block-props/use-nav-mode-exit.js +1 -1
  80. package/build-module/components/block-list/use-block-props/use-nav-mode-exit.js.map +1 -1
  81. package/build-module/components/block-lock/menu-item.js +3 -1
  82. package/build-module/components/block-lock/menu-item.js.map +1 -1
  83. package/build-module/components/block-lock/toolbar.js +3 -1
  84. package/build-module/components/block-lock/toolbar.js.map +1 -1
  85. package/build-module/components/block-popover/inbetween.js +4 -5
  86. package/build-module/components/block-popover/inbetween.js.map +1 -1
  87. package/build-module/components/block-popover/index.js +3 -2
  88. package/build-module/components/block-popover/index.js.map +1 -1
  89. package/build-module/components/color-style-selector/index.js +1 -1
  90. package/build-module/components/color-style-selector/index.js.map +1 -1
  91. package/build-module/components/iframe/index.js +1 -0
  92. package/build-module/components/iframe/index.js.map +1 -1
  93. package/build-module/components/inserter/reusable-blocks-tab.native.js +8 -4
  94. package/build-module/components/inserter/reusable-blocks-tab.native.js.map +1 -1
  95. package/build-module/components/inserter/search-results.native.js +11 -8
  96. package/build-module/components/inserter/search-results.native.js.map +1 -1
  97. package/build-module/components/inspector-controls/fill.js +1 -1
  98. package/build-module/components/inspector-controls/fill.js.map +1 -1
  99. package/build-module/components/inspector-controls/fill.native.js +1 -1
  100. package/build-module/components/inspector-controls/fill.native.js.map +1 -1
  101. package/build-module/components/inspector-controls/slot.js +1 -1
  102. package/build-module/components/inspector-controls/slot.js.map +1 -1
  103. package/build-module/components/inspector-controls/slot.native.js +1 -1
  104. package/build-module/components/inspector-controls/slot.native.js.map +1 -1
  105. package/build-module/components/link-control/index.js +1 -7
  106. package/build-module/components/link-control/index.js.map +1 -1
  107. package/build-module/components/list-view/block-select-button.js +48 -7
  108. package/build-module/components/list-view/block-select-button.js.map +1 -1
  109. package/build-module/components/list-view/drop-indicator.js +3 -3
  110. package/build-module/components/list-view/drop-indicator.js.map +1 -1
  111. package/build-module/components/list-view/index.js +14 -8
  112. package/build-module/components/list-view/index.js.map +1 -1
  113. package/build-module/components/list-view/use-list-view-images.js +5 -4
  114. package/build-module/components/list-view/use-list-view-images.js.map +1 -1
  115. package/build-module/components/preview-options/index.js +3 -1
  116. package/build-module/components/preview-options/index.js.map +1 -1
  117. package/build-module/components/provider/index.js +3 -1
  118. package/build-module/components/provider/index.js.map +1 -1
  119. package/build-module/components/rich-text/use-remove-browser-shortcuts.js +1 -1
  120. package/build-module/components/rich-text/use-remove-browser-shortcuts.js.map +1 -1
  121. package/build-module/components/use-block-commands/index.js +74 -63
  122. package/build-module/components/use-block-commands/index.js.map +1 -1
  123. package/build-module/components/warning/index.js +2 -2
  124. package/build-module/components/warning/index.js.map +1 -1
  125. package/build-module/hooks/auto-inserting-blocks.js +167 -0
  126. package/build-module/hooks/auto-inserting-blocks.js.map +1 -0
  127. package/build-module/hooks/index.js +1 -0
  128. package/build-module/hooks/index.js.map +1 -1
  129. package/build-module/store/selectors.js +1 -1
  130. package/build-module/store/selectors.js.map +1 -1
  131. package/build-style/content-rtl.css +8 -7
  132. package/build-style/content.css +8 -7
  133. package/build-style/style-rtl.css +0 -3
  134. package/build-style/style.css +0 -3
  135. package/package.json +32 -32
  136. package/src/components/block-icon/test/index.js +2 -2
  137. package/src/components/block-list/block-outline.native.js +5 -2
  138. package/src/components/block-list/content.scss +2 -3
  139. package/src/components/block-list/index.js +4 -3
  140. package/src/components/block-list/use-block-props/index.js +26 -0
  141. package/src/components/block-list/use-block-props/use-nav-mode-exit.js +1 -1
  142. package/src/components/block-lock/menu-item.js +2 -0
  143. package/src/components/block-lock/toolbar.js +3 -1
  144. package/src/components/block-popover/inbetween.js +4 -3
  145. package/src/components/block-popover/index.js +3 -2
  146. package/src/components/block-styles/style.scss +0 -3
  147. package/src/components/button-block-appender/content.scss +8 -0
  148. package/src/components/color-style-selector/index.js +1 -1
  149. package/src/components/iframe/index.js +1 -0
  150. package/src/components/inserter/reusable-blocks-tab.native.js +7 -2
  151. package/src/components/inserter/search-results.native.js +13 -9
  152. package/src/components/link-control/index.js +1 -5
  153. package/src/components/link-control/test/index.js +1 -0
  154. package/src/components/list-view/block-select-button.js +67 -15
  155. package/src/components/list-view/drop-indicator.js +4 -5
  156. package/src/components/list-view/index.js +19 -13
  157. package/src/components/list-view/use-list-view-images.js +8 -4
  158. package/src/components/observe-typing/README.md +2 -2
  159. package/src/components/preview-options/index.js +2 -0
  160. package/src/components/provider/index.js +8 -1
  161. package/src/components/rich-text/use-remove-browser-shortcuts.js +1 -1
  162. package/src/components/use-block-commands/index.js +92 -88
  163. package/src/components/warning/index.js +2 -2
  164. package/src/hooks/auto-inserting-blocks.js +232 -0
  165. package/src/hooks/index.js +1 -0
  166. package/src/store/selectors.js +1 -1
  167. package/build/utils/pre-parse-patterns.js +0 -68
  168. package/build/utils/pre-parse-patterns.js.map +0 -1
  169. package/build-module/utils/pre-parse-patterns.js +0 -61
  170. package/build-module/utils/pre-parse-patterns.js.map +0 -1
  171. package/src/components/url-popover/test/__snapshots__/index.js.snap +0 -133
  172. package/src/components/url-popover/test/index.js +0 -75
  173. package/src/utils/pre-parse-patterns.js +0 -69
@@ -6,6 +6,7 @@ import classnames from 'classnames';
6
6
  /**
7
7
  * WordPress dependencies
8
8
  */
9
+ import { hasBlockSupport } from '@wordpress/blocks';
9
10
  import {
10
11
  Button,
11
12
  __experimentalHStack as HStack,
@@ -55,13 +56,15 @@ function ListViewBlockSelectButton(
55
56
  } );
56
57
  const { isLocked } = useBlockLock( clientId );
57
58
  const {
59
+ canInsertBlockType,
58
60
  getSelectedBlockClientIds,
59
61
  getPreviousBlockClientId,
60
62
  getBlockRootClientId,
61
63
  getBlockOrder,
64
+ getBlocksByClientId,
62
65
  canRemoveBlocks,
63
66
  } = useSelect( blockEditorStore );
64
- const { removeBlocks } = useDispatch( blockEditorStore );
67
+ const { duplicateBlocks, removeBlocks } = useDispatch( blockEditorStore );
65
68
  const isMatch = useShortcutEventMatch();
66
69
  const isSticky = blockInformation?.positionType === 'sticky';
67
70
  const images = useListViewImages( { clientId, isExpanded } );
@@ -83,10 +86,35 @@ function ListViewBlockSelectButton(
83
86
  onDragStart?.( event );
84
87
  };
85
88
 
89
+ // Determine which blocks to update:
90
+ // If the current (focused) block is part of the block selection, use the whole selection.
91
+ // If the focused block is not part of the block selection, only update the focused block.
92
+ function getBlocksToUpdate() {
93
+ const selectedBlockClientIds = getSelectedBlockClientIds();
94
+ const isUpdatingSelectedBlocks =
95
+ selectedBlockClientIds.includes( clientId );
96
+ const firstBlockClientId = isUpdatingSelectedBlocks
97
+ ? selectedBlockClientIds[ 0 ]
98
+ : clientId;
99
+ const firstBlockRootClientId =
100
+ getBlockRootClientId( firstBlockClientId );
101
+
102
+ const blocksToUpdate = isUpdatingSelectedBlocks
103
+ ? selectedBlockClientIds
104
+ : [ clientId ];
105
+
106
+ return {
107
+ blocksToUpdate,
108
+ firstBlockClientId,
109
+ firstBlockRootClientId,
110
+ selectedBlockClientIds,
111
+ };
112
+ }
113
+
86
114
  /**
87
115
  * @param {KeyboardEvent} event
88
116
  */
89
- function onKeyDownHandler( event ) {
117
+ async function onKeyDownHandler( event ) {
90
118
  if ( event.keyCode === ENTER || event.keyCode === SPACE ) {
91
119
  onClick( event );
92
120
  } else if (
@@ -94,18 +122,12 @@ function ListViewBlockSelectButton(
94
122
  event.keyCode === DELETE ||
95
123
  isMatch( 'core/block-editor/remove', event )
96
124
  ) {
97
- const selectedBlockClientIds = getSelectedBlockClientIds();
98
- const isDeletingSelectedBlocks =
99
- selectedBlockClientIds.includes( clientId );
100
- const firstBlockClientId = isDeletingSelectedBlocks
101
- ? selectedBlockClientIds[ 0 ]
102
- : clientId;
103
- const firstBlockRootClientId =
104
- getBlockRootClientId( firstBlockClientId );
105
-
106
- const blocksToDelete = isDeletingSelectedBlocks
107
- ? selectedBlockClientIds
108
- : [ clientId ];
125
+ const {
126
+ blocksToUpdate: blocksToDelete,
127
+ firstBlockClientId,
128
+ firstBlockRootClientId,
129
+ selectedBlockClientIds,
130
+ } = getBlocksToUpdate();
109
131
 
110
132
  // Don't update the selection if the blocks cannot be deleted.
111
133
  if ( ! canRemoveBlocks( blocksToDelete, firstBlockRootClientId ) ) {
@@ -131,6 +153,36 @@ function ListViewBlockSelectButton(
131
153
  }
132
154
 
133
155
  updateFocusAndSelection( blockToFocus, shouldUpdateSelection );
156
+ } else if ( isMatch( 'core/block-editor/duplicate', event ) ) {
157
+ if ( event.defaultPrevented ) {
158
+ return;
159
+ }
160
+ event.preventDefault();
161
+
162
+ const { blocksToUpdate, firstBlockRootClientId } =
163
+ getBlocksToUpdate();
164
+
165
+ const canDuplicate = getBlocksByClientId( blocksToUpdate ).every(
166
+ ( block ) => {
167
+ return (
168
+ !! block &&
169
+ hasBlockSupport( block.name, 'multiple', true ) &&
170
+ canInsertBlockType( block.name, firstBlockRootClientId )
171
+ );
172
+ }
173
+ );
174
+
175
+ if ( canDuplicate ) {
176
+ const updatedBlocks = await duplicateBlocks(
177
+ blocksToUpdate,
178
+ false
179
+ );
180
+
181
+ if ( updatedBlocks?.length ) {
182
+ // If blocks have been duplicated, focus the first duplicated block.
183
+ updateFocusAndSelection( updatedBlocks[ 0 ], false );
184
+ }
185
+ }
134
186
  }
135
187
  }
136
188
 
@@ -194,7 +246,7 @@ function ListViewBlockSelectButton(
194
246
  { images.map( ( image, index ) => (
195
247
  <span
196
248
  className="block-editor-list-view-block-select-button__image"
197
- key={ `img-${ image.url }` }
249
+ key={ image.clientId }
198
250
  style={ {
199
251
  backgroundImage: `url(${ image.url })`,
200
252
  zIndex: images.length - index, // Ensure the first image is on top, and subsequent images are behind.
@@ -159,10 +159,8 @@ export default function ListViewDropIndicator( {
159
159
  return undefined;
160
160
  }
161
161
 
162
- const ownerDocument = targetElement.ownerDocument;
163
-
164
162
  return {
165
- ownerDocument,
163
+ contextElement: targetElement,
166
164
  getBoundingClientRect() {
167
165
  const rect = targetElement.getBoundingClientRect();
168
166
  const indent = getDropIndicatorIndent( rect );
@@ -189,9 +187,10 @@ export default function ListViewDropIndicator( {
189
187
  'horizontal'
190
188
  );
191
189
 
190
+ const doc = targetElement.ownerDocument;
192
191
  const windowScroll =
193
- scrollContainer === ownerDocument.body ||
194
- scrollContainer === ownerDocument.documentElement;
192
+ scrollContainer === doc.body ||
193
+ scrollContainer === doc.documentElement;
195
194
 
196
195
  // If the scroll container is not the window, offset the left position, if need be.
197
196
  if ( scrollContainer && ! windowScroll ) {
@@ -159,19 +159,6 @@ function ListViewComponent(
159
159
  isMounted.current = true;
160
160
  }, [] );
161
161
 
162
- // List View renders a fixed number of items and relies on each having a fixed item height of 36px.
163
- // If this value changes, we should also change the itemHeight value set in useFixedWindowList.
164
- // See: https://github.com/WordPress/gutenberg/pull/35230 for additional context.
165
- const [ fixedListWindow ] = useFixedWindowList(
166
- elementRef,
167
- BLOCK_LIST_ITEM_HEIGHT,
168
- visibleBlockCount,
169
- {
170
- useWindowing: true,
171
- windowOverscan: 40,
172
- }
173
- );
174
-
175
162
  const expand = useCallback(
176
163
  ( clientId ) => {
177
164
  if ( ! clientId ) {
@@ -242,6 +229,25 @@ function ListViewComponent(
242
229
  ]
243
230
  );
244
231
 
232
+ // List View renders a fixed number of items and relies on each having a fixed item height of 36px.
233
+ // If this value changes, we should also change the itemHeight value set in useFixedWindowList.
234
+ // See: https://github.com/WordPress/gutenberg/pull/35230 for additional context.
235
+ const [ fixedListWindow ] = useFixedWindowList(
236
+ elementRef,
237
+ BLOCK_LIST_ITEM_HEIGHT,
238
+ visibleBlockCount,
239
+ {
240
+ // Ensure that the windowing logic is recalculated when the expanded state changes.
241
+ // This is necessary because expanding a collapsed block in a short list view can
242
+ // switch the list view to a tall list view with a scrollbar, and vice versa.
243
+ // When this happens, the windowing logic needs to be recalculated to ensure that
244
+ // the correct number of blocks are rendered, by rechecking for a scroll container.
245
+ expandedState,
246
+ useWindowing: true,
247
+ windowOverscan: 40,
248
+ }
249
+ );
250
+
245
251
  // If there are no blocks to show and we're not showing the appender, do not render the list view.
246
252
  if ( ! clientIdsTree.length && ! showAppender ) {
247
253
  return null;
@@ -12,13 +12,17 @@ import { store as blockEditorStore } from '../../store';
12
12
  // Maximum number of images to display in a list view row.
13
13
  const MAX_IMAGES = 3;
14
14
 
15
- function getImageUrl( block ) {
15
+ function getImage( block ) {
16
16
  if ( block.name !== 'core/image' ) {
17
17
  return;
18
18
  }
19
19
 
20
20
  if ( block.attributes?.url ) {
21
- return { url: block.attributes.url, alt: block.attributes.alt };
21
+ return {
22
+ url: block.attributes.url,
23
+ alt: block.attributes.alt,
24
+ clientId: block.clientId,
25
+ };
22
26
  }
23
27
  }
24
28
 
@@ -30,7 +34,7 @@ function getImagesFromGallery( block ) {
30
34
  const images = [];
31
35
 
32
36
  for ( const innerBlock of block.innerBlocks ) {
33
- const img = getImageUrl( innerBlock );
37
+ const img = getImage( innerBlock );
34
38
  if ( img ) {
35
39
  images.push( img );
36
40
  }
@@ -43,7 +47,7 @@ function getImagesFromGallery( block ) {
43
47
  }
44
48
 
45
49
  function getImagesFromBlock( block, isExpanded ) {
46
- const img = getImageUrl( block );
50
+ const img = getImage( block );
47
51
  if ( img ) {
48
52
  return [ img ];
49
53
  }
@@ -1,6 +1,6 @@
1
1
  # Observe Typing
2
2
 
3
- `<ObserveTyping />` is a component used in managing the editor's internal typing flag. When used to wrap content — typically the top-level block list — it observes keyboard and mouse events to set and unset the typing flag. The typing flag is used in considering whether the block border and controls should be visible. While typing, these elements are hidden for a distraction-free experience.
3
+ `<ObserveTyping />` is a component used in managing the editor's internal typing flag. When used to wrap content, it observes keyboard and mouse events to set and unset the typing flag. The typing flag is used in considering whether the block border and controls should be visible. While typing, these elements are hidden for a distraction-free experience.
4
4
 
5
5
  ## Usage
6
6
 
@@ -10,7 +10,7 @@ Wrap the component where blocks are to be rendered with `<ObserveTyping />`:
10
10
  function VisualEditor() {
11
11
  return (
12
12
  <ObserveTyping>
13
- <BlockList />
13
+ <MyInput />
14
14
  </ObserveTyping>
15
15
  );
16
16
  }
@@ -33,6 +33,7 @@ export default function PreviewOptions( {
33
33
  const toggleProps = {
34
34
  className: 'block-editor-post-preview__button-toggle',
35
35
  disabled: ! isEnabled,
36
+ __experimentalIsFocusable: ! isEnabled,
36
37
  children: viewLabel,
37
38
  };
38
39
  const menuProps = {
@@ -53,6 +54,7 @@ export default function PreviewOptions( {
53
54
  menuProps={ menuProps }
54
55
  icon={ deviceIcons[ deviceType.toLowerCase() ] }
55
56
  label={ label || __( 'Preview' ) }
57
+ disableOpenOnArrowDown={ ! isEnabled }
56
58
  >
57
59
  { ( renderProps ) => (
58
60
  <>
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { useDispatch } from '@wordpress/data';
5
5
  import { useEffect } from '@wordpress/element';
6
+ import { SlotFillProvider } from '@wordpress/components';
6
7
 
7
8
  /**
8
9
  * Internal dependencies
@@ -12,6 +13,7 @@ import useBlockSync from './use-block-sync';
12
13
  import { store as blockEditorStore } from '../../store';
13
14
  import { BlockRefsProvider } from './block-refs-provider';
14
15
  import { unlock } from '../../lock-unlock';
16
+ import KeyboardShortcuts from '../keyboard-shortcuts';
15
17
 
16
18
  /** @typedef {import('@wordpress/data').WPDataRegistry} WPDataRegistry */
17
19
 
@@ -42,7 +44,12 @@ export const ExperimentalBlockEditorProvider = withRegistryProvider(
42
44
  // Syncs the entity provider with changes in the block-editor store.
43
45
  useBlockSync( props );
44
46
 
45
- return <BlockRefsProvider>{ children }</BlockRefsProvider>;
47
+ return (
48
+ <SlotFillProvider>
49
+ <KeyboardShortcuts.Register />
50
+ <BlockRefsProvider>{ children }</BlockRefsProvider>
51
+ </SlotFillProvider>
52
+ );
46
53
  }
47
54
  );
48
55
 
@@ -23,7 +23,7 @@ export function useRemoveBrowserShortcuts() {
23
23
  }
24
24
  node.addEventListener( 'keydown', onKeydown );
25
25
  return () => {
26
- node.addEventListener( 'keydown', onKeydown );
26
+ node.removeEventListener( 'keydown', onKeydown );
27
27
  };
28
28
  }, [] );
29
29
  }
@@ -126,28 +126,12 @@ const useActionsCommands = () => {
126
126
  getBlocksByClientId,
127
127
  canMoveBlocks,
128
128
  canRemoveBlocks,
129
+ getBlockCount,
129
130
  } = useSelect( blockEditorStore );
130
131
  const { getDefaultBlockName, getGroupingBlockName } =
131
132
  useSelect( blocksStore );
132
133
 
133
134
  const blocks = getBlocksByClientId( clientIds );
134
- const rootClientId = getBlockRootClientId( clientIds[ 0 ] );
135
-
136
- const canDuplicate = blocks.every( ( block ) => {
137
- return (
138
- !! block &&
139
- hasBlockSupport( block.name, 'multiple', true ) &&
140
- canInsertBlockType( block.name, rootClientId )
141
- );
142
- } );
143
-
144
- const canInsertDefaultBlock = canInsertBlockType(
145
- getDefaultBlockName(),
146
- rootClientId
147
- );
148
-
149
- const canMove = canMoveBlocks( clientIds, rootClientId );
150
- const canRemove = canRemoveBlocks( clientIds, rootClientId );
151
135
 
152
136
  const {
153
137
  removeBlocks,
@@ -160,42 +144,6 @@ const useActionsCommands = () => {
160
144
  selectBlock,
161
145
  } = useDispatch( blockEditorStore );
162
146
 
163
- const onDuplicate = () => {
164
- if ( ! canDuplicate ) {
165
- return;
166
- }
167
- return duplicateBlocks( clientIds, true );
168
- };
169
- const onRemove = () => {
170
- if ( ! canRemove ) {
171
- return;
172
- }
173
- return removeBlocks( clientIds, true );
174
- };
175
- const onAddBefore = () => {
176
- if ( ! canInsertDefaultBlock ) {
177
- return;
178
- }
179
- const clientId = Array.isArray( clientIds ) ? clientIds[ 0 ] : clientId;
180
- insertBeforeBlock( clientId );
181
- };
182
- const onAddAfter = () => {
183
- if ( ! canInsertDefaultBlock ) {
184
- return;
185
- }
186
- const clientId = Array.isArray( clientIds )
187
- ? clientIds[ clientIds.length - 1 ]
188
- : clientId;
189
- insertAfterBlock( clientId );
190
- };
191
- const onMoveTo = () => {
192
- if ( ! canMove ) {
193
- return;
194
- }
195
- setNavigationMode( true );
196
- selectBlock( clientIds[ 0 ] );
197
- setBlockMovingClientId( clientIds[ 0 ] );
198
- };
199
147
  const onGroup = () => {
200
148
  if ( ! blocks.length ) {
201
149
  return;
@@ -229,47 +177,103 @@ const useActionsCommands = () => {
229
177
  return { isLoading: false, commands: [] };
230
178
  }
231
179
 
232
- const icons = {
233
- ungroup,
234
- group,
235
- move,
236
- add,
237
- remove,
238
- duplicate: copy,
239
- };
180
+ const rootClientId = getBlockRootClientId( clientIds[ 0 ] );
181
+ const canInsertDefaultBlock = canInsertBlockType(
182
+ getDefaultBlockName(),
183
+ rootClientId
184
+ );
185
+ const canDuplicate = blocks.every( ( block ) => {
186
+ return (
187
+ !! block &&
188
+ hasBlockSupport( block.name, 'multiple', true ) &&
189
+ canInsertBlockType( block.name, rootClientId )
190
+ );
191
+ } );
192
+ const canRemove = canRemoveBlocks( clientIds, rootClientId );
193
+ const canMove =
194
+ canMoveBlocks( clientIds, rootClientId ) &&
195
+ getBlockCount( rootClientId ) !== 1;
240
196
 
241
197
  const commands = [
242
- onUngroup,
243
- onGroup,
244
- onMoveTo,
245
- onAddAfter,
246
- onAddBefore,
247
- onRemove,
248
- onDuplicate,
249
- ].map( ( callback ) => {
250
- const action = callback.name
251
- .replace( 'on', '' )
252
- .replace( /([a-z])([A-Z])/g, '$1 $2' );
198
+ {
199
+ name: 'ungroup',
200
+ label: __( 'Ungroup' ),
201
+ callback: onUngroup,
202
+ icon: ungroup,
203
+ },
204
+ {
205
+ name: 'Group',
206
+ label: __( 'Group' ),
207
+ callback: onGroup,
208
+ icon: group,
209
+ },
210
+ ];
211
+ if ( canInsertDefaultBlock ) {
212
+ commands.push(
213
+ {
214
+ name: 'add-after',
215
+ label: __( 'Add after' ),
216
+ callback: () => {
217
+ const clientId = Array.isArray( clientIds )
218
+ ? clientIds[ clientIds.length - 1 ]
219
+ : clientId;
220
+ insertAfterBlock( clientId );
221
+ },
222
+ icon: add,
223
+ },
224
+ {
225
+ name: 'add-before',
226
+ label: __( 'Add before' ),
227
+ callback: () => {
228
+ const clientId = Array.isArray( clientIds )
229
+ ? clientIds[ 0 ]
230
+ : clientId;
231
+ insertBeforeBlock( clientId );
232
+ },
233
+ icon: add,
234
+ }
235
+ );
236
+ }
237
+ if ( canRemove ) {
238
+ commands.push( {
239
+ name: 'remove',
240
+ label: __( 'Remove' ),
241
+ callback: () => removeBlocks( clientIds, true ),
242
+ icon: remove,
243
+ } );
244
+ }
245
+ if ( canDuplicate ) {
246
+ commands.push( {
247
+ name: 'duplicate',
248
+ label: __( 'Duplicate' ),
249
+ callback: () => duplicateBlocks( clientIds, true ),
250
+ icon: copy,
251
+ } );
252
+ }
253
+ if ( canMove ) {
254
+ commands.push( {
255
+ name: 'move-to',
256
+ label: __( 'Move to' ),
257
+ callback: () => {
258
+ setNavigationMode( true );
259
+ selectBlock( clientIds[ 0 ] );
260
+ setBlockMovingClientId( clientIds[ 0 ] );
261
+ },
262
+ icon: move,
263
+ } );
264
+ }
253
265
 
254
- return {
255
- name: 'core/block-editor/action-' + callback.name,
256
- // translators: %s: type of the command.
257
- label: action,
258
- icon: icons[
259
- callback.name
260
- .replace( 'on', '' )
261
- .match( /[A-Z]{1}[a-z]*/ )
262
- .toString()
263
- .toLowerCase()
264
- ],
266
+ return {
267
+ isLoading: false,
268
+ commands: commands.map( ( command ) => ( {
269
+ ...command,
270
+ name: 'core/block-editor/action-' + command.name,
265
271
  callback: ( { close } ) => {
266
- callback();
272
+ command.callback();
267
273
  close();
268
274
  },
269
- };
270
- } );
271
-
272
- return { isLoading: false, commands };
275
+ } ) ),
276
+ };
273
277
  };
274
278
 
275
279
  export const useBlockCommands = () => {
@@ -9,7 +9,7 @@ import classnames from 'classnames';
9
9
  import { Children } from '@wordpress/element';
10
10
  import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components';
11
11
  import { __ } from '@wordpress/i18n';
12
- import { moreHorizontal } from '@wordpress/icons';
12
+ import { moreVertical } from '@wordpress/icons';
13
13
 
14
14
  function Warning( { className, actions, children, secondaryActions } ) {
15
15
  return (
@@ -34,7 +34,7 @@ function Warning( { className, actions, children, secondaryActions } ) {
34
34
  { secondaryActions && (
35
35
  <DropdownMenu
36
36
  className="block-editor-warning__secondary"
37
- icon={ moreHorizontal }
37
+ icon={ moreVertical }
38
38
  label={ __( 'More options' ) }
39
39
  popoverProps={ {
40
40
  position: 'bottom left',