Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
fc82266
Add block visibility breakpoints support for dynamic configuration
ramonjd Dec 3, 2025
90c3248
Combine menu
ramonjd Dec 4, 2025
a2b012e
Refactor block visibility breakpoints: remove menu item, streamline t…
ramonjd Dec 4, 2025
ab4d101
Enhance block visibility breakpoints handling by adding a check for t…
ramonjd Dec 4, 2025
c459385
Refactor block visibility breakpoints modal to ensure that visibility…
ramonjd Dec 4, 2025
2f8d4e0
Enhance block visibility logic by adding checks for block visibility …
ramonjd Dec 4, 2025
d0c8eb6
Add block visibility breakpoints constants and enhance modal function…
ramonjd Dec 8, 2025
15e8869
Remove block visibility breakpoints support and enhance block visibil…
ramonjd Dec 8, 2025
157c45d
Refactored block visibility checks to use a common function, reducing…
ramonjd Dec 8, 2025
1bf3c23
Update block visibility logic to use strict boolean checks for breakp…
ramonjd Dec 8, 2025
f7c8af8
Refactor block visibility components to integrate modal functionality…
ramonjd Dec 8, 2025
5ae1a8f
Update block visibility breakpoints modal text and improve z-index ha…
ramonjd Dec 8, 2025
a504192
Remove block visibility breakpoints documentation file. This deletion…
ramonjd Dec 8, 2025
e1db740
Refactor block-list styles to consolidate hidden block visibility rul…
ramonjd Dec 9, 2025
386fcf1
Added useRef and useEffect hooks to manage the visibility button's s…
ramonjd Dec 9, 2025
f6ed9b8
Integrate success notices in block visibility breakpoints modal. Adde…
ramonjd Dec 9, 2025
68bc072
e2e tests for block visibility breakpoints
ramonjd Dec 9, 2025
5c33808
Using WP components
ramonjd Dec 10, 2025
bd6e84a
Refactor block visibility logic in useBlockProps to improve clarity a…
ramonjd Dec 10, 2025
2004e96
Extract logic for preview breakpoints to a separate hook.
ramonjd Dec 10, 2025
386c102
Refactor block visibility logic in isBlockHidden selector to simplify…
ramonjd Dec 10, 2025
0626bff
Enhance block visibility breakpoints modal with conditional rendering…
ramonjd Dec 10, 2025
b9ae19e
List view icon will now show if the block is hidden everywhere or has…
ramonjd Dec 10, 2025
27cc0c7
Add device type handling to block visibility logic
ramonjd Dec 10, 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
67 changes: 66 additions & 1 deletion lib/block-supports/block-visibility.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@
/**
* Block visibility block support flag.
*
* Handles both "hide everywhere" and responsive breakpoint visibility.
*
* @package gutenberg
*/

/**
* Render nothing if the block is hidden.
* Render visibility support for blocks.
*
* Handles two visibility modes:
* 1. Hide everywhere: Renders empty string when blockVisibility is false
* 2. Responsive breakpoints: Adds CSS classes for hiding at specific viewport sizes
*
* @param string $block_content Rendered block content.
* @param array $block Block object.
Expand All @@ -19,14 +25,73 @@ function gutenberg_render_block_visibility_support( $block_content, $block ) {
return $block_content;
}

// Handle "hide everywhere" - return empty string to hide completely.
if ( isset( $block['attrs']['metadata']['blockVisibility'] ) && false === $block['attrs']['metadata']['blockVisibility'] ) {
return '';
}

// Handle responsive breakpoint visibility - add CSS classes.
if ( isset( $block['attrs']['metadata']['blockVisibilityBreakpoints'] ) ) {
$breakpoint_visibility = $block['attrs']['metadata']['blockVisibilityBreakpoints'];

if ( $breakpoint_visibility && is_array( $breakpoint_visibility ) ) {
// Build array of classes to add.
$classes = array();
if ( true === $breakpoint_visibility['mobile'] ) {
$classes[] = 'wp-block-hidden-mobile';
}
if ( true === $breakpoint_visibility['tablet'] ) {
$classes[] = 'wp-block-hidden-tablet';
}
if ( true === $breakpoint_visibility['desktop'] ) {
$classes[] = 'wp-block-hidden-desktop';
}

// Add classes to the first element, presuming it's the wrapper.
if ( ! empty( $classes ) ) {
$tags = new WP_HTML_Tag_Processor( $block_content );
if ( $tags->next_tag() ) {
foreach ( $classes as $class ) {
$tags->add_class( $class );
}
return $tags->get_updated_html();
}
}
}
}

return $block_content;
}

/**
* Enqueue frontend CSS for breakpoint visibility classes.
*/
function gutenberg_enqueue_block_visibility_breakpoints_styles() {
$css = '
@media (max-width: 599px) {
.wp-block-hidden-mobile {
display: none !important;
}
}

@media (min-width: 600px) and (max-width: 959px) {
.wp-block-hidden-tablet {
display: none !important;
}
}

@media (min-width: 960px) {
.wp-block-hidden-desktop {
display: none !important;
}
}
';

wp_add_inline_style( 'wp-block-library', $css );
}

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 );
add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_block_visibility_breakpoints_styles' );
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 would use the style engine enqueuing system eventually, just a shortcut for now

