Skip to content

Commit

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

- 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 Jun 2, 2023
1 parent ad3049f commit d378185
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

1 comment on commit d378185

@github-actions
Copy link

Choose a reason for hiding this comment

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

Flaky tests detected in d378185.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/5150966336
📝 Reported issues:

Please sign in to comment.