UNPKG

@atlaskit/editor-plugin-type-ahead

Version:

Type-ahead plugin for @atlaskit/editor-core

192 lines (187 loc) 7.57 kB
import { TypeAheadAvailableNodes, typeAheadListMessages } from '@atlaskit/editor-common/type-ahead'; import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments'; import { updateSelectedIndex } from './commands/update-selected-index'; import { itemIsDisabled } from './item-is-disabled'; import { pluginKey as typeAheadPluginKey } from './key'; import { StatsModifier } from './stats-modifier'; export const isTypeAheadHandler = handler => { return handler && Object.values(TypeAheadAvailableNodes).includes(handler.id) && typeof handler.trigger === 'string' && typeof handler.selectItem === 'function' && typeof handler.getItems === 'function'; }; /** Is a typeahead plugin open? */ export const isTypeAheadOpen = editorState => { var _typeAheadPluginKey$g, _typeAheadPluginKey$g2; return !!(typeAheadPluginKey !== null && typeAheadPluginKey !== void 0 && (_typeAheadPluginKey$g = typeAheadPluginKey.getState(editorState)) !== null && _typeAheadPluginKey$g !== void 0 && (_typeAheadPluginKey$g2 = _typeAheadPluginKey$g.decorationSet) !== null && _typeAheadPluginKey$g2 !== void 0 && _typeAheadPluginKey$g2.find().length); }; export const getPluginState = editorState => { return typeAheadPluginKey.getState(editorState); }; export const getTypeAheadHandler = editorState => { var _typeAheadPluginKey$g3; return (_typeAheadPluginKey$g3 = typeAheadPluginKey.getState(editorState)) === null || _typeAheadPluginKey$g3 === void 0 ? void 0 : _typeAheadPluginKey$g3.triggerHandler; }; export const getTypeAheadQuery = editorState => { var _typeAheadPluginKey$g4; return (_typeAheadPluginKey$g4 = typeAheadPluginKey.getState(editorState)) === null || _typeAheadPluginKey$g4 === void 0 ? void 0 : _typeAheadPluginKey$g4.query; }; export const isTypeAheadAllowed = state => { const isOpen = isTypeAheadOpen(state); // if the TypeAhead is open // we should not allow it return !isOpen; }; export const findHandler = (id, state) => { const pluginState = typeAheadPluginKey.getState(state); if (!pluginState || !pluginState.typeAheadHandlers || pluginState.typeAheadHandlers.length === 0) { return null; } const { typeAheadHandlers } = pluginState; return typeAheadHandlers.find(h => h.id === id) || null; }; export const skipForwardToSafeItem = ({ currentIndex, nextIndex, listSize, itemIsDisabled }) => { // Use a loop to find the next selectable item for (let idx = nextIndex; idx < listSize; idx++) { if (!itemIsDisabled(idx)) { return idx; } } // We got to the end of the list ^, now try from the start if (editorExperiment('platform_editor_offline_editing_web', true)) { for (let idx = 0; idx < nextIndex; idx++) { if (!itemIsDisabled(idx)) { return idx; } } } // If no selectable items are found, return currentIndex return currentIndex; }; export const skipBackwardToSafeItem = ({ currentIndex, nextIndex, listSize, itemIsDisabled }) => { // Use a loop to find the next non-selectable item when going backwards for (let idx = nextIndex; idx >= 0; idx--) { if (!itemIsDisabled(idx)) { return idx; } } // We got to the start of the list ^, now try from the end if (editorExperiment('platform_editor_offline_editing_web', true)) { for (let idx = listSize; idx > nextIndex; idx--) { if (!itemIsDisabled(idx)) { return idx; } } } // If no non-selectable items are found, return currentIndex return currentIndex; }; export const findHandlerByTrigger = ({ trigger, editorState }) => { const pluginState = typeAheadPluginKey.getState(editorState); if (!pluginState || !pluginState.typeAheadHandlers || pluginState.typeAheadHandlers.length === 0) { return null; } const { typeAheadHandlers } = pluginState; return typeAheadHandlers.find(h => h.trigger === trigger) || null; }; export const moveSelectedIndex = ({ editorView, direction, api }) => () => { const typeAheadState = getPluginState(editorView.state); if (!typeAheadState) { return; } const { selectedIndex, items } = typeAheadState; const stats = typeAheadState.stats instanceof StatsModifier ? typeAheadState.stats : new StatsModifier(); let nextIndex; const isDisabled = idx => itemIsDisabled(items[idx], api); if (direction === 'next') { stats.increaseArrowDown(); /** * See: https://product-fabric.atlassian.net/browse/ED-17200 * `selectedIndex` is forced to -1 now to not immediately focus the typeahead * and only do so when there is explicit logic to focus into the typeahead * options. * * This check for "set index to 1 when -1" * - is a temporary workaround to get back the previous behaviour without * entirely reverting the a11y improvements * */ if (selectedIndex === -1 && items.length > 1) { // If the first item is disabled we actually want to skip to the 3rd item // on the first arrow down nextIndex = isDisabled(0) && items.length > 2 ? 2 : 1; } else { nextIndex = selectedIndex >= items.length - 1 ? 0 : selectedIndex + 1; } nextIndex = skipForwardToSafeItem({ currentIndex: selectedIndex, nextIndex, listSize: items.length, itemIsDisabled: isDisabled }); } else { stats.increaseArrowUp(); nextIndex = selectedIndex <= 0 ? items.length - 1 : selectedIndex - 1; nextIndex = skipBackwardToSafeItem({ currentIndex: selectedIndex, nextIndex, listSize: items.length, itemIsDisabled: isDisabled }); } updateSelectedIndex(nextIndex, api)(editorView.state, editorView.dispatch); }; export const getTypeAheadListAriaLabels = (trigger, intl, item) => { var _item$mention, _item$mention2, _item$emoji, _item$emoji2, _item$emoji3; switch (trigger) { case '@': return { popupAriaLabel: intl.formatMessage(typeAheadListMessages.mentionPopupLabel), listItemAriaLabel: intl.formatMessage(typeAheadListMessages.metionListItemLabel, { name: (item === null || item === void 0 ? void 0 : (_item$mention = item.mention) === null || _item$mention === void 0 ? void 0 : _item$mention.name) || '', shortName: (item === null || item === void 0 ? void 0 : (_item$mention2 = item.mention) === null || _item$mention2 === void 0 ? void 0 : _item$mention2.mentionName) || '' }) }; case '/': return { popupAriaLabel: intl.formatMessage(typeAheadListMessages.quickInsertPopupLabel), listItemAriaLabel: intl.formatMessage(typeAheadListMessages.emojiListItemLabel, { name: (item === null || item === void 0 ? void 0 : item.title) || '', shortcut: (item === null || item === void 0 ? void 0 : (_item$emoji = item.emoji) === null || _item$emoji === void 0 ? void 0 : _item$emoji.shortName) || '' }) }; case ':': return { popupAriaLabel: intl.formatMessage(typeAheadListMessages.emojiPopupLabel), listItemAriaLabel: intl.formatMessage(typeAheadListMessages.emojiListItemLabel, { name: (item === null || item === void 0 ? void 0 : (_item$emoji2 = item.emoji) === null || _item$emoji2 === void 0 ? void 0 : _item$emoji2.name) || '', shortcut: (item === null || item === void 0 ? void 0 : (_item$emoji3 = item.emoji) === null || _item$emoji3 === void 0 ? void 0 : _item$emoji3.shortName) || '' }) }; default: return { popupAriaLabel: intl.formatMessage(typeAheadListMessages.typeAheadPopupLabel) }; } };