@wordpress/block-editor
Version:
349 lines (337 loc) • 10.5 kB
JavaScript
/**
* External dependencies
*/
import clsx from 'clsx';
/**
* WordPress dependencies
*/
import { speak } from '@wordpress/a11y';
import { __, _x, sprintf } from '@wordpress/i18n';
import { Dropdown, Button } from '@wordpress/components';
import { Component } from '@wordpress/element';
import { withDispatch, withSelect } from '@wordpress/data';
import { compose, ifCondition } from '@wordpress/compose';
import { createBlock, store as blocksStore } from '@wordpress/blocks';
import { plus } from '@wordpress/icons';
/**
* Internal dependencies
*/
import InserterMenu from './menu';
import QuickInserter from './quick-inserter';
import { store as blockEditorStore } from '../../store';
import { jsx as _jsx } from "react/jsx-runtime";
const defaultRenderToggle = ({
onToggle,
disabled,
isOpen,
blockTitle,
hasSingleBlockType,
toggleProps = {}
}) => {
const {
as: Wrapper = Button,
label: labelProp,
onClick,
...rest
} = toggleProps;
let label = labelProp;
if (!label && hasSingleBlockType) {
label = sprintf(
// translators: %s: the name of the block when there is only one
_x('Add %s', 'directly add the only allowed block'), blockTitle);
} else if (!label) {
label = _x('Add block', 'Generic label for block inserter button');
}
// Handle both onClick functions from the toggle and the parent component.
function handleClick(event) {
if (onToggle) {
onToggle(event);
}
if (onClick) {
onClick(event);
}
}
return /*#__PURE__*/_jsx(Wrapper, {
__next40pxDefaultSize: toggleProps.as ? undefined : true,
icon: plus,
label: label,
tooltipPosition: "bottom",
onClick: handleClick,
className: "block-editor-inserter__toggle",
"aria-haspopup": !hasSingleBlockType ? 'true' : false,
"aria-expanded": !hasSingleBlockType ? isOpen : false,
disabled: disabled,
...rest
});
};
class Inserter extends Component {
constructor() {
super(...arguments);
this.onToggle = this.onToggle.bind(this);
this.renderToggle = this.renderToggle.bind(this);
this.renderContent = this.renderContent.bind(this);
}
onToggle(isOpen) {
const {
onToggle
} = this.props;
// Surface toggle callback to parent component.
if (onToggle) {
onToggle(isOpen);
}
}
/**
* Render callback to display Dropdown toggle element.
*
* @param {Object} options
* @param {Function} options.onToggle Callback to invoke when toggle is
* pressed.
* @param {boolean} options.isOpen Whether dropdown is currently open.
*
* @return {Element} Dropdown toggle element.
*/
renderToggle({
onToggle,
isOpen
}) {
const {
disabled,
blockTitle,
hasSingleBlockType,
directInsertBlock,
toggleProps,
hasItems,
renderToggle = defaultRenderToggle
} = this.props;
return renderToggle({
onToggle,
isOpen,
disabled: disabled || !hasItems,
blockTitle,
hasSingleBlockType,
directInsertBlock,
toggleProps
});
}
/**
* Render callback to display Dropdown content element.
*
* @param {Object} options
* @param {Function} options.onClose Callback to invoke when dropdown is
* closed.
*
* @return {Element} Dropdown content element.
*/
renderContent({
onClose
}) {
const {
rootClientId,
clientId,
isAppender,
showInserterHelpPanel,
// This prop is experimental to give some time for the quick inserter to mature
// Feel free to make them stable after a few releases.
__experimentalIsQuick: isQuick,
onSelectOrClose,
selectBlockOnInsert
} = this.props;
if (isQuick) {
return /*#__PURE__*/_jsx(QuickInserter, {
onSelect: blocks => {
const firstBlock = Array.isArray(blocks) && blocks?.length ? blocks[0] : blocks;
if (onSelectOrClose && typeof onSelectOrClose === 'function') {
onSelectOrClose(firstBlock);
}
onClose();
},
rootClientId: rootClientId,
clientId: clientId,
isAppender: isAppender,
selectBlockOnInsert: selectBlockOnInsert
});
}
return /*#__PURE__*/_jsx(InserterMenu, {
onSelect: () => {
onClose();
},
rootClientId: rootClientId,
clientId: clientId,
isAppender: isAppender,
showInserterHelpPanel: showInserterHelpPanel
});
}
render() {
const {
position,
hasSingleBlockType,
directInsertBlock,
insertOnlyAllowedBlock,
__experimentalIsQuick: isQuick,
onSelectOrClose
} = this.props;
if (hasSingleBlockType || directInsertBlock) {
return this.renderToggle({
onToggle: insertOnlyAllowedBlock
});
}
return /*#__PURE__*/_jsx(Dropdown, {
className: "block-editor-inserter",
contentClassName: clsx('block-editor-inserter__popover', {
'is-quick': isQuick
}),
popoverProps: {
position,
shift: true
},
onToggle: this.onToggle,
expandOnMobile: true,
headerTitle: __('Add a block'),
renderToggle: this.renderToggle,
renderContent: this.renderContent,
onClose: onSelectOrClose
});
}
}
export default compose([withSelect((select, {
clientId,
rootClientId,
shouldDirectInsert = true
}) => {
const {
getBlockRootClientId,
hasInserterItems,
getAllowedBlocks,
getDirectInsertBlock
} = select(blockEditorStore);
const {
getBlockVariations
} = select(blocksStore);
rootClientId = rootClientId || getBlockRootClientId(clientId) || undefined;
const allowedBlocks = getAllowedBlocks(rootClientId);
const directInsertBlock = shouldDirectInsert && getDirectInsertBlock(rootClientId);
const hasSingleBlockType = allowedBlocks?.length === 1 && getBlockVariations(allowedBlocks[0].name, 'inserter')?.length === 0;
let allowedBlockType = false;
if (hasSingleBlockType) {
allowedBlockType = allowedBlocks[0];
}
return {
hasItems: hasInserterItems(rootClientId),
hasSingleBlockType,
blockTitle: allowedBlockType ? allowedBlockType.title : '',
allowedBlockType,
directInsertBlock,
rootClientId
};
}), withDispatch((dispatch, ownProps, {
select
}) => {
return {
insertOnlyAllowedBlock() {
const {
rootClientId,
clientId,
isAppender,
hasSingleBlockType,
allowedBlockType,
directInsertBlock,
onSelectOrClose,
selectBlockOnInsert
} = ownProps;
if (!hasSingleBlockType && !directInsertBlock) {
return;
}
function getAdjacentBlockAttributes(attributesToCopy) {
const {
getBlock,
getPreviousBlockClientId
} = select(blockEditorStore);
if (!attributesToCopy || !clientId && !rootClientId) {
return {};
}
const result = {};
let adjacentAttributes = {};
// If there is no clientId, then attempt to get attributes
// from the last block within innerBlocks of the root block.
if (!clientId) {
const parentBlock = getBlock(rootClientId);
if (parentBlock?.innerBlocks?.length) {
const lastInnerBlock = parentBlock.innerBlocks[parentBlock.innerBlocks.length - 1];
if (directInsertBlock && directInsertBlock?.name === lastInnerBlock.name) {
adjacentAttributes = lastInnerBlock.attributes;
}
}
} else {
// Otherwise, attempt to get attributes from the
// previous block relative to the current clientId.
const currentBlock = getBlock(clientId);
const previousBlock = getBlock(getPreviousBlockClientId(clientId));
if (currentBlock?.name === previousBlock?.name) {
adjacentAttributes = previousBlock?.attributes || {};
}
}
// Copy over only those attributes flagged to be copied.
attributesToCopy.forEach(attribute => {
if (adjacentAttributes.hasOwnProperty(attribute)) {
result[attribute] = adjacentAttributes[attribute];
}
});
return result;
}
function getInsertionIndex() {
const {
getBlockIndex,
getBlockSelectionEnd,
getBlockOrder,
getBlockRootClientId
} = select(blockEditorStore);
// If the clientId is defined, we insert at the position of the block.
if (clientId) {
return getBlockIndex(clientId);
}
// If there a selected block, we insert after the selected block.
const end = getBlockSelectionEnd();
if (!isAppender && end && getBlockRootClientId(end) === rootClientId) {
return getBlockIndex(end) + 1;
}
// Otherwise, we insert at the end of the current rootClientId.
return getBlockOrder(rootClientId).length;
}
const {
insertBlock
} = dispatch(blockEditorStore);
let blockToInsert;
// Attempt to augment the directInsertBlock with attributes from an adjacent block.
// This ensures styling from nearby blocks is preserved in the newly inserted block.
// See: https://github.com/WordPress/gutenberg/issues/37904
if (directInsertBlock) {
const newAttributes = getAdjacentBlockAttributes(directInsertBlock.attributesToCopy);
blockToInsert = createBlock(directInsertBlock.name, {
...(directInsertBlock.attributes || {}),
...newAttributes
});
} else {
blockToInsert = createBlock(allowedBlockType.name);
}
insertBlock(blockToInsert, getInsertionIndex(), rootClientId, selectBlockOnInsert);
if (onSelectOrClose) {
onSelectOrClose({
clientId: blockToInsert?.clientId
});
}
const message = sprintf(
// translators: %s: the name of the block that has been added
__('%s block added'), allowedBlockType.title);
speak(message);
}
};
}),
// The global inserter should always be visible, we are using ( ! isAppender && ! rootClientId && ! clientId ) as
// a way to detect the global Inserter.
ifCondition(({
hasItems,
isAppender,
rootClientId,
clientId
}) => hasItems || !isAppender && !rootClientId && !clientId)])(Inserter);
//# sourceMappingURL=index.js.map