UNPKG

@atlaskit/editor-plugin-quick-insert

Version:

Quick insert plugin for @atlaskit/editor-core

400 lines (399 loc) • 20.1 kB
/* RegistryElementBrowser.tsx generated by @compiled/babel-plugin v3.0.2 */ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; import _slicedToArray from "@babel/runtime/helpers/slicedToArray"; import "./RegistryElementBrowser.compiled.css"; import { ax, ix } from "@compiled/react/runtime"; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useIntl } from 'react-intl'; import IconButton from '@atlaskit/button/icon/button'; // eslint-disable-next-line @atlaskit/platform/no-barrel-entry-imports import { messages } from '@atlaskit/editor-common/quick-insert'; import { MENU, RECOMMENDED_SECTION } from '@atlaskit/editor-common/quick-insert/keys'; import { createRegisteredMenuSurfaceContext } from '@atlaskit/editor-common/registered-menu/surface-context'; import { getMenuFooterSectionKey } from '@atlaskit/editor-common/type-ahead-get-menu-footer-section-key'; import { getSectionOverflowItemKey } from '@atlaskit/editor-common/type-ahead-get-section-overflow-item-key'; import CrossIcon from '@atlaskit/icon/core/cross'; import ModalBody from '@atlaskit/modal-dialog/modal-body'; import Modal from '@atlaskit/modal-dialog/modal-dialog'; import ModalFooter from '@atlaskit/modal-dialog/modal-footer'; import ModalHeader from '@atlaskit/modal-dialog/modal-header'; import ModalTitle from '@atlaskit/modal-dialog/modal-title'; import ModalTransition from '@atlaskit/modal-dialog/modal-transition'; import Textfield from '@atlaskit/textfield/text-field'; import { useQuickInsertSearchAnalytics } from '../registered-menu/useQuickInsertSearchAnalytics'; import { getArrowKeyTargetIndex, RETURN_TO_SEARCH } from './getArrowKeyTargetIndex'; import { createRegistryElementBrowserModel, getInitialRegistryElementBrowserSection, getRegistryElementBrowserItems } from './model'; import { getFirstEnabledOption, isOptionDisabled } from './options'; import { RegistryElementBrowserCategories } from './RegistryElementBrowserCategories'; import { RegistryElementBrowserFooter } from './RegistryElementBrowserFooter'; import { RegistryElementBrowserSearchResults } from './RegistryElementBrowserSearchResults'; var browserGridTemplateColumns = '182px minmax(0, 1fr)'; var categoryColumnGap = "calc(".concat("var(--ds-space-200, 16px)", " + ", "var(--ds-space-150, 12px)", ")"); var browserLayoutStyles = null; // The modal header keeps its 24px inline padding, so a narrower gap lines the search field up // with the results below (16px body padding + 182px + 28px gap). var headerContentStyles = null; var headerSearchStyles = null; var EMPTY_ITEMS = []; var resultsId = 'registry-element-browser-results'; var modalTestId = 'registry-element-browser-modal'; var arrowKeys = new Set(['ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowUp']); var isArrowKey = function isArrowKey(key) { return arrowKeys.has(key); }; var toArrowKeyOption = function toArrowKeyOption(option) { return { getRect: function getRect() { return option.getBoundingClientRect(); }, isDisabled: isOptionDisabled(option) }; }; export var RegistryElementBrowser = function RegistryElementBrowser(_ref) { var _model$fallbackItems, _model$fallbackItems$, _model$fallbackItems2; var components = _ref.components, emptyStateHandler = _ref.emptyStateHandler, fireAnalyticsEvent = _ref.fireAnalyticsEvent, defaultSection = _ref.defaultSection, getRecommendedItems = _ref.getRecommendedItems, helpUrl = _ref.helpUrl, editorView = _ref.editorView, _ref$isLoading = _ref.isLoading, isLoading = _ref$isLoading === void 0 ? false : _ref$isLoading, isOffline = _ref.isOffline, isOpen = _ref.isOpen, onClearSelection = _ref.onClearSelection, onClose = _ref.onClose, onCloseComplete = _ref.onCloseComplete, onConfirmInsert = _ref.onConfirmInsert, onSelect = _ref.onSelect; var _useIntl = useIntl(), formatMessage = _useIntl.formatMessage; var _useState = useState(function () { return { isOpen: isOpen, menuOpenId: Symbol('element-browser') }; }), _useState2 = _slicedToArray(_useState, 2), session = _useState2[0], setSession = _useState2[1]; if (session.isOpen !== isOpen) { setSession({ isOpen: isOpen, menuOpenId: isOpen ? Symbol('element-browser') : session.menuOpenId }); } var surfaceContext = useMemo(function () { return createRegisteredMenuSurfaceContext({ menuOpenId: session.menuOpenId, surface: 'element-browser' }); }, [session.menuOpenId]); var _useState3 = useState({ query: '', section: defaultSection }), _useState4 = _slicedToArray(_useState3, 2), browserState = _useState4[0], setBrowserState = _useState4[1]; var query = browserState.query, section = browserState.section, selectedRegistration = browserState.selectedRegistration; var selectedRegistrationRef = useRef(undefined); // Recommendations depend only on the registry, so typing a query does not re-run the resolver. var recommendedRegistrations = useMemo(function () { return getRecommendedItems === null || getRecommendedItems === void 0 ? void 0 : getRecommendedItems({ components: components, surfaceContext: surfaceContext }); }, [components, getRecommendedItems, surfaceContext]); var browserModel = useMemo(function () { var model = createRegistryElementBrowserModel(components, { footerKey: getMenuFooterSectionKey(MENU.key), surfaceContext: surfaceContext, overflowKeys: new Set(components.filter(function (component) { return component.type === 'menu-section'; }).map(function (component) { return getSectionOverflowItemKey(component.key); })), recommendedRegistrations: recommendedRegistrations, recommendedSectionKey: RECOMMENDED_SECTION.key, rootKey: MENU.key }); var effectiveSection = section && model.sections.some(function (_ref2) { var key = _ref2.key; return key === section; }) ? section : undefined; return _objectSpread({ model: model, effectiveSection: effectiveSection }, getRegistryElementBrowserItems({ formatMessage: formatMessage, model: model, query: query, section: effectiveSection })); }, [components, formatMessage, query, recommendedRegistrations, section, surfaceContext]); var model = browserModel.model, effectiveSection = browserModel.effectiveSection, items = browserModel.items, recommendedItems = browserModel.recommendedItems; var resultItems = useMemo(function () { return [].concat(_toConsumableArray(recommendedItems), _toConsumableArray(items)); }, [items, recommendedItems]); var visibleItems = resultItems.length === 0 && !isLoading && query.trim().length > 0 ? (_model$fallbackItems = model.fallbackItems) !== null && _model$fallbackItems !== void 0 ? _model$fallbackItems : EMPTY_ITEMS : resultItems; var isSelectedRegistrationCurrent = Boolean(selectedRegistration && browserState.selectionIsOffline === isOffline && visibleItems.some(function (_ref3) { var registration = _ref3.registration; return registration === selectedRegistration; })); var selectedKey = isSelectedRegistrationCurrent ? selectedRegistration === null || selectedRegistration === void 0 ? void 0 : selectedRegistration.key : undefined; var invalidSection = Boolean(section && section !== effectiveSection); var invalidSelection = Boolean(selectedRegistration && !isSelectedRegistrationCurrent); if (isOpen && (invalidSection || invalidSelection)) { setBrowserState(_objectSpread(_objectSpread({}, browserState), {}, { section: effectiveSection, selectedRegistration: undefined, selectionIsOffline: undefined, invalidation: { section: invalidSection ? section : undefined, selection: invalidSelection } })); } var searchInputRef = useRef(null); var selectedCategoryRef = useRef(null); var focusedCategoryRef = useRef(); var handledInvalidationRef = useRef(browserState.invalidation); var wasOpen = useRef(false); useEffect(function () { if (isOpen && !wasOpen.current) { selectedRegistrationRef.current = undefined; setBrowserState({ query: '', section: getInitialRegistryElementBrowserSection(model, defaultSection), selectedRegistration: undefined }); onClearSelection(); } wasOpen.current = isOpen; }, [defaultSection, isOpen, model, onClearSelection]); useEffect(function () { var _searchInputRef$curre; var invalidation = browserState.invalidation; if (!isOpen || !invalidation || handledInvalidationRef.current === invalidation) { return; } handledInvalidationRef.current = invalidation; selectedRegistrationRef.current = undefined; onClearSelection(); var ownerDocument = (_searchInputRef$curre = searchInputRef.current) === null || _searchInputRef$curre === void 0 ? void 0 : _searchInputRef$curre.ownerDocument; var activeElement = ownerDocument === null || ownerDocument === void 0 ? void 0 : ownerDocument.activeElement; if (invalidation.section && focusedCategoryRef.current === invalidation.section && activeElement === (ownerDocument === null || ownerDocument === void 0 ? void 0 : ownerDocument.body)) { var _selectedCategoryRef$; (_selectedCategoryRef$ = selectedCategoryRef.current) === null || _selectedCategoryRef$ === void 0 || _selectedCategoryRef$.focus(); } else if (invalidation.selection && (activeElement === (ownerDocument === null || ownerDocument === void 0 ? void 0 : ownerDocument.body) || activeElement !== null && activeElement !== void 0 && activeElement.closest('[role="option"]'))) { var _searchInputRef$curre2; (_searchInputRef$curre2 = searchInputRef.current) === null || _searchInputRef$curre2 === void 0 || _searchInputRef$curre2.focus(); } }, [browserState.invalidation, isOpen, onClearSelection]); var resultCount = resultItems.length || (!isLoading && query.trim() ? (_model$fallbackItems$ = (_model$fallbackItems2 = model.fallbackItems) === null || _model$fallbackItems2 === void 0 ? void 0 : _model$fallbackItems2.length) !== null && _model$fallbackItems$ !== void 0 ? _model$fallbackItems$ : 0 : 0); var flushSearchAnalytics = useQuickInsertSearchAnalytics({ query: query, resultCount: resultItems.length, surface: 'element-browser', category: effectiveSection !== null && effectiveSection !== void 0 ? effectiveSection : 'all', isOpen: isOpen && !isLoading, fireAnalyticsEvent: fireAnalyticsEvent }); var closeBrowser = useCallback(function () { flushSearchAnalytics(); onClose(); }, [flushSearchAnalytics, onClose]); var onSearchFocus = useCallback(function () { focusedCategoryRef.current = undefined; }, []); var onBrowserFocusCapture = useCallback(function (event) { var _category$dataset$sec; var categories = event.currentTarget.querySelector('[data-testid="registry-element-browser-categories"]'); var category = event.target instanceof HTMLElement && categories !== null && categories !== void 0 && categories.contains(event.target) ? event.target.closest('[data-section]') : null; focusedCategoryRef.current = category ? (_category$dataset$sec = category.dataset.section) !== null && _category$dataset$sec !== void 0 ? _category$dataset$sec : '' : undefined; }, []); var onSearchChange = useCallback(function (event) { var nextQuery = event.currentTarget.value; selectedRegistrationRef.current = undefined; setBrowserState(function (current) { return _objectSpread(_objectSpread({}, current), {}, { query: nextQuery, selectedRegistration: undefined }); }); onClearSelection(); }, [onClearSelection]); var onSectionClick = useCallback(function (event) { var nextSection = event.currentTarget.dataset.section || undefined; selectedRegistrationRef.current = undefined; setBrowserState({ query: '', section: nextSection, selectedRegistration: undefined }); onClearSelection(); }, [onClearSelection]); var onRegistrySelect = useCallback(function (key, handler) { var _model$items$find; var registration = (_model$items$find = model.items.find(function (_ref4) { var registration = _ref4.registration; return registration.key === key; })) === null || _model$items$find === void 0 ? void 0 : _model$items$find.registration; selectedRegistrationRef.current = registration; setBrowserState(function (current) { return _objectSpread(_objectSpread({}, current), {}, { selectedRegistration: registration, selectionIsOffline: isOffline }); }); onSelect(handler); }, [isOffline, model.items, onSelect]); var confirmInsert = useCallback(function () { var selectedRegistration = selectedRegistrationRef.current; if (!selectedRegistration || !visibleItems.some(function (_ref5) { var registration = _ref5.registration; return registration === selectedRegistration; })) { return; } onConfirmInsert(); }, [onConfirmInsert, visibleItems]); var onModalOpenComplete = useCallback(function (node) { node.style.borderRadius = "var(--ds-radius-large, 8px)"; var footer = node.querySelector("[data-testid=\"".concat(modalTestId, "--footer\"]")); footer === null || footer === void 0 || footer.style.setProperty('box-sizing', 'border-box'); footer === null || footer === void 0 || footer.style.setProperty('height', '64px'); footer === null || footer === void 0 || footer.style.setProperty('padding-block', "var(--ds-space-200, 16px)"); var scrollableBody = node.querySelector("[data-testid=\"".concat(modalTestId, "--scrollable\"]")); scrollableBody === null || scrollableBody === void 0 || scrollableBody.style.setProperty('border-block-start', 'none'); scrollableBody === null || scrollableBody === void 0 || scrollableBody.style.setProperty('border-block-end', 'none'); }, []); var onKeyDown = useCallback(function (event) { if (event.key === 'Tab' && event.shiftKey && event.target instanceof HTMLElement && event.target.getAttribute('role') === 'option') { var _selectedCategoryRef$2; event.preventDefault(); (_selectedCategoryRef$2 = selectedCategoryRef.current) === null || _selectedCategoryRef$2 === void 0 || _selectedCategoryRef$2.focus(); return; } if (isArrowKey(event.key)) { var _resultCollection$que; if (event.currentTarget === searchInputRef.current && (event.key === 'ArrowLeft' || event.key === 'ArrowRight')) { return; } var resultCollection = event.currentTarget.ownerDocument.getElementById(resultsId); var options = Array.from((_resultCollection$que = resultCollection === null || resultCollection === void 0 ? void 0 : resultCollection.querySelectorAll('[role="option"]')) !== null && _resultCollection$que !== void 0 ? _resultCollection$que : []); var currentIndex = options.findIndex(function (option) { return option === event.currentTarget.ownerDocument.activeElement; }); var targetIndex = getArrowKeyTargetIndex(options.map(toArrowKeyOption), currentIndex, event.key); var nextOption = targetIndex === undefined ? undefined : options[targetIndex]; if (nextOption) { event.preventDefault(); nextOption.focus(); nextOption.click(); } else if (targetIndex === RETURN_TO_SEARCH) { var _searchInputRef$curre3; event.preventDefault(); (_searchInputRef$curre3 = searchInputRef.current) === null || _searchInputRef$curre3 === void 0 || _searchInputRef$curre3.focus(); } return; } if (event.key === 'Escape') { event.preventDefault(); closeBrowser(); return; } if (event.key === 'Enter') { var focusedOption = event.target instanceof HTMLElement ? event.target.closest('[role="option"]') : null; var firstOption = getFirstEnabledOption(event.currentTarget.ownerDocument.getElementById(resultsId)); var optionToActivate = focusedOption !== null && focusedOption !== void 0 ? focusedOption : firstOption; if (isSelectedRegistrationCurrent || optionToActivate) { event.preventDefault(); if (!isSelectedRegistrationCurrent) { optionToActivate === null || optionToActivate === void 0 || optionToActivate.click(); } confirmInsert(); } } }, [closeBrowser, confirmInsert, isSelectedRegistrationCurrent]); return /*#__PURE__*/React.createElement(ModalTransition, null, isOpen && /*#__PURE__*/React.createElement(Modal, { label: formatMessage({ defaultMessage: 'Insert elements', id: 'editor.quick-insert.title' }), height: "804px", onClose: closeBrowser, onCloseComplete: onCloseComplete, onOpenComplete: onModalOpenComplete, testId: modalTestId, width: "min(1072px, calc(100vw - 32px))" }, /*#__PURE__*/React.createElement(ModalHeader, null, /*#__PURE__*/React.createElement("div", { className: ax(["_zulpv47k _1e0c11p5 _yv0e1xuv _1bsb1osq _1ikai47z"]) }, /*#__PURE__*/React.createElement(ModalTitle, null, formatMessage({ defaultMessage: 'Insert elements', id: 'editor.quick-insert.title' })), /*#__PURE__*/React.createElement("div", { className: ax(["_zulp1ejb _4cvr1h6o _1e0c11p5 _yv0ehpfy _1bsb1osq"]) }, /*#__PURE__*/React.createElement(Textfield, { autoFocus: true, "aria-controls": resultsId, "aria-label": formatMessage({ defaultMessage: 'Search elements', id: 'editor.quick-insert.search' }), isCompact: true, name: "registry-element-browser-search", onFocus: onSearchFocus, onChange: onSearchChange, onKeyDown: onKeyDown, ref: searchInputRef, value: query }), /*#__PURE__*/React.createElement(IconButton, { appearance: "subtle", icon: CrossIcon, label: formatMessage(messages.close), onClick: closeBrowser, spacing: "compact" })))), /*#__PURE__*/React.createElement(ModalBody, { hasInlinePadding: false }, /*#__PURE__*/React.createElement("div", { onFocusCapture: onBrowserFocusCapture, className: ax(["_zulp1fka _18zrpxbi _1e0c11p5 _yv0e1xuv _1q511b66 _1ikai47z _uzjvutpp"]) }, /*#__PURE__*/React.createElement(RegistryElementBrowserCategories, { section: effectiveSection, sections: model.sections, onSectionClick: onSectionClick, selectedButtonRef: selectedCategoryRef }), /*#__PURE__*/React.createElement(RegistryElementBrowserSearchResults, { editorView: editorView, emptyStateHandler: emptyStateHandler, isLoading: isLoading, isOffline: isOffline, items: items, fallbackItems: model.fallbackItems, query: query, recommendedItems: recommendedItems, resultsId: resultsId, surfaceContext: surfaceContext, section: effectiveSection, selectedKey: selectedKey, onKeyDown: onKeyDown, onSelect: onRegistrySelect }))), /*#__PURE__*/React.createElement(ModalFooter, null, /*#__PURE__*/React.createElement(RegistryElementBrowserFooter, { helpUrl: helpUrl, isInsertEnabled: isSelectedRegistrationCurrent, onClose: closeBrowser, onConfirmInsert: confirmInsert, resultCount: resultCount })))); };