@wordpress/block-library 9.19.2 → 9.21.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 (124) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/build/archives/edit.js +2 -2
  3. package/build/archives/edit.js.map +1 -1
  4. package/build/audio/edit.js +66 -33
  5. package/build/audio/edit.js.map +1 -1
  6. package/build/avatar/index.js +8 -3
  7. package/build/avatar/index.js.map +1 -1
  8. package/build/button/edit.js +43 -16
  9. package/build/button/edit.js.map +1 -1
  10. package/build/categories/edit.js +3 -3
  11. package/build/categories/edit.js.map +1 -1
  12. package/build/comment-template/hooks.js +6 -0
  13. package/build/comment-template/hooks.js.map +1 -1
  14. package/build/cover/index.js +8 -1
  15. package/build/cover/index.js.map +1 -1
  16. package/build/embed/edit.js +4 -1
  17. package/build/embed/edit.js.map +1 -1
  18. package/build/image/constants.js +2 -1
  19. package/build/image/constants.js.map +1 -1
  20. package/build/image/edit.js +3 -2
  21. package/build/image/edit.js.map +1 -1
  22. package/build/image/image.js +98 -80
  23. package/build/image/image.js.map +1 -1
  24. package/build/navigation/edit/index.js +8 -4
  25. package/build/navigation/edit/index.js.map +1 -1
  26. package/build/navigation-link/edit.js +27 -8
  27. package/build/navigation-link/edit.js.map +1 -1
  28. package/build/post-author/index.js +8 -1
  29. package/build/post-author/index.js.map +1 -1
  30. package/build/post-featured-image/edit.js +2 -1
  31. package/build/post-featured-image/edit.js.map +1 -1
  32. package/build/rss/edit.js +21 -1
  33. package/build/rss/edit.js.map +1 -1
  34. package/build/rss/index.js +7 -0
  35. package/build/rss/index.js.map +1 -1
  36. package/build/site-logo/index.js +8 -1
  37. package/build/site-logo/index.js.map +1 -1
  38. package/build/site-title/edit.js +1 -1
  39. package/build/site-title/edit.js.map +1 -1
  40. package/build/social-links/index.js +1 -0
  41. package/build/social-links/index.js.map +1 -1
  42. package/build/table-of-contents/edit.js +50 -8
  43. package/build/table-of-contents/edit.js.map +1 -1
  44. package/build/table-of-contents/hooks.js +13 -4
  45. package/build/table-of-contents/hooks.js.map +1 -1
  46. package/build/table-of-contents/index.js +3 -0
  47. package/build/table-of-contents/index.js.map +1 -1
  48. package/build-module/archives/edit.js +2 -2
  49. package/build-module/archives/edit.js.map +1 -1
  50. package/build-module/audio/edit.js +68 -35
  51. package/build-module/audio/edit.js.map +1 -1
  52. package/build-module/avatar/index.js +8 -3
  53. package/build-module/avatar/index.js.map +1 -1
  54. package/build-module/button/edit.js +44 -17
  55. package/build-module/button/edit.js.map +1 -1
  56. package/build-module/categories/edit.js +3 -3
  57. package/build-module/categories/edit.js.map +1 -1
  58. package/build-module/comment-template/hooks.js +6 -0
  59. package/build-module/comment-template/hooks.js.map +1 -1
  60. package/build-module/cover/index.js +8 -1
  61. package/build-module/cover/index.js.map +1 -1
  62. package/build-module/embed/edit.js +4 -1
  63. package/build-module/embed/edit.js.map +1 -1
  64. package/build-module/image/constants.js +1 -0
  65. package/build-module/image/constants.js.map +1 -1
  66. package/build-module/image/edit.js +3 -2
  67. package/build-module/image/edit.js.map +1 -1
  68. package/build-module/image/image.js +102 -84
  69. package/build-module/image/image.js.map +1 -1
  70. package/build-module/navigation/edit/index.js +8 -4
  71. package/build-module/navigation/edit/index.js.map +1 -1
  72. package/build-module/navigation-link/edit.js +28 -9
  73. package/build-module/navigation-link/edit.js.map +1 -1
  74. package/build-module/post-author/index.js +8 -1
  75. package/build-module/post-author/index.js.map +1 -1
  76. package/build-module/post-featured-image/edit.js +2 -1
  77. package/build-module/post-featured-image/edit.js.map +1 -1
  78. package/build-module/rss/edit.js +22 -2
  79. package/build-module/rss/edit.js.map +1 -1
  80. package/build-module/rss/index.js +7 -0
  81. package/build-module/rss/index.js.map +1 -1
  82. package/build-module/site-logo/index.js +8 -1
  83. package/build-module/site-logo/index.js.map +1 -1
  84. package/build-module/site-title/edit.js +1 -1
  85. package/build-module/site-title/edit.js.map +1 -1
  86. package/build-module/social-links/index.js +1 -0
  87. package/build-module/social-links/index.js.map +1 -1
  88. package/build-module/table-of-contents/edit.js +52 -10
  89. package/build-module/table-of-contents/edit.js.map +1 -1
  90. package/build-module/table-of-contents/hooks.js +13 -4
  91. package/build-module/table-of-contents/hooks.js.map +1 -1
  92. package/build-module/table-of-contents/index.js +3 -0
  93. package/build-module/table-of-contents/index.js.map +1 -1
  94. package/build-style/editor-rtl.css +0 -9
  95. package/build-style/editor.css +0 -9
  96. package/build-style/image/editor-rtl.css +0 -9
  97. package/build-style/image/editor.css +0 -9
  98. package/package.json +35 -35
  99. package/src/archives/edit.js +2 -2
  100. package/src/audio/edit.js +84 -33
  101. package/src/avatar/block.json +8 -3
  102. package/src/button/edit.js +69 -24
  103. package/src/categories/edit.js +3 -3
  104. package/src/comment-template/hooks.js +14 -6
  105. package/src/cover/block.json +8 -1
  106. package/src/embed/edit.js +7 -1
  107. package/src/image/constants.js +1 -0
  108. package/src/image/edit.js +3 -3
  109. package/src/image/editor.scss +0 -13
  110. package/src/image/image.js +124 -134
  111. package/src/navigation/edit/index.js +4 -0
  112. package/src/navigation-link/edit.js +45 -11
  113. package/src/post-author/block.json +8 -1
  114. package/src/post-featured-image/edit.js +2 -1
  115. package/src/rss/block.json +7 -0
  116. package/src/rss/edit.js +21 -0
  117. package/src/rss/index.php +27 -9
  118. package/src/site-logo/block.json +8 -1
  119. package/src/site-title/edit.js +1 -1
  120. package/src/site-title/index.php +1 -1
  121. package/src/social-links/block.json +1 -0
  122. package/src/table-of-contents/block.json +3 -0
  123. package/src/table-of-contents/edit.js +45 -4
  124. package/src/table-of-contents/hooks.js +12 -3
