@wordpress/block-library 8.28.5 → 8.28.6

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.
@@ -49,7 +49,7 @@ function NavigationMenuSelector( {
49
49
  /* translators: %s: The name of a menu. */
50
50
  const createActionLabel = __( "Create from '%s'" );
51
51
 
52
- const [ isCreatingMenu, setIsCreatingMenu ] = useState( false );
52
+ const [ isUpdatingMenuRef, setIsUpdatingMenuRef ] = useState( false );
53
53
 
54
54
  actionLabel = actionLabel || createActionLabel;
55
55
 
@@ -82,10 +82,20 @@ function NavigationMenuSelector( {
82
82
  value: id,
83
83
  label,
84
84
  ariaLabel: sprintf( actionLabel, label ),
85
+ disabled:
86
+ isUpdatingMenuRef ||
87
+ isResolvingNavigationMenus ||
88
+ ! hasResolvedNavigationMenus,
85
89
  };
86
90
  } ) || []
87
91
  );
88
- }, [ navigationMenus, actionLabel ] );
92
+ }, [
93
+ navigationMenus,
94
+ actionLabel,
95
+ isResolvingNavigationMenus,
96
+ hasResolvedNavigationMenus,
97
+ isUpdatingMenuRef,
98
+ ] );
89
99
 
90
100
  const hasNavigationMenus = !! navigationMenus?.length;
91
101
  const hasClassicMenus = !! classicMenus?.length;
