@wordpress/block-editor
Version:
369 lines (355 loc) • 11.8 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.Inserter = void 0;
var _reactNative = require("react-native");
var _i18n = require("@wordpress/i18n");
var _components = require("@wordpress/components");
var _element = require("@wordpress/element");
var _data = require("@wordpress/data");
var _compose = require("@wordpress/compose");
var _blocks = require("@wordpress/blocks");
var _icons = require("@wordpress/icons");
var _reactNativeBridge = require("@wordpress/react-native-bridge");
var _style = _interopRequireDefault(require("./style.scss"));
var _menu = _interopRequireDefault(require("./menu"));
var _insertionPoint = _interopRequireDefault(require("../block-list/insertion-point"));
var _store = require("../../store");
var _jsxRuntime = require("react/jsx-runtime");
/**
* External dependencies
*/
/**
* WordPress dependencies
*/
/**
* Internal dependencies
*/
const VOICE_OVER_ANNOUNCEMENT_DELAY = 1000;
const defaultRenderToggle = ({
onToggle,
disabled,
iconStyle,
buttonStyle,
onLongPress
}) => {
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.ToolbarButton, {
title: (0, _i18n._x)('Add block', 'Generic label for block inserter button'),
icon: /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.Icon, {
icon: _icons.plus,
style: iconStyle
}),
onClick: onToggle,
extraProps: {
hint: (0, _i18n.__)('Double tap to add a block'),
// testID is present to disambiguate this element for native UI tests. It's not
// usually required for components. See: https://github.com/WordPress/gutenberg/pull/18832#issuecomment-561411389.
testID: 'add-block-button',
onLongPress,
hitSlop: {
top: 10,
bottom: 10,
left: 10,
right: 10
}
},
isDisabled: disabled,
customContainerStyles: buttonStyle,
fixedRatio: false
});
};
class Inserter extends _element.Component {
announcementTimeout;
constructor() {
super(...arguments);
this.onToggle = this.onToggle.bind(this);
this.renderInserterToggle = this.renderInserterToggle.bind(this);
this.renderContent = this.renderContent.bind(this);
}
componentWillUnmount() {
clearTimeout(this.announcementTimeout);
}
getInsertionOptions() {
const addBeforeOption = {
value: 'before',
label: (0, _i18n.__)('Add Block Before'),
icon: _icons.plusCircle
};
const replaceCurrentOption = {
value: 'replace',
label: (0, _i18n.__)('Replace Current Block'),
icon: _icons.plusCircleFilled
};
const addAfterOption = {
value: 'after',
label: (0, _i18n.__)('Add Block After'),
icon: _icons.plusCircle
};
const addToBeginningOption = {
value: 'start',
label: (0, _i18n.__)('Add To Beginning'),
icon: _icons.insertBefore
};
const addToEndOption = {
value: 'end',
label: (0, _i18n.__)('Add To End'),
icon: _icons.insertAfter
};
const {
isAnyBlockSelected,
isSelectedBlockReplaceable
} = this.props;
if (isAnyBlockSelected) {
if (isSelectedBlockReplaceable) {
return [addToBeginningOption, addBeforeOption, replaceCurrentOption, addAfterOption, addToEndOption];
}
return [addToBeginningOption, addBeforeOption, addAfterOption, addToEndOption];
}
return [addToBeginningOption, addToEndOption];
}
getInsertionIndex(insertionType) {
const {
insertionIndexDefault,
insertionIndexStart,
insertionIndexBefore,
insertionIndexAfter,
insertionIndexEnd
} = this.props;
if (insertionType === 'start') {
return insertionIndexStart;
}
if (insertionType === 'before' || insertionType === 'replace') {
return insertionIndexBefore;
}
if (insertionType === 'after') {
return insertionIndexAfter;
}
if (insertionType === 'end') {
return insertionIndexEnd;
}
return insertionIndexDefault;
}
shouldReplaceBlock(insertionType) {
const {
isSelectedBlockReplaceable
} = this.props;
if (insertionType === 'replace') {
return true;
}
if (insertionType === 'default' && isSelectedBlockReplaceable) {
return true;
}
return false;
}
onToggle(isOpen) {
const {
blockTypeImpressions,
onToggle,
updateSettings
} = this.props;
if (!isOpen) {
const impressionsRemain = Object.values(blockTypeImpressions).some(count => count > 0);
if (impressionsRemain) {
const decrementedImpressions = Object.entries(blockTypeImpressions).reduce((acc, [blockName, count]) => ({
...acc,
[blockName]: Math.max(count - 1, 0)
}), {});
// Persist block type impression to JavaScript store.
updateSettings({
impressions: decrementedImpressions
});
// Persist block type impression count to native app store.
(0, _reactNativeBridge.setBlockTypeImpressions)(decrementedImpressions);
}
}
// Surface toggle callback to parent component.
if (onToggle) {
onToggle(isOpen);
}
this.onInserterToggledAnnouncement(isOpen);
}
onInserterToggledAnnouncement(isOpen) {
_reactNative.AccessibilityInfo.isScreenReaderEnabled().then(isEnabled => {
if (isEnabled) {
const isIOS = _reactNative.Platform.OS === 'ios';
const announcement = isOpen ? (0, _i18n.__)('Scrollable block menu opened. Select a block.') : (0, _i18n.__)('Scrollable block menu closed.');
this.announcementTimeout = setTimeout(() => _reactNative.AccessibilityInfo.announceForAccessibility(announcement), isIOS ? VOICE_OVER_ANNOUNCEMENT_DELAY : 0);
}
});
}
/**
* 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.
*/
renderInserterToggle({
onToggle,
isOpen
}) {
const {
disabled,
renderToggle = defaultRenderToggle,
getStylesFromColorScheme,
showSeparator
} = this.props;
if (showSeparator && isOpen) {
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_insertionPoint.default, {});
}
const buttonStyle = getStylesFromColorScheme(_style.default['inserter-menu__add-block-button'], _style.default['inserter-menu__add-block-button--dark']);
const iconStyle = getStylesFromColorScheme(_style.default['inserter-menu__add-block-button-icon'], _style.default['inserter-menu__add-block-button-icon--dark']);
const onPress = () => {
this.setState({
destinationRootClientId: this.props.destinationRootClientId,
shouldReplaceBlock: this.shouldReplaceBlock('default'),
insertionIndex: this.getInsertionIndex('default')
}, onToggle);
};
const onLongPress = () => {
if (this.picker) {
this.picker.presentPicker();
}
};
const onPickerSelect = insertionType => {
this.setState({
destinationRootClientId: this.props.destinationRootClientId,
shouldReplaceBlock: this.shouldReplaceBlock(insertionType),
insertionIndex: this.getInsertionIndex(insertionType)
}, onToggle);
};
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
children: [renderToggle({
onToggle: onPress,
isOpen,
disabled,
iconStyle,
buttonStyle,
onLongPress
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Picker, {
ref: instance => this.picker = instance,
options: this.getInsertionOptions(),
onChange: onPickerSelect,
hideCancelButton: true
})]
});
}
/**
* Render callback to display Dropdown content element.
*
* @param {Object} options
* @param {Function} options.onClose Callback to invoke when dropdown is
* closed.
* @param {boolean} options.isOpen Whether dropdown is currently open.
*
* @return {Element} Dropdown content element.
*/
renderContent({
onClose,
isOpen
}) {
const {
clientId,
isAppender
} = this.props;
const {
destinationRootClientId,
shouldReplaceBlock,
insertionIndex
} = this.state;
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_menu.default, {
isOpen: isOpen,
onSelect: onClose,
onDismiss: onClose,
rootClientId: destinationRootClientId,
clientId: clientId,
isAppender: isAppender,
shouldReplaceBlock: shouldReplaceBlock,
insertionIndex: insertionIndex
});
}
render() {
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Dropdown, {
onToggle: this.onToggle,
headerTitle: (0, _i18n.__)('Add a block'),
renderToggle: this.renderInserterToggle,
renderContent: this.renderContent
});
}
}
exports.Inserter = Inserter;
var _default = exports.default = (0, _compose.compose)([(0, _data.withDispatch)(dispatch => {
const {
updateSettings
} = dispatch(_store.store);
return {
updateSettings
};
}), (0, _data.withSelect)((select, {
clientId,
isAppender,
rootClientId
}) => {
const {
getBlockRootClientId,
getBlockSelectionEnd,
getBlockOrder,
getBlockIndex,
getBlock,
getSettings: getBlockEditorSettings
} = select(_store.store);
const end = getBlockSelectionEnd();
// `end` argument (id) can refer to the component which is removed
// due to pressing `undo` button, that's why we need to check
// if `getBlock( end) is valid, otherwise `null` is passed
const isAnyBlockSelected = !isAppender && end && getBlock(end);
const destinationRootClientId = isAnyBlockSelected ? getBlockRootClientId(end) : rootClientId;
const selectedBlockIndex = getBlockIndex(end);
const endOfRootIndex = getBlockOrder(rootClientId).length;
const isSelectedUnmodifiedDefaultBlock = isAnyBlockSelected ? (0, _blocks.isUnmodifiedDefaultBlock)(getBlock(end)) : undefined;
function getDefaultInsertionIndex() {
const {
__experimentalShouldInsertAtTheTop: shouldInsertAtTheTop
} = getBlockEditorSettings();
// If post title is selected insert as first block.
if (shouldInsertAtTheTop) {
return 0;
}
// If the clientId is defined, we insert at the position of the block.
if (clientId) {
return getBlockIndex(clientId);
}
// If there is a selected block,
if (isAnyBlockSelected) {
// And the last selected block is unmodified (empty), it will be replaced.
if (isSelectedUnmodifiedDefaultBlock) {
return selectedBlockIndex;
}
// We insert after the selected block.
return selectedBlockIndex + 1;
}
// Otherwise, we insert at the end of the current rootClientId.
return endOfRootIndex;
}
const insertionIndexStart = 0;
const insertionIndexBefore = isAnyBlockSelected ? selectedBlockIndex : insertionIndexStart;
const insertionIndexAfter = isAnyBlockSelected ? selectedBlockIndex + 1 : endOfRootIndex;
const insertionIndexEnd = endOfRootIndex;
return {
blockTypeImpressions: getBlockEditorSettings().impressions,
destinationRootClientId,
insertionIndexDefault: getDefaultInsertionIndex(),
insertionIndexBefore,
insertionIndexAfter,
insertionIndexStart,
insertionIndexEnd,
isAnyBlockSelected: !!isAnyBlockSelected,
isSelectedBlockReplaceable: isSelectedUnmodifiedDefaultBlock
};
}), _compose.withPreferredColorScheme])(Inserter);
//# sourceMappingURL=index.native.js.map