UNPKG

@wordpress/block-editor

Version:
283 lines (278 loc) 10.8 kB
/** * WordPress dependencies */ import { getBlockType, serialize, store as blocksStore } from '@wordpress/blocks'; import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; import { moreVertical } from '@wordpress/icons'; import { Children, cloneElement } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; import { pipe, useCopyToClipboard } from '@wordpress/compose'; /** * Internal dependencies */ import BlockActions from '../block-actions'; import CommentIconSlotFill from '../../components/collab/block-comment-icon-slot'; import BlockHTMLConvertButton from './block-html-convert-button'; import __unstableBlockSettingsMenuFirstItem from './block-settings-menu-first-item'; import BlockSettingsMenuControls from '../block-settings-menu-controls'; import BlockParentSelectorMenuItem from './block-parent-selector-menu-item'; import { store as blockEditorStore } from '../../store'; import { unlock } from '../../lock-unlock'; import { useNotifyCopy } from '../../utils/use-notify-copy'; import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; const POPOVER_PROPS = { className: 'block-editor-block-settings-menu__popover', placement: 'bottom-start' }; function CopyMenuItem({ clientIds, onCopy, label, shortcut, eventType = 'copy', __experimentalUpdateSelection: updateSelection = false }) { const { getBlocksByClientId } = useSelect(blockEditorStore); const { removeBlocks } = useDispatch(blockEditorStore); const notifyCopy = useNotifyCopy(); const ref = useCopyToClipboard(() => serialize(getBlocksByClientId(clientIds)), () => { switch (eventType) { case 'copy': case 'copyStyles': onCopy(); notifyCopy(eventType, clientIds); break; case 'cut': notifyCopy(eventType, clientIds); removeBlocks(clientIds, updateSelection); break; default: break; } }); const copyMenuItemLabel = label ? label : __('Copy'); return /*#__PURE__*/_jsx(MenuItem, { ref: ref, shortcut: shortcut, children: copyMenuItemLabel }); } export function BlockSettingsDropdown({ block, clientIds, children, __experimentalSelectBlock, ...props }) { // Get the client id of the current block for this menu, if one is set. const currentClientId = block?.clientId; const count = clientIds.length; const firstBlockClientId = clientIds[0]; const { firstParentClientId, parentBlockType, previousBlockClientId, selectedBlockClientIds, openedBlockSettingsMenu, isContentOnly } = useSelect(select => { const { getBlockName, getBlockRootClientId, getPreviousBlockClientId, getSelectedBlockClientIds, getBlockAttributes, getOpenedBlockSettingsMenu, getBlockEditingMode } = unlock(select(blockEditorStore)); const { getActiveBlockVariation } = select(blocksStore); const _firstParentClientId = getBlockRootClientId(firstBlockClientId); const parentBlockName = _firstParentClientId && getBlockName(_firstParentClientId); return { firstParentClientId: _firstParentClientId, parentBlockType: _firstParentClientId && (getActiveBlockVariation(parentBlockName, getBlockAttributes(_firstParentClientId)) || getBlockType(parentBlockName)), previousBlockClientId: getPreviousBlockClientId(firstBlockClientId), selectedBlockClientIds: getSelectedBlockClientIds(), openedBlockSettingsMenu: getOpenedBlockSettingsMenu(), isContentOnly: getBlockEditingMode(firstBlockClientId) === 'contentOnly' }; }, [firstBlockClientId]); const { getBlockOrder, getSelectedBlockClientIds } = useSelect(blockEditorStore); const { setOpenedBlockSettingsMenu } = unlock(useDispatch(blockEditorStore)); const shortcuts = useSelect(select => { const { getShortcutRepresentation } = select(keyboardShortcutsStore); return { copy: getShortcutRepresentation('core/block-editor/copy'), cut: getShortcutRepresentation('core/block-editor/cut'), duplicate: getShortcutRepresentation('core/block-editor/duplicate'), remove: getShortcutRepresentation('core/block-editor/remove'), insertAfter: getShortcutRepresentation('core/block-editor/insert-after'), insertBefore: getShortcutRepresentation('core/block-editor/insert-before') }; }, []); const hasSelectedBlocks = selectedBlockClientIds.length > 0; async function updateSelectionAfterDuplicate(clientIdsPromise) { if (!__experimentalSelectBlock) { return; } const ids = await clientIdsPromise; if (ids && ids[0]) { __experimentalSelectBlock(ids[0], false); } } function updateSelectionAfterRemove() { if (!__experimentalSelectBlock) { return; } let blockToFocus = previousBlockClientId || firstParentClientId; // Focus the first block if there's no previous block nor parent block. if (!blockToFocus) { blockToFocus = getBlockOrder()[0]; } // Only update the selection if the original selection is removed. const shouldUpdateSelection = hasSelectedBlocks && getSelectedBlockClientIds().length === 0; __experimentalSelectBlock(blockToFocus, shouldUpdateSelection); } // This can occur when the selected block (the parent) // displays child blocks within a List View. const parentBlockIsSelected = selectedBlockClientIds?.includes(firstParentClientId); // When a currentClientId is in use, treat the menu as a controlled component. // This ensures that only one block settings menu is open at a time. // This is a temporary solution to work around an issue with `onFocusOutside` // where it does not allow a dropdown to be closed if focus was never within // the dropdown to begin with. Examples include a user either CMD+Clicking or // right clicking into an inactive window. // See: https://github.com/WordPress/gutenberg/pull/54083 const open = !currentClientId ? undefined : openedBlockSettingsMenu === currentClientId || false; function onToggle(localOpen) { if (localOpen && openedBlockSettingsMenu !== currentClientId) { setOpenedBlockSettingsMenu(currentClientId); } else if (!localOpen && openedBlockSettingsMenu && openedBlockSettingsMenu === currentClientId) { setOpenedBlockSettingsMenu(undefined); } } const shouldShowBlockParentMenuItem = !parentBlockIsSelected && !!firstParentClientId; return /*#__PURE__*/_jsx(BlockActions, { clientIds: clientIds, __experimentalUpdateSelection: !__experimentalSelectBlock, children: ({ canCopyStyles, canDuplicate, canInsertBlock, canRemove, onDuplicate, onInsertAfter, onInsertBefore, onRemove, onCopy, onPasteStyles }) => { // It is possible that some plugins register fills for this menu // even if Core doesn't render anything in the block settings menu. // in which case, we may want to render the menu anyway. // That said for now, we can start more conservative. const isEmpty = !canRemove && !canDuplicate && !canInsertBlock && isContentOnly; if (isEmpty) { return null; } return /*#__PURE__*/_jsx(DropdownMenu, { icon: moreVertical, label: __('Options'), className: "block-editor-block-settings-menu", popoverProps: POPOVER_PROPS, open: open, onToggle: onToggle, noIcons: true, ...props, children: ({ onClose }) => /*#__PURE__*/_jsxs(_Fragment, { children: [/*#__PURE__*/_jsxs(MenuGroup, { children: [/*#__PURE__*/_jsx(__unstableBlockSettingsMenuFirstItem.Slot, { fillProps: { onClose } }), shouldShowBlockParentMenuItem && /*#__PURE__*/_jsx(BlockParentSelectorMenuItem, { parentClientId: firstParentClientId, parentBlockType: parentBlockType }), count === 1 && /*#__PURE__*/_jsx(BlockHTMLConvertButton, { clientId: firstBlockClientId }), /*#__PURE__*/_jsx(CopyMenuItem, { clientIds: clientIds, onCopy: onCopy, shortcut: shortcuts.copy }), /*#__PURE__*/_jsx(CopyMenuItem, { clientIds: clientIds, label: __('Cut'), eventType: "cut", shortcut: shortcuts.cut, __experimentalUpdateSelection: !__experimentalSelectBlock }), canDuplicate && /*#__PURE__*/_jsx(MenuItem, { onClick: pipe(onClose, onDuplicate, updateSelectionAfterDuplicate), shortcut: shortcuts.duplicate, children: __('Duplicate') }), canInsertBlock && !isContentOnly && /*#__PURE__*/_jsxs(_Fragment, { children: [/*#__PURE__*/_jsx(MenuItem, { onClick: pipe(onClose, onInsertBefore), shortcut: shortcuts.insertBefore, children: __('Add before') }), /*#__PURE__*/_jsx(MenuItem, { onClick: pipe(onClose, onInsertAfter), shortcut: shortcuts.insertAfter, children: __('Add after') })] }), /*#__PURE__*/_jsx(CommentIconSlotFill.Slot, { fillProps: { onClose } })] }), canCopyStyles && !isContentOnly && /*#__PURE__*/_jsxs(MenuGroup, { children: [/*#__PURE__*/_jsx(CopyMenuItem, { clientIds: clientIds, onCopy: onCopy, label: __('Copy styles'), eventType: "copyStyles" }), /*#__PURE__*/_jsx(MenuItem, { onClick: onPasteStyles, children: __('Paste styles') })] }), !isContentOnly && /*#__PURE__*/_jsx(BlockSettingsMenuControls.Slot, { fillProps: { onClose, count, firstBlockClientId }, clientIds: clientIds }), typeof children === 'function' ? children({ onClose }) : Children.map(child => cloneElement(child, { onClose })), canRemove && /*#__PURE__*/_jsx(MenuGroup, { children: /*#__PURE__*/_jsx(MenuItem, { onClick: pipe(onClose, onRemove, updateSelectionAfterRemove), shortcut: shortcuts.remove, children: __('Delete') }) })] }) }); } }); } export default BlockSettingsDropdown; //# sourceMappingURL=block-settings-dropdown.js.map