Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@ class BlockCrashBoundary extends Component {
};
}

componentDidCatch() {
componentDidCatch( error ) {
this.props.onError( error );

this.setState( {
hasError: true,
} );
}

render() {
if ( this.state.hasError ) {
return this.props.fallback;
return null;
}

return this.props.children;
Expand Down
180 changes: 140 additions & 40 deletions packages/block-editor/src/components/block-list/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,34 @@ import { omit } from 'lodash';
/**
* WordPress dependencies
*/
import { createContext, useMemo, useCallback } from '@wordpress/element';
import {
useState,
createContext,
useMemo,
useCallback,
} from '@wordpress/element';
import {
getBlockType,
getSaveElement,
isReusableBlock,
isUnmodifiedDefaultBlock,
getUnregisteredTypeHandlerName,
hasBlockSupport,
getBlockDefaultClassName,
} from '@wordpress/blocks';
import { withFilters } from '@wordpress/components';
import { withDispatch, withSelect, useDispatch } from '@wordpress/data';
import { compose, pure, ifCondition } from '@wordpress/compose';
import {
withDispatch,
withSelect,
useSelect,
useDispatch,
} from '@wordpress/data';
import {
compose,
pure,
ifCondition,
useViewportMatch,
} from '@wordpress/compose';

/**
* Internal dependencies
Expand Down Expand Up @@ -68,6 +86,9 @@ function BlockListBlock( {
isLocked,
clientId,
isSelected,
isMultiSelected,
isTypingWithinBlock,
isAncestorOfSelectedBlock,
isSelectionEnabled,
className,
name,
Expand All @@ -80,10 +101,84 @@ function BlockListBlock( {
onMerge,
toggleSelection,
index,
activeEntityBlockId,
} ) {
const isLargeViewport = useViewportMatch( 'medium' );
// In addition to withSelect, we should favor using useSelect in this
// component going forward to avoid leaking new props to the public API
// (editor.BlockListBlock filter)
const { isDragging, isHighlighted, isFocusMode, isOutlineMode } = useSelect(
( select ) => {
const {
isBlockBeingDragged,
isBlockHighlighted,
getSettings,
} = select( blockEditorStore );
return {
isDragging: isBlockBeingDragged( clientId ),
isHighlighted: isBlockHighlighted( clientId ),
isFocusMode: getSettings().focusMode,
isOutlineMode: getSettings().outlineMode,
};
},
[ clientId ]
);
const { removeBlock } = useDispatch( blockEditorStore );
const onRemove = useCallback( () => removeBlock( clientId ), [ clientId ] );

// Handling the error state
const [ hasError, setErrorState ] = useState( false );
const onBlockError = () => setErrorState( true );

const blockType = getBlockType( name );
const lightBlockWrapper =
blockType.apiVersion > 1 ||
hasBlockSupport( blockType, 'lightBlockWrapper', false );
const isUnregisteredBlock = name === getUnregisteredTypeHandlerName();

// Determine whether the block has props to apply to the wrapper.
if ( blockType.getEditWrapperProps ) {
wrapperProps = mergeWrapperProps(
wrapperProps,
blockType.getEditWrapperProps( attributes )
);
}

const generatedClassName =
lightBlockWrapper && hasBlockSupport( blockType, 'className', true )
? getBlockDefaultClassName( name )
: null;
const customClassName = lightBlockWrapper ? attributes.className : null;
const isAligned = wrapperProps && !! wrapperProps[ 'data-align' ];

// The wp-block className is important for editor styles.
// Generate the wrapper class names handling the different states of the
// block.
const wrapperClassName = classnames(
generatedClassName,
customClassName,
'block-editor-block-list__block',
{
'wp-block': ! isAligned,
'has-warning': ! isValid || !! hasError || isUnregisteredBlock,
'is-selected': isSelected && ! isDragging,
'is-highlighted': isHighlighted,
'is-multi-selected': isMultiSelected,
'is-reusable': isReusableBlock( blockType ),
'is-dragging': isDragging,
'is-typing': isTypingWithinBlock,
'is-focused':
isFocusMode &&
isLargeViewport &&
( isSelected || isAncestorOfSelectedBlock ),
'is-focus-mode': isFocusMode && isLargeViewport,
'is-outline-mode': isOutlineMode,
'has-child-selected': isAncestorOfSelectedBlock && ! isDragging,
'is-active-entity': activeEntityBlockId === clientId,
},
className
);

// We wrap the BlockEdit component in a div that hides it when editing in
// HTML mode. This allows us to render all of the ancillary pieces
// (InspectorControls, etc.) which are inside `BlockEdit` but not
Expand All @@ -104,39 +199,33 @@ function BlockListBlock( {
/>
);

const blockType = getBlockType( name );
const lightBlockWrapper =
blockType.apiVersion > 1 ||
hasBlockSupport( blockType, 'lightBlockWrapper', false );

// Determine whether the block has props to apply to the wrapper.
if ( blockType.getEditWrapperProps ) {
wrapperProps = mergeWrapperProps(
wrapperProps,
blockType.getEditWrapperProps( attributes )
);
}

const isAligned = wrapperProps && !! wrapperProps[ 'data-align' ];

// For aligned blocks, provide a wrapper element so the block can be
// positioned relative to the block column.
if ( isAligned ) {
const alignmentWrapperProps = {
'data-align': wrapperProps[ 'data-align' ],
};
blockEdit = (
<div
className="wp-block"
data-align={ wrapperProps[ 'data-align' ] }
>
<div className="wp-block" { ...alignmentWrapperProps }>
{ blockEdit }
</div>
);
}

const value = {
clientId,
isSelected,
index,
className: wrapperClassName,
wrapperProps: omit( wrapperProps, [ 'data-align' ] ),
};
const memoizedValue = useMemo( () => value, Object.values( value ) );

let block;

if ( ! isValid ) {
block = (
<Block className="has-warning">
<Block>
<BlockInvalidWarning clientId={ clientId } />
<div>{ getSaveElement( blockType, attributes ) }</div>
</Block>
Expand All @@ -158,44 +247,44 @@ function BlockListBlock( {
block = <Block { ...wrapperProps }>{ blockEdit }</Block>;
}

const value = {
clientId,
isSelected,
index,
// The wp-block className is important for editor styles.
className: classnames( className, { 'wp-block': ! isAligned } ),
wrapperProps: omit( wrapperProps, [ 'data-align' ] ),
};
const memoizedValue = useMemo( () => value, Object.values( value ) );

return (
<BlockListBlockContext.Provider value={ memoizedValue }>
<BlockCrashBoundary
fallback={
<Block className="has-warning">
<BlockCrashWarning />
</Block>
}
>
<BlockCrashBoundary onError={ onBlockError }>
{ block }
</BlockCrashBoundary>
{ !! hasError && (
<Block>
<BlockCrashWarning />
</Block>
) }
</BlockListBlockContext.Provider>
);
}

const applyWithSelect = withSelect( ( select, { clientId, rootClientId } ) => {
const {
isBlockSelected,
isBlockMultiSelected,
isFirstMultiSelectedBlock,
isTyping,
getBlockMode,
isSelectionEnabled,
hasSelectedInnerBlock,
getTemplateLock,
__unstableGetBlockWithoutInnerBlocks,
getMultiSelectedBlockClientIds,
} = select( blockEditorStore );
const block = __unstableGetBlockWithoutInnerBlocks( clientId );
const isSelected = isBlockSelected( clientId );
const templateLock = getTemplateLock( rootClientId );
const checkDeep = true;

// "ancestor" is the more appropriate label due to "deep" check
const isAncestorOfSelectedBlock = hasSelectedInnerBlock(
clientId,
checkDeep
);

// The fallback to `{}` is a temporary fix.
// This function should never be called when a block is not present in
// the state. It happens now because the order in withSelect rendering
Expand All @@ -206,22 +295,33 @@ const applyWithSelect = withSelect( ( select, { clientId, rootClientId } ) => {
// Do not add new properties here, use `useSelect` instead to avoid
// leaking new props to the public API (editor.BlockListBlock filter).
return {
isMultiSelected: isBlockMultiSelected( clientId ),
isFirstMultiSelected,
multiSelectedClientIds: isFirstMultiSelected
? getMultiSelectedBlockClientIds()
: undefined,

// We only care about this prop when the block is selected
// Thus to avoid unnecessary rerenders we avoid updating the prop if
// the block is not selected.
isTypingWithinBlock:
( isSelected || isAncestorOfSelectedBlock ) && isTyping(),

mode: getBlockMode( clientId ),
isSelectionEnabled: isSelectionEnabled(),
isLocked: !! templateLock,

// Users of the editor.BlockListBlock filter used to be able to
// access the block prop.
// Ideally these blocks would rely on the clientId prop only.
// This is kept for backward compatibility reasons.
block,

name,
attributes,
isValid,
isSelected,
isAncestorOfSelectedBlock,
};
} );

Expand Down
12 changes: 12 additions & 0 deletions packages/block-editor/src/components/block-list/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,25 @@ function Items( {
const {
getBlockOrder,
getBlockListSettings,
getSettings,
getSelectedBlockClientId,
getMultiSelectedBlockClientIds,
hasMultiSelection,
__experimentalGetActiveBlockIdByBlockNames,
} = select( blockEditorStore );

// Determine if there is an active entity area to spotlight.
const activeEntityBlockId = __experimentalGetActiveBlockIdByBlockNames(
getSettings().__experimentalSpotlightEntityBlocks
);

return {
blockClientIds: getBlockOrder( rootClientId ),
selectedBlockClientId: getSelectedBlockClientId(),
multiSelectedBlockClientIds: getMultiSelectedBlockClientIds(),
orientation: getBlockListSettings( rootClientId )?.orientation,
hasMultiSelection: hasMultiSelection(),
activeEntityBlockId,
};
}

Expand All @@ -78,6 +87,7 @@ function Items( {
multiSelectedBlockClientIds,
orientation,
hasMultiSelection,
activeEntityBlockId,
} = useSelect( selector, [ rootClientId ] );

const dropTargetIndex = useBlockDropZone( {
Expand Down Expand Up @@ -113,7 +123,9 @@ function Items( {
'is-dropping-horizontally':
isDropTarget &&
orientation === 'horizontal',
'has-active-entity': activeEntityBlockId,
} ) }
activeEntityBlockId={ activeEntityBlockId }
/>
</AsyncModeProvider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ import useMovingAnimation from '../../use-moving-animation';
import { BlockListBlockContext } from '../block';
import { useFocusFirstElement } from './use-focus-first-element';
import { useIsHovered } from './use-is-hovered';
import { useBlockClassNames } from './use-block-class-names';
import { useBlockDefaultClassName } from './use-block-default-class-name';
import { useBlockCustomClassName } from './use-block-custom-class-name';
import { useBlockMovingModeClassNames } from './use-block-moving-mode-class-names';
import { useEventHandlers } from './use-event-handlers';
import { useBlockNodes } from './use-block-nodes';
Expand Down Expand Up @@ -127,9 +124,6 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) {
className,
props.className,
wrapperProps.className,
useBlockClassNames( clientId ),
useBlockDefaultClassName( clientId ),
useBlockCustomClassName( clientId ),
useBlockMovingModeClassNames( clientId )
),
style: { ...wrapperProps.style, ...props.style },
Expand Down
Loading