Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
6436944
Block API: Add block visibility control support and UI
t-hamano Aug 8, 2025
c12c3cf
Merge branch 'trunk' into block-visibility
t-hamano Aug 15, 2025
7f8d055
Render hidden block block
t-hamano Aug 15, 2025
52913e1
Remove unused code
t-hamano Aug 15, 2025
a5d0146
Always hide block when it's not selected
t-hamano Aug 18, 2025
c3f7135
Extend existing isBlockVisible selector and use it
t-hamano Aug 18, 2025
5f459c7
Revert "Extend existing isBlockVisible selector and use it"
t-hamano Aug 18, 2025
1a46d6b
Remove SCSS import statement
t-hamano Aug 18, 2025
6577955
Use "isBlockHidden"
t-hamano Aug 18, 2025
19b7b9c
Remove useBlockVisibility hook
t-hamano Aug 18, 2025
b1dad49
useBlockDropZone: Filter out blocks that are hidden
t-hamano Aug 18, 2025
6e42b65
Remove useBlockVisibility hook
t-hamano Aug 18, 2025
7849d2f
Update doc
t-hamano Aug 18, 2025
2bfa54e
Show hidden block placeholder when dragging
t-hamano Aug 18, 2025
913ad06
Revert "Show hidden block placeholder when dragging"
t-hamano Aug 19, 2025
8c5c625
Show block toolbar button only when editing mode is default
t-hamano Aug 19, 2025
d8dd684
Merge branch 'trunk' into block-visibility
t-hamano Aug 25, 2025
fb2d668
Process metadata updates for multiple blocks at once
t-hamano Aug 25, 2025
03c1826
Preserve block visibility metadata in block transforms
t-hamano Aug 25, 2025
9c12705
Merge branch 'trunk' into block-visibility
t-hamano Sep 16, 2025
a78bdd5
Remove support from schema and docs
t-hamano Sep 16, 2025
599ed80
Add experiments setting
t-hamano Sep 16, 2025
6c04b64
Make Block Visibility as experiment feature
t-hamano Sep 16, 2025
e54603e
Rename __experimentalBlockVisibility to blockVisibility
t-hamano Sep 18, 2025
854b5d5
Merge branch 'trunk' into block-visibility
t-hamano Sep 30, 2025
3b152ca
Fix: other metadata attributes get stripped when toggling via the too…
t-hamano Sep 30, 2025
fffcf35
Show block toolbar button when multiple blocks are selected
t-hamano Sep 30, 2025
1c9b407
Fix incorrect block.json definition
t-hamano Sep 30, 2025
5596f83
Apply opacity to hidden and selected blocks
t-hamano Sep 30, 2025
c39d72a
Revert "Apply opacity to hidden and selected blocks"
t-hamano Sep 30, 2025
7de5b65
Provide blockVilibility metadata to preview context
t-hamano Sep 30, 2025
f773c15
Accordion Header, Accordion Panel: disable blockVisibility support
t-hamano Sep 30, 2025
f36f529
Visually hide block
t-hamano Sep 30, 2025
f5c1572
Merge branch 'trunk' into block-visibility
t-hamano Oct 1, 2025
bb9ec37
Stabilize feature
t-hamano Oct 1, 2025
4c7338c
Add backport changelog
t-hamano Oct 1, 2025
861689c
Update server side function
t-hamano Oct 1, 2025
0929559
Use "visibility:hidden" to visually hide blocks
t-hamano Oct 1, 2025
dd0d1cb
Combine multiple useSelect call into one
t-hamano Oct 1, 2025
96428fd
Reset styles from hidden blocks
t-hamano Oct 1, 2025
465777d
Update lib/block-supports/block-visibility.php
t-hamano Oct 1, 2025
ff7a8d5
Better behavior for hidden blocks
t-hamano Oct 1, 2025
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
4 changes: 2 additions & 2 deletions docs/reference-guides/core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ Show a block pattern. ([Source](https://github.com/WordPress/gutenberg/tree/trun

- **Name:** core/pattern
- **Category:** theme
- **Supports:** interactivity (clientNavigation), ~~html~~, ~~inserter~~, ~~renaming~~
- **Supports:** interactivity (clientNavigation), ~~blockVisibility~~, ~~html~~, ~~inserter~~, ~~renaming~~
- **Attributes:** slug

## Author
Expand Down Expand Up @@ -975,7 +975,7 @@ Edit the different global regions of your site, like the header, footer, sidebar

- **Name:** core/template-part
- **Category:** theme
- **Supports:** align, interactivity (clientNavigation), ~~html~~, ~~renaming~~, ~~reusable~~
- **Supports:** align, interactivity (clientNavigation), ~~blockVisibility~~, ~~html~~, ~~renaming~~, ~~reusable~~
- **Attributes:** area, slug, tagName, theme

## Term Description
Expand Down
27 changes: 27 additions & 0 deletions lib/block-supports/block-visibility.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
/**
* Block visibility block support flag.
*
* @package gutenberg
*/

/**
* Render nothing if the block is hidden.
*
* @param string $block_content The block content.
* @param array $block The block.
*
* @return string The block content.
*/
function gutenberg_render_block_visibility_support( $block_content, $block ) {
if ( isset( $block['attrs']['metadata']['blockVisibility'] ) && false === $block['attrs']['metadata']['blockVisibility'] ) {
return '';
}

return $block_content;
}

if ( function_exists( 'wp_render_block_visibility_support' ) ) {
remove_filter( 'render_block', 'wp_render_block_visibility_support' );
}
add_filter( 'render_block', 'gutenberg_render_block_visibility_support', 10, 2 );
3 changes: 3 additions & 0 deletions lib/experimental/editor-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ function gutenberg_enable_experiments() {
if ( $gutenberg_experiments && array_key_exists( 'gutenberg-content-only-pattern-insertion', $gutenberg_experiments ) ) {
wp_add_inline_script( 'wp-block-editor', 'window.__experimentalContentOnlyPatternInsertion = true', 'before' );
}
if ( $gutenberg_experiments && array_key_exists( 'gutenberg-block-visibility', $gutenberg_experiments ) ) {
wp_add_inline_script( 'wp-block-editor', 'window.__experimentalBlockVisibility = true', 'before' );
}
}

add_action( 'admin_init', 'gutenberg_enable_experiments' );
Expand Down
12 changes: 12 additions & 0 deletions lib/experiments-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,18 @@ function gutenberg_initialize_experiments_settings() {
)
);

add_settings_field(
'gutenberg-block-visibility',
__( 'Block Visibility', 'gutenberg' ),
'gutenberg_display_experiment_field',
'gutenberg-experiments',
'gutenberg_experiments_section',
array(
'label' => __( 'Add block visibility support and UI.', 'gutenberg' ),
'id' => 'gutenberg-block-visibility',
)
);

register_setting(
'gutenberg-experiments',
'gutenberg-experiments'
Expand Down
5 changes: 5 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,8 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/experimental/interactivity-api/class-gutenberg-interactivity-api-full-page-navigation.php';
Gutenberg_Interactivity_API_Full_Page_Navigation::instance();
}

// Block visibility support and UI.
if ( gutenberg_is_experiment_enabled( 'gutenberg-block-visibility' ) ) {
require __DIR__ . '/block-supports/block-visibility.php';
}
15 changes: 15 additions & 0 deletions packages/block-editor/src/components/block-list/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,9 @@ function BlockListBlockProvider( props ) {
return previewContext;
}

const { isBlockHidden: _isBlockHidden } = unlock(
select( blockEditorStore )
);
const _isSelected = isBlockSelected( clientId );
const canRemove = canRemoveBlock( clientId );
const canMove = canMoveBlock( clientId );
Expand Down Expand Up @@ -705,6 +708,7 @@ function BlockListBlockProvider( props ) {
originalBlockClientId: isInvalid
? blocksWithSameName[ 0 ]
: false,
isBlockHidden: _isBlockHidden( clientId ),
};
},
[ clientId, rootClientId ]
Expand Down Expand Up @@ -747,6 +751,7 @@ function BlockListBlockProvider( props ) {
className,
defaultClassName,
originalBlockClientId,
isBlockHidden,
} = selectedProps;