@@ -143,6 +143,7 @@ function ColorTools( {
143
143
  onColorChange: setTextColor,
144
144
  resetAllFilter: () => setTextColor(),
145
145
  clearable: true,
146
+ enableAlpha: true,
146
147
  },
147
148
  {
148
149
  colorValue: backgroundColor.color,
@@ -150,6 +151,7 @@ function ColorTools( {
150
151
  onColorChange: setBackgroundColor,
151
152
  resetAllFilter: () => setBackgroundColor(),
152
153
  clearable: true,
154
+ enableAlpha: true,
153
155
  },
154
156
  {
155
157
  colorValue: overlayTextColor.color,
@@ -157,6 +159,7 @@ function ColorTools( {
157
159
  onColorChange: setOverlayTextColor,
158
160
  resetAllFilter: () => setOverlayTextColor(),
159
161
  clearable: true,
162
+ enableAlpha: true,
160
163
  },
161
164
  {
162
165
  colorValue: overlayBackgroundColor.color,
@@ -164,6 +167,7 @@ function ColorTools( {
164
167
  onColorChange: setOverlayBackgroundColor,
165
168
  resetAllFilter: () => setOverlayBackgroundColor(),
166
169
  clearable: true,
170
+ enableAlpha: true,
167
171
  },
168
172
  ] }
169
173
  panelId={ clientId }
@@ -26,6 +26,7 @@ import {
26
26
  store as blockEditorStore,
27
27
  getColorClassName,
28
28
  useInnerBlocksProps,
29
+ useBlockEditingMode,
29
30
  } from '@wordpress/block-editor';
30
31
  import { isURL, prependHTTP, safeDecodeURI } from '@wordpress/url';
31
32
  import { useState, useEffect, useRef } from '@wordpress/element';
@@ -43,6 +44,10 @@ import { updateAttributes } from './update-attributes';
43
44
  import { getColors } from '../navigation/edit/utils';
44
45
 
45
46
  const DEFAULT_BLOCK = { name: 'core/navigation-link' };
47
+ const NESTING_BLOCK_NAMES = [
48
+ 'core/navigation-link',
49
+ 'core/navigation-submenu',
50
+ ];
46
51
 
47
52
  /**
48
53
  * A React hook to determine if it's dragging within the target element.
@@ -95,19 +100,29 @@ const useIsDraggingWithin = ( elementRef ) => {
95
100
  return isDraggingWithin;
96
101
  };
97
102
 
98
- const useIsInvalidLink = ( kind, type, id ) => {
103
+ const useIsInvalidLink = ( kind, type, id, enabled ) => {
99
104
  const isPostType =
100
105
  kind === 'post-type' || type === 'post' || type === 'page';
101
106
  const hasId = Number.isInteger( id );
107
+ const blockEditingMode = useBlockEditingMode();
108
+
102
109
  const postStatus = useSelect(
103
110
  ( select ) => {
104
111
  if ( ! isPostType ) {
105
112
  return null;
106
113
  }
114
+
115
+ // Fetching the posts status is an "expensive" operation. Especially for sites with large navigations.
116
+ // When the block is rendered in a template or other disabled contexts we can skip this check in order
117
+ // to avoid all these additional requests that don't really add any value in that mode.
118
+ if ( blockEditingMode === 'disabled' || ! enabled ) {
119
+ return null;
120
+ }
121
+
107
122
  const { getEntityRecord } = select( coreStore );
108
123
  return getEntityRecord( 'postType', type, id )?.status;
109
124
  },
110
- [ isPostType, type, id ]
125
+ [ isPostType, blockEditingMode, enabled, type, id ]
111
126
  );
112
127
 
113
128
  // Check Navigation Link validity if:
@@ -280,8 +295,6 @@ export default function NavigationLinkEdit( {
280
295
  clientId,
281
296
  } ) {
282
297
  const { id, label, type, url, description, kind } = attributes;
283
-
284
- const [ isInvalid, isDraft ] = useIsInvalidLink( kind, type, id );
285
298
  const { maxNestingLevel } = context;
286
299
 
287
300
  const {
@@ -313,6 +326,7 @@ export default function NavigationLinkEdit( {
313
326
  isTopLevelLink,
314
327
  isParentOfSelectedBlock,
315
328
  hasChildren,
329
+ validateLinkStatus,
316
330
  } = useSelect(
317
331
  ( select ) => {
318
332
  const {
@@ -321,28 +335,48 @@ export default function NavigationLinkEdit( {
321
335
  getBlockRootClientId,
322
336
  hasSelectedInnerBlock,
323
337
  getBlockParentsByBlockName,
338
+ getSelectedBlockClientId,
324
339
  } = select( blockEditorStore );
340
+ const rootClientId = getBlockRootClientId( clientId );
341
+ const isTopLevel =
342
+ getBlockName( rootClientId ) === 'core/navigation';
343
+ const selectedBlockClientId = getSelectedBlockClientId();
344
+ const rootNavigationClientId = isTopLevel
345
+ ? rootClientId
346
+ : getBlockParentsByBlockName(
347
+ clientId,
348
+ 'core/navigation'
349
+ )[ 0 ];
350
+
351
+ // Enable when the root Navigation block is selected or any of its inner blocks.
352
+ const enableLinkStatusValidation =
353
+ selectedBlockClientId === rootNavigationClientId ||
354
+ hasSelectedInnerBlock( rootNavigationClientId, true );
325
355
 
326
356
  return {
327
357
  isAtMaxNesting:
328
- getBlockParentsByBlockName( clientId, [
329
- 'core/navigation-link',
330
- 'core/navigation-submenu',
331
- ] ).length >= maxNestingLevel,
332
- isTopLevelLink:
333
- getBlockName( getBlockRootClientId( clientId ) ) ===
334
- 'core/navigation',
358
+ getBlockParentsByBlockName( clientId, NESTING_BLOCK_NAMES )
359
+ .length >= maxNestingLevel,
360
+ isTopLevelLink: isTopLevel,
335
361
  isParentOfSelectedBlock: hasSelectedInnerBlock(
336
362
  clientId,
337
363
  true
338
364
  ),
339
365
  hasChildren: !! getBlockCount( clientId ),
366
+ validateLinkStatus: enableLinkStatusValidation,
340
367
  };
341
368
  },
342
369
  [ clientId, maxNestingLevel ]
343
370
  );
344
371
  const { getBlocks } = useSelect( blockEditorStore );
345
372
 
373
+ const [ isInvalid, isDraft ] = useIsInvalidLink(
374
+ kind,
375
+ type,
376
+ id,
377
+ validateLinkStatus
378
+ );
379
+
346
380
  /**
347
381
  * Transform to submenu block.
348
382
  */
@@ -58,7 +58,6 @@
58
58
  "color": {
59
59
  "gradients": true,
60
60
  "link": true,
61
- "__experimentalDuotone": ".wp-block-post-author__avatar img",
62
61
  "__experimentalDefaultControls": {
63
62
  "background": true,
64
63
  "text": true
@@ -78,6 +77,14 @@
78
77
  "width": true,
79
78
  "style": true
80
79
  }
80
+ },
81
+ "filter": {
82
+ "duotone": true
83
+ }
84
+ },
85
+ "selectors": {
86
+ "filter": {
87
+ "duotone": ".wp-block-post-author .wp-block-post-author__avatar img"
81
88
  }
82
89
  },
83
90
  "editorStyle": "wp-block-post-author-editor",
@@ -398,7 +398,8 @@ export default function PostFeaturedImageEdit( {
398
398
  label={ label }
399
399
  showTooltip
400
400
  tooltipPosition="top center"
401
- onClick={ () => {
401
+ onClick={ ( e ) => {
402
+ e.preventDefault();
402
403
  open();
403
404
  } }
404
405
  />
@@ -39,6 +39,13 @@
39
39
  "excerptLength": {
40
40
  "type": "number",
41
41
  "default": 55
42
+ },
43
+ "openInNewTab": {
44
+ "type": "boolean",
45
+ "default": false
46
+ },
47
+ "rel": {
48
+ "type": "string"
42
49
  }
43
50
  },
44
51
  "supports": {
package/src/rss/edit.js CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  RangeControl,
15
15
  ToggleControl,
16
16
  ToolbarGroup,
17
+ TextControl,
17
18
  __experimentalInputControl as InputControl,
18
19
  } from '@wordpress/components';
19
20
  import { useState } from '@wordpress/element';
@@ -37,6 +38,8 @@ export default function RSSEdit( { attributes, setAttributes } ) {
37
38
  excerptLength,
38
39
  feedURL,
39
40
  itemsToShow,
41
+ openInNewTab,
42
+ rel,
40
43
  } = attributes;
41
44
 
42
45
  function toggleAttribute( propName ) {
@@ -197,8 +200,26 @@ export default function RSSEdit( { attributes, setAttributes } ) {
197
200
  required
198
201
  />
199
202
  ) }
203
+
204
+ <ToggleControl
205
+ __nextHasNoMarginBottom
206
+ label={ __( 'Open links in new tab' ) }
207
+ checked={ openInNewTab }
208
+ onChange={ ( value ) =>
209
+ setAttributes( { openInNewTab: value } )
210
+ }
211
+ />
200
212
  </PanelBody>
201
213
  </InspectorControls>
214
+ <InspectorControls group="advanced">
215
+ <TextControl
216
+ __next40pxDefaultSize
217
+ __nextHasNoMarginBottom
218
+ label={ __( 'Link rel' ) }
219
+ value={ rel || '' }
220
+ onChange={ ( value ) => setAttributes( { rel: value } ) }
221
+ />
222
+ </InspectorControls>
202
223
  <div { ...blockProps }>
203
224
  <Disabled>
204
225
  <ServerSideRender
package/src/rss/index.php CHANGED
@@ -31,6 +31,20 @@ function render_block_core_rss( $attributes ) {
31
31
 
32
32
  $rss_items = $rss->get_items( 0, $attributes['itemsToShow'] );
33
33
  $list_items = '';
34
+
35
+ $open_in_new_tab = ! empty( $attributes['openInNewTab'] );
36
+ $rel = ! empty( $attributes['rel'] ) ? trim( $attributes['rel'] ) : '';
37
+
38
+ $link_attributes = '';
39
+
40
+ if ( $open_in_new_tab ) {
41
+ $link_attributes .= ' target="_blank"';
42
+ }
43
+
44
+ if ( '' !== $rel ) {
45
+ $link_attributes .= ' rel="' . esc_attr( $rel ) . '"';
46
+ }
47
+
34
48
  foreach ( $rss_items as $item ) {
35
49
  $title = esc_html( trim( strip_tags( $item->get_title() ) ) );
36
50
  if ( empty( $title ) ) {
@@ -38,20 +52,24 @@ function render_block_core_rss( $attributes ) {
38
52
  }
39
53
  $link = $item->get_link();
40
54
  $link = esc_url( $link );
55
+
41
56
  if ( $link ) {
42
- $title = "<a href='{$link}'>{$title}</a>";
57
+ $title = "<a href='{$link}'{$link_attributes}>{$title}</a>";
43
58
  }
44
59
  $title = "<div class='wp-block-rss__item-title'>{$title}</div>";
45
60
 
46
- $date = '';
47
- if ( $attributes['displayDate'] ) {
48
- $date = $item->get_date( 'U' );
61
+ $date_markup = '';
62
+ if ( ! empty( $attributes['displayDate'] ) ) {
63
+ $timestamp = $item->get_date( 'U' );
64
+
65
+ if ( $timestamp ) {
66
+ $gmt_offset = get_option( 'gmt_offset' );
67
+ $timestamp += (int) ( (float) $gmt_offset * HOUR_IN_SECONDS );
49
68
 
50
- if ( $date ) {
51
- $date = sprintf(
69
+ $date_markup = sprintf(
52
70
  '<time datetime="%1$s" class="wp-block-rss__item-publish-date">%2$s</time> ',
53
- esc_attr( date_i18n( 'c', $date ) ),
54
- esc_attr( date_i18n( get_option( 'date_format' ), $date ) )
71
+ esc_attr( date_i18n( 'c', $timestamp ) ),
72
+ esc_html( date_i18n( get_option( 'date_format' ), $timestamp ) )
55
73
  );
56
74
  }
57
75
  }
@@ -85,7 +103,7 @@ function render_block_core_rss( $attributes ) {
85
103
  $excerpt = '<div class="wp-block-rss__item-excerpt">' . esc_html( $excerpt ) . '</div>';
86
104
  }
87
105
 
88
- $list_items .= "<li class='wp-block-rss__item'>{$title}{$date}{$author}{$excerpt}</li>";
106
+ $list_items .= "<li class='wp-block-rss__item'>{$title}{$date_markup}{$author}{$excerpt}</li>";
89
107
  }
90
108
 
91
109
  $classnames = array();
@@ -36,7 +36,6 @@
36
36
  "align": true,
37
37
  "alignWide": false,
38
38
  "color": {
39
- "__experimentalDuotone": "img, .components-placeholder__illustration, .components-placeholder::before",
40
39
  "text": false,
41
40
  "background": false
42
41
  },
@@ -50,6 +49,9 @@
50
49
  },
51
50
  "interactivity": {
52
51
  "clientNavigation": true
52
+ },
53
+ "filter": {
54
+ "duotone": true
53
55
  }
54
56
  },
55
57
  "styles": [
@@ -60,6 +62,11 @@
60
62
  },
61
63
  { "name": "rounded", "label": "Rounded" }
62
64
  ],
65
+ "selectors": {
66
+ "filter": {
67
+ "duotone": ".wp-block-site-logo img, .wp-block-site-logo .components-placeholder__illustration, .wp-block-site-logo .components-placeholder::before"
68
+ }
69
+ },
63
70
  "editorStyle": "wp-block-site-logo-editor",
64
71
  "style": "wp-block-site-logo"
65
72
  }
@@ -56,7 +56,7 @@ export default function SiteTitleEdit( {
56
56
 
57
57
  function setTitle( newTitle ) {
58
58
  editEntityRecord( 'root', 'site', undefined, {
59
- title: newTitle,
59
+ title: newTitle.trim(),
60
60
  } );
61
61
  }
62
62
 
@@ -16,7 +16,7 @@
16
16
  */
17
17
  function render_block_core_site_title( $attributes ) {
18
18
  $site_title = get_bloginfo( 'name' );
19
- if ( ! $site_title ) {
19
+ if ( ! trim( $site_title ) ) {
20
20
  return;
21
21
  }
22
22
 
@@ -50,6 +50,7 @@
50
50
  "supports": {
51
51
  "align": [ "left", "center", "right" ],
52
52
  "anchor": true,
53
+ "html": false,
53
54
  "__experimentalExposeControlsToChildren": true,
54
55
  "layout": {
55
56
  "allowSwitching": false,
@@ -19,6 +19,9 @@
19
19
  "onlyIncludeCurrentPage": {
20
20
  "type": "boolean",
21
21
  "default": false
22
+ },
23
+ "maxLevel": {
24
+ "type": "number"
22
25
  }
23
26
  },
24
27
  "supports": {
@@ -12,6 +12,7 @@ import { createBlock } from '@wordpress/blocks';
12
12
  import {
13
13
  Placeholder,
14
14
  ToggleControl,
15
+ SelectControl,
15
16
  ToolbarButton,
16
17
  ToolbarGroup,
17
18
  __experimentalToolsPanel as ToolsPanel,
@@ -39,15 +40,16 @@ import { useToolsPanelDropdownMenuProps } from '../utils/hooks';
39
40
  *
40
41
  * @param {Object} props The props.
41
42
  * @param {Object} props.attributes The block attributes.
42
- * @param {HeadingData[]} props.attributes.headings A list of data for each heading in the post.
43
+ * @param {HeadingData[]} props.attributes.headings The list of data for each heading in the post.
43
44
  * @param {boolean} props.attributes.onlyIncludeCurrentPage Whether to only include headings from the current page (if the post is paginated).
44
- * @param {string} props.clientId
45
- * @param {(attributes: Object) => void} props.setAttributes
45
+ * @param {number|undefined} props.attributes.maxLevel The maximum heading level to include, or null to include all levels.
46
+ * @param {string} props.clientId The client id.
47
+ * @param {(attributes: Object) => void} props.setAttributes The set attributes function.
46
48
  *
47
49
  * @return {Component} The component.
48
50
  */
49
51
  export default function TableOfContentsEdit( {
50
- attributes: { headings = [], onlyIncludeCurrentPage },
52
+ attributes: { headings = [], onlyIncludeCurrentPage, maxLevel },
51
53
  clientId,
52
54
  setAttributes,
53
55
  } ) {
@@ -115,6 +117,7 @@ export default function TableOfContentsEdit( {
115
117
  resetAll={ () => {
116
118
  setAttributes( {
117
119
  onlyIncludeCurrentPage: false,
120
+ maxLevel: undefined,
118
121
  } );
119
122
  } }
120
123
  dropdownMenuProps={ dropdownMenuProps }
@@ -145,6 +148,44 @@ export default function TableOfContentsEdit( {
145
148
  }
146
149
  />
147
150
  </ToolsPanelItem>
151
+ <ToolsPanelItem
152
+ hasValue={ () => !! maxLevel }
153
+ label={ __( 'Limit heading levels' ) }
154
+ onDeselect={ () =>
155
+ setAttributes( { maxLevel: undefined } )
156
+ }
157
+ isShownByDefault
158
+ >
159
+ <SelectControl
160
+ __nextHasNoMarginBottom
161
+ __next40pxDefaultSize
162
+ label={ __( 'Include headings down to level' ) }
163
+ value={ maxLevel || '' }
164
+ options={ [
165
+ { value: '', label: __( 'All levels' ) },
166
+ { value: '1', label: __( 'Heading 1' ) },
167
+ { value: '2', label: __( 'Heading 2' ) },
168
+ { value: '3', label: __( 'Heading 3' ) },
169
+ { value: '4', label: __( 'Heading 4' ) },
170
+ { value: '5', label: __( 'Heading 5' ) },
171
+ { value: '6', label: __( 'Heading 6' ) },
172
+ ] }
173
+ onChange={ ( value ) =>
174
+ setAttributes( {
175
+ maxLevel: value ? parseInt( value ) : undefined,
176
+ } )
177
+ }
178
+ help={
179
+ maxLevel
180
+ ? __(
181
+ 'Including all heading levels in the table of contents.'
182
+ )
183
+ : __(
184
+ 'Only include headings up to and including this level.'
185
+ )
186
+ }
187
+ />
188
+ </ToolsPanelItem>
148
189
  </ToolsPanel>
149
190
  </InspectorControls>
150
191
  );
@@ -16,8 +16,8 @@ function getLatestHeadings( select, clientId ) {
16
16
  const {
17
17
  getBlockAttributes,
18
18
  getBlockName,
19
- getClientIdsWithDescendants,
20
19
  getBlocksByName,
20
+ getClientIdsOfDescendants,
21
21
  } = select( blockEditorStore );
22
22
 
23
23
  // FIXME: @wordpress/block-library should not depend on @wordpress/editor.
@@ -29,10 +29,14 @@ function getLatestHeadings( select, clientId ) {
29
29
  const permalink = select( 'core/editor' ).getPermalink() ?? null;
30
30
 
31
31
  const isPaginated = getBlocksByName( 'core/nextpage' ).length !== 0;
32
- const { onlyIncludeCurrentPage } = getBlockAttributes( clientId ) ?? {};
32
+ const { onlyIncludeCurrentPage, maxLevel } =
33
+ getBlockAttributes( clientId ) ?? {};
34
+
35
+ // Get post-content block client ID.
36
+ const [ postContentClientId = '' ] = getBlocksByName( 'core/post-content' );
33
37
 
34
38
  // Get the client ids of all blocks in the editor.
35
- const allBlockClientIds = getClientIdsWithDescendants();
39
+ const allBlockClientIds = getClientIdsOfDescendants( postContentClientId );
36
40
 
37
41
  // If onlyIncludeCurrentPage is true, calculate the page (of a paginated post) this block is part of, so we know which headings to include; otherwise, skip the calculation.
38
42
  let tocPage = 1;
@@ -97,6 +101,11 @@ function getLatestHeadings( select, clientId ) {
97
101
  if ( blockName === 'core/heading' ) {
98
102
  const headingAttributes = getBlockAttributes( blockClientId );
99
103
 
104
+ // Skip headings that are deeper than maxLevel
105
+ if ( maxLevel && headingAttributes.level > maxLevel ) {
106
+ continue;
107
+ }
108
+
100
109
  const canBeLinked =
101
110
  typeof headingPageLink === 'string' &&
102
111
  typeof headingAttributes.anchor === 'string' &&