@@ -99,7 +109,7 @@ function NavigationMenuSelector( {
99
109
 
100
110
  let selectorLabel = '';
101
111
 
102
- if ( isCreatingMenu || isResolvingNavigationMenus ) {
112
+ if ( isResolvingNavigationMenus ) {
103
113
  selectorLabel = __( 'Loading…' );
104
114
  } else if ( noMenuSelected || noBlockMenus || menuUnavailable ) {
105
115
  // Note: classic Menus may be available.
@@ -111,17 +121,17 @@ function NavigationMenuSelector( {
111
121
 
112
122
  useEffect( () => {
113
123
  if (
114
- isCreatingMenu &&
124
+ isUpdatingMenuRef &&
115
125
  ( createNavigationMenuIsSuccess || createNavigationMenuIsError )
116
126
  ) {
117
- setIsCreatingMenu( false );
127
+ setIsUpdatingMenuRef( false );
118
128
  }
119
129
  }, [
120
130
  hasResolvedNavigationMenus,
121
131
  createNavigationMenuIsSuccess,
122
132
  canUserCreateNavigationMenu,
123
133
  createNavigationMenuIsError,
124
- isCreatingMenu,
134
+ isUpdatingMenuRef,
125
135
  menuUnavailable,
126
136
  noBlockMenus,
127
137
  noMenuSelected,
@@ -140,12 +150,10 @@ function NavigationMenuSelector( {
140
150
  <MenuItemsChoice
141
151
  value={ currentMenuId }
142
152
  onSelect={ ( menuId ) => {
143
- setIsCreatingMenu( true );
144
153
  onSelectNavigationMenu( menuId );
145
154
  onClose();
146
155
  } }
147
156
  choices={ menuChoices }
148
- disabled={ isCreatingMenu }
149
157
  />
150
158
  </MenuGroup>
151
159
  ) }
@@ -155,9 +163,10 @@ function NavigationMenuSelector( {
155
163
  const label = decodeEntities( menu.name );
156
164
  return (
157
165
  <MenuItem
158
- onClick={ () => {
159
- setIsCreatingMenu( true );
160
- onSelectClassicMenu( menu );
166
+ onClick={ async () => {
167
+ setIsUpdatingMenuRef( true );
168
+ await onSelectClassicMenu( menu );
169
+ setIsUpdatingMenuRef( false );
161
170
  onClose();
162
171
  } }
163
172
  key={ menu.id }
@@ -165,7 +174,11 @@ function NavigationMenuSelector( {
165
174
  createActionLabel,
166
175
  label
167
176
  ) }
168
- disabled={ isCreatingMenu }
177
+ disabled={
178
+ isUpdatingMenuRef ||
179
+ isResolvingNavigationMenus ||
180
+ ! hasResolvedNavigationMenus
181
+ }
169
182
  >
170
183
  { label }
171
184
  </MenuItem>
@@ -177,12 +190,17 @@ function NavigationMenuSelector( {
177
190
  { canUserCreateNavigationMenu && (
178
191
  <MenuGroup label={ __( 'Tools' ) }>
179
192
  <MenuItem
180
- disabled={ isCreatingMenu }
181
- onClick={ () => {
193
+ onClick={ async () => {
194
+ setIsUpdatingMenuRef( true );
195
+ await onCreateNew();
196
+ setIsUpdatingMenuRef( false );
182
197
  onClose();
183
- onCreateNew();
184
- setIsCreatingMenu( true );
185
198
  } }
199
+ disabled={
200
+ isUpdatingMenuRef ||
201
+ isResolvingNavigationMenus ||
202
+ ! hasResolvedNavigationMenus
203
+ }
186
204
  >
187
205
  { __( 'Create new menu' ) }
188
206
  </MenuItem>
@@ -250,6 +250,7 @@ describe( 'NavigationMenuSelector', () => {
250
250
  const user = userEvent.setup();
251
251
  const handler = jest.fn();
252
252
 
253
+ // at the start we have the menus and we're not waiting on network
253
254
  useNavigationMenu.mockReturnValue( {
254
255
  navigationMenus: [],
255
256
  hasResolvedNavigationMenus: true,
@@ -271,6 +272,18 @@ describe( 'NavigationMenuSelector', () => {
271
272
  } )
272
273
  );
273
274
 
275
+ // creating a menu is a network activity
276
+ // so we have to wait on it
277
+ useNavigationMenu.mockReturnValue( {
278
+ navigationMenus: [],
279
+ hasResolvedNavigationMenus: false,
280
+ isResolvingNavigationMenus: true,
281
+ canUserCreateNavigationMenu: true,
282
+ canSwitchNavigationMenu: true,
283
+ } );
284
+
285
+ rerender( <NavigationMenuSelector onCreateNew={ handler } /> );
286
+
274
287
  // Re-open the dropdown (it's closed when the "Create menu" button is clicked).
275
288
  await user.click( toggleButton );
276
289
 
@@ -284,6 +297,16 @@ describe( 'NavigationMenuSelector', () => {
284
297
  } )
285
298
  ).toBeDisabled();
286
299
 
300
+ // once the menu is created
301
+ // no more network activity to wait on
302
+ useNavigationMenu.mockReturnValue( {
303
+ navigationMenus: [],
304
+ hasResolvedNavigationMenus: true,
305
+ isResolvingNavigationMenus: false,
306
+ canUserCreateNavigationMenu: true,
307
+ canSwitchNavigationMenu: true,
308
+ } );
309
+
287
310
  // Simulate the menu being created and component being re-rendered.
288
311
  rerender(
289
312
  <NavigationMenuSelector
@@ -422,7 +445,7 @@ describe( 'NavigationMenuSelector', () => {
422
445
  expect( menuItem ).toBeChecked();
423
446
  } );
424
447
 
425
- it( 'should call the handler when the navigation menu is selected and disable all options during the import/creation process', async () => {
448
+ it( 'should call the handler when the navigation menu is selected', async () => {
426
449
  const user = userEvent.setup();
427
450
 
428
451
  const handler = jest.fn();
@@ -434,7 +457,7 @@ describe( 'NavigationMenuSelector', () => {
434
457
  canSwitchNavigationMenu: true,
435
458
  } );
436
459
 
437
- const { rerender } = render(
460
+ render(
438
461
  <NavigationMenuSelector
439
462
  onSelectNavigationMenu={ handler }
440
463
  />
@@ -455,42 +478,6 @@ describe( 'NavigationMenuSelector', () => {
455
478
 
456
479
  // Check the dropdown has been closed.
457
480
  expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();
458
-
459
- // Re-open the dropdown
460
- await user.click( screen.getByRole( 'button' ) );
461
-
462
- // Check the dropdown is again open and is in the "loading" state.
463
- expect(
464
- screen.getByRole( 'menu', {
465
- name: /Loading/,
466
- } )
467
- ).toBeInTheDocument();
468
-
469
- // // Check all menu items are present but disabled.
470
- screen.getAllByRole( 'menuitem' ).forEach( ( item ) => {
471
- // // Check all menu items are present but disabled.
472
- expect( item ).toBeDisabled();
473
- } );
474
-
475
- // // Simulate the menu being created and component being re-rendered.
476
- rerender(
477
- <NavigationMenuSelector
478
- createNavigationMenuIsSuccess={ true } // classic menu import creates a Navigation menu.
479
- />
480
- );
481
-
482
- // Todo: fix bug where aria label is not updated.
483
- // expect(
484
- // screen.getByRole( 'menu', {
485
- // name: `You are currently editing ${ navigationMenusFixture[ 0 ].title.rendered }`,
486
- // } )
487
- // ).toBeInTheDocument();
488
-
489
- // Check all menu items are re-enabled.
490
- screen.getAllByRole( 'menuitem' ).forEach( ( item ) => {
491
- // // Check all menu items are present but disabled.
492
- expect( item ).toBeEnabled();
493
- } );
494
481
  } );
495
482
  } );
496
483
 
@@ -568,9 +555,14 @@ describe( 'NavigationMenuSelector', () => {
568
555
 
569
556
  it( 'should call the handler when the classic menu item is selected and disable all options during the import/creation process', async () => {
570
557
  const user = userEvent.setup();
571
- const handler = jest.fn();
558
+ const handler = jest.fn( async () => {} );
572
559
 
560
+ // initially we have the menus, and we're not waiting on network
573
561
  useNavigationMenu.mockReturnValue( {
562
+ navigationMenus: [],
563
+ isResolvingNavigationMenus: false,
564
+ hasResolvedNavigationMenus: true,
565
+ canSwitchNavigationMenu: true,
574
566
  canUserCreateNavigationMenu: true,
575
567
  } );
576
568
 
@@ -597,6 +589,23 @@ describe( 'NavigationMenuSelector', () => {
597
589
  // Check the dropdown has been closed.
598
590
  expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();
599
591
 
592
+ // since we're importing we are doing network activity
593
+ // so we have to wait on it
594
+ useNavigationMenu.mockReturnValue( {
595
+ navigationMenus: [],
596
+ isResolvingNavigationMenus: true,
597
+ hasResolvedNavigationMenus: false,
598
+ canUserCreateNavigationMenu: true,
599
+ } );
600
+
601
+ useNavigationEntities.mockReturnValue( {
602
+ menus: classicMenusFixture,
603
+ } );
604
+
605
+ rerender(
606
+ <NavigationMenuSelector onSelectClassicMenu={ handler } />
607
+ );
608
+
600
609
  // // Re-open the dropdown (it's closed when the "Create menu" button is clicked).
601
610
  await user.click( screen.getByRole( 'button' ) );
602
611
 
@@ -613,6 +622,19 @@ describe( 'NavigationMenuSelector', () => {
613
622
  expect( item ).toBeDisabled();
614
623
  } );
615
624
 
625
+ // once the menu is imported
626
+ // no more network activity to wait on
627
+ useNavigationMenu.mockReturnValue( {
628
+ navigationMenus: [],
629
+ isResolvingNavigationMenus: false,
630
+ hasResolvedNavigationMenus: true,
631
+ canUserCreateNavigationMenu: true,
632
+ } );
633
+
634
+ useNavigationEntities.mockReturnValue( {
635
+ menus: classicMenusFixture,
636
+ } );
637
+
616
638
  // Simulate the menu being created and component being re-rendered.
617
639
  rerender(
618
640
  <NavigationMenuSelector
@@ -1458,13 +1458,18 @@ function block_core_navigation_set_ignored_hooked_blocks_metadata( $inner_blocks
1458
1458
  /**
1459
1459
  * Updates the post meta with the list of ignored hooked blocks when the navigation is created or updated via the REST API.
1460
1460
  *
1461
- * @param WP_Post $post Post object.
1461
+ * @param stdClass $post Post object.
1462
1462
  */
1463
1463
  function block_core_navigation_update_ignore_hooked_blocks_meta( $post ) {
1464
1464
  // We run the Block Hooks mechanism to inject the `metadata.ignoredHookedBlocks` attribute into
1465
1465
  // all anchor blocks. For the root level, we create a mock Navigation and extract them from there.
1466
1466
  $blocks = parse_blocks( $post->post_content );
1467
- $markup = block_core_navigation_set_ignored_hooked_blocks_metadata( $blocks, $post );
1467
+
1468
+ // Block Hooks logic requires a `WP_Post` object (rather than the `stdClass` with the updates that
1469
+ // we're getting from the `rest_pre_insert_wp_navigation` filter) as its second argument (to be
1470
+ // used as context for hooked blocks insertion).
1471
+ // We thus have to look it up from the DB,based on `$post->ID`.
1472
+ $markup = block_core_navigation_set_ignored_hooked_blocks_metadata( $blocks, get_post( $post->ID ) );
1468
1473
 
1469
1474
  $root_nav_block = parse_blocks( $markup )[0];
1470
1475
  $ignored_hooked_blocks = isset( $root_nav_block['attrs']['metadata']['ignoredHookedBlocks'] )
@@ -1480,14 +1485,8 @@ function block_core_navigation_update_ignore_hooked_blocks_meta( $post ) {
1480
1485
  update_post_meta( $post->ID, '_wp_ignored_hooked_blocks', json_encode( $ignored_hooked_blocks ) );
1481
1486
  }
1482
1487
 
1483
- $serialized_inner_blocks = block_core_navigation_remove_serialized_parent_block( $markup );
1484
-
1485
- wp_update_post(
1486
- array(
1487
- 'ID' => $post->ID,
1488
- 'post_content' => $serialized_inner_blocks,
1489
- )
1490
- );
1488
+ $post->post_content = block_core_navigation_remove_serialized_parent_block( $markup );
1489
+ return $post;
1491
1490
  }
1492
1491
 
1493
1492
  // Before adding our filter, we verify if it's already added in Core.
@@ -1497,8 +1496,15 @@ $rest_insert_wp_navigation_core_callback = 'block_core_navigation_' . 'update_ig
1497
1496
 
1498
1497
  // Injection of hooked blocks into the Navigation block relies on some functions present in WP >= 6.5
1499
1498
  // that are not present in Gutenberg's WP 6.5 compatibility layer.
1500
- if ( function_exists( 'set_ignored_hooked_blocks_metadata' ) && ! has_filter( 'rest_insert_wp_navigation', $rest_insert_wp_navigation_core_callback ) ) {
1501
- add_action( 'rest_insert_wp_navigation', 'block_core_navigation_update_ignore_hooked_blocks_meta', 10, 3 );
1499
+ if ( function_exists( 'set_ignored_hooked_blocks_metadata' ) && ! has_filter( 'rest_pre_insert_wp_navigation', $rest_insert_wp_navigation_core_callback ) ) {
1500
+ add_filter( 'rest_pre_insert_wp_navigation', 'block_core_navigation_update_ignore_hooked_blocks_meta', 10 );
1501
+ }
1502
+
1503
+ // Previous versions of Gutenberg and WordPress 6.5 Betas were attaching the block_core_navigation_update_ignore_hooked_blocks_meta
1504
+ // function to the `rest_insert_wp_navigation` _action_ (rather than the `rest_pre_insert_wp_navigation` _filter_).
1505
+ // To avoid collisions, we need to remove the filter from that action if it's present.
1506
+ if ( has_filter( 'rest_insert_wp_navigation', $rest_insert_wp_navigation_core_callback ) ) {
1507
+ remove_filter( 'rest_insert_wp_navigation', $rest_insert_wp_navigation_core_callback, 10 );
1502
1508
  }
1503
1509
 
1504
1510
  /**