UNPKG

@wordpress/block-editor

Version:
318 lines (287 loc) 9.02 kB
/** * External dependencies */ import clsx from 'clsx'; /** * WordPress dependencies */ import { useMergeRefs } from '@wordpress/compose'; import { forwardRef, useMemo, memo } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; import { getBlockSupport, store as blocksStore, __unstableGetInnerBlocksProps as getInnerBlocksProps, } from '@wordpress/blocks'; /** * Internal dependencies */ import ButtonBlockAppender from './button-block-appender'; import DefaultBlockAppender from './default-block-appender'; import useNestedSettingsUpdate from './use-nested-settings-update'; import useInnerBlockTemplateSync from './use-inner-block-template-sync'; import useBlockContext from './use-block-context'; import { BlockListItems } from '../block-list'; import { BlockContextProvider } from '../block-context'; import { useBlockEditContext } from '../block-edit/context'; import useBlockSync from '../provider/use-block-sync'; import { store as blockEditorStore } from '../../store'; import useBlockDropZone from '../use-block-drop-zone'; import { unlock } from '../../lock-unlock'; const EMPTY_OBJECT = {}; function BlockContext( { children, clientId } ) { const context = useBlockContext( clientId ); return ( <BlockContextProvider value={ context }> { children } </BlockContextProvider> ); } const BlockListItemsMemo = memo( BlockListItems ); /** * InnerBlocks is a component which allows a single block to have multiple blocks * as children. The UncontrolledInnerBlocks component is used whenever the inner * blocks are not controlled by another entity. In other words, it is normally * used for inner blocks in the post editor * * @param {Object} props The component props. */ function UncontrolledInnerBlocks( props ) { const { clientId, allowedBlocks, prioritizedInserterBlocks, defaultBlock, directInsert, __experimentalDefaultBlock, __experimentalDirectInsert, template, templateLock, wrapperRef, templateInsertUpdatesSelection, __experimentalCaptureToolbars: captureToolbars, __experimentalAppenderTagName, renderAppender, orientation, placeholder, layout, name, blockType, parentLock, defaultLayout, } = props; useNestedSettingsUpdate( clientId, parentLock, allowedBlocks, prioritizedInserterBlocks, defaultBlock, directInsert, __experimentalDefaultBlock, __experimentalDirectInsert, templateLock, captureToolbars, orientation, layout ); useInnerBlockTemplateSync( clientId, template, templateLock, templateInsertUpdatesSelection ); const defaultLayoutBlockSupport = getBlockSupport( name, 'layout' ) || getBlockSupport( name, '__experimentalLayout' ) || EMPTY_OBJECT; const { allowSizingOnChildren = false } = defaultLayoutBlockSupport; const usedLayout = layout || defaultLayoutBlockSupport; const memoedLayout = useMemo( () => ( { // Default layout will know about any content/wide size defined by the theme. ...defaultLayout, ...usedLayout, ...( allowSizingOnChildren && { allowSizingOnChildren: true, } ), } ), [ defaultLayout, usedLayout, allowSizingOnChildren ] ); // For controlled inner blocks, we don't want a change in blocks to // re-render the blocks list. const items = ( <BlockListItemsMemo rootClientId={ clientId } renderAppender={ renderAppender } __experimentalAppenderTagName={ __experimentalAppenderTagName } layout={ memoedLayout } wrapperRef={ wrapperRef } placeholder={ placeholder } /> ); if ( ! blockType?.providesContext || Object.keys( blockType.providesContext ).length === 0 ) { return items; } return <BlockContext clientId={ clientId }>{ items }</BlockContext>; } /** * The controlled inner blocks component wraps the uncontrolled inner blocks * component with the blockSync hook. This keeps the innerBlocks of the block in * the block-editor store in sync with the blocks of the controlling entity. An * example of an inner block controller is a template part block, which provides * its own blocks from the template part entity data source. * * @param {Object} props The component props. */ function ControlledInnerBlocks( props ) { useBlockSync( props ); return <UncontrolledInnerBlocks { ...props } />; } const ForwardedInnerBlocks = forwardRef( ( props, ref ) => { const innerBlocksProps = useInnerBlocksProps( { ref }, props ); return ( <div className="block-editor-inner-blocks"> <div { ...innerBlocksProps } /> </div> ); } ); /** * 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. * * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/inner-blocks/README.md * * @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. */ export function useInnerBlocksProps( props = {}, options = {} ) { const { __unstableDisableLayoutClassNames, __unstableDisableDropZone, dropZoneElement, } = options; const { clientId, layout = null, __unstableLayoutClassNames: layoutClassNames = '', } = useBlockEditContext(); const selected = useSelect( ( select ) => { const { getBlockName, isZoomOut, getTemplateLock, getBlockRootClientId, getBlockEditingMode, getBlockSettings, getSectionRootClientId, } = unlock( select( blockEditorStore ) ); if ( ! clientId ) { const sectionRootClientId = getSectionRootClientId(); // Disable the root drop zone when zoomed out and the section root client id // is not the root block list (represented by an empty string). // This avoids drag handling bugs caused by having two block lists acting as // drop zones - the actual 'root' block list and the section root. return { isDropZoneDisabled: isZoomOut() && sectionRootClientId !== '', }; } const { hasBlockSupport, getBlockType } = select( blocksStore ); const blockName = getBlockName( clientId ); const blockEditingMode = getBlockEditingMode( clientId ); const parentClientId = getBlockRootClientId( clientId ); const [ defaultLayout ] = getBlockSettings( clientId, 'layout' ); let _isDropZoneDisabled = blockEditingMode === 'disabled'; if ( isZoomOut() ) { // In zoom out mode, we want to disable the drop zone for the sections. // The inner blocks belonging to the section drop zone is // already disabled by the blocks themselves being disabled. const sectionRootClientId = getSectionRootClientId(); _isDropZoneDisabled = clientId !== sectionRootClientId; } return { __experimentalCaptureToolbars: hasBlockSupport( blockName, '__experimentalExposeControlsToChildren', false ), name: blockName, blockType: getBlockType( blockName ), parentLock: getTemplateLock( parentClientId ), parentClientId, isDropZoneDisabled: _isDropZoneDisabled, defaultLayout, }; }, [ clientId ] ); const { __experimentalCaptureToolbars, name, blockType, parentLock, parentClientId, isDropZoneDisabled, defaultLayout, } = selected; const blockDropZoneRef = useBlockDropZone( { dropZoneElement, rootClientId: clientId, parentClientId, } ); const ref = useMergeRefs( [ props.ref, __unstableDisableDropZone || isDropZoneDisabled || ( layout?.isManualPlacement && window.__experimentalEnableGridInteractivity ) ? null : blockDropZoneRef, ] ); const innerBlocksProps = { __experimentalCaptureToolbars, layout, name, blockType, parentLock, defaultLayout, ...options, }; const InnerBlocks = innerBlocksProps.value && innerBlocksProps.onChange ? ControlledInnerBlocks : UncontrolledInnerBlocks; return { ...props, ref, className: clsx( props.className, 'block-editor-block-list__layout', __unstableDisableLayoutClassNames ? '' : layoutClassNames ), children: clientId ? ( <InnerBlocks { ...innerBlocksProps } clientId={ clientId } /> ) : ( <BlockListItems { ...options } /> ), }; } useInnerBlocksProps.save = getInnerBlocksProps; // Expose default appender placeholders as components. ForwardedInnerBlocks.DefaultBlockAppender = DefaultBlockAppender; ForwardedInnerBlocks.ButtonBlockAppender = ButtonBlockAppender; ForwardedInnerBlocks.Content = () => useInnerBlocksProps.save().children; /** * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/inner-blocks/README.md */ export default ForwardedInnerBlocks;