Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
22 changes: 21 additions & 1 deletion packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a new flag right? I'm a little bit hesitant about it.

I mean "scope" have been used in variations to define where a variation should appear. And by adding this flag we now have two properties to define that. I may have a small preference for another "search" scope.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not being set by the consumer, it's just an internal flag to explicitly treat "block" context to show up in search. I implemented the search scope initially but it seemed unnecessary and confusing, since you'd think you need to pass the extra search scope to ensure something shows up on searches, but "inserter" already handles that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, maybe we can just pass down the scope or something no?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regardless, I'm way less concerned now :)

};
};

Expand Down Expand Up @@ -2151,6 +2153,24 @@ 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.
Comment on lines +2157 to +2158
Copy link
Contributor

@t-hamano t-hamano Dec 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also modifies the behavior of "block" scope in variations so that they are searchable via slash commands.

This means variations with only 'block' scope (not 'inserter') are marked as isSearchOnly and filtered from the inserter browse view, but remain searchable in inserter and slash commands.

This new rule causes unintended behavior: variations that are only block-scoped can be searched with the slash inserter, for example, column block variations.

Image

const inserterVariationNames = new Set(
inserterVariations.map( ( variation ) => variation.name )
);
const allVariations = [
...inserterVariations,
...blockVariations
.filter(
( variation ) =>
! inserterVariationNames.has( variation.name )
)
.map( ( variation ) => ( {
...variation,
isSearchOnly: true,
} ) ),
];
return {
...blockItemBase,
initialAttributes: {},
Expand All @@ -2159,7 +2179,7 @@ const buildBlockTypeItem =
keywords: blockType.keywords,
parent: blockType.parent,
ancestor: blockType.ancestor,
variations: inserterVariations,
variations: allVariations,
example: blockType.example,
utility: 1, // Deprecated.
};
Expand Down
11 changes: 1 addition & 10 deletions packages/block-library/src/heading/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
RichText,
useBlockProps,
store as blockEditorStore,
HeadingLevelDropdown,
useBlockEditingMode,
} from '@wordpress/block-editor';

Expand All @@ -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( {
Expand Down Expand Up @@ -94,13 +92,6 @@ function HeadingEdit( {
<>
{ blockEditingMode === 'default' && (
<BlockControls group="block">
<HeadingLevelDropdown
value={ level }
options={ levelOptions }
onChange={ ( newLevel ) =>
setAttributes( { level: newLevel } )
}
/>
<AlignmentControl
value={ textAlign }
onChange={ ( nextAlign ) => {
Expand Down
25 changes: 23 additions & 2 deletions packages/block-library/src/heading/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 }` );
}
} );
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works and I think it's totally fine for now but:

changing a "default" attribute value to define what levels are available seems like a bad thing to start with. I wonder if we should deprecate that attribute. Unregistering a variation seems like a way better approach.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would break sites that rely on the default options attribute to remove the heading levels.

I think @fabiankaegy is also suggesting keeping the toolbar dropdown item - #73823 (comment).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is why I said "deprecate" and not "remove" :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said, reading @fabiankaegy's comment I do agree that at a "pattern" level it's a good idea to allow disabling level options and things like that.


return block;
};
42 changes: 32 additions & 10 deletions packages/block-library/src/heading/variations.js
Original file line number Diff line number Diff line change
@@ -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.
{
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/specs/editor/blocks/heading.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', {
Expand Down
Loading