@wordpress/block-editor
Version:
334 lines (322 loc) • 13.8 kB
JavaScript
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
;