UNPKG

@wordpress/block-editor

Version:
334 lines (322 loc) 13.8 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.PrivateListView = exports.BLOCK_LIST_ITEM_HEIGHT = void 0; var _clsx = _interopRequireDefault(require("clsx")); var _compose = require("@wordpress/compose"); var _components = require("@wordpress/components"); var _data = require("@wordpress/data"); var _deprecated = _interopRequireDefault(require("@wordpress/deprecated")); var _element = require("@wordpress/element"); var _i18n = require("@wordpress/i18n"); var _branch = _interopRequireDefault(require("./branch")); var _context = require("./context"); var _dropIndicator = _interopRequireDefault(require("./drop-indicator")); var _useBlockSelection = _interopRequireDefault(require("./use-block-selection")); var _useListViewBlockIndexes = _interopRequireDefault(require("./use-list-view-block-indexes")); var _useListViewClientIds = _interopRequireDefault(require("./use-list-view-client-ids")); var _useListViewCollapseItems = _interopRequireDefault(require("./use-list-view-collapse-items")); var _useListViewDropZone = _interopRequireDefault(require("./use-list-view-drop-zone")); var _useListViewExpandSelectedItem = _interopRequireDefault(require("./use-list-view-expand-selected-item")); var _store = require("../../store"); var _blockSettingsDropdown = require("../block-settings-menu/block-settings-dropdown"); var _utils = require("./utils"); var _useClipboardHandler = _interopRequireDefault(require("./use-clipboard-handler")); var _jsxRuntime = require("react/jsx-runtime"); /** * External dependencies */ /** * WordPress dependencies */ /** * Internal dependencies */ const expanded = (state, action) => { if (action.type === 'clear') { return {}; } if (Array.isArray(action.clientIds)) { return { ...state, ...action.clientIds.reduce((newState, id) => ({ ...newState, [id]: action.type === 'expand' }), {}) }; } return state; }; const BLOCK_LIST_ITEM_HEIGHT = exports.BLOCK_LIST_ITEM_HEIGHT = 32; /** @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.BlockSettingsDropdown, rootClientId, description, onSelect, additionalBlockContent: AdditionalBlockContent }, ref) { // This can be removed once we no longer need to support the blocks prop. if (blocks) { (0, _deprecated.default)('`blocks` property in `wp.blockEditor.__experimentalListView`', { since: '6.3', alternative: '`rootClientId` property' }); } const instanceId = (0, _compose.useInstanceId)(ListViewComponent); const { clientIdsTree, draggedClientIds, selectedClientIds } = (0, _useListViewClientIds.default)({ blocks, rootClientId }); const blockIndexes = (0, _useListViewBlockIndexes.default)(clientIdsTree); const { getBlock } = (0, _data.useSelect)(_store.store); const { visibleBlockCount } = (0, _data.useSelect)(select => { const { getGlobalBlockCount, getClientIdsOfDescendants } = select(_store.store); const draggedBlockCount = draggedClientIds?.length > 0 ? getClientIdsOfDescendants(draggedClientIds).length + 1 : 0; return { visibleBlockCount: getGlobalBlockCount() - draggedBlockCount }; }, [draggedClientIds]); const { updateBlockSelection } = (0, _useBlockSelection.default)(); const [expandedState, setExpandedState] = (0, _element.useReducer)(expanded, {}); const [insertedBlock, setInsertedBlock] = (0, _element.useState)(null); const { setSelectedTreeId } = (0, _useListViewExpandSelectedItem.default)({ firstSelectedBlockClientId: selectedClientIds[0], setExpandedState }); const selectEditorBlock = (0, _element.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]); const { ref: dropZoneRef, target: blockDropTarget } = (0, _useListViewDropZone.default)({ dropZoneElement, expandedState, setExpandedState }); const elementRef = (0, _element.useRef)(); // Allow handling of copy, cut, and paste events. const clipBoardRef = (0, _useClipboardHandler.default)({ selectBlock: selectEditorBlock }); const treeGridRef = (0, _compose.useMergeRefs)([clipBoardRef, elementRef, dropZoneRef, ref]); (0, _element.useEffect)(() => { // If a blocks are already selected when the list view is initially // mounted, shift focus to the first selected block. if (selectedClientIds?.length) { (0, _utils.focusListItem)(selectedClientIds[0], elementRef?.current); } // Only focus on the selected item when the list view is mounted. }, []); const expand = (0, _element.useCallback)(clientId => { if (!clientId) { return; } const clientIds = Array.isArray(clientId) ? clientId : [clientId]; setExpandedState({ type: 'expand', clientIds }); }, [setExpandedState]); const collapse = (0, _element.useCallback)(clientId => { if (!clientId) { return; } setExpandedState({ type: 'collapse', clientIds: [clientId] }); }, [setExpandedState]); const collapseAll = (0, _element.useCallback)(() => { setExpandedState({ type: 'clear' }); }, [setExpandedState]); const expandRow = (0, _element.useCallback)(row => { expand(row?.dataset?.block); }, [expand]); const collapseRow = (0, _element.useCallback)(row => { collapse(row?.dataset?.block); }, [collapse]); const focusRow = (0, _element.useCallback)((event, startRow, endRow) => { if (event.shiftKey) { updateBlockSelection(event, startRow?.dataset?.block, endRow?.dataset?.block); } }, [updateBlockSelection]); (0, _useListViewCollapseItems.default)({ collapseAll, expand }); const firstDraggedBlockClientId = draggedClientIds?.[0]; // Convert a blockDropTarget into indexes relative to the blocks in the list view. // These values are used to determine which blocks should be displaced to make room // for the drop indicator. See `ListViewBranch` and `getDragDisplacementValues`. const { blockDropTargetIndex, blockDropPosition, firstDraggedBlockIndex } = (0, _element.useMemo)(() => { let _blockDropTargetIndex, _firstDraggedBlockIndex; if (blockDropTarget?.clientId) { const foundBlockIndex = blockIndexes[blockDropTarget.clientId]; // If dragging below or inside the block, treat the drop target as the next block. _blockDropTargetIndex = foundBlockIndex === undefined || blockDropTarget?.dropPosition === 'top' ? foundBlockIndex : foundBlockIndex + 1; } else if (blockDropTarget === null) { // A `null` value is used to indicate that the user is dragging outside of the list view. _blockDropTargetIndex = null; } if (firstDraggedBlockClientId) { const foundBlockIndex = blockIndexes[firstDraggedBlockClientId]; _firstDraggedBlockIndex = foundBlockIndex === undefined || blockDropTarget?.dropPosition === 'top' ? foundBlockIndex : foundBlockIndex + 1; } return { blockDropTargetIndex: _blockDropTargetIndex, blockDropPosition: blockDropTarget?.dropPosition, firstDraggedBlockIndex: _firstDraggedBlockIndex }; }, [blockDropTarget, blockIndexes, firstDraggedBlockClientId]); const contextValue = (0, _element.useMemo)(() => ({ blockDropPosition, blockDropTargetIndex, blockIndexes, draggedClientIds, expandedState, expand, firstDraggedBlockIndex, collapse, collapseAll, BlockSettingsMenu, listViewInstanceId: instanceId, AdditionalBlockContent, insertedBlock, setInsertedBlock, treeGridElementRef: elementRef, rootClientId }), [blockDropPosition, blockDropTargetIndex, blockIndexes, draggedClientIds, expandedState, expand, firstDraggedBlockIndex, collapse, collapseAll, BlockSettingsMenu, instanceId, AdditionalBlockContent, insertedBlock, setInsertedBlock, rootClientId]); // 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] = (0, _compose.__experimentalUseFixedWindowList)(elementRef, BLOCK_LIST_ITEM_HEIGHT, visibleBlockCount, { // Ensure that the windowing logic is recalculated when the expanded state changes. // This is necessary because expanding a collapsed block in a short list view can // switch the list view to a tall list view with a scrollbar, and vice versa. // When this happens, the windowing logic needs to be recalculated to ensure that // the correct number of blocks are rendered, by rechecking for a scroll container. expandedState, useWindowing: true, windowOverscan: 40 }); // 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; } const describedById = description && `block-editor-list-view-description-${instanceId}`; return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_data.AsyncModeProvider, { value: true, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_dropIndicator.default, { draggedBlockClientId: firstDraggedBlockClientId, listViewRef: elementRef, blockDropTarget: blockDropTarget }), description && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.VisuallyHidden, { id: describedById, children: description }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.__experimentalTreeGrid, { id: id, className: (0, _clsx.default)('block-editor-list-view-tree', { 'is-dragging': draggedClientIds?.length > 0 && blockDropTargetIndex !== undefined }), "aria-label": (0, _i18n.__)('Block navigation structure'), ref: treeGridRef, onCollapseRow: collapseRow, onExpandRow: expandRow, onFocusRow: focusRow, applicationAriaLabel: (0, _i18n.__)('Block navigation structure'), "aria-describedby": describedById, style: { '--wp-admin--list-view-dragged-items-height': draggedClientIds?.length ? `${BLOCK_LIST_ITEM_HEIGHT * (draggedClientIds.length - 1)}px` : null }, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_context.ListViewContext.Provider, { value: contextValue, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_branch.default, { blocks: clientIdsTree, parentId: rootClientId, selectBlock: selectEditorBlock, showBlockMovers: showBlockMovers, fixedListWindow: fixedListWindow, selectedClientIds: selectedClientIds, isExpanded: isExpanded, showAppender: showAppender }) }) })] }); } // This is the private API for the ListView component. // It allows access to all props, not just the public ones. const PrivateListView = exports.PrivateListView = (0, _element.forwardRef)(ListViewComponent); // This is the public API for the ListView component. // We wrap the PrivateListView component to hide some props from the public API. var _default = exports.default = (0, _element.forwardRef)((props, ref) => { return /*#__PURE__*/(0, _jsxRuntime.jsx)(PrivateListView, { ref: ref, ...props, showAppender: false, rootClientId: null, onSelect: null, additionalBlockContent: null, blockSettingsMenu: undefined }); }); //# sourceMappingURL=index.js.map