UNPKG

@gechiui/block-editor

Version:
165 lines (152 loc) 4.6 kB
/** * External dependencies */ import classnames from 'classnames'; /** * GeChiUI dependencies */ import { AsyncModeProvider, useSelect } from '@gechiui/data'; import { useViewportMatch, useMergeRefs } from '@gechiui/compose'; import { createContext, useState, useMemo } from '@gechiui/element'; /** * Internal dependencies */ import BlockListBlock from './block'; import BlockListAppender from '../block-list-appender'; import { useInBetweenInserter } from './use-in-between-inserter'; import { store as blockEditorStore } from '../../store'; import { usePreParsePatterns } from '../../utils/pre-parse-patterns'; import { LayoutProvider, defaultLayout } from './layout'; import BlockToolsBackCompat from '../block-tools/back-compat'; import { useBlockSelectionClearer } from '../block-selection-clearer'; import { useInnerBlocksProps } from '../inner-blocks'; import { BlockEditContextProvider, DEFAULT_BLOCK_EDIT_CONTEXT, } from '../block-edit/context'; const elementContext = createContext(); export const IntersectionObserver = createContext(); function Root( { className, ...settings } ) { const [ element, setElement ] = useState(); const isLargeViewport = useViewportMatch( 'medium' ); const { isOutlineMode, isFocusMode, isNavigationMode } = useSelect( ( select ) => { const { getSettings, isNavigationMode: _isNavigationMode } = select( blockEditorStore ); const { outlineMode, focusMode } = getSettings(); return { isOutlineMode: outlineMode, isFocusMode: focusMode, isNavigationMode: _isNavigationMode(), }; }, [] ); const innerBlocksProps = useInnerBlocksProps( { ref: useMergeRefs( [ useBlockSelectionClearer(), useInBetweenInserter(), setElement, ] ), className: classnames( 'is-root-container', className, { 'is-outline-mode': isOutlineMode, 'is-focus-mode': isFocusMode && isLargeViewport, 'is-navigate-mode': isNavigationMode, } ), }, settings ); return ( <elementContext.Provider value={ element }> <div { ...innerBlocksProps } /> </elementContext.Provider> ); } export default function BlockList( settings ) { usePreParsePatterns(); return ( <BlockToolsBackCompat> <BlockEditContextProvider value={ DEFAULT_BLOCK_EDIT_CONTEXT }> <Root { ...settings } /> </BlockEditContextProvider> </BlockToolsBackCompat> ); } BlockList.__unstableElementContext = elementContext; 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( blockEditorStore ); return { order: getBlockOrder( rootClientId ), selectedBlocks: getSelectedBlockClientIds(), }; }, [ rootClientId ] ); 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.length < 1 && placeholder } <BlockListAppender tagName={ __experimentalAppenderTagName } rootClientId={ rootClientId } renderAppender={ renderAppender } /> </LayoutProvider> ); } 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 }> <Items { ...props } /> </AsyncModeProvider> ); }