// Users of the editor.BlockListBlock filter used to be able to
Expand Down Expand Up @@ -797,6 +802,16 @@ function BlockListBlockProvider( props ) {
canMove,
};

if (
window.__experimentalBlockVisibility &&
isBlockHidden &&
! isSelected &&
! isMultiSelected &&
! hasChildSelected
) {
return null;
}

// Here we separate between the props passed to BlockListBlock and any other
// information we selected for internal use. BlockListBlock is a filtered
// component and thus ALL the props are PUBLIC API.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
MenuGroup,
__experimentalStyleProvider as StyleProvider,
} from '@wordpress/components';
import { hasBlockSupport } from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';

/**
Expand All @@ -20,13 +21,20 @@ import { store as blockEditorStore } from '../../store';
import BlockModeToggle from '../block-settings-menu/block-mode-toggle';
import { ModifyContentLockMenuItem } from '../content-lock';
import { BlockRenameControl, useBlockRename } from '../block-rename';
import { BlockVisibilityMenuItem } from '../block-visibility';

const { Fill, Slot } = createSlotFill( 'BlockSettingsMenuControls' );

const BlockSettingsMenuControlsSlot = ( { fillProps, clientIds = null } ) => {
const { selectedBlocks, selectedClientIds, isContentOnly } = useSelect(
const {
selectedBlocks,
selectedClientIds,
isContentOnly,
canToggleSelectedBlocksVisibility,
} = useSelect(
( select ) => {
const {
getBlocksByClientId,
getBlockNamesByClientId,
getSelectedBlockClientIds,
getBlockEditingMode,
Expand All @@ -38,6 +46,11 @@ const BlockSettingsMenuControlsSlot = ( { fillProps, clientIds = null } ) => {
selectedClientIds: ids,
isContentOnly:
getBlockEditingMode( ids[ 0 ] ) === 'contentOnly',
canToggleSelectedBlocksVisibility: getBlocksByClientId(
ids
).every( ( block ) =>
hasBlockSupport( block.name, 'blockVisibility', true )
),
};
},
[ clientIds ]
Expand All @@ -49,6 +62,10 @@ const BlockSettingsMenuControlsSlot = ( { fillProps, clientIds = null } ) => {
selectedClientIds.length === 1 && canLock && ! isContentOnly;
const showRenameButton =
selectedClientIds.length === 1 && canRename && ! isContentOnly;
const showVisibilityButton =
window.__experimentalBlockVisibility &&
canToggleSelectedBlocksVisibility &&
! isContentOnly;

// Check if current selection of blocks is Groupable or Ungroupable
// and pass this props down to ConvertToGroupButton.
Expand Down Expand Up @@ -93,6 +110,11 @@ const BlockSettingsMenuControlsSlot = ( { fillProps, clientIds = null } ) => {
clientId={ selectedClientIds[ 0 ] }
/>
) }
{ showVisibilityButton && (
<BlockVisibilityMenuItem
clientIds={ selectedClientIds }
/>
) }
{ fills }
{ selectedClientIds.length === 1 && (
<ModifyContentLockMenuItem
Expand Down
11 changes: 11 additions & 0 deletions packages/block-editor/src/components/block-toolbar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import BlockControls from '../block-controls';
import __unstableBlockToolbarLastItem from './block-toolbar-last-item';
import BlockSettingsMenu from '../block-settings-menu';
import { BlockLockToolbar } from '../block-lock';
import { BlockVisibilityToolbar } from '../block-visibility';
import { BlockGroupToolbar } from '../convert-to-group-buttons';
import BlockEditVisuallyButton from '../block-edit-visually-button';
import { useShowHoveredOrFocusedGestures } from './utils';
Expand Down Expand Up @@ -73,6 +74,7 @@ export function PrivateBlockToolbar( {
showSlots,
showGroupButtons,
showLockButtons,
showBlockVisibilityButton,
showSwitchSectionStyleButton,
hasFixedToolbar,
isNavigationMode,
Expand Down Expand Up @@ -163,6 +165,7 @@ export function PrivateBlockToolbar( {
showSlots: ! _isZoomOut,
showGroupButtons: ! _isZoomOut,
showLockButtons: ! _isZoomOut,
showBlockVisibilityButton: ! _isZoomOut,
showSwitchSectionStyleButton: _showSwitchSectionStyleButton,
hasFixedToolbar: getSettings().hasFixedToolbar,
isNavigationMode: isNavigationModeEnabled,
Expand Down Expand Up @@ -227,6 +230,14 @@ export function PrivateBlockToolbar( {
>
<ToolbarGroup className="block-editor-block-toolbar__block-controls">
<BlockSwitcher clientIds={ blockClientIds } />
{ ! isMultiToolbar &&
isDefaultEditingMode &&
showBlockVisibilityButton &&
window.__experimentalBlockVisibility && (
<BlockVisibilityToolbar
clientId={ blockClientId }
/>
) }
{ ! isMultiToolbar &&
isDefaultEditingMode &&
showLockButtons && (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as BlockVisibilityMenuItem } from './menu-item';
export { default as BlockVisibilityToolbar } from './toolbar';
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { MenuItem } from '@wordpress/components';
import { seen, unseen } from '@wordpress/icons';
import { useSelect, useDispatch } from '@wordpress/data';

/**
* Internal dependencies
*/
import { cleanEmptyObject } from '../../hooks/utils';
import { store as blockEditorStore } from '../../store';

