@atlaskit/editor-plugin-quick-insert
Version:
Quick insert plugin for @atlaskit/editor-core
196 lines • 8.63 kB
JavaScript
import React, { useCallback, useId, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { useIntl } from 'react-intl';
import { INPUT_METHOD } from '@atlaskit/editor-common/analytics/types/enums';
import { toolbarInsertBlockMessages } from '@atlaskit/editor-common/messages/insert-block';
import { QuickInsertProvider } from '@atlaskit/editor-common/quick-insert/provider';
import { useRegisteredMenuContext } from '@atlaskit/editor-common/registered-menu/context';
import { REGISTERED_MENU_SURFACE_CONTEXT } from '@atlaskit/editor-common/registered-menu/surface-context';
import { useRegisteredMenu } from '@atlaskit/editor-common/registered-menu/use-registered-menu';
import { isSectionOverflowItemKey } from '@atlaskit/editor-common/type-ahead-is-section-overflow-item-key';
import { typeAheadListMessages } from '@atlaskit/editor-common/type-ahead/messages';
import MenuGroup from '@atlaskit/menu/menu-group';
import { getQuickInsertMenuModel } from './get-quick-insert-menu-model';
import { getQuickInsertMenuItemId } from './getQuickInsertMenuItemId';
import { RegisteredQuickInsertMenuItem } from './RegisteredQuickInsertMenuItem';
import { selectQuickInsertCategoryItems } from './selectQuickInsertCategoryItems';
import { ToolbarQuickInsertMenu } from './ToolbarQuickInsertMenu';
import { TypeAheadQuickInsertMenu } from './TypeAheadQuickInsertMenu';
import { useQuickInsertSearchAnalytics } from './useQuickInsertSearchAnalytics';
const DEFAULT_MENU_MAX_HEIGHT = 480;
const isImeEnter = event => event.isComposing || event.keyCode === 229;
const QuickInsertMenuRoot = ({
emptyStateHandler,
fireAnalyticsEvent,
getItemRank,
getRecommendedItems
}) => {
var _surfaceContext$get, _registeredMenu$maxHe;
const registeredMenu = useRegisteredMenuContext();
const {
query = '',
select: selectFromHost,
surfaceContext
} = registeredMenu;
const surface = (_surfaceContext$get = surfaceContext.get(REGISTERED_MENU_SURFACE_CONTEXT)) === null || _surfaceContext$get === void 0 ? void 0 : _surfaceContext$get.surface;
if (!selectFromHost || !surface || surface === 'element-browser') {
throw new Error('QuickInsertMenuRoot requires a selection action and registered menu surface');
}
const {
formatMessage
} = useIntl();
const {
components,
getItemRank: runtimeItemRank,
rootComponent
} = registeredMenu;
const model = useMemo(() => getQuickInsertMenuModel({
components,
formatMessage,
getItemRank: runtimeItemRank !== null && runtimeItemRank !== void 0 ? runtimeItemRank : getItemRank,
getRecommendedItems,
query,
rootComponent,
selectCategoryItems: components.some(({
key
}) => isSectionOverflowItemKey(key)) ? selectQuickInsertCategoryItems : undefined,
surfaceContext
}), [components, formatMessage, getItemRank, getRecommendedItems, query, rootComponent, runtimeItemRank, surfaceContext]);
const resultCount = useMemo(() => model.sections.reduce((count, [, ...items]) => count + items.filter(({
key
}) => !isSectionOverflowItemKey(key)).length, 0), [model.sections]);
const flushSearchAnalytics = useQuickInsertSearchAnalytics({
surface,
fireAnalyticsEvent,
query,
resultCount
});
const selectItem = useCallback(selection => {
flushSearchAnalytics();
selectFromHost(insert => selection({
insert,
source: surface === 'toolbar' ? INPUT_METHOD.INSERT_MENU : INPUT_METHOD.QUICK_INSERT
}));
}, [flushSearchAnalytics, selectFromHost, surface]);
const menuContextValue = useMemo(() => ({
editorView: registeredMenu.editorView,
isOffline: registeredMenu.isOffline,
popupsMountPoint: registeredMenu.portalMountPoint,
select: selectItem,
surfaceContext: registeredMenu.surfaceContext
}), [registeredMenu.editorView, registeredMenu.isOffline, registeredMenu.portalMountPoint, registeredMenu.surfaceContext, selectItem]);
const listLabel = formatMessage(surface === 'typeahead' ? typeAheadListMessages.quickInsertInputLabel : toolbarInsertBlockMessages.insertMenu);
const {
rows,
selectableItems,
selectedItemIndex,
activeItemKey,
selectionSource,
...listEvents
} = useRegisteredMenu({
model,
resetKey: query
});
const listId = useId();
const [renderedItems, setRenderedItems] = useState({
startIndex: -1,
stopIndex: -1
});
const menuRef = useRef(null);
const maxHeight = (_registeredMenu$maxHe = registeredMenu.maxHeight) !== null && _registeredMenu$maxHe !== void 0 ? _registeredMenu$maxHe : DEFAULT_MENU_MAX_HEIGHT;
const itemCount = selectableItems.length + (model.footer ? 1 : 0);
const activeItemId = selectedItemIndex >= 0 && selectedItemIndex < itemCount ? getQuickInsertMenuItemId(listId, selectedItemIndex) : undefined;
const isActiveItemMounted = selectedItemIndex >= 0 && selectedItemIndex < itemCount && (selectedItemIndex >= renderedItems.startIndex && selectedItemIndex <= renderedItems.stopIndex || Boolean(model.footer) && selectedItemIndex === itemCount - 1);
const selectActiveItem = useCallback(() => {
if (isActiveItemMounted && activeItemId) {
var _menuRef$current, _menuRef$current$owne;
(_menuRef$current = menuRef.current) === null || _menuRef$current === void 0 ? void 0 : (_menuRef$current$owne = _menuRef$current.ownerDocument.getElementById(activeItemId)) === null || _menuRef$current$owne === void 0 ? void 0 : _menuRef$current$owne.click();
}
}, [activeItemId, isActiveItemMounted]);
const onMenuKeyDown = useCallback(event => {
if (event.key === 'ArrowDown') {
listEvents.selectNext();
return true;
}
if (event.key === 'ArrowUp') {
listEvents.selectPrevious();
return true;
}
if (event.key === 'Enter') {
if (isImeEnter(event)) {
return false;
}
selectActiveItem();
return true;
}
return false;
}, [listEvents.selectNext, listEvents.selectPrevious, selectActiveItem]);
const containsTarget = useCallback(target => {
var _menuRef$current2;
return Boolean((_menuRef$current2 = menuRef.current) === null || _menuRef$current2 === void 0 ? void 0 : _menuRef$current2.contains(target));
}, []);
const interaction = useMemo(() => ({
activeDescendantId: isActiveItemMounted ? activeItemId : undefined,
containsTarget,
expanded: itemCount > 0,
label: listLabel,
listId,
onKeyDown: onMenuKeyDown,
onClose: flushSearchAnalytics,
optionCount: itemCount,
selectNext: listEvents.selectNext,
selectPrevious: listEvents.selectPrevious
}), [activeItemId, containsTarget, flushSearchAnalytics, isActiveItemMounted, itemCount, listId, listLabel, listEvents.selectNext, listEvents.selectPrevious, onMenuKeyDown]);
useLayoutEffect(() => {
if (!model.root) {
return;
}
return registeredMenu.registerInteraction(interaction);
}, [model.root, interaction, registeredMenu.registerInteraction]);
if (!model.root) {
return null;
}
return /*#__PURE__*/React.createElement(QuickInsertProvider, {
value: menuContextValue
}, /*#__PURE__*/React.createElement("div", {
ref: menuRef
}, /*#__PURE__*/React.createElement(MenuGroup, null, surface === 'typeahead' ? /*#__PURE__*/React.createElement(TypeAheadQuickInsertMenu, {
Item: RegisteredQuickInsertMenuItem,
activeItemKey: activeItemKey,
hasScrollPadding: true,
itemCount: itemCount,
listId: listId,
listLabel: listLabel,
maxHeight: maxHeight,
model: model,
onItemBlur: listEvents.onItemBlur,
onItemFocus: listEvents.onItemFocus,
onItemHover: listEvents.onItemHover,
onItemLeave: listEvents.onItemLeave,
onItemVisibilityChange: listEvents.onItemVisibilityChange,
onRenderedItemsChange: setRenderedItems,
query: query,
rows: rows,
selectedItemIndex: selectedItemIndex,
selectionSource: selectionSource
}) : /*#__PURE__*/React.createElement(ToolbarQuickInsertMenu, {
Item: RegisteredQuickInsertMenuItem,
activePreviewItemKey: activeItemKey,
emptyStateHandler: emptyStateHandler,
itemCount: itemCount,
listId: listId,
listLabel: listLabel,
maxHeight: maxHeight,
model: model,
onItemBlur: listEvents.onItemBlur,
onItemFocus: listEvents.onItemFocus,
onItemHover: listEvents.onItemHover,
onItemLeave: listEvents.onItemLeave,
onItemVisibilityChange: listEvents.onItemVisibilityChange,
onRenderedItemsChange: setRenderedItems,
query: query,
rows: rows,
selectedItemIndex: selectedItemIndex,
selectionSource: selectionSource
}))));
};
export default QuickInsertMenuRoot;