@wordpress/block-editor 15.18.0 → 15.19.1-next.v.202605131006.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 (86) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/build/components/block-controls/fill.cjs.map +2 -2
  3. package/build/components/block-inspector/edit-contents.cjs +7 -1
  4. package/build/components/block-inspector/edit-contents.cjs.map +2 -2
  5. package/build/components/block-toolbar/edit-section-button.cjs +7 -1
  6. package/build/components/block-toolbar/edit-section-button.cjs.map +2 -2
  7. package/build/components/block-variation-transforms/index.cjs +2 -2
  8. package/build/components/block-variation-transforms/index.cjs.map +2 -2
  9. package/build/components/block-visibility/modal.cjs +0 -10
  10. package/build/components/block-visibility/modal.cjs.map +3 -3
  11. package/build/components/dimensions-tool/scale-tool.cjs +2 -2
  12. package/build/components/dimensions-tool/scale-tool.cjs.map +2 -2
  13. package/build/components/global-styles/state-control.cjs +111 -37
  14. package/build/components/global-styles/state-control.cjs.map +3 -3
  15. package/build/components/gradients/use-gradient.cjs +2 -2
  16. package/build/components/gradients/use-gradient.cjs.map +2 -2
  17. package/build/components/iframe/use-scale-canvas.cjs.map +2 -2
  18. package/build/components/inserter/index.cjs +41 -43
  19. package/build/components/inserter/index.cjs.map +2 -2
  20. package/build/components/inserter/menu.cjs +30 -2
  21. package/build/components/inserter/menu.cjs.map +3 -3
  22. package/build/components/inserter/tips.cjs +1 -1
  23. package/build/components/inserter/tips.cjs.map +2 -2
  24. package/build/components/rich-text/event-listeners/index.cjs.map +2 -2
  25. package/build/store/actions.cjs +2 -2
  26. package/build/store/actions.cjs.map +2 -2
  27. package/build/store/reducer.cjs +8 -30
  28. package/build/store/reducer.cjs.map +2 -2
  29. package/build/store/selectors.cjs.map +2 -2
  30. package/build-module/components/block-controls/fill.mjs.map +2 -2
  31. package/build-module/components/block-inspector/edit-contents.mjs +7 -1
  32. package/build-module/components/block-inspector/edit-contents.mjs.map +2 -2
  33. package/build-module/components/block-toolbar/edit-section-button.mjs +7 -1
  34. package/build-module/components/block-toolbar/edit-section-button.mjs.map +2 -2
  35. package/build-module/components/block-variation-transforms/index.mjs +2 -2
  36. package/build-module/components/block-variation-transforms/index.mjs.map +2 -2
  37. package/build-module/components/block-visibility/modal.mjs +0 -10
  38. package/build-module/components/block-visibility/modal.mjs.map +3 -3
  39. package/build-module/components/dimensions-tool/scale-tool.mjs +2 -2
  40. package/build-module/components/dimensions-tool/scale-tool.mjs.map +2 -2
  41. package/build-module/components/global-styles/state-control.mjs +119 -40
  42. package/build-module/components/global-styles/state-control.mjs.map +2 -2
  43. package/build-module/components/gradients/use-gradient.mjs +2 -2
  44. package/build-module/components/gradients/use-gradient.mjs.map +2 -2
  45. package/build-module/components/iframe/use-scale-canvas.mjs.map +2 -2
  46. package/build-module/components/inserter/index.mjs +41 -43
  47. package/build-module/components/inserter/index.mjs.map +2 -2
  48. package/build-module/components/inserter/menu.mjs +31 -2
  49. package/build-module/components/inserter/menu.mjs.map +2 -2
  50. package/build-module/components/inserter/tips.mjs +1 -1
  51. package/build-module/components/inserter/tips.mjs.map +2 -2
  52. package/build-module/components/rich-text/event-listeners/index.mjs.map +2 -2
  53. package/build-module/store/actions.mjs +2 -2
  54. package/build-module/store/actions.mjs.map +2 -2
  55. package/build-module/store/reducer.mjs +8 -30
  56. package/build-module/store/reducer.mjs.map +2 -2
  57. package/build-module/store/selectors.mjs.map +2 -2
  58. package/build-style/style-rtl.css +29 -3
  59. package/build-style/style.css +29 -3
  60. package/build-types/components/block-context/index.d.ts +9 -16
  61. package/build-types/components/block-context/index.d.ts.map +1 -1
  62. package/build-types/utils/dom.d.ts +7 -7
  63. package/build-types/utils/dom.d.ts.map +1 -1
  64. package/package.json +39 -39
  65. package/src/components/block-controls/fill.js +1 -0
  66. package/src/components/block-inspector/edit-contents.js +4 -2
  67. package/src/components/block-manager/style.scss +3 -2
  68. package/src/components/block-toolbar/edit-section-button.js +5 -1
  69. package/src/components/block-variation-transforms/index.js +2 -2
  70. package/src/components/block-visibility/modal.js +0 -1
  71. package/src/components/dimensions-tool/scale-tool.js +2 -2
  72. package/src/components/global-styles/state-control.js +152 -49
  73. package/src/components/global-styles/style.scss +9 -0
  74. package/src/components/gradients/use-gradient.js +3 -1
  75. package/src/components/iframe/use-scale-canvas.js +0 -4
  76. package/src/components/inner-blocks/README.md +5 -1
  77. package/src/components/inner-blocks/index.native.js +1 -1
  78. package/src/components/inserter/index.js +58 -69
  79. package/src/components/inserter/menu.js +32 -1
  80. package/src/components/inserter/style.scss +18 -3
  81. package/src/components/inserter/tips.js +1 -1
  82. package/src/components/rich-text/event-listeners/index.js +0 -1
  83. package/src/store/actions.js +12 -6
  84. package/src/store/reducer.js +11 -39
  85. package/src/store/selectors.js +6 -0
  86. package/src/store/test/reducer.js +39 -0