export default function BlockVisibilityMenuItem( { clientIds } ) {
const { updateBlockAttributes } = useDispatch( blockEditorStore );
const blocks = useSelect(
( select ) => {
return select( blockEditorStore ).getBlocksByClientId( clientIds );
},
[ clientIds ]
);

const hasHiddenBlock = blocks.some(
( block ) => block.attributes.metadata?.blockVisibility === false
);

const toggleBlockVisibility = () => {
const attributesByClientId = Object.fromEntries(
blocks?.map( ( { clientId, attributes } ) => [
clientId,
{
metadata: cleanEmptyObject( {
...attributes?.metadata,
blockVisibility: hasHiddenBlock ? undefined : false,
} ),
},
] )
);
updateBlockAttributes( clientIds, attributesByClientId, {
uniqueByBlock: true,
} );
};

return (
<MenuItem
icon={ hasHiddenBlock ? seen : unseen }
onClick={ toggleBlockVisibility }
>
{ hasHiddenBlock ? __( 'Show' ) : __( 'Hide' ) }
</MenuItem>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { ToolbarButton, ToolbarGroup } from '@wordpress/components';
import { useRef, useEffect } from '@wordpress/element';
import { seen, unseen } from '@wordpress/icons';
import { useSelect, useDispatch } from '@wordpress/data';
import { hasBlockSupport } from '@wordpress/blocks';

/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../../store';
import { unlock } from '../../lock-unlock';

export default function BlockVisibilityToolbar( { clientId } ) {
const { canToggleBlockVisibility, isBlockHidden } = useSelect(
( select ) => {
const { getBlockName } = select( blockEditorStore );
const { isBlockHidden: _isBlockHidden } = unlock(
select( blockEditorStore )
);
return {
canToggleBlockVisibility: hasBlockSupport(
getBlockName( clientId ),
'blockVisibility',
true
),
isBlockHidden: _isBlockHidden( clientId ),
};
},
[ clientId ]
);
const hasBlockVisibilityButtonShownRef = useRef( false );
const { updateBlockAttributes } = useDispatch( blockEditorStore );

// If the block visibility button has been shown, we don't want to
// remove it from the toolbar until the toolbar is rendered again
// without it. Removing it beforehand can cause focus loss issues.
// It needs to return focus from whence it came, and to do that,
// we need to leave the button in the toolbar.
useEffect( () => {
if ( isBlockHidden ) {
hasBlockVisibilityButtonShownRef.current = true;
}
}, [ isBlockHidden ] );

if ( ! isBlockHidden && ! hasBlockVisibilityButtonShownRef.current ) {
return null;
}

const label = isBlockHidden ? __( 'Show' ) : __( 'Hide' );

return (
<>
<ToolbarGroup className="block-editor-block-lock-toolbar">
<ToolbarButton
disabled={ ! canToggleBlockVisibility }
icon={ isBlockHidden ? unseen : seen }
label={ label }
onClick={ () => {
const newBlockHidden = ! isBlockHidden;
updateBlockAttributes( [ clientId ], {
metadata: {
blockVisibility: newBlockHidden
? false
: undefined,
},
} );
} }
/>
</ToolbarGroup>
</>
);
}
Loading
Loading