diff --git a/packages/block-editor/src/components/block-variation-transforms/index.js b/packages/block-editor/src/components/block-variation-transforms/index.js index 327b94286bfcbf..0519a9b1f98c26 100644 --- a/packages/block-editor/src/components/block-variation-transforms/index.js +++ b/packages/block-editor/src/components/block-variation-transforms/index.js @@ -229,16 +229,13 @@ function __experimentalBlockVariationTransforms( { blockClientId } ) { ( v ) => v.name !== 'stretchy-paragraph' ); } else if ( blockName === 'core/heading' ) { - if ( - activeBlockVariation?.name === 'stretchy-heading' || - unfilteredVariations.every( ( v ) => - [ 'heading', 'stretchy-heading' ].includes( v.name ) - ) - ) { + // Hide variations picker when stretchy-heading is active. + if ( activeBlockVariation?.name === 'stretchy-heading' ) { return []; } + // Filter out stretchy-heading. return unfilteredVariations.filter( - ( v ) => v.name !== 'stretchy-heading' + ( variation ) => variation.name !== 'stretchy-heading' ); } return unfilteredVariations; diff --git a/packages/block-editor/src/components/block-variation-transforms/style.scss b/packages/block-editor/src/components/block-variation-transforms/style.scss index faddedc52858fa..e8ab1811e2f6ce 100644 --- a/packages/block-editor/src/components/block-variation-transforms/style.scss +++ b/packages/block-editor/src/components/block-variation-transforms/style.scss @@ -6,10 +6,10 @@ padding: 0 $grid-unit-20 $grid-unit-20 52px; &:where(fieldset) { - // Reset `fieldset` browser defaults. + // Reset `fieldset` browser defaults, keeping intentional padding. border: 0; - padding: 0; margin: 0; + min-inline-size: 0; } } diff --git a/packages/block-editor/src/components/inserter/block-types-tab.js b/packages/block-editor/src/components/inserter/block-types-tab.js index d37a6ca5694b09..c20eca95bb49f8 100644 --- a/packages/block-editor/src/components/inserter/block-types-tab.js +++ b/packages/block-editor/src/components/inserter/block-types-tab.js @@ -186,6 +186,11 @@ export function BlockTypesTab( continue; } + // Skip search-only items from browse view (they're still searchable). + if ( item.isSearchOnly ) { + continue; + } + if ( item.isAllowedInCurrentRoot ) { itemsForCurrentRoot.push( item ); } else { diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 733f3418f98d42..8fe3b0ce101b2d 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -2078,6 +2078,8 @@ const getItemFromVariation = ( state, item ) => ( variation ) => { innerBlocks: variation.innerBlocks, keywords: variation.keywords || item.keywords, frecency: calculateFrecency( time, count ), + // Pass through search-only flag for block-scope variations. + isSearchOnly: variation.isSearchOnly, }; }; @@ -2151,6 +2153,27 @@ const buildBlockTypeItem = blockType.name, 'inserter' ); + const blockVariations = getBlockVariations( blockType.name, 'block' ); + // Combine inserter and block variations. Block-scope variations without + // inserter scope are searchable via slash commands but hidden from browse. + const inserterVariationNames = new Set( + inserterVariations.map( ( variation ) => variation.name ) + ); + const allVariations = [ + ...inserterVariations, + ...blockVariations + .filter( + ( variation ) => + ! inserterVariationNames.has( variation.name ) + ) + .map( ( variation ) => ( { + ...variation, + isSearchOnly: true, + // Block-scope `isDefault` is for the placeholder picker, + // not for the inserter, so don't carry it over. + isDefault: false, + } ) ), + ]; return { ...blockItemBase, initialAttributes: {}, @@ -2159,7 +2182,7 @@ const buildBlockTypeItem = keywords: blockType.keywords, parent: blockType.parent, ancestor: blockType.ancestor, - variations: inserterVariations, + variations: allVariations, example: blockType.example, utility: 1, // Deprecated. }; diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js index ea58a75d51cda5..636e200c425b92 100644 --- a/packages/block-library/src/heading/edit.js +++ b/packages/block-library/src/heading/edit.js @@ -15,7 +15,6 @@ import { RichText, useBlockProps, store as blockEditorStore, - HeadingLevelDropdown, useBlockEditingMode, } from '@wordpress/block-editor'; @@ -32,8 +31,7 @@ function HeadingEdit( { style, clientId, } ) { - const { textAlign, content, level, levelOptions, placeholder, anchor } = - attributes; + const { textAlign, content, level, placeholder, anchor } = attributes; const tagName = 'h' + level; const blockProps = useBlockProps( { className: clsx( { @@ -94,13 +92,6 @@ function HeadingEdit( { <> { blockEditingMode === 'default' && ( - - setAttributes( { level: newLevel } ) - } - /> { diff --git a/packages/block-library/src/heading/index.js b/packages/block-library/src/heading/index.js index 3e5c7bf23972e1..7dde61dd7e068c 100644 --- a/packages/block-library/src/heading/index.js +++ b/packages/block-library/src/heading/index.js @@ -3,7 +3,11 @@ */ import { heading as icon } from '@wordpress/icons'; import { __, sprintf } from '@wordpress/i18n'; -import { privateApis as blocksPrivateApis } from '@wordpress/blocks'; +import { + privateApis as blocksPrivateApis, + getBlockType, + unregisterBlockVariation, +} from '@wordpress/blocks'; /** * Internal dependencies @@ -86,4 +90,21 @@ if ( window.__experimentalContentOnlyInspectorFields ) { }; } -export const init = () => initBlock( { name, metadata, settings } ); +export const init = () => { + const block = initBlock( { name, metadata, settings } ); + + // Unregister heading level variations based on `levelOptions` attribute. + // This is for backwards compatibility, as extenders can now unregister the + // variation directly: `wp.blocks.unregisterBlockVariation( 'core/heading', 'h1' )`. + const levelOptions = + getBlockType( name )?.attributes?.levelOptions?.default; + if ( levelOptions ) { + [ 1, 2, 3, 4, 5, 6 ].forEach( ( level ) => { + if ( ! levelOptions.includes( level ) ) { + unregisterBlockVariation( name, `h${ level }` ); + } + } ); + } + + return block; +}; diff --git a/packages/block-library/src/heading/variations.js b/packages/block-library/src/heading/variations.js index 158adaa41e6692..2a842f9cad2c62 100644 --- a/packages/block-library/src/heading/variations.js +++ b/packages/block-library/src/heading/variations.js @@ -1,22 +1,44 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { Path, SVG } from '@wordpress/primitives'; -import { heading } from '@wordpress/icons'; +import { + headingLevel1, + headingLevel2, + headingLevel3, + headingLevel4, + headingLevel5, + headingLevel6, +} from '@wordpress/icons'; + +const LEVEL_ICONS = [ + headingLevel1, + headingLevel2, + headingLevel3, + headingLevel4, + headingLevel5, + headingLevel6, +]; const variations = [ - { - name: 'heading', - title: __( 'Heading' ), + ...[ 1, 2, 3, 4, 5, 6 ].map( ( level ) => ( { + name: `h${ level }`, + title: sprintf( + /* translators: %d: heading level e.g: "1", "2", "3" */ + __( 'Heading %d' ), + level + ), description: __( 'Introduce new sections and organize content to help visitors (and search engines) understand the structure of your content.' ), - isDefault: true, - scope: [ 'inserter', 'transform' ], - attributes: { fitText: undefined }, - icon: heading, - }, + icon: LEVEL_ICONS[ level - 1 ], + attributes: { level }, + scope: [ 'block', 'transform' ], + keywords: [ `h${ level }` ], + isActive: ( blockAttributes ) => + ! blockAttributes.fitText && blockAttributes.level === level, + } ) ), // There is a hardcoded workaround in packages/block-editor/src/store/selectors.js // to make Stretchy variations appear as the last of their sections in the inserter. { diff --git a/test/e2e/specs/editor/blocks/heading.spec.js b/test/e2e/specs/editor/blocks/heading.spec.js index b27c0fa52e4e6b..7ffbf6490f0809 100644 --- a/test/e2e/specs/editor/blocks/heading.spec.js +++ b/test/e2e/specs/editor/blocks/heading.spec.js @@ -365,8 +365,8 @@ test.describe( 'Heading', () => { await expect( headingListViewItem, - 'should show default block name if the content is empty' - ).toHaveText( 'Heading' ); + 'should show variation name if the content is empty' + ).toHaveText( 'Heading 2' ); await editor.canvas .getByRole( 'document', { diff --git a/test/e2e/specs/editor/plugins/block-variations.spec.js b/test/e2e/specs/editor/plugins/block-variations.spec.js index ec7987f0c352d9..42ea508a6f9c8b 100644 --- a/test/e2e/specs/editor/plugins/block-variations.spec.js +++ b/test/e2e/specs/editor/plugins/block-variations.spec.js @@ -112,7 +112,9 @@ test.describe( 'Block variations', () => { .locator( 'role=button[name="Add default block"i]' ) .click(); await page.keyboard.type( '/Columns' ); - await page.getByRole( 'option', { name: 'Columns' } ).click(); + await page + .getByRole( 'option', { name: 'Columns', exact: true } ) + .click(); await editor.canvas .getByRole( 'list', { name: 'Block variations' } ) diff --git a/test/e2e/specs/editor/various/autocomplete-and-mentions.spec.js b/test/e2e/specs/editor/various/autocomplete-and-mentions.spec.js index 7143f3dfc96b7b..fe5c644357a724 100644 --- a/test/e2e/specs/editor/various/autocomplete-and-mentions.spec.js +++ b/test/e2e/specs/editor/various/autocomplete-and-mentions.spec.js @@ -566,13 +566,11 @@ test.describe( 'Autocomplete (@firefox, @webkit)', () => { await page.keyboard.type( 'heading' ); await expect( - page.locator( `role=option[name="Heading"i]` ) + page.getByRole( 'option', { name: 'Heading', exact: true } ) ).toBeVisible(); // Get the assertive live region screen reader announcement. await expect( - page.getByText( - '3 results found, use up and down arrow keys to navigate.' - ) + page.getByText( 'use up and down arrow keys to navigate.' ) ).toBeVisible(); } ); } ); diff --git a/test/e2e/specs/editor/various/content-only-lock.spec.js b/test/e2e/specs/editor/various/content-only-lock.spec.js index 10852700ba5e6d..2138a4c0e402a0 100644 --- a/test/e2e/specs/editor/various/content-only-lock.spec.js +++ b/test/e2e/specs/editor/various/content-only-lock.spec.js @@ -120,7 +120,7 @@ test.describe( 'Content-only lock', () => { await editor.clickBlockOptionsMenuItem( 'Delete' ); // Select an outside block await editor.canvas - .locator( 'role=document[name="Block: Heading"i]' ) + .locator( 'role=document[name="Block: Heading 2"i]' ) .click(); // Select a locked nested paragraph block again await pageUtils.pressKeys( 'ArrowUp' ); diff --git a/test/e2e/specs/editor/various/keep-styles-on-block-transforms.spec.js b/test/e2e/specs/editor/various/keep-styles-on-block-transforms.spec.js index 074db6e1bac685..76c131449d7235 100644 --- a/test/e2e/specs/editor/various/keep-styles-on-block-transforms.spec.js +++ b/test/e2e/specs/editor/various/keep-styles-on-block-transforms.spec.js @@ -20,7 +20,10 @@ test.describe( 'Keep styles on block transforms', () => { await page.click( 'role=button[name="Text"i]' ); await page.click( 'role=option[name="Luminous vivid orange"i]' ); - await page.click( 'role=button[name="Heading"i]' ); + await page + .getByRole( 'toolbar', { name: 'Block tools' } ) + .getByRole( 'button', { name: 'Heading 2' } ) + .click(); await page.click( 'role=menuitem[name="Paragraph"i]' ); await expect.poll( editor.getBlocks ).toMatchObject( [ diff --git a/test/e2e/specs/editor/various/list-view.spec.js b/test/e2e/specs/editor/various/list-view.spec.js index 4f83a3713ddd36..c9335edb46f0de 100644 --- a/test/e2e/specs/editor/various/list-view.spec.js +++ b/test/e2e/specs/editor/various/list-view.spec.js @@ -58,7 +58,7 @@ test.describe( 'List View', () => { exact: true, } ); const headingBlockItem = listView.getByRole( 'gridcell', { - name: 'Heading', + name: 'Heading 2', exact: true, } ); @@ -1090,7 +1090,7 @@ test.describe( 'List View', () => { await page.keyboard.press( 'ArrowRight' ); // Move focus and select the Heading block. await listView - .getByRole( 'gridcell', { name: 'Heading', exact: true } ) + .getByRole( 'gridcell', { name: 'Heading 2', exact: true } ) .dblclick(); // Select both inner blocks in the column. await page.keyboard.press( 'Shift+ArrowDown' ); @@ -1188,7 +1188,7 @@ test.describe( 'List View', () => { } ); // Click on the Heading block to select it. await listView - .getByRole( 'gridcell', { name: 'Heading', exact: true } ) + .getByRole( 'gridcell', { name: 'Heading 2', exact: true } ) .click(); await listView .getByRole( 'gridcell', { name: 'File' } ) diff --git a/test/e2e/specs/editor/various/rich-text.spec.js b/test/e2e/specs/editor/various/rich-text.spec.js index b38fae9dafaf78..d9dc2d0475ab7d 100644 --- a/test/e2e/specs/editor/various/rich-text.spec.js +++ b/test/e2e/specs/editor/various/rich-text.spec.js @@ -19,8 +19,12 @@ test.describe( 'RichText (@firefox, @webkit)', () => { // // See: https://github.com/WordPress/gutenberg/issues/3091 await editor.insertBlock( { name: 'core/heading' } ); - await editor.clickBlockToolbarButton( 'Change level' ); - await page.locator( 'role=menuitemradio[name="Heading 3"]' ).click(); + + // Open the block inspector sidebar and use variations to change level. + await editor.openDocumentSettingsSidebar(); + await page + .getByRole( 'button', { name: 'Heading 3', exact: true } ) + .click(); expect( await editor.getBlocks() ).toMatchObject( [ { diff --git a/test/e2e/specs/editor/various/toolbar-roving-tabindex.spec.js b/test/e2e/specs/editor/various/toolbar-roving-tabindex.spec.js index 73680e841e7ea7..20ab3bcb024e6d 100644 --- a/test/e2e/specs/editor/various/toolbar-roving-tabindex.spec.js +++ b/test/e2e/specs/editor/various/toolbar-roving-tabindex.spec.js @@ -45,13 +45,15 @@ test.describe( 'Toolbar roving tabindex', () => { await editor.insertBlock( { name: 'core/heading' } ); await page.keyboard.type( 'Heading' ); await ToolbarRovingTabindexUtils.testBlockToolbarKeyboardNavigation( - 'Block: Heading', - 'Heading' + 'Block: Heading 2', + 'Heading 2' + ); + await ToolbarRovingTabindexUtils.wrapCurrentBlockWithGroup( + 'Heading 2' ); - await ToolbarRovingTabindexUtils.wrapCurrentBlockWithGroup( 'Heading' ); await ToolbarRovingTabindexUtils.testGroupKeyboardNavigation( - 'Block: Heading', - 'Heading' + 'Block: Heading 2', + 'Heading 2' ); // ensures list block toolbar uses roving tabindex diff --git a/test/e2e/specs/widgets/customizing-widgets.spec.js b/test/e2e/specs/widgets/customizing-widgets.spec.js index cc73ad73886b9c..ed0f4b4a95748b 100644 --- a/test/e2e/specs/widgets/customizing-widgets.spec.js +++ b/test/e2e/specs/widgets/customizing-widgets.spec.js @@ -286,7 +286,7 @@ test.describe( 'Widgets Customizer', () => { await editHeadingWidget.click(); const headingBlock = page.locator( - 'role=document[name="Block: Heading"i] >> text="First Heading"' + 'role=document[name="Block: Heading 2"i] >> text="First Heading"' ); await expect( headingBlock ).toBeFocused(); } );