UNPKG

@wordpress/block-editor

Version:
250 lines (232 loc) 9.19 kB
import _extends from "@babel/runtime/helpers/esm/extends"; import { createElement } from "@wordpress/element"; /** * WordPress dependencies */ import { useInstanceId, useMergeRefs, __experimentalUseFixedWindowList as useFixedWindowList } from '@wordpress/compose'; import { __experimentalTreeGrid as TreeGrid } from '@wordpress/components'; import { AsyncModeProvider, useSelect } from '@wordpress/data'; import deprecated from '@wordpress/deprecated'; import { useCallback, useEffect, useMemo, useRef, useReducer, forwardRef, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import ListViewBranch from './branch'; import { ListViewContext } from './context'; import ListViewDropIndicator from './drop-indicator'; import useBlockSelection from './use-block-selection'; import useListViewClientIds from './use-list-view-client-ids'; import useListViewDropZone from './use-list-view-drop-zone'; import useListViewExpandSelectedItem from './use-list-view-expand-selected-item'; import { store as blockEditorStore } from '../../store'; import { BlockSettingsDropdown } from '../block-settings-menu/block-settings-dropdown'; const expanded = (state, action) => { if (Array.isArray(action.clientIds)) { return { ...state, ...action.clientIds.reduce((newState, id) => ({ ...newState, [id]: action.type === 'expand' }), {}) }; } return state; }; export const BLOCK_LIST_ITEM_HEIGHT = 36; /** @typedef {import('react').ComponentType} ComponentType */ /** @typedef {import('react').Ref<HTMLElement>} Ref */ /** * Show a hierarchical list of blocks. * * @param {Object} props Components props. * @param {string} props.id An HTML element id for the root element of ListView. * @param {Array} props.blocks _deprecated_ Custom subset of block client IDs to be used instead of the default hierarchy. * @param {?HTMLElement} props.dropZoneElement Optional element to be used as the drop zone. * @param {?boolean} props.showBlockMovers Flag to enable block movers. Defaults to `false`. * @param {?boolean} props.isExpanded Flag to determine whether nested levels are expanded by default. Defaults to `false`. * @param {?boolean} props.showAppender Flag to show or hide the block appender. Defaults to `false`. * @param {?ComponentType} props.blockSettingsMenu Optional more menu substitution. Defaults to the standard `BlockSettingsDropdown` component. * @param {string} props.rootClientId The client id of the root block from which we determine the blocks to show in the list. * @param {string} props.description Optional accessible description for the tree grid component. * @param {?Function} props.onSelect Optional callback to be invoked when a block is selected. Receives the block object that was selected. * @param {?ComponentType} props.additionalBlockContent Component that renders additional block content UI. * @param {Ref} ref Forwarded ref */ function ListViewComponent({ id, blocks, dropZoneElement, showBlockMovers = false, isExpanded = false, showAppender = false, blockSettingsMenu: BlockSettingsMenu = BlockSettingsDropdown, rootClientId, description, onSelect, additionalBlockContent: AdditionalBlockContent }, ref) { // This can be removed once we no longer need to support the blocks prop. if (blocks) { deprecated('`blocks` property in `wp.blockEditor.__experimentalListView`', { since: '6.3', alternative: '`rootClientId` property' }); } const instanceId = useInstanceId(ListViewComponent); const { clientIdsTree, draggedClientIds, selectedClientIds } = useListViewClientIds({ blocks, rootClientId }); const { getBlock } = useSelect(blockEditorStore); const { visibleBlockCount, shouldShowInnerBlocks } = useSelect(select => { const { getGlobalBlockCount, getClientIdsOfDescendants, __unstableGetEditorMode } = select(blockEditorStore); const draggedBlockCount = draggedClientIds?.length > 0 ? getClientIdsOfDescendants(draggedClientIds).length + 1 : 0; return { visibleBlockCount: getGlobalBlockCount() - draggedBlockCount, shouldShowInnerBlocks: __unstableGetEditorMode() !== 'zoom-out' }; }, [draggedClientIds]); const { updateBlockSelection } = useBlockSelection(); const [expandedState, setExpandedState] = useReducer(expanded, {}); const { ref: dropZoneRef, target: blockDropTarget } = useListViewDropZone({ dropZoneElement }); const elementRef = useRef(); const treeGridRef = useMergeRefs([elementRef, dropZoneRef, ref]); const isMounted = useRef(false); const [insertedBlock, setInsertedBlock] = useState(null); const { setSelectedTreeId } = useListViewExpandSelectedItem({ firstSelectedBlockClientId: selectedClientIds[0], setExpandedState }); const selectEditorBlock = useCallback( /** * @param {MouseEvent | KeyboardEvent | undefined} event * @param {string} blockClientId * @param {null | undefined | -1 | 1} focusPosition */ (event, blockClientId, focusPosition) => { updateBlockSelection(event, blockClientId, null, focusPosition); setSelectedTreeId(blockClientId); if (onSelect) { onSelect(getBlock(blockClientId)); } }, [setSelectedTreeId, updateBlockSelection, onSelect, getBlock]); useEffect(() => { isMounted.current = true; }, []); // List View renders a fixed number of items and relies on each having a fixed item height of 36px. // If this value changes, we should also change the itemHeight value set in useFixedWindowList. // See: https://github.com/WordPress/gutenberg/pull/35230 for additional context. const [fixedListWindow] = useFixedWindowList(elementRef, BLOCK_LIST_ITEM_HEIGHT, visibleBlockCount, { useWindowing: true, windowOverscan: 40 }); const expand = useCallback(clientId => { if (!clientId) { return; } setExpandedState({ type: 'expand', clientIds: [clientId] }); }, [setExpandedState]); const collapse = useCallback(clientId => { if (!clientId) { return; } setExpandedState({ type: 'collapse', clientIds: [clientId] }); }, [setExpandedState]); const expandRow = useCallback(row => { expand(row?.dataset?.block); }, [expand]); const collapseRow = useCallback(row => { collapse(row?.dataset?.block); }, [collapse]); const focusRow = useCallback((event, startRow, endRow) => { if (event.shiftKey) { updateBlockSelection(event, startRow?.dataset?.block, endRow?.dataset?.block); } }, [updateBlockSelection]); const contextValue = useMemo(() => ({ isTreeGridMounted: isMounted.current, draggedClientIds, expandedState, expand, collapse, BlockSettingsMenu, listViewInstanceId: instanceId, AdditionalBlockContent, insertedBlock, setInsertedBlock, treeGridElementRef: elementRef }), [draggedClientIds, expandedState, expand, collapse, BlockSettingsMenu, instanceId, AdditionalBlockContent, insertedBlock, setInsertedBlock]); // If there are no blocks to show and we're not showing the appender, do not render the list view. if (!clientIdsTree.length && !showAppender) { return null; } return createElement(AsyncModeProvider, { value: true }, createElement(ListViewDropIndicator, { listViewRef: elementRef, blockDropTarget: blockDropTarget }), createElement(TreeGrid, { id: id, className: "block-editor-list-view-tree", "aria-label": __('Block navigation structure'), ref: treeGridRef, onCollapseRow: collapseRow, onExpandRow: expandRow, onFocusRow: focusRow, applicationAriaLabel: __('Block navigation structure') // eslint-disable-next-line jsx-a11y/aria-props , "aria-description": description }, createElement(ListViewContext.Provider, { value: contextValue }, createElement(ListViewBranch, { blocks: clientIdsTree, parentId: rootClientId, selectBlock: selectEditorBlock, showBlockMovers: showBlockMovers, fixedListWindow: fixedListWindow, selectedClientIds: selectedClientIds, isExpanded: isExpanded, shouldShowInnerBlocks: shouldShowInnerBlocks, showAppender: showAppender })))); } // This is the private API for the ListView component. // It allows access to all props, not just the public ones. export const PrivateListView = forwardRef(ListViewComponent); // This is the public API for the ListView component. // We wrap the PrivateListView component to hide some props from the public API. export default forwardRef((props, ref) => { return createElement(PrivateListView, _extends({ ref: ref }, props, { showAppender: false, rootClientId: null, onSelect: null, additionalBlockContent: null, blockSettingsMenu: undefined })); }); //# sourceMappingURL=index.js.map