Skip to content

Commit

Permalink
Only show the inserter for blocks that are actually visible on the sc…
Browse files Browse the repository at this point in the history
…reen
  • Loading branch information
youknowriad committed May 17, 2022
1 parent 9df427c commit 071e7d9
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 47 deletions.
22 changes: 22 additions & 0 deletions docs/reference-guides/data/data-core-block-editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,19 @@ _Returns_

- `boolean`: Is Valid.

### isBlockVisible

Tells if the block is visible on the canvas or not.

_Parameters_

- _state_ `Object`: Global application state.
- _clientId_ `Object`: Client Id of the block.

_Returns_

- `boolean`: True if the block is visible.

### isBlockWithinSelection

Returns true if the block corresponding to the specified client ID is
Expand Down Expand Up @@ -1456,6 +1469,15 @@ _Parameters_

- _hasBlockMovingClientId_ `string|null`: Enable/Disable block moving mode.

### setBlockVisibility

Action that sets whether a block has controlled inner blocks.

_Parameters_

- _clientId_ `string`: The block's clientId.
- _isVisible_ `boolean`: True if the block's is visible on the canvas.

### setHasControlledInnerBlocks

Action that sets whether a block has controlled inner blocks.
Expand Down
99 changes: 59 additions & 40 deletions packages/block-editor/src/components/block-list/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { AsyncModeProvider, useSelect } from '@wordpress/data';
import {
AsyncModeProvider,
useSelect,
useDispatch,
useRegistry,
} from '@wordpress/data';
import { useViewportMatch, useMergeRefs } from '@wordpress/compose';
import { createContext, useState, useMemo } from '@wordpress/element';

