diff --git a/packages/block-editor/src/components/block-toolbar/block-toolbar-icon.js b/packages/block-editor/src/components/block-toolbar/block-toolbar-icon.js
index f9b95504c312b7..0ee3192bac7810 100644
--- a/packages/block-editor/src/components/block-toolbar/block-toolbar-icon.js
+++ b/packages/block-editor/src/components/block-toolbar/block-toolbar-icon.js
@@ -34,7 +34,10 @@ function getBlockIconVariant( { select, clientIds } ) {
getSettings,
} = unlock( select( blockEditorStore ) );
const { getBlockStyles } = select( blocksStore );
- const isIsolatedEditor = !! getSettings()?.[ isIsolatedEditorKey ];
+ const settings = getSettings();
+ const isIsolatedEditor = !! settings?.[ isIsolatedEditorKey ];
+ const disableContentOnlyForUnsyncedPatterns =
+ !! settings?.disableContentOnlyForUnsyncedPatterns;
const hasTemplateLock = clientIds.some(
( id ) => getTemplateLock( id ) === 'contentOnly'
@@ -49,7 +52,8 @@ function getBlockIconVariant( { select, clientIds } ) {
( id ) =>
!! getBlockAttributes( id )?.metadata?.patternName &&
! isWithinEditedContentOnlySection( id ) &&
- ! isIsolatedEditor
+ ! isIsolatedEditor &&
+ ! disableContentOnlyForUnsyncedPatterns
);
const hasPatternOverrides = clientIds.every( ( clientId ) =>
hasPatternOverridesDefaultBinding(
@@ -99,7 +103,10 @@ function getBlockIcon( { select, clientIds } ) {
isWithinEditedContentOnlySection,
getSettings,
} = unlock( select( blockEditorStore ) );
- const isIsolatedEditor = !! getSettings()?.[ isIsolatedEditorKey ];
+ const settings = getSettings();
+ const isIsolatedEditor = !! settings?.[ isIsolatedEditorKey ];
+ const disableContentOnlyForUnsyncedPatterns =
+ !! settings?.disableContentOnlyForUnsyncedPatterns;
const _isSingleBlock = clientIds.length === 1;
const firstClientId = clientIds[ 0 ];
@@ -109,7 +116,8 @@ function getBlockIcon( { select, clientIds } ) {
_isSingleBlock &&
blockAttributes?.metadata?.patternName &&
! isWithinEditedContentOnlySection( firstClientId ) &&
- ! isIsolatedEditor
+ ! isIsolatedEditor &&
+ ! disableContentOnlyForUnsyncedPatterns
) {
return symbol;
}
diff --git a/packages/block-editor/src/components/block-toolbar/test/block-toolbar-icon.js b/packages/block-editor/src/components/block-toolbar/test/block-toolbar-icon.js
index df6fa4d07b6991..878f3355249970 100644
--- a/packages/block-editor/src/components/block-toolbar/test/block-toolbar-icon.js
+++ b/packages/block-editor/src/components/block-toolbar/test/block-toolbar-icon.js
@@ -241,6 +241,23 @@ describe( 'BlockToolbarIcon', () => {
'core/group-icon'
);
} );
+
+ it( 'should show the block switcher when content-only editing is disabled', () => {
+ setupToolbarSelectors( {
+ settings: {
+ disableContentOnlyForUnsyncedPatterns: true,
+ },
+ } );
+
+ render( );
+
+ expect(
+ screen.getByTestId( 'block-switcher' )
+ ).toBeInTheDocument();
+ expect( screen.getByTestId( 'block-icon' ) ).toHaveTextContent(
+ 'core/group-icon'
+ );
+ } );
} );
describe( 'label calculation', () => {
diff --git a/packages/block-editor/src/components/use-block-display-information/index.js b/packages/block-editor/src/components/use-block-display-information/index.js
index b2516a2d41d243..fd83292e8fed18 100644
--- a/packages/block-editor/src/components/use-block-display-information/index.js
+++ b/packages/block-editor/src/components/use-block-display-information/index.js
@@ -91,7 +91,10 @@ export default function useBlockDisplayInformation( clientId ) {
const attributes = getBlockAttributes( clientId );
const { isWithinEditedContentOnlySection } =
unlock( blockEditorSelect );
- const isIsolatedEditor = !! getSettings()?.[ isIsolatedEditorKey ];
+ const settings = getSettings();
+ const isIsolatedEditor = !! settings?.[ isIsolatedEditorKey ];
+ const disableContentOnlyForUnsyncedPatterns =
+ !! settings?.disableContentOnlyForUnsyncedPatterns;
// Check if this block is a pattern
const patternName = attributes?.metadata?.patternName;
@@ -101,7 +104,8 @@ export default function useBlockDisplayInformation( clientId ) {
if (
patternName &&
! isEditedContentOnlySection &&
- ! isIsolatedEditor
+ ! isIsolatedEditor &&
+ ! disableContentOnlyForUnsyncedPatterns
) {
const pattern = __experimentalGetParsedPattern( patternName );
const positionLabel = getPositionTypeLabel( attributes );
diff --git a/packages/block-editor/src/components/use-block-display-information/test/index.js b/packages/block-editor/src/components/use-block-display-information/test/index.js
index 0f47170f7f89d5..66bce469a66cbb 100644
--- a/packages/block-editor/src/components/use-block-display-information/test/index.js
+++ b/packages/block-editor/src/components/use-block-display-information/test/index.js
@@ -137,4 +137,24 @@ describe( 'useBlockDisplayInformation', () => {
} )
);
} );
+
+ it( 'displays block information for a pattern wrapper when content-only editing is disabled', () => {
+ setupUseSelect( {
+ settings: {
+ disableContentOnlyForUnsyncedPatterns: true,
+ },
+ } );
+ const onChange = jest.fn();
+
+ render( );
+
+ expect( onChange ).toHaveBeenCalledWith(
+ expect.objectContaining( {
+ title: 'Group',
+ icon: groupIcon,
+ description: 'Gather blocks in a layout container.',
+ name: 'Hero pattern',
+ } )
+ );
+ } );
} );
diff --git a/packages/editor/src/components/commands/index.js b/packages/editor/src/components/commands/index.js
index cd57ab6bc0ae61..a54b37fa58e3e0 100644
--- a/packages/editor/src/components/commands/index.js
+++ b/packages/editor/src/components/commands/index.js
@@ -54,6 +54,8 @@ const getEditorCommandLoader = () =>
isCodeEditingEnabled,
isRichEditingEnabled,
isPublishSidebarEnabled,
+ disableContentOnlyForUnsyncedPatterns,
+ disableContentOnlyForTemplateParts,
} = useSelect( ( select ) => {
const { get } = select( preferencesStore );
const { isListViewOpened, getCurrentPostType, getEditorSettings } =
@@ -74,6 +76,11 @@ const getEditorCommandLoader = () =>
isRichEditingEnabled: getEditorSettings().richEditingEnabled,
isPublishSidebarEnabled:
select( editorStore ).isPublishSidebarEnabled(),
+ disableContentOnlyForUnsyncedPatterns:
+ !! getEditorSettings()
+ .disableContentOnlyForUnsyncedPatterns,
+ disableContentOnlyForTemplateParts:
+ !! getEditorSettings().disableContentOnlyForTemplateParts,
};
}, [] );
const { getActiveComplementaryArea } = useSelect( interfaceStore );
@@ -86,7 +93,12 @@ const getEditorCommandLoader = () =>
toggleDistractionFree,
toggleSpotlightMode,
toggleTopToolbar,
+ updateEditorSettings,
} = useDispatch( editorStore );
+ // eslint-disable-next-line @wordpress/no-unused-vars-before-return
+ const { stopEditingContentOnlySection } = unlock(
+ useDispatch( blockEditorStore )
+ );
const { openModal, enableComplementaryArea, disableComplementaryArea } =
useDispatch( interfaceStore );
const { getCurrentPostId } = useSelect( editorStore );
@@ -98,6 +110,9 @@ const getEditorCommandLoader = () =>
}
const commands = [];
+ const disableContentOnlyForPatternsAndTemplateParts =
+ disableContentOnlyForUnsyncedPatterns &&
+ disableContentOnlyForTemplateParts;
commands.push( {
name: 'core/open-shortcut-help',
@@ -176,6 +191,25 @@ const getEditorCommandLoader = () =>
},
} );
+ commands.push( {
+ name: 'core/toggle-pattern-editing',
+ label: disableContentOnlyForPatternsAndTemplateParts
+ ? __( 'Disable editing all patterns' )
+ : __( 'Enable editing all patterns' ),
+ icon: symbol,
+ category: 'command',
+ callback: ( { close } ) => {
+ const disableContentOnly =
+ ! disableContentOnlyForPatternsAndTemplateParts;
+ stopEditingContentOnlySection();
+ updateEditorSettings( {
+ disableContentOnlyForUnsyncedPatterns: disableContentOnly,
+ disableContentOnlyForTemplateParts: disableContentOnly,
+ } );
+ close();
+ },
+ } );
+
if ( allowSwitchEditorMode ) {
commands.push( {
name: 'core/toggle-code-editor',
diff --git a/test/e2e/specs/editor/various/patterns.spec.js b/test/e2e/specs/editor/various/patterns.spec.js
index e4872edafd5375..64ee9e8dc0262c 100644
--- a/test/e2e/specs/editor/various/patterns.spec.js
+++ b/test/e2e/specs/editor/various/patterns.spec.js
@@ -500,6 +500,58 @@ test.describe( 'Unsynced pattern', () => {
await expect( blockHeading ).toHaveAccessibleName( 'Header Group' );
} );
+ test( 'shows the source block in the inspector when full pattern editing is enabled', async ( {
+ editor,
+ page,
+ pageUtils,
+ } ) => {
+ await editor.setContent( `
+
+` );
+
+ await pageUtils.pressKeys( 'access+o' );
+ const listView = page.getByRole( 'treegrid', {
+ name: 'Block navigation structure',
+ } );
+ await listView
+ .getByRole( 'gridcell', { name: 'Header', exact: true } )
+ .click();
+
+ await editor.openDocumentSettingsSidebar();
+ const editorSettings = page.getByRole( 'region', {
+ name: 'Editor settings',
+ } );
+ const blockHeading = editorSettings
+ .getByRole( 'tabpanel', { name: 'Block' } )
+ .getByRole( 'heading' )
+ .first();
+
+ await expect( blockHeading ).toHaveAccessibleName( 'Header Pattern' );
+
+ await editorSettings
+ .getByRole( 'button', { name: 'Edit pattern' } )
+ .click();
+ await expect( blockHeading ).toHaveAccessibleName( 'Header Group' );
+ await expect(
+ editorSettings.getByRole( 'button', { name: 'Exit pattern' } )
+ ).toBeVisible();
+
+ await pageUtils.pressKeys( 'primary+k' );
+ await page
+ .getByRole( 'combobox', { name: 'Search commands and settings' } )
+ .fill( 'editing all patterns' );
+ await page
+ .getByRole( 'option', { name: 'Enable editing all patterns' } )
+ .click();
+
+ await expect(
+ editorSettings.getByRole( 'button', { name: 'Exit pattern' } )
+ ).toBeHidden();
+ await expect( blockHeading ).toHaveAccessibleName( 'Header Group' );
+ } );
+
test( 'detaches an unsynced pattern via the block options menu', async ( {
editor,
page,