Skip to content

Commit

Permalink
Improvements to how blocks with a 'disabled' editing mode behave (Wor…
Browse files Browse the repository at this point in the history
…dPress#51148)

- Prevent DefaultAppender from appearing in a disabled block.
- Disable selection (using user-select: none) in disabled blocks.
- Prevent blocks from being inserted into a disabled block via global inserter.
- Prevent disabled blocks from being removed via keyboard shortcut.
- Prevent disabled blocks from being moved via List View drag and drop.
- Prevent block overlay from appearing on a disabled block.
  • Loading branch information
noisysocks authored and sethrubenstein committed Jul 13, 2023
1 parent 413c017 commit 816e09e
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { getDefaultBlockName } from '@wordpress/blocks';
import DefaultBlockAppender from '../default-block-appender';
import ButtonBlockAppender from '../button-block-appender';
import { store as blockEditorStore } from '../../store';
import { unlock } from '../../lock-unlock';

function DefaultAppender( { rootClientId } ) {
const canInsertDefaultBlock = useSelect( ( select ) =>
Expand Down Expand Up @@ -46,13 +47,15 @@ function useAppender( rootClientId, CustomAppender ) {
getTemplateLock,
getSelectedBlockClientId,
__unstableGetEditorMode,
} = select( blockEditorStore );
getBlockEditingMode,
} = unlock( select( blockEditorStore ) );

const selectedBlockClientId = getSelectedBlockClientId();

return {
hideInserter:
!! getTemplateLock( rootClientId ) ||
getBlockEditingMode( rootClientId ) === 'disabled' ||
__unstableGetEditorMode() === 'zoom-out',
isParentSelected:
rootClientId === selectedBlockClientId ||
Expand Down
20 changes: 8 additions & 12 deletions packages/block-editor/src/components/block-list/content.scss
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,6 @@
padding: 0;
}

.block-editor-block-list__layout,
.block-editor-block-list__block {
pointer-events: initial;

&.is-editing-disabled {
pointer-events: none;
}
}

.block-editor-block-list__layout .block-editor-block-list__block {
// With `position: static`, Safari marks a full-width selection rectangle, including margins.
// With `position: relative`, Safari marks an inline selection rectangle, similar to that of
Expand All @@ -178,12 +169,17 @@
// We choose relative, as that matches the multi-selection, which is limited to the block footprint.
position: relative;

// Re-enable text-selection on editable blocks.
user-select: text;

// Break long strings of text without spaces so they don't overflow the block.
overflow-wrap: break-word;

pointer-events: auto;
user-select: text;

&.is-editing-disabled {
pointer-events: none;
user-select: none;
}

.reusable-block-edit-panel * {
z-index: z-index(".block-editor-block-list__block .reusable-block-edit-panel *");
}
Expand Down
58 changes: 28 additions & 30 deletions packages/block-editor/src/store/private-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import createSelector from 'rememo';
/**
* WordPress dependencies
*/
import { createRegistrySelector } from '@wordpress/data';
import { select } from '@wordpress/data';
import { store as blocksStore } from '@wordpress/blocks';

/**
Expand Down Expand Up @@ -72,35 +72,33 @@ export function getLastInsertedBlocksClientIds( state ) {
* @return {BlockEditingMode} The block editing mode. One of `'disabled'`,
* `'contentOnly'`, or `'default'`.
*/
export const getBlockEditingMode = createRegistrySelector(
( select ) =>
( state, clientId = '' ) => {
const explicitEditingMode = getExplicitBlockEditingMode(
state,
clientId
);
const rootClientId = getBlockRootClientId( state, clientId );
const templateLock = getTemplateLock( state, rootClientId );
const name = getBlockName( state, clientId );
const isContent =
select( blocksStore ).__experimentalHasContentRoleAttribute(
name
);
if (
explicitEditingMode === 'disabled' ||
( templateLock === 'contentOnly' && ! isContent )
) {
return 'disabled';
}
if (
explicitEditingMode === 'contentOnly' ||
( templateLock === 'contentOnly' && isContent )
) {
return 'contentOnly';
}
return 'default';
}
);
export const getBlockEditingMode = ( state, clientId = '' ) => {
const explicitEditingMode = getExplicitBlockEditingMode( state, clientId );
const rootClientId = getBlockRootClientId( state, clientId );
const templateLock = getTemplateLock( state, rootClientId );
const name = getBlockName( state, clientId );
// TODO: Terrible hack! We're calling the global select() function here
// instead of using createRegistrySelector(). The problem with using
// createRegistrySelector() is that then the public block-editor selectors
// (e.g. canInsertBlockTypeUnmemoized) can't call this private block-editor
// selector due to a bug in @wordpress/data. See
// https://github.com/WordPress/gutenberg/pull/50985.
const isContent =
select( blocksStore ).__experimentalHasContentRoleAttribute( name );
if (
explicitEditingMode === 'disabled' ||
( templateLock === 'contentOnly' && ! isContent )
) {
return 'disabled';
}
if (
explicitEditingMode === 'contentOnly' ||
( templateLock === 'contentOnly' && isContent )
) {
return 'contentOnly';
}
return 'default';
};

const getExplicitBlockEditingMode = createSelector(
( state, clientId = '' ) => {
Expand Down
51 changes: 31 additions & 20 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import deprecated from '@wordpress/deprecated';
*/
import { mapRichTextSettings } from './utils';
import { orderBy } from '../utils/sorting';
import { getBlockEditingMode } from './private-selectors';

/**
* A block selection object.
Expand Down Expand Up @@ -1539,6 +1540,10 @@ const canInsertBlockTypeUnmemoized = (
return false;
}

if ( getBlockEditingMode( state, rootClientId ?? '' ) === 'disabled' ) {
return false;
}

const parentBlockListSettings = getBlockListSettings( state, rootClientId );

// The parent block doesn't have settings indicating it doesn't support
Expand Down Expand Up @@ -1633,6 +1638,7 @@ export const canInsertBlockType = createSelector(
state.blocks.byClientId.get( rootClientId ),
state.settings.allowedBlockTypes,
state.settings.templateLock,
state.blockEditingModes,
]
);

Expand Down Expand Up @@ -1663,21 +1669,19 @@ export function canInsertBlocks( state, clientIds, rootClientId = null ) {
*/
export function canRemoveBlock( state, clientId, rootClientId = null ) {
const attributes = getBlockAttributes( state, clientId );

// attributes can be null if the block is already deleted.
if ( attributes === null ) {
return true;
}

const { lock } = attributes;
const parentIsLocked = !! getTemplateLock( state, rootClientId );
// If we don't have a lock on the blockType level, we defer to the parent templateLock.
if ( lock === undefined || lock?.remove === undefined ) {
return ! parentIsLocked;
if ( attributes.lock?.remove ) {
return false;
}

// When remove is true, it means we cannot remove it.
return ! lock?.remove;
if ( getTemplateLock( state, rootClientId ) ) {
return false;
}
if ( getBlockEditingMode( state, rootClientId ) === 'disabled' ) {
return false;
}
return true;
}

/**
Expand Down Expand Up @@ -1709,16 +1713,16 @@ export function canMoveBlock( state, clientId, rootClientId = null ) {
if ( attributes === null ) {
return;
}

const { lock } = attributes;
const parentIsLocked = getTemplateLock( state, rootClientId ) === 'all';
// If we don't have a lock on the blockType level, we defer to the parent templateLock.
if ( lock === undefined || lock?.move === undefined ) {
return ! parentIsLocked;
if ( attributes.lock?.move ) {
return false;
}

// When move is true, it means we cannot move it.
return ! lock?.move;
if ( getTemplateLock( state, rootClientId ) === 'all' ) {
return false;
}
if ( getBlockEditingMode( state, rootClientId ) === 'disabled' ) {
return false;
}
return true;
}

/**
Expand Down Expand Up @@ -2812,6 +2816,13 @@ export function __unstableGetTemporarilyEditingAsBlocks( state ) {
}

export function __unstableHasActiveBlockOverlayActive( state, clientId ) {
// Prevent overlay on disabled blocks. It's redundant since disabled blocks
// can't be selected, and prevents non-disabled nested blocks from being
// selected.
if ( getBlockEditingMode( state, clientId ) === 'disabled' ) {
return false;
}

// If the block editing is locked, the block overlay is always active.
if ( ! canEditBlock( state, clientId ) ) {
return true;
Expand Down
17 changes: 12 additions & 5 deletions packages/block-editor/src/store/test/private-selectors.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* WordPress dependencies
*/
import { select } from '@wordpress/data';

/**
* Internal dependencies
*/
Expand All @@ -8,6 +13,10 @@ import {
isBlockSubtreeDisabled,
} from '../private-selectors';

jest.mock( '@wordpress/data/src/select', () => ( {
select: jest.fn(),
} ) );

describe( 'private selectors', () => {
describe( 'isBlockInterfaceHidden', () => {
it( 'should return the true if toggled true in state', () => {
Expand Down Expand Up @@ -117,11 +126,9 @@ describe( 'private selectors', () => {
const __experimentalHasContentRoleAttribute = jest.fn(
() => false
);
getBlockEditingMode.registry = {
select: jest.fn( () => ( {
__experimentalHasContentRoleAttribute,
} ) ),
};
select.mockReturnValue( {
__experimentalHasContentRoleAttribute,
} );

it( 'should return default by default', () => {
expect(
Expand Down
Loading

0 comments on commit 816e09e

Please sign in to comment.