Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inner blocks: try hook approach #25633

Merged
merged 1 commit into from
Oct 8, 2020
Merged
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
157 changes: 80 additions & 77 deletions packages/block-editor/src/components/block-list/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,36 @@ const appenderNodesMap = new Map();
*/
const BLOCK_ANIMATION_THRESHOLD = 200;

function BlockList(
{
className,
rootClientId,
renderAppender,
__experimentalTagName = 'div',
__experimentalAppenderTagName,
__experimentalPassedProps = {},
},
ref
) {
function BlockList( { className, rootClientId, renderAppender }, ref ) {
const Container = rootClientId ? 'div' : RootContainer;
const fallbackRef = useRef();
const wrapperRef = ref || fallbackRef;

return (
<AppenderNodesContext.Provider value={ appenderNodesMap }>
<Container
ref={ wrapperRef }
className={ classnames(
'block-editor-block-list__layout',
className
) }
>
<BlockListItems
rootClientId={ rootClientId }
renderAppender={ renderAppender }
wrapperRef={ wrapperRef }
/>
</Container>
</AppenderNodesContext.Provider>
);
}

function Items( {
rootClientId,
renderAppender,
__experimentalAppenderTagName,
wrapperRef,
} ) {
function selector( select ) {
const {
getBlockOrder,
Expand Down Expand Up @@ -76,85 +95,69 @@ function BlockList(
isDraggingBlocks,
} = useSelect( selector, [ rootClientId ] );

const fallbackRef = useRef();
const element = __experimentalPassedProps.ref || ref || fallbackRef;

const Container = rootClientId ? __experimentalTagName : RootContainer;
const dropTargetIndex = useBlockDropZone( {
element,
element: wrapperRef,
rootClientId,
} );

const isAppenderDropTarget =
dropTargetIndex === blockClientIds.length && isDraggingBlocks;

return (
<AppenderNodesContext.Provider value={ appenderNodesMap }>
<Container
{ ...__experimentalPassedProps }
ref={ element }
className={ classnames(
'block-editor-block-list__layout',
className,
__experimentalPassedProps.className
) }
>
{ blockClientIds.map( ( clientId, index ) => {
const isBlockInSelection = hasMultiSelection
? multiSelectedBlockClientIds.includes( clientId )
: selectedBlockClientId === clientId;

const isDropTarget =
dropTargetIndex === index && isDraggingBlocks;

return (
<AsyncModeProvider
key={ clientId }
value={ ! isBlockInSelection }
>
<BlockListBlock
rootClientId={ rootClientId }
clientId={ clientId }
// This prop is explicitely computed and passed down
// to avoid being impacted by the async mode
// otherwise there might be a small delay to trigger the animation.
index={ index }
enableAnimation={ enableAnimation }
className={ classnames( {
'is-drop-target': isDropTarget,
'is-dropping-horizontally':
isDropTarget &&
orientation === 'horizontal',
} ) }
/>
</AsyncModeProvider>
);
<>
{ blockClientIds.map( ( clientId, index ) => {
const isBlockInSelection = hasMultiSelection
? multiSelectedBlockClientIds.includes( clientId )
: selectedBlockClientId === clientId;

const isDropTarget =
dropTargetIndex === index && isDraggingBlocks;

return (
<AsyncModeProvider
key={ clientId }
value={ ! isBlockInSelection }
>
<BlockListBlock
rootClientId={ rootClientId }
clientId={ clientId }
// This prop is explicitely computed and passed down
// to avoid being impacted by the async mode
// otherwise there might be a small delay to trigger the animation.
index={ index }
enableAnimation={ enableAnimation }
className={ classnames( {
'is-drop-target': isDropTarget,
'is-dropping-horizontally':
isDropTarget &&
orientation === 'horizontal',
} ) }
/>
</AsyncModeProvider>
);
} ) }
<BlockListAppender
tagName={ __experimentalAppenderTagName }
rootClientId={ rootClientId }
renderAppender={ renderAppender }
className={ classnames( {
'is-drop-target': isAppenderDropTarget,
'is-dropping-horizontally':
isAppenderDropTarget && orientation === 'horizontal',
} ) }
<BlockListAppender
tagName={ __experimentalAppenderTagName }
rootClientId={ rootClientId }
renderAppender={ renderAppender }
className={ classnames( {
'is-drop-target': isAppenderDropTarget,
'is-dropping-horizontally':
isAppenderDropTarget &&
orientation === 'horizontal',
} ) }
/>
</Container>
</AppenderNodesContext.Provider>
/>
</>
);
}

const ForwardedBlockList = forwardRef( BlockList );

// This component needs to always be synchronous
// as it's the one changing the async mode
// depending on the block selection.
export default forwardRef( ( props, ref ) => {
export function BlockListItems( props ) {
// This component needs to always be synchronous as it's the one changing
// the async mode depending on the block selection.
return (
<AsyncModeProvider value={ false }>
<ForwardedBlockList ref={ ref } { ...props } />
<Items { ...props } />
</AsyncModeProvider>
);
} );
}

export default forwardRef( BlockList );
5 changes: 4 additions & 1 deletion packages/block-editor/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ export { default as __experimentalGradientPickerPanel } from './gradient-picker/
export { default as __experimentalColorGradientControl } from './colors-gradients/control';
export { default as __experimentalPanelColorGradientSettings } from './colors-gradients/panel-color-gradient-settings';
export { default as __experimentalImageSizeControl } from './image-size-control';
export { default as InnerBlocks } from './inner-blocks';
export {
default as InnerBlocks,
useInnerBlocksProps as __experimentalUseInnerBlocksProps,
} from './inner-blocks';
export { default as InspectorAdvancedControls } from './inspector-advanced-controls';
export { default as InspectorControls } from './inspector-controls';
export { default as __experimentalLinkControl } from './link-control';
Expand Down
155 changes: 89 additions & 66 deletions packages/block-editor/src/components/inner-blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import getBlockContext from './get-block-context';
/**
* Internal dependencies
*/
import BlockList from '../block-list';
import { BlockListItems } from '../block-list';
import { BlockContextProvider } from '../block-context';
import { useBlockEditContext } from '../block-edit/context';
import useBlockSync from '../provider/use-block-sync';
Expand All @@ -42,31 +42,27 @@ function UncontrolledInnerBlocks( props ) {
allowedBlocks,
template,
templateLock,
forwardedRef,
wrapperRef,
templateInsertUpdatesSelection,
__experimentalCaptureToolbars: captureToolbars,
__experimentalAppenderTagName,
renderAppender,
orientation,
} = props;

const isSmallScreen = useViewportMatch( 'medium', '<' );
useNestedSettingsUpdate(
clientId,
allowedBlocks,
templateLock,
captureToolbars,
orientation
);

const hasOverlay = useSelect(
( select ) => {
const {
getBlockName,
isBlockSelected,
hasSelectedInnerBlock,
isNavigationMode,
} = select( 'core/block-editor' );
const enableClickThrough = isNavigationMode() || isSmallScreen;
return (
getBlockName( clientId ) !== 'core/template' &&
! isBlockSelected( clientId ) &&
! hasSelectedInnerBlock( clientId, true ) &&
enableClickThrough
);
},
[ clientId, isSmallScreen ]
useInnerBlockTemplateSync(
clientId,
template,
templateLock,
templateInsertUpdatesSelection
);

const context = useSelect(
Expand All @@ -83,42 +79,18 @@ function UncontrolledInnerBlocks( props ) {
[ clientId ]
);

useNestedSettingsUpdate(
clientId,
allowedBlocks,
templateLock,
captureToolbars,
orientation
);

useInnerBlockTemplateSync(
clientId,
template,
templateLock,
templateInsertUpdatesSelection
);

const classes = classnames( {
'has-overlay': hasOverlay,
'is-capturing-toolbar': captureToolbars,
} );

const blockList = (
// This component needs to always be synchronous as it's the one changing
// the async mode depending on the block selection.
return (
<BlockContextProvider value={ context }>
<BlockList
{ ...props }
ref={ forwardedRef }
<BlockListItems
rootClientId={ clientId }
className={ classes }
renderAppender={ renderAppender }
__experimentalAppenderTagName={ __experimentalAppenderTagName }
wrapperRef={ wrapperRef }
/>
</BlockContextProvider>
);

if ( props.__experimentalTagName ) {
return blockList;
}

return <div className="block-editor-inner-blocks">{ blockList }</div>;
}

/**
Expand All @@ -135,27 +107,78 @@ function ControlledInnerBlocks( props ) {
return <UncontrolledInnerBlocks { ...props } />;
}

const ForwardedInnerBlocks = forwardRef( ( props, ref ) => {
const innerBlocksProps = useInnerBlocksProps( { ref }, props );
return (
<div className="block-editor-inner-blocks">
<div { ...innerBlocksProps } />
</div>
);
} );

/**
* Wrapped InnerBlocks component which detects whether to use the controlled or
* uncontrolled variations of the InnerBlocks component. This is the component
* which should be used throughout the application.
* This hook is used to lightly mark an element as an inner blocks wrapper
* element. Call this hook and pass the returned props to the element to mark as
* an inner blocks wrapper, automatically rendering inner blocks as children. If
* you define a ref for the element, it is important to pass the ref to this
* hook, which the hook in turn will pass to the component through the props it
* returns. Optionally, you can also pass any other props through this hook, and
* they will be merged and returned.
*
* @param {Object} props Optional. Props to pass to the element. Must contain
* the ref if one is defined.
* @param {Object} options Optional. Inner blocks options.
*
* @see https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/inner-blocks/README.md
*/
const ForwardedInnerBlocks = forwardRef( ( props, ref ) => {
const { clientId } = useBlockEditContext();
export function useInnerBlocksProps( props = {}, options = {} ) {
const fallbackRef = useRef();
const { clientId } = useBlockEditContext();
const isSmallScreen = useViewportMatch( 'medium', '<' );
const hasOverlay = useSelect(
( select ) => {
const {
getBlockName,
isBlockSelected,
hasSelectedInnerBlock,
isNavigationMode,
} = select( 'core/block-editor' );
const enableClickThrough = isNavigationMode() || isSmallScreen;
return (
getBlockName( clientId ) !== 'core/template' &&
! isBlockSelected( clientId ) &&
! hasSelectedInnerBlock( clientId, true ) &&
enableClickThrough
);
},
[ clientId, isSmallScreen ]
);

const allProps = {
clientId,
forwardedRef: ref || fallbackRef,
const ref = props.ref || fallbackRef;
const InnerBlocks =
options.value && options.onChange
? ControlledInnerBlocks
: UncontrolledInnerBlocks;

return {
...props,
ref,
className: classnames(
props.className,
'block-editor-block-list__layout',
{
'has-overlay': hasOverlay,
}
),
children: (
<InnerBlocks
{ ...options }
clientId={ clientId }
wrapperRef={ ref }
/>
),
};

// Detects if the InnerBlocks should be controlled by an incoming value.
if ( props.value && props.onChange ) {
return <ControlledInnerBlocks { ...allProps } />;
}
return <UncontrolledInnerBlocks { ...allProps } />;
} );
}

// Expose default appender placeholders as components.
ForwardedInnerBlocks.DefaultBlockAppender = DefaultBlockAppender;
Expand Down
Loading