6 changes: 5 additions & 1 deletion packages/block-editor/src/components/block-list/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -821,8 +821,12 @@ function BlockListBlockProvider( props ) {
bindableAttributes,
};

// Only remove blocks from DOM if hidden everywhere (not for breakpoint visibility)
// Breakpoint visibility is handled by CSS classes
const isHiddenEverywhere = attributes?.metadata?.blockVisibility === false;

if (
isBlockHidden &&
isHiddenEverywhere &&
! isSelected &&
! isMultiSelected &&
! hasChildSelected
Expand Down
20 changes: 20 additions & 0 deletions packages/block-editor/src/components/block-list/content.scss
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,26 @@ _::-webkit-full-page-media, _:future, :root [data-has-multi-selection="true"] .b
white-space: nowrap !important;
}

// Breakpoint-specific hidden blocks.
.block-editor-block-list__block.wp-block-hidden-mobile,
.block-editor-block-list__block.wp-block-hidden-tablet,
.block-editor-block-list__block.wp-block-hidden-desktop {
visibility: hidden;
overflow: hidden;
height: 0;
border: none !important;
padding: 0 !important;
}

&.is-layout-flex:not(.is-vertical) > .wp-block-hidden-mobile,
&.is-layout-flex:not(.is-vertical) > .wp-block-hidden-tablet,
&.is-layout-flex:not(.is-vertical) > .wp-block-hidden-desktop {
width: 0;
height: auto;
align-self: stretch;
white-space: nowrap !important;
}