@@ -1,75 +1,178 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import { __, sprintf } from '@wordpress/i18n';
4
+ import { __ } from '@wordpress/i18n';
5
5
  import { check, chevronDown } from '@wordpress/icons';
6
- import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components';
6
+ import {
7
+ DropdownMenu,
8
+ MenuGroup,
9
+ MenuItem,
10
+ privateApis as componentsPrivateApis,
11
+ } from '@wordpress/components';
12
+ import { Stack } from '@wordpress/ui';
7
13
 
8
14
  /**
9
- * State control for managing block state styles (hover, focus, etc.).
10
- * Displays a dropdown menu to select between different states.
15
+ * Internal dependencies
16
+ */
17
+ import { unlock } from '../../lock-unlock';
18
+
19
+ const { Badge: WCBadge } = unlock( componentsPrivateApis );
20
+
21
+ /**
22
+ * State control for managing viewport and pseudo-state styles.
23
+ * Displays a dropdown menu with separate groups for each selector.
11
24
  *
12
- * @param {Object} props Component props.
13
- * @param {Array} props.states Array of available states with value and label.
14
- * @param {string} props.value Currently selected state value.
15
- * @param {Function} props.onChange Callback when selection changes.
25
+ * @param {Object} props Component props.
26
+ * @param {Array} props.viewportStates Array of available viewport states.
27
+ * @param {Array} props.pseudoStates Array of available pseudo states.
28
+ * @param {string} props.viewportValue Currently selected viewport value.
29
+ * @param {string} props.pseudoStateValue Currently selected pseudo state value.
30
+ * @param {Function} props.onChangeViewport Callback when viewport selection changes.
31
+ * @param {Function} props.onChangePseudoState Callback when pseudo state selection changes.
16
32
  * @return {Element|null} State control component.
17
33
  */
