UNPKG

@atlaskit/editor-plugin-quick-insert

Version:

Quick insert plugin for @atlaskit/editor-core

196 lines • 8.63 kB
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;