// Re-enable it on components inside.
[class^="components-"] {
user-select: text;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { useIntersectionObserver } from './use-intersection-observer';
import { useScrollIntoView } from './use-scroll-into-view';
import { useFlashEditableBlocks } from '../../use-flash-editable-blocks';
import { useFirefoxDraggableCompatibility } from './use-firefox-draggable-compatibility';
import { usePreviewBreakpoint } from './use-preview-breakpoint';

/**
* This hook is used to lightly mark an element as a block element. The element
Expand Down Expand Up @@ -102,7 +103,6 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) {
isSectionBlock,
isWithinSectionBlock,
canMove,
isBlockHidden,
} = useContext( PrivateBlockContext );

// translators: %s: Type of block (i.e. Text, Image etc)
Expand Down Expand Up @@ -138,6 +138,10 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) {
}
: {};

// Get visibility classes and state based on breakpoint visibility settings
const { breakpointClasses, isHiddenEverywhere } =
usePreviewBreakpoint( clientId );

// Ensures it warns only inside the `edit` implementation for the block.
if ( blockApiVersion < 2 && clientId === blockEditContext.clientId ) {
warning(
Expand Down Expand Up @@ -185,7 +189,8 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) {
'has-editable-outline': hasEditableOutline,
'has-negative-margin': hasNegativeMargin,
'is-editing-content-only-section': isEditingContentOnlySection,
'is-block-hidden': isBlockHidden,
'is-block-hidden': isHiddenEverywhere,
...breakpointClasses,
},
className,
props.className,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* WordPress dependencies
*/
import { useViewportMatch } from '@wordpress/compose';
import { useSelect } from '@wordpress/data';
import { useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../../../store';
import { deviceTypeKey } from '../../../store/private-keys';

/**
* Detects the current viewport breakpoint and returns visibility classes for blocks.
*
* When deviceType is 'Desktop', uses actual viewport detection via useViewportMatch.
* When deviceType is 'Mobile' or 'Tablet', overrides viewport detection with the device type.
*
* This hook:
* 1. Gets block visibility settings from block attributes
* 2. Gets device type from block editor settings
* 3. Detects viewport (either from deviceType override or actual viewport)
* 4. Returns the appropriate CSS classes and hidden state
*
* @param {string} clientId Block client ID.
* @return {Object} Visibility classes and state with `breakpointClasses` (Object) and `isHiddenEverywhere` (boolean).
*/
export function usePreviewBreakpoint( clientId ) {
// Get visibility settings from block attributes and device type from settings
const { blockVisibility, breakpointVisibility, deviceType } = useSelect(
( select ) => {
const block = select( blockEditorStore ).getBlock( clientId );
const metadata = block?.attributes?.metadata;
const settings = select( blockEditorStore ).getSettings();
return {
blockVisibility: metadata?.blockVisibility,
breakpointVisibility: metadata?.blockVisibilityBreakpoints,
deviceType: settings?.[ deviceTypeKey ] || 'Desktop',
};
},
[ clientId ]
);

// When Desktop is selected, use actual viewport detection
// When Mobile/Tablet is selected, override with device type
// All hooks must be called unconditionally
const isSmallOrLarger = useViewportMatch( 'small', '>=' ); // >= 600px
const isLargeOrLarger = useViewportMatch( 'large', '>=' ); // >= 960px

// Determine viewport flags based on deviceType
let isMobileViewport, isTabletViewport, isDesktopViewport;

if ( deviceType === 'Mobile' ) {
// Override: force mobile viewport
isMobileViewport = true;
isTabletViewport = false;
isDesktopViewport = false;
} else if ( deviceType === 'Tablet' ) {
// Override: force tablet viewport
isMobileViewport = false;
isTabletViewport = true;
isDesktopViewport = false;
} else {
// Desktop: use actual viewport detection
// Mobile: viewport < 600px (matches PHP: max-width: 599px)
isMobileViewport = ! isSmallOrLarger;
// Tablet: viewport >= 600px and < 960px (matches PHP: 600px-959px)
isTabletViewport = isSmallOrLarger && ! isLargeOrLarger;
// Desktop: viewport >= 960px (matches PHP: min-width: 960px)
isDesktopViewport = isLargeOrLarger;
}

// Only apply is-block-hidden class if hidden everywhere (not for breakpoint visibility)
// Breakpoint visibility is handled by specific classes below
const isHiddenEverywhere = blockVisibility === false;

// Memoize breakpoint classes to avoid recreating object on every render
const breakpointClasses = useMemo( () => {
if ( ! breakpointVisibility ) {
return {};
}
return {
'wp-block-hidden-mobile':
breakpointVisibility.mobile && isMobileViewport,
'wp-block-hidden-tablet':
breakpointVisibility.tablet && isTabletViewport,
'wp-block-hidden-desktop':
breakpointVisibility.desktop && isDesktopViewport,
};
}, [
breakpointVisibility,
isMobileViewport,
isTabletViewport,
isDesktopViewport,
] );

// Memoize return object to maintain referential equality
return useMemo(
() => ( {
breakpointClasses,
isHiddenEverywhere,
} ),
[ breakpointClasses, isHiddenEverywhere ]
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import { store as blockEditorStore } from '../../store';
import BlockModeToggle from '../block-settings-menu/block-mode-toggle';
import { ModifyContentOnlySectionMenuItem } from '../content-lock';
import { BlockRenameControl, useBlockRename } from '../block-rename';
import { BlockVisibilityMenuItem } from '../block-visibility';
import { EditSectionMenuItem } from './edit-section-menu-item';
import { BlockVisibilityMenuItem } from '../block-visibility';

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

Expand All @@ -35,22 +35,23 @@ const BlockSettingsMenuControlsSlot = ( { fillProps, clientIds = null } ) => {
} = useSelect(
( select ) => {
const {
getBlocksByClientId,
getBlockNamesByClientId,
getSelectedBlockClientIds,
getBlockEditingMode,
getBlockName,
} = select( blockEditorStore );
const ids =
clientIds !== null ? clientIds : getSelectedBlockClientIds();
const blocks = ids.map( ( id ) => getBlockName( id ) );
return {
selectedBlocks: getBlockNamesByClientId( ids ),
selectedClientIds: ids,
isContentOnly:
getBlockEditingMode( ids[ 0 ] ) === 'contentOnly',
canToggleSelectedBlocksVisibility: getBlocksByClientId(
ids
).every( ( block ) =>
hasBlockSupport( block.name, 'visibility', true )
canToggleSelectedBlocksVisibility: blocks.every(
( blockName ) =>
blockName &&
hasBlockSupport( blockName, 'visibility', true )
),
};
},
Expand Down Expand Up @@ -118,6 +119,7 @@ const BlockSettingsMenuControlsSlot = ( { fillProps, clientIds = null } ) => {
{ showVisibilityButton && (
<BlockVisibilityMenuItem
clientIds={ selectedClientIds }
onClose={ fillProps?.onClose }
/>
) }
{ fills }
Expand Down
4 changes: 2 additions & 2 deletions packages/block-editor/src/components/block-toolbar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,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 { BlockVisibilityBreakpointsToolbar } from '../block-visibility-breakpoints';
import { BlockGroupToolbar } from '../convert-to-group-buttons';
import BlockEditVisuallyButton from '../block-edit-visually-button';
import { useShowHoveredOrFocusedGestures } from './utils';
Expand Down Expand Up @@ -213,7 +213,7 @@ export function PrivateBlockToolbar( {
/>
{ isDefaultEditingMode &&
showBlockVisibilityButton && (
<BlockVisibilityToolbar
<BlockVisibilityBreakpointsToolbar
clientIds={ blockClientIds }
/>
) }
Expand Down
Loading
Loading