18
34
  export default function StateControl( {
19
- states = [],
20
- value = 'default',
21
- onChange,
35
+ viewportStates = [],
36
+ pseudoStates = [],
37
+ viewportValue = 'default',
38
+ pseudoStateValue = 'default',
39
+ onChangeViewport,
40
+ onChangePseudoState,
22
41
  } ) {
23
- if ( ! states || states.length === 0 ) {
42
+ if ( ! viewportStates.length && ! pseudoStates.length ) {
24
43
  return null;
25
44
  }
26
45
 
27
- const stateOptions = [
46
+ const viewportOptions = [
47
+ { label: __( 'Default' ), value: 'default' },
48
+ ...viewportStates.map( ( state ) => ( {
49
+ label: state.label,
50
+ value: state.value,
51
+ } ) ),
52
+ ];
53
+ const pseudoStateOptions = [
28
54
  { label: __( 'Default' ), value: 'default' },
29
- ...states.map( ( state ) => ( {
55
+ ...pseudoStates.map( ( state ) => ( {
30
56
  label: state.label,
31
57
  value: state.value,
32
58
  } ) ),
33
59
  ];
34
60
 
35
- const getCurrentStateLabel = () => {
36
- const currentOption = stateOptions.find(
37
- ( option ) => option.value === value
61
+ const hasViewportOptions = viewportStates.length > 0;
62
+ const hasPseudoStateOptions = pseudoStates.length > 0;
63
+ const triggerLabel = __( 'States' );
64
+ const activeStates = [];
65
+
66
+ if ( hasViewportOptions && viewportValue !== 'default' ) {
67
+ const selectedViewport = viewportOptions.find(
68
+ ( option ) => option.value === viewportValue
69
+ );
70
+
71
+ if ( selectedViewport ) {
72
+ activeStates.push( {
73
+ key: `viewport-${ selectedViewport.value }`,
74
+ label: selectedViewport.label,
75
+ } );
76
+ }
77
+ }
78
+
79
+ if ( hasPseudoStateOptions && pseudoStateValue !== 'default' ) {
80
+ const selectedPseudoState = pseudoStateOptions.find(
81
+ ( option ) => option.value === pseudoStateValue
38
82
  );
39
- return currentOption?.label || __( 'Default' );
40
- };
83
+
84
+ if ( selectedPseudoState ) {
85
+ activeStates.push( {
86
+ key: `pseudo-${ selectedPseudoState.value }`,
87
+ label: selectedPseudoState.label,
88
+ } );
89
+ }
90
+ }
41
91
 
42
92
  return (
43
- <DropdownMenu
44
- icon={ chevronDown }
45
- label={ sprintf(
46
- /* translators: %s: Current state (e.g. "Hover", "Focus") */
47
- __( 'State: %s' ),
48
- getCurrentStateLabel()
49
- ) }
50
- text={ getCurrentStateLabel() }
51
- toggleProps={ {
52
- size: 'compact',
53
- variant: 'tertiary',
54
- iconPosition: 'right',
55
- } }
93
+ <Stack
94
+ direction="column"
95
+ gap="sm"
96
+ align="flex-end"
97
+ className="block-editor-global-styles-state-control"
56
98
  >
57
- { ( { onClose } ) => (
58
- <MenuGroup label={ __( 'State' ) }>
59
- { stateOptions.map( ( option ) => (
60
- <MenuItem
61
- key={ option.value }
62
- onClick={ () => {
63
- onChange( option.value );
64
- onClose();
65
- } }
66
- icon={ value === option.value ? check : null }
67
- >
68
- { option.label }
69
- </MenuItem>
70
- ) ) }
71
- </MenuGroup>
72
- ) }
73
- </DropdownMenu>
99
+ <DropdownMenu
100
+ icon={ chevronDown }
101
+ label={ triggerLabel }
102
+ popoverProps={ {
103
+ placement: 'right-start',
104
+ } }
105
+ text={ triggerLabel }
106
+ toggleProps={ {
107
+ size: 'compact',
108
+ variant: 'tertiary',
109
+ iconPosition: 'right',
110
+ } }
111
+ >
112
+ { ( { onClose } ) => (
113
+ <>
114
+ { hasViewportOptions && (
115
+ <MenuGroup label={ __( 'Viewport' ) }>
116
+ { viewportOptions.map( ( option ) => (
117
+ <MenuItem
118
+ key={ `viewport-${ option.value }` }
119
+ onClick={ () => {
120
+ onChangeViewport?.( option.value );
121
+ if ( ! hasPseudoStateOptions ) {
122
+ onClose();
123
+ }
124
+ } }
125
+ icon={
126
+ viewportValue === option.value
127
+ ? check
128
+ : null
129
+ }
130
+ >
131
+ { option.label }
132
+ </MenuItem>
133
+ ) ) }
134
+ </MenuGroup>
135
+ ) }
136
+ { hasPseudoStateOptions && (
137
+ <MenuGroup label={ __( 'Pseudo state' ) }>
138
+ { pseudoStateOptions.map( ( option ) => (
139
+ <MenuItem
140
+ key={ `pseudo-${ option.value }` }
141
+ onClick={ () => {
142
+ onChangePseudoState?.(
143
+ option.value
144
+ );
145
+ if ( ! hasViewportOptions ) {
146
+ onClose();
147
+ }
148
+ } }
149
+ icon={
150
+ pseudoStateValue === option.value
151
+ ? check
152
+ : null
153
+ }
154
+ >
155
+ { option.label }
156
+ </MenuItem>
157
+ ) ) }
158
+ </MenuGroup>
159
+ ) }
160
+ </>
161
+ ) }
162
+ </DropdownMenu>
163
+ <Stack
164
+ className="block-editor-global-styles-state-control__badges"
165
+ direction="row"
166
+ justify="flex-start"
167
+ gap="xs"
168
+ wrap="wrap"
169
+ >
170
+ { activeStates.map( ( activeState ) => (
171
+ <WCBadge key={ activeState.key } intent="info">
172
+ { activeState.label }
173
+ </WCBadge>
174
+ ) ) }
175
+ </Stack>
176
+ </Stack>
74
177
  );
75
178
  }
@@ -23,6 +23,15 @@
23
23
  text-align: right;
24
24
  }
25
25
 
26
+ .block-editor-global-styles-state-control {
27
+ // Ensure alignment with block heading.
28
+ margin-top: -$grid-unit-05;
29
+ }
30
+
31
+ .block-editor-global-styles-state-control__badges {
32
+ min-height: $grid-unit-30;
33
+ }
34
+
26
35
  .block-editor-global-styles-filters-panel__dropdown,
27
36
  .block-editor-global-styles__shadow-dropdown {
28
37
  display: block;
@@ -54,7 +54,7 @@ export function getGradientSlugByValue( gradients, value ) {
54
54
  return gradient && gradient.slug;
55
55
  }
56
56
 
57
- export function __experimentalUseGradient( {
57
+ function useGradient( {
58
58
  gradientAttribute = 'gradient',
59
59
  customGradientAttribute = 'customGradient',
60
60
  } = {} ) {
@@ -120,3 +120,5 @@ export function __experimentalUseGradient( {
120
120
  }
121
121
  return { gradientClass, gradientValue, setGradient };
122
122
  }
123
+
124
+ export { useGradient as __experimentalUseGradient };
@@ -286,10 +286,6 @@ export function useScaleCanvas( {
286
286
 
287
287
  iframeDocument.documentElement.classList.remove( 'zoom-out-animation' );
288
288
 
289
- // Set the final scroll position that was just animated to.
290
- // Disable reason: Eslint isn't smart enough to know that this is a
291
- // DOM element. https://github.com/facebook/react/issues/31483
292
- // eslint-disable-next-line react-compiler/react-compiler
293
289
  iframeDocument.documentElement.scrollTop =
294
290
  transitionToRef.current.scrollTop;
295
291
 
@@ -205,4 +205,8 @@ const DEFAULT_BLOCK = { name: 'core/paragraph', attributes: { content: 'Lorem ip
205
205
  ### `directInsert`
206
206
 
207
207
  - **Type:** `Boolean`
208
- - **Default:** - `undefined`. Determines whether the default block should be inserted directly into the InnerBlocks area by the block appender.
208
+ - **Default:** `undefined`.
209
+
210
+ When `true`, the appender inserts `defaultBlock` directly and skips the inserter dropdown, **including any registered inserter variations** of that block type. Use this to opt out of the variation picker; otherwise leave unset.
211
+
212
+ Note: redundant when `allowedBlocks` resolves to a single block type with no variations, the appender already inserts directly.
@@ -35,7 +35,7 @@ import { MAX_NESTING_DEPTH } from './constants';
35
35
  * returns. Optionally, you can also pass any other props through this hook, and
36
36
  * they will be merged and returned.
37
37
  *
38
- * @see https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/inner-blocks/README.md
38
+ * @see https://github.com/WordPress/gutenberg/blob/trunk/packages/block-editor/src/components/inner-blocks/README.md
39
39
  *
40
40
  * @param {Object} props Optional. Props to pass to the element. Must contain
41
41
  * the ref if one is defined.
@@ -144,7 +144,6 @@ class Inserter extends Component {
144
144
  clientId,
145
145
  isAppender,
146
146
  showInserterHelpPanel,
147
-
148
147
  // This prop is experimental to give some time for the quick inserter to mature
149
148
  // Feel free to make them stable after a few releases.
150
149
  __experimentalIsQuick: isQuick,
@@ -194,13 +193,13 @@ class Inserter extends Component {
194
193
  const {
195
194
  position,
196
195
  hasSingleBlockType,
197
- directInsertBlock,
196
+ blockToInsert,
198
197
  insertOnlyAllowedBlock,
199
198
  __experimentalIsQuick: isQuick,
200
199
  onSelectOrClose,
201
200
  } = this.props;
202
201
 
203
- if ( hasSingleBlockType || directInsertBlock ) {
202
+ if ( hasSingleBlockType || blockToInsert ) {
204
203
  return this.renderToggle( { onToggle: insertOnlyAllowedBlock } );
205
204
  }
206
205
 
@@ -230,26 +229,35 @@ export default compose( [
230
229
  hasInserterItems,
231
230
  getAllowedBlocks,
232
231
  getDirectInsertBlock,
232
+ getBlockListSettings,
233
233
  } = select( blockEditorStore );
234
-
235
234
  const { getBlockVariations, getBlockType } = select( blocksStore );
236
235
 
237
236
  rootClientId =
238
237
  rootClientId || getBlockRootClientId( clientId ) || undefined;
239
238
 
240
239
  const allowedBlocks = getAllowedBlocks( rootClientId );
241
-
242
240
  const directInsertBlock =
243
241
  shouldDirectInsert && getDirectInsertBlock( rootClientId );
242
+ const { defaultBlock } = getBlockListSettings( rootClientId ) ?? {};
244
243
 
245
244
  const hasSingleBlockType =
246
245
  allowedBlocks?.length === 1 &&
247
246
  getBlockVariations( allowedBlocks[ 0 ].name, 'inserter' )
248
247
  ?.length === 0;
248
+ const allowedBlockType = hasSingleBlockType
249
+ ? allowedBlocks[ 0 ]
250
+ : null;
249
251
 
250
- let allowedBlockType = false;
251
- if ( hasSingleBlockType ) {
252
- allowedBlockType = allowedBlocks[ 0 ];
252
+ // Single-block-type parents get adjacent-attribute copying
253
+ // without needing to set `directInsert: true`.
254
+ let blockToInsert = directInsertBlock || null;
255
+ if (
256
+ ! blockToInsert &&
257
+ hasSingleBlockType &&
258
+ defaultBlock?.name === allowedBlockType.name
259
+ ) {
260
+ blockToInsert = defaultBlock;
253
261
  }
254
262
 
255
263
  const defaultBlockType = directInsertBlock
@@ -265,7 +273,7 @@ export default compose( [
265
273
  hasSingleBlockType,
266
274
  blockTitle: allowedBlockType ? allowedBlockType.title : '',
267
275
  allowedBlockType,
268
- directInsertBlock,
276
+ blockToInsert,
269
277
  appenderLabel,
270
278
  rootClientId,
271
279
  };
@@ -280,70 +288,57 @@ export default compose( [
280
288
  isAppender,
281
289
  hasSingleBlockType,
282
290
  allowedBlockType,
283
- directInsertBlock,
291
+ blockToInsert,
284
292
  onSelectOrClose,
285
293
  selectBlockOnInsert,
286
294
  } = ownProps;
287
295
 
288
- if ( ! hasSingleBlockType && ! directInsertBlock ) {
296
+ if ( ! hasSingleBlockType && ! blockToInsert ) {
289
297
  return;
290
298
  }
291
299
 
292
- function getAdjacentBlockAttributes( attributesToCopy ) {
293
- const { getBlock, getPreviousBlockClientId } =
294
- select( blockEditorStore );
300
+ const blockName = blockToInsert?.name ?? allowedBlockType.name;
295
301
 
296
- if (
297
- ! attributesToCopy ||
298
- ( ! clientId && ! rootClientId )
299
- ) {
302
+ function getAdjacentBlockAttributes( attributesToCopy ) {
303
+ if ( ! attributesToCopy?.length ) {
300
304
  return {};
301
305
  }
302
306
 
303
- const result = {};
304
- let adjacentAttributes = {};
305
-
306
- // If there is no clientId, then attempt to get attributes
307
- // from the last block within innerBlocks of the root block.
308
- if ( ! clientId ) {
309
- const parentBlock = getBlock( rootClientId );
310
-
311
- if ( parentBlock?.innerBlocks?.length ) {
312
- const lastInnerBlock =
313
- parentBlock.innerBlocks[
314
- parentBlock.innerBlocks.length - 1
315
- ];
316
-
317
- if (
318
- directInsertBlock &&
319
- directInsertBlock?.name === lastInnerBlock.name
320
- ) {
321
- adjacentAttributes = lastInnerBlock.attributes;
322
- }
323
- }
324
- } else {
325
- // Otherwise, attempt to get attributes from the
326
- // previous block relative to the current clientId.
307
+ const { getBlock, getPreviousBlockClientId } =
308
+ select( blockEditorStore );
309
+
310
+ // Find the adjacent block of the same type whose attributes
311
+ // should be copied: previous sibling when inserting next to
312
+ // an existing block, otherwise the last child of the root.
313
+ let adjacentAttributes;
314
+ if ( clientId ) {
327
315
  const currentBlock = getBlock( clientId );
328
316
  const previousBlock = getBlock(
329
317
  getPreviousBlockClientId( clientId )
330
318
  );
331
-
332
319
  if ( currentBlock?.name === previousBlock?.name ) {
333
- adjacentAttributes =
334
- previousBlock?.attributes || {};
320
+ adjacentAttributes = previousBlock?.attributes;
321
+ }
322
+ } else if ( rootClientId ) {
323
+ const lastInnerBlock =
324
+ getBlock( rootClientId )?.innerBlocks?.at( -1 );
325
+ if ( lastInnerBlock?.name === blockName ) {
326
+ adjacentAttributes = lastInnerBlock.attributes;
335
327
  }
336
328
  }
337
329
 
338
- // Copy over only those attributes flagged to be copied.
339
- attributesToCopy.forEach( ( attribute ) => {
340
- if ( adjacentAttributes.hasOwnProperty( attribute ) ) {
341
- result[ attribute ] =
342
- adjacentAttributes[ attribute ];
343
- }
344
- } );
330
+ if ( ! adjacentAttributes ) {
331
+ return {};
332
+ }
345
333
 
346
- return result;
334
+ return Object.fromEntries(
335
+ attributesToCopy
336
+ .filter( ( attr ) => attr in adjacentAttributes )
337
+ .map( ( attr ) => [
338
+ attr,
339
+ adjacentAttributes[ attr ],
340
+ ] )
341
+ );
347
342
  }
348
343
 
349
344
  function getInsertionIndex() {
@@ -375,33 +370,27 @@ export default compose( [
375
370
 
376
371
  const { insertBlock } = dispatch( blockEditorStore );
377
372
 
378
- let blockToInsert;
379
-
380
- // Attempt to augment the directInsertBlock with attributes from an adjacent block.
373
+ // Attempt to augment the inserted block with attributes from an adjacent block.
381
374
  // This ensures styling from nearby blocks is preserved in the newly inserted block.
382
375
  // See: https://github.com/WordPress/gutenberg/issues/37904
383
- if ( directInsertBlock ) {
384
- const newAttributes = getAdjacentBlockAttributes(
385
- directInsertBlock.attributesToCopy
386
- );
376
+ const newAttributes = getAdjacentBlockAttributes(
377
+ blockToInsert?.attributesToCopy
378
+ );
387
379
 
388
- blockToInsert = createBlock( directInsertBlock.name, {
389
- ...( directInsertBlock.attributes || {} ),
390
- ...newAttributes,
391
- } );
392
- } else {
393
- blockToInsert = createBlock( allowedBlockType.name );
394
- }
380
+ const newBlock = createBlock( blockName, {
381
+ ...( blockToInsert?.attributes || {} ),
382
+ ...newAttributes,
383
+ } );
395
384
 
396
385
  insertBlock(
397
- blockToInsert,
386
+ newBlock,
398
387
  getInsertionIndex(),
399
388
  rootClientId,
400
389
  selectBlockOnInsert
401
390
  );
402
391
 
403
392
  if ( onSelectOrClose ) {
404
- onSelectOrClose( blockToInsert );
393
+ onSelectOrClose( newBlock );
405
394
  }
406
395
 
407
396
  const message = sprintf(
@@ -10,6 +10,7 @@ import {
10
10
  forwardRef,
11
11
  useState,
12
12
  useCallback,
13
+ useEffect,
13
14
  useMemo,
14
15
  useRef,
15
16
  useLayoutEffect,
@@ -169,6 +170,30 @@ function InserterMenu(
169
170
 
170
171
  const showMediaPanel = selectedTab === 'media' && !! selectedMediaCategory;
171
172
 
173
+ const [ isScrolled, setIsScrolled ] = useState( false );
174
+ const blocksPanelRef = useRef( null );
175
+ const patternsPanelRef = useRef( null );
176
+ const mediaPanelRef = useRef( null );
177
+ useEffect( () => {
178
+ const handleScroll = ( event ) => {
179
+ setIsScrolled( event.currentTarget.scrollTop > 0 );
180
+ };
181
+ const panels = [
182
+ blocksPanelRef.current,
183
+ patternsPanelRef.current,
184
+ mediaPanelRef.current,
185
+ ].filter( Boolean );
186
+ panels.forEach( ( panel ) =>
187
+ panel.addEventListener( 'scroll', handleScroll )
188
+ );
189
+
190
+ return () => {
191
+ panels.forEach( ( panel ) =>
192
+ panel.removeEventListener( 'scroll', handleScroll )
193
+ );
194
+ };
195
+ }, [] );
196
+
172
197
  const inserterSearch = useMemo( () => {
173
198
  if ( selectedTab === 'media' ) {
174
199
  return null;
@@ -177,7 +202,9 @@ function InserterMenu(
177
202
  return (
178
203
  <>
179
204
  <SearchControl
180
- className="block-editor-inserter__search"
205
+ className={ clsx( 'block-editor-inserter__search', {
206
+ 'is-scrolled': isScrolled,
207
+ } ) }
181
208
  onChange={ ( value ) => {
182
209
  if ( hoveredItem ) {
183
210
  setHoveredItem( null );
@@ -220,6 +247,7 @@ function InserterMenu(
220
247
  rootClientId,
221
248
  __experimentalInsertionIndex,
222
249
  isAppender,
250
+ isScrolled,
223
251
  ] );
224
252
 
225
253
  const blocksTab = useMemo( () => {
@@ -344,6 +372,7 @@ function InserterMenu(
344
372
  {
345
373
  name: 'blocks',
346
374
  title: __( 'Blocks' ),
375
+ panelRef: blocksPanelRef,
347
376
  panel: (
348
377
  <>
349
378
  { inserterSearch }
@@ -356,6 +385,7 @@ function InserterMenu(
356
385
  {
357
386
  name: 'patterns',
358
387
  title: __( 'Patterns' ),
388
+ panelRef: patternsPanelRef,
359
389
  panel: (
360
390
  <>
361
391
  { inserterSearch }
@@ -368,6 +398,7 @@ function InserterMenu(
368
398
  {
369
399
  name: 'media',
370
400
  title: __( 'Media' ),
401
+ panelRef: mediaPanelRef,
371
402
  panel: (
372
403
  <>
373
404
  { inserterSearch }
@@ -119,7 +119,18 @@ $block-inserter-tabs-height: 44px;
119
119
  }
120
120
 
121
121
  .block-editor-inserter__search {
122
- padding: $grid-unit-20 $grid-unit-20 0 $grid-unit-20;
122
+ padding: $grid-unit-20;
123
+ position: sticky;
124
+ top: 0;
125
+ z-index: 2;
126
+ background-color: $white;
127
+ border-bottom: 1px solid transparent;
128
+ @media not ( prefers-reduced-motion ) {
129
+ transition: border-bottom-color 0.1s linear;
130
+ }
131
+ &.is-scrolled {
132
+ border-bottom-color: $gray-200;
133
+ }
123
134
  }
124
135
 
125
136
  .block-editor-inserter__no-tab-container {
@@ -138,7 +149,7 @@ $block-inserter-tabs-height: 44px;
138
149
 
139
150
  display: inline-flex;
140
151
  align-items: center;
141
- padding: $grid-unit-20 $grid-unit-20 0;
152
+ padding: 0 $grid-unit-20;
142
153
  }
143
154
 
144
155
  .block-editor-inserter__panel-content {
@@ -224,12 +235,16 @@ $block-inserter-tabs-height: 44px;
224
235
  .block-editor-inserter__media-tabs-container,
225
236
  .block-editor-inserter__block-patterns-tabs-container {
226
237
  flex-grow: 1;
227
- padding: $grid-unit-20;
238
+ padding: 0 $grid-unit-20 $grid-unit-20;
228
239
  display: flex;
229
240
  flex-direction: column;
230
241
  justify-content: space-between;
231
242
  }
232
243
 
244
+ .block-editor-inserter__media-tabs-container {
245
+ padding-top: $grid-unit-20;
246
+ }
247
+
233
248
  .block-editor-inserter__category-tablist {
234
249
  margin-bottom: $grid-unit-10;
235
250
  }
@@ -29,7 +29,7 @@ const globalTips = [
29
29
  ];
30
30
 
31
31
  function Tips() {
32
- const [ randomIndex ] = useState(
32
+ const [ randomIndex ] = useState( () =>
33
33
  Math.floor( Math.random() * globalTips.length )
34
34
  );
35
35
 
@@ -36,7 +36,6 @@ const allEventListeners = [
36
36
  export function useEventListeners( props ) {
37
37
  const propsRef = useRef( props );
38
38
  useInsertionEffect( () => {
39
- // eslint-disable-next-line react-compiler/react-compiler -- false positive, see https://github.com/facebook/react/issues/29196
40
39
  propsRef.current = props;
41
40
  } );
42
41
  const refEffects = useMemo(