Expand Down Expand Up @@ -48,6 +53,24 @@ function Root( { className, ...settings } ) {
},
[]
);
const registry = useRegistry();
const { setBlockVisibility } = useDispatch( blockEditorStore );
const intersectionObserver = useMemo( () => {
const { IntersectionObserver: Observer } = window;

if ( ! Observer ) {
return;
}

return new Observer( ( entries ) => {
registry.batch( () => {
for ( const entry of entries ) {
const clientId = entry.target.getAttribute( 'data-block' );
setBlockVisibility( clientId, entry.isIntersecting );
}
} );
} );
}, [] );
const innerBlocksProps = useInnerBlocksProps(
{
ref: useMergeRefs( [
Expand All @@ -66,7 +89,9 @@ function Root( { className, ...settings } ) {
);
return (
<elementContext.Provider value={ element }>
<div { ...innerBlocksProps } />
<IntersectionObserver.Provider value={ intersectionObserver }>
<div { ...innerBlocksProps } />
</IntersectionObserver.Provider>
</elementContext.Provider>
);
}
Expand All @@ -84,33 +109,37 @@ export default function BlockList( settings ) {

BlockList.__unstableElementContext = elementContext;

function Item( { rootClientId, isSelected, clientId } ) {
const isVisible = useSelect(
( select ) => {
return select( blockEditorStore ).isBlockVisible( clientId );
},
[ clientId ]
);

return (
<AsyncModeProvider
value={
// Only provide data asynchronously if the block is
// not visible and not selected.
! isVisible && ! isSelected
}
>
<BlockListBlock
rootClientId={ rootClientId }
clientId={ clientId }
/>
</AsyncModeProvider>
);
}

function Items( {
placeholder,
rootClientId,
renderAppender,
__experimentalAppenderTagName,
__experimentalLayout: layout = defaultLayout,
} ) {
const [ intersectingBlocks, setIntersectingBlocks ] = useState( new Set() );
const intersectionObserver = useMemo( () => {
const { IntersectionObserver: Observer } = window;

if ( ! Observer ) {
return;
}

return new Observer( ( entries ) => {
setIntersectingBlocks( ( oldIntersectingBlocks ) => {
const newIntersectingBlocks = new Set( oldIntersectingBlocks );
for ( const entry of entries ) {
const clientId = entry.target.getAttribute( 'data-block' );
const action = entry.isIntersecting ? 'add' : 'delete';
newIntersectingBlocks[ action ]( clientId );
}
return newIntersectingBlocks;
} );
} );
}, [ setIntersectingBlocks ] );
const { order, selectedBlocks } = useSelect(
( select ) => {
const { getBlockOrder, getSelectedBlockClientIds } = select(
Expand All @@ -126,24 +155,14 @@ function Items( {

return (
<LayoutProvider value={ layout }>
<IntersectionObserver.Provider value={ intersectionObserver }>
{ order.map( ( clientId ) => (
<AsyncModeProvider
key={ clientId }
value={
// Only provide data asynchronously if the block is
// not visible and not selected.
! intersectingBlocks.has( clientId ) &&
! selectedBlocks.includes( clientId )
}
>
<BlockListBlock
rootClientId={ rootClientId }
clientId={ clientId }
/>
</AsyncModeProvider>
) ) }
</IntersectionObserver.Provider>
{ order.map( ( clientId ) => (
<Item
key={ clientId }
clientId={ clientId }
isSelected={ selectedBlocks.includes( clientId ) }
rootClientId={ rootClientId }
/>
) ) }
{ order.length < 1 && placeholder }
<BlockListAppender
tagName={ __experimentalAppenderTagName }
Expand Down
19 changes: 12 additions & 7 deletions packages/block-editor/src/components/block-popover/inbetween.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,23 @@ function BlockPopoverInbetween( {
return () => clearInterval( intervalHandle );
}, [] );

const { orientation, rootClientId } = useSelect(
const { orientation, rootClientId, isVisible } = useSelect(
( select ) => {
const { getBlockListSettings, getBlockRootClientId } = select(
blockEditorStore
);
const {
getBlockListSettings,
getBlockRootClientId,
isBlockVisible,
} = select( blockEditorStore );

const _rootClientId = getBlockRootClientId( previousClientId );
return {
orientation:
getBlockListSettings( _rootClientId )?.orientation ||
'vertical',
rootClientId: _rootClientId,
isVisible:
isBlockVisible( previousClientId ) &&
isBlockVisible( nextClientId ),
};
},
[ previousClientId ]
Expand All @@ -61,7 +66,7 @@ function BlockPopoverInbetween( {
const nextElement = useBlockElement( nextClientId );
const isVertical = orientation === 'vertical';
const style = useMemo( () => {
if ( ! previousElement && ! nextElement ) {
if ( ( ! previousElement && ! nextElement ) || ! isVisible ) {
return {};
}

Expand Down Expand Up @@ -96,7 +101,7 @@ function BlockPopoverInbetween( {
}, [ previousElement, nextElement, isVertical, positionRecompute ] );

const getAnchorRect = useCallback( () => {
if ( ! previousElement && ! nextElement ) {
if ( ( ! previousElement && ! nextElement ) || ! isVisible ) {
return {};
}

Expand Down Expand Up @@ -150,7 +155,7 @@ function BlockPopoverInbetween( {

const popoverScrollRef = usePopoverScroll( __unstableContentRef );

if ( ! previousElement || ! nextElement ) {
if ( ! previousElement || ! nextElement || ! isVisible ) {
return null;
}

Expand Down
14 changes: 14 additions & 0 deletions packages/block-editor/src/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -1624,3 +1624,17 @@ export function setHasControlledInnerBlocks(
clientId,
};
}

/**
* Action that sets whether a block has controlled inner blocks.
*
* @param {string} clientId The block's clientId.
* @param {boolean} isVisible True if the block's is visible on the canvas.
*/
export function setBlockVisibility( clientId, isVisible ) {
return {
type: 'SET_BLOCK_VISIBILITY',
isVisible,
clientId,
};
}
11 changes: 11 additions & 0 deletions packages/block-editor/src/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1139,6 +1139,17 @@ export const blocks = flow(
}
return state;
},

visibility( state = {}, action ) {
if ( action.type === 'SET_BLOCK_VISIBILITY' ) {
return {
...state,
[ action.clientId ]: action.isVisible,
};
}

return state;
},
} );

/**
Expand Down
11 changes: 11 additions & 0 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2662,3 +2662,14 @@ export function wasBlockJustInserted( state, clientId, source ) {
lastBlockInserted.source === source
);
}

/**
* Tells if the block is visible on the canvas or not.
*
* @param {Object} state Global application state.
* @param {Object} clientId Client Id of the block.
* @return {boolean} True if the block is visible.
*/
export function isBlockVisible( state, clientId ) {
return state.blocks.visibility?.[ clientId ] ?? true;
}

0 comments on commit 071e7d9

Please sign in to comment.