UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

881 lines (879 loc) 34.6 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; /* eslint-disable @atlaskit/design-system/prefer-primitives */ /** * @jsxRuntime classic * @jsx jsx */ import React, { Component } from 'react'; // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766 import { jsx } from '@emotion/react'; import { injectIntl } from 'react-intl'; import { TableSortOrder as SortOrder } from '@atlaskit/custom-steps'; import { INPUT_METHOD } from '@atlaskit/editor-common/analytics'; import { addColumnAfter, addRowAfter, backspace, tooltip } from '@atlaskit/editor-common/keymaps'; import { tableMessages as messages } from '@atlaskit/editor-common/messages'; import { DropdownMenuSharedCssClassName } from '@atlaskit/editor-common/styles'; import { backgroundPaletteTooltipMessages, cellBackgroundColorPalette, ColorPalette, getSelectedRowAndColumnFromPalette } from '@atlaskit/editor-common/ui-color'; import { ArrowKeyNavigationProvider, ArrowKeyNavigationType, DropdownMenu } from '@atlaskit/editor-common/ui-menu'; import { UserIntentPopupWrapper } from '@atlaskit/editor-common/user-intent'; import { closestElement } from '@atlaskit/editor-common/utils'; import { hexToEditorBackgroundPaletteColor } from '@atlaskit/editor-palette'; import { shortcutStyle } from '@atlaskit/editor-shared-styles/shortcut'; import { splitCell } from '@atlaskit/editor-tables/utils'; import PaintBucketIcon from '@atlaskit/icon/core/paint-bucket'; import TableCellClearIcon from '@atlaskit/icon/core/table-cell-clear'; import TableCellMergeIcon from '@atlaskit/icon/core/table-cell-merge'; import TableCellSplitIcon from '@atlaskit/icon/core/table-cell-split'; import TableColumnAddRightIcon from '@atlaskit/icon/core/table-column-add-right'; import TableColumnDeleteIcon from '@atlaskit/icon/core/table-column-delete'; import TableColumnsDistributeIcon from '@atlaskit/icon/core/table-columns-distribute'; import TableRowAddBelowIcon from '@atlaskit/icon/core/table-row-add-below'; import TableRowDeleteIcon from '@atlaskit/icon/core/table-row-delete'; // eslint-disable-next-line @atlaskit/design-system/no-emotion-primitives -- to be migrated to @atlaskit/primitives/compiled – go/akcss import { Box, xcss } from '@atlaskit/primitives'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments'; import { clearHoverSelection, hoverColumns, hoverMergedCells, hoverRows, setFocusToCellMenu, toggleContextualMenu } from '../../pm-plugins/commands'; import { deleteColumnsWithAnalytics, deleteRowsWithAnalytics, distributeColumnsWidthsWithAnalytics, emptyMultipleCellsWithAnalytics, insertColumnWithAnalytics, insertRowWithAnalytics, mergeCellsWithAnalytics, setColorWithAnalytics, sortColumnWithAnalytics, splitCellWithAnalytics } from '../../pm-plugins/commands/commands-with-analytics'; import { getPluginState } from '../../pm-plugins/plugin-factory'; import { pluginKey as tablePluginKey } from '../../pm-plugins/plugin-key'; import { getNewResizeStateFromSelectedColumns } from '../../pm-plugins/table-resizing/utils/resize-state'; import { canMergeCells } from '../../pm-plugins/transforms/merge'; import { getSelectedColumnIndexes, getSelectedRowIndexes } from '../../pm-plugins/utils/selection'; import { getMergedCellsPositions } from '../../pm-plugins/utils/table'; import { TableCssClassName as ClassName } from '../../types'; import { colorPalletteColumns, contextualMenuDropdownWidth, contextualMenuDropdownWidthDnD } from '../consts'; import { cellColourPreviewStyles } from './styles'; const arrowsList = new Set(!expValEquals('platform_editor_toolbar_submenu_open_click', 'isEnabled', true) ? ['ArrowRight', 'ArrowLeft'] : ['ArrowRight']); const elementBeforeIconStyles = xcss({ marginRight: 'space.negative.075', display: 'flex' }); // eslint-disable-next-line @repo/internal/react/no-class-components export class ContextualMenu extends Component { constructor(...args) { super(...args); _defineProperty(this, "state", { isSubmenuOpen: false, isOpenAllowed: false }); _defineProperty(this, "dropdownMenuRef", /*#__PURE__*/React.createRef()); _defineProperty(this, "handleSubMenuRef", ref => { // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting const dom = this.props.editorView.dom; const parent = closestElement(dom, '.fabric-editor-popup-scroll-parent') || closestElement(dom, '.ak-editor-content-area'); if (!(parent && ref)) { return; } const boundariesRect = parent.getBoundingClientRect(); const rect = ref.getBoundingClientRect(); if (!!this.props.mountPoint) { return; } const offsetParent = ref === null || ref === void 0 ? void 0 : ref.offsetParent; if (!offsetParent) { return; } const offsetParentRect = offsetParent.getBoundingClientRect(); const rightOverflow = offsetParentRect.right + rect.width - boundariesRect.right; const leftOverflow = boundariesRect.left - (offsetParentRect.left - rect.width); if (rightOverflow > leftOverflow) { ref.style.left = `-${rect.width}px`; } // if it overflows regardless of side, let it overlap with the parent menu if (leftOverflow > 0 && rightOverflow > 0) { if (rightOverflow < leftOverflow) { ref.style.left = `${offsetParentRect.width - rightOverflow}px`; } else { ref.style.left = `-${rect.width - leftOverflow}px`; } } }); _defineProperty(this, "createBackgroundColorItem", () => { const { allowBackgroundColor, editorView: { state }, isOpen, intl: { formatMessage }, editorView, isCellMenuOpenByKeyboard } = this.props; const { isSubmenuOpen } = this.state; const { targetCellPosition, isDragAndDropEnabled } = getPluginState(editorView.state); if (allowBackgroundColor) { var _node$attrs, _node$attrs2; const node = isOpen && targetCellPosition ? state.doc.nodeAt(targetCellPosition) : null; const background = hexToEditorBackgroundPaletteColor((node === null || node === void 0 ? void 0 : (_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.background) || '#ffffff'); const selectedRowAndColumnFromPalette = getSelectedRowAndColumnFromPalette(cellBackgroundColorPalette, // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion background, colorPalletteColumns); const selectedRowIndex = selectedRowAndColumnFromPalette.selectedRowIndex; const selectedColumnIndex = selectedRowAndColumnFromPalette.selectedColumnIndex; return { content: isDragAndDropEnabled ? formatMessage(messages.backgroundColor) : formatMessage(messages.cellBackground), value: { name: 'background' }, elemBefore: isDragAndDropEnabled ? jsx(Box, { xcss: elementBeforeIconStyles }, jsx(PaintBucketIcon, { color: "currentColor", spacing: "spacious", label: formatMessage(messages.backgroundColor) })) : undefined, elemAfter: // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766 jsx("div", { className: DropdownMenuSharedCssClassName.SUBMENU }, jsx("div", { // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 css: cellColourPreviewStyles(background) // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766 , className: isDragAndDropEnabled ? ClassName.CONTEXTUAL_MENU_ICON_SMALL : ClassName.CONTEXTUAL_MENU_ICON }), isSubmenuOpen && jsx("div", { // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766 className: ClassName.CONTEXTUAL_SUBMENU, ref: this.handleSubMenuRef }, jsx(ArrowKeyNavigationProvider, { type: ArrowKeyNavigationType.COLOR, selectedRowIndex: selectedRowIndex || 0, selectedColumnIndex: selectedColumnIndex || 0 // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , handleClose: () => { this.setState({ isSubmenuOpen: false }); if (this.dropdownMenuRef && this.dropdownMenuRef.current) { const focusableItems = this.dropdownMenuRef.current.querySelectorAll('div[tabindex="-1"]:not([disabled])'); if (focusableItems && focusableItems.length) { focusableItems[0].focus(); } } }, isPopupPositioned: true // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion , isOpenedByKeyboard: isCellMenuOpenByKeyboard }, jsx(ColorPalette, { cols: 7, onClick: this.setColor, selectedColor: (node === null || node === void 0 ? void 0 : (_node$attrs2 = node.attrs) === null || _node$attrs2 === void 0 ? void 0 : _node$attrs2.background) || '#ffffff' // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , paletteOptions: { palette: cellBackgroundColorPalette, paletteColorTooltipMessages: backgroundPaletteTooltipMessages, hexToPaletteColor: hexToEditorBackgroundPaletteColor } })))), 'aria-expanded': isSubmenuOpen }; } }); // Used in the NewContextMenuItems object _defineProperty(this, "newDistributeColumnsItem", () => { const { intl: { formatMessage } } = this.props; return this.createDistributeColumnsItemInternal({ elemBefore: jsx(Box, { xcss: elementBeforeIconStyles }, jsx(TableColumnsDistributeIcon, { color: "currentColor", spacing: "spacious", label: formatMessage(messages.distributeColumns) })) }); }); _defineProperty(this, "createMergeSplitCellItems", () => { const { allowMergeCells, editorView: { state }, intl: { formatMessage }, editorView } = this.props; const { isDragAndDropEnabled } = getPluginState(editorView.state); if (allowMergeCells) { return [{ content: formatMessage(messages.mergeCells), value: { name: 'merge' }, isDisabled: !canMergeCells(state.tr), elemBefore: isDragAndDropEnabled ? jsx(Box, { xcss: elementBeforeIconStyles }, jsx(TableCellMergeIcon, { color: "currentColor", spacing: "spacious", label: formatMessage(messages.mergeCells) })) : undefined }, { content: formatMessage(messages.splitCell), value: { name: 'split' }, isDisabled: !splitCell(state), elemBefore: isDragAndDropEnabled ? jsx(Box, { xcss: elementBeforeIconStyles }, jsx(TableCellSplitIcon, { color: "currentColor", spacing: "spacious", label: formatMessage(messages.splitCell) })) : undefined }]; } return []; }); _defineProperty(this, "createInsertColumnItem", () => { const { intl: { formatMessage }, editorView } = this.props; const { isDragAndDropEnabled } = getPluginState(editorView.state); const content = formatMessage(isDragAndDropEnabled ? messages.addColumnRight : messages.insertColumn); return { content, value: { name: 'insert_column' }, // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 elemAfter: jsx("div", { css: shortcutStyle }, tooltip(addColumnAfter)), elemBefore: isDragAndDropEnabled ? // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 jsx(Box, { xcss: elementBeforeIconStyles }, jsx(TableColumnAddRightIcon, { color: "currentColor", spacing: "spacious", label: formatMessage(messages.addColumnRight) })) : undefined, 'aria-label': tooltip(addColumnAfter, String(content)) }; }); _defineProperty(this, "createInsertRowItem", () => { const { intl: { formatMessage }, editorView } = this.props; const { isDragAndDropEnabled } = getPluginState(editorView.state); const content = formatMessage(isDragAndDropEnabled ? messages.addRowBelow : messages.insertRow); return { content, value: { name: 'insert_row' }, // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 elemAfter: jsx("div", { css: shortcutStyle }, tooltip(addRowAfter)), elemBefore: isDragAndDropEnabled ? jsx(Box, { xcss: elementBeforeIconStyles }, jsx(TableRowAddBelowIcon, { color: "currentColor", spacing: "spacious", label: formatMessage(messages.addRowBelow) })) : undefined, 'aria-label': tooltip(addRowAfter, String(content)) }; }); _defineProperty(this, "createClearCellsItem", () => { const { selectionRect, intl: { formatMessage }, editorView } = this.props; const { isDragAndDropEnabled } = getPluginState(editorView.state); const { top, bottom, right, left } = selectionRect; const noOfColumns = right - left; const noOfRows = bottom - top; const content = formatMessage(messages.clearCells, { 0: Math.max(noOfColumns, noOfRows) }); return { content, value: { name: 'clear' }, // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 elemAfter: jsx("div", { css: shortcutStyle }, tooltip(backspace)), elemBefore: isDragAndDropEnabled ? jsx(Box, { xcss: elementBeforeIconStyles }, jsx(TableCellClearIcon, { color: "currentColor", spacing: "spacious", label: formatMessage(messages.clearCells, { 0: Math.max(noOfColumns, noOfRows) }) })) : undefined, 'aria-label': tooltip(backspace, String(content)) }; }); _defineProperty(this, "createDeleteColumnItem", () => { const { selectionRect, intl: { formatMessage }, editorView } = this.props; const { isDragAndDropEnabled } = getPluginState(editorView.state); const { right, left } = selectionRect; const noOfColumns = right - left; return { content: formatMessage(messages.removeColumns, { 0: noOfColumns }), value: { name: 'delete_column' }, elemBefore: isDragAndDropEnabled ? jsx(Box, { xcss: elementBeforeIconStyles }, jsx(TableColumnDeleteIcon, { color: "currentColor", spacing: "spacious", label: formatMessage(messages.removeColumns, { 0: noOfColumns }) })) : undefined }; }); _defineProperty(this, "createDeleteRowItem", () => { const { selectionRect, intl: { formatMessage }, editorView } = this.props; const { isDragAndDropEnabled } = getPluginState(editorView.state); const { bottom, top } = selectionRect; const noOfRows = bottom - top; return { content: formatMessage(messages.removeRows, { 0: noOfRows }), value: { name: 'delete_row' }, elemBefore: isDragAndDropEnabled ? jsx(Box, { xcss: elementBeforeIconStyles }, jsx(TableRowDeleteIcon, { color: "currentColor", spacing: "spacious", label: formatMessage(messages.removeRows, { 0: noOfRows }) })) : undefined }; }); _defineProperty(this, "createDistributeColumnsItemInternal", partialMenuItem => { var _newResizeState$chang; const { selectionRect, editorView, getEditorContainerWidth, getEditorFeatureFlags, intl: { formatMessage } } = this.props; const { isTableScalingEnabled = false } = getPluginState(editorView.state); const { tableWithFixedColumnWidthsOption = false } = getEditorFeatureFlags ? getEditorFeatureFlags() : {}; const newResizeState = getNewResizeStateFromSelectedColumns(selectionRect, editorView.state, editorView.domAtPos.bind(editorView), getEditorContainerWidth, isTableScalingEnabled, tableWithFixedColumnWidthsOption); const wouldChange = (_newResizeState$chang = newResizeState === null || newResizeState === void 0 ? void 0 : newResizeState.changed) !== null && _newResizeState$chang !== void 0 ? _newResizeState$chang : false; return { content: formatMessage(messages.distributeColumns), value: { name: 'distribute_columns' }, isDisabled: !wouldChange, ...partialMenuItem }; }); _defineProperty(this, "createDistributeColumnsItem", () => { const { editorView } = this.props; const { isDragAndDropEnabled, pluginConfig: { allowDistributeColumns } } = getPluginState(editorView.state); if (allowDistributeColumns && !isDragAndDropEnabled) { return this.createDistributeColumnsItemInternal(); } return null; }); _defineProperty(this, "createSortColumnItems", () => { const { intl: { formatMessage }, editorView, allowColumnSorting } = this.props; const { isDragAndDropEnabled } = getPluginState(editorView.state); if (allowColumnSorting && !isDragAndDropEnabled) { const hasMergedCellsInTable = getMergedCellsPositions(editorView.state.tr).length > 0; const warning = hasMergedCellsInTable ? { tooltipDescription: formatMessage(messages.canNotSortTable), isDisabled: true } : {}; return [{ content: formatMessage(messages.sortColumnASC), value: { name: 'sort_column_asc' }, ...warning }, { content: formatMessage(messages.sortColumnDESC), value: { name: 'sort_column_desc' }, ...warning }]; } return null; }); _defineProperty(this, "createOriginalContextMenuItems", () => { const items = []; const sortColumnItems = this.createSortColumnItems(); const backgroundColorItem = this.createBackgroundColorItem(); const distributeColumnsItem = this.createDistributeColumnsItem(); sortColumnItems && items.push(...sortColumnItems); backgroundColorItem && items.push(backgroundColorItem); items.push(this.createInsertColumnItem()); items.push(this.createInsertRowItem()); items.push(this.createDeleteColumnItem()); items.push(this.createDeleteRowItem()); items.push(...this.createMergeSplitCellItems()); distributeColumnsItem && items.push(distributeColumnsItem); items.push(this.createClearCellsItem()); return [{ items }]; }); _defineProperty(this, "createNewContextMenuItems", () => { const backgroundColorItem = this.createBackgroundColorItem(); const mergeSplitCellItems = this.createMergeSplitCellItems(); const insertColumnItem = this.createInsertColumnItem(); const insertRowItem = this.createInsertRowItem(); const clearCellsItem = this.createClearCellsItem(); const deleteColumnItem = this.createDeleteColumnItem(); const deleteRowItem = this.createDeleteRowItem(); // Group items so when table.menu.group-items FF is enabled, a divider shows under split cell, above add column const items = [{ items: [] }, { items: [] }]; backgroundColorItem && items[0].items.push(backgroundColorItem); items[0].items.push(...mergeSplitCellItems); items[1].items.push(insertColumnItem); items[1].items.push(insertRowItem); if (editorExperiment('platform_editor_controls', 'variant1')) { items[1].items.push(this.newDistributeColumnsItem()); } items[1].items.push(clearCellsItem); items[1].items.push(deleteColumnItem); items[1].items.push(deleteRowItem); return items; }); _defineProperty(this, "onMenuItemActivated", ({ item }) => { const { editorView, selectionRect, editorAnalyticsAPI, getEditorContainerWidth, getEditorFeatureFlags, isCellMenuOpenByKeyboard, isCommentEditor } = this.props; // TargetCellPosition could be outdated: https://product-fabric.atlassian.net/browse/ED-8129 const { state, dispatch } = editorView; const { targetCellPosition, isTableScalingEnabled = false } = getPluginState(state); const { tableWithFixedColumnWidthsOption = false } = getEditorFeatureFlags ? getEditorFeatureFlags() : {}; // context menu opened by keyboard and any item except 'background' activated // or color has been chosen from color palette if (isCellMenuOpenByKeyboard && (item.value.name !== 'background' || item.value.name === 'background' && this.state.isSubmenuOpen)) { const { tr } = state; tr.setMeta(tablePluginKey, { type: 'SET_CELL_MENU_OPEN', data: { isCellMenuOpenByKeyboard: false } }); dispatch(tr); editorView.dom.focus(); // otherwise cursor disappears from cell } const shouldUseIncreasedScalingPercent = isTableScalingEnabled && (tableWithFixedColumnWidthsOption || // When in comment editor, we need the scaling percent to be 40% while tableWithFixedColumnWidthsOption is not visible isCommentEditor); switch (item.value.name) { case 'sort_column_desc': sortColumnWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.CONTEXT_MENU, selectionRect.left, SortOrder.DESC)(state, dispatch); this.toggleOpen(); break; case 'sort_column_asc': sortColumnWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.CONTEXT_MENU, selectionRect.left, SortOrder.ASC)(state, dispatch); this.toggleOpen(); break; case 'merge': mergeCellsWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.CONTEXT_MENU)(state, dispatch); this.toggleOpen(); break; case 'split': splitCellWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.CONTEXT_MENU)(state, dispatch); this.toggleOpen(); break; case 'distribute_columns': const newResizeStateWithAnalytics = getNewResizeStateFromSelectedColumns(selectionRect, state, editorView.domAtPos.bind(editorView), getEditorContainerWidth, isTableScalingEnabled, tableWithFixedColumnWidthsOption, isCommentEditor); if (newResizeStateWithAnalytics) { distributeColumnsWidthsWithAnalytics(editorAnalyticsAPI, this.props.api)(INPUT_METHOD.CONTEXT_MENU, newResizeStateWithAnalytics)(state, dispatch); this.toggleOpen(); } break; case 'clear': emptyMultipleCellsWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.CONTEXT_MENU, targetCellPosition)(state, dispatch); this.toggleOpen(); break; case 'insert_column': insertColumnWithAnalytics(this.props.api, editorAnalyticsAPI, isTableScalingEnabled, tableWithFixedColumnWidthsOption, shouldUseIncreasedScalingPercent, isCommentEditor)(INPUT_METHOD.CONTEXT_MENU, selectionRect.right)(state, dispatch, editorView); this.toggleOpen(); break; case 'insert_row': insertRowWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.CONTEXT_MENU, { index: selectionRect.bottom, moveCursorToInsertedRow: true })(state, dispatch); this.toggleOpen(); break; case 'delete_column': deleteColumnsWithAnalytics(editorAnalyticsAPI, this.props.api, isTableScalingEnabled, tableWithFixedColumnWidthsOption, shouldUseIncreasedScalingPercent, isCommentEditor)(INPUT_METHOD.CONTEXT_MENU, selectionRect)(state, dispatch, editorView); this.toggleOpen(); break; case 'delete_row': const { pluginConfig: { isHeaderRowRequired } } = getPluginState(state); deleteRowsWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.CONTEXT_MENU, selectionRect, !!isHeaderRowRequired)(state, dispatch); this.toggleOpen(); break; case 'background': { if (!expValEquals('platform_editor_toolbar_submenu_open_click', 'isEnabled', true)) { // This is called twice. // 1st time when user chooses the background color item. // 2nd when color has been chosen from color palette. // here we are handling the 1st call relying on the isSubmenuOpen state value if (isCellMenuOpenByKeyboard && !this.state.isSubmenuOpen) { this.setState({ isSubmenuOpen: true }); } } else { this.setState(prevState => ({ isSubmenuOpen: !prevState.isSubmenuOpen })); } break; } } }); _defineProperty(this, "toggleOpen", () => { const { isOpen, editorView: { state, dispatch } } = this.props; toggleContextualMenu()(state, dispatch); if (!isOpen) { this.setState({ isSubmenuOpen: false }); } }); _defineProperty(this, "handleOpenChange", payload => { const { editorView: { state, dispatch, dom }, isCellMenuOpenByKeyboard } = this.props; if (payload) { const { event } = payload; if (event && event instanceof KeyboardEvent) { if (!this.state.isSubmenuOpen) { if (arrowsList.has(event.key)) { // preventing default behavior for avoiding cursor jump to next/previous table column // when left/right arrow pressed. event.preventDefault(); if (expValEquals('platform_editor_toolbar_submenu_open_click', 'isEnabled', true)) { this.setState({ isSubmenuOpen: true }); return; } } toggleContextualMenu()(state, dispatch); this.setState({ isSubmenuOpen: false }); setFocusToCellMenu(false)(state, dispatch); dom.focus(); } } else { // mouse click outside toggleContextualMenu()(state, dispatch); this.setState({ isSubmenuOpen: false }); if (isCellMenuOpenByKeyboard) { setFocusToCellMenu(false)(state, dispatch); } } } }); // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any _defineProperty(this, "handleItemMouseEnter", ({ item }) => { const { editorView: { state, dispatch }, selectionRect } = this.props; if (!expValEquals('platform_editor_toolbar_submenu_open_click', 'isEnabled', true)) { if (item.value.name === 'background') { if (!this.state.isSubmenuOpen) { this.setState({ isSubmenuOpen: true }); } } } if (item.value.name === 'delete_column') { hoverColumns(getSelectedColumnIndexes(selectionRect), true)(state, dispatch); } if (item.value.name === 'delete_row') { hoverRows(getSelectedRowIndexes(selectionRect), true)(state, dispatch); } if (['sort_column_asc', 'sort_column_desc'].indexOf(item.value.name) > -1 && getMergedCellsPositions(state.tr).length !== 0) { hoverMergedCells()(state, dispatch); } }); // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any _defineProperty(this, "handleItemMouseLeave", ({ item }) => { const { state, dispatch } = this.props.editorView; if (!expValEquals('platform_editor_toolbar_submenu_open_click', 'isEnabled', true)) { if (item.value.name === 'background') { this.closeSubmenu(); } } if (['sort_column_asc', 'sort_column_desc', 'delete_column', 'delete_row'].indexOf(item.value.name) > -1) { clearHoverSelection()(state, dispatch); } }); _defineProperty(this, "closeSubmenu", () => { if (this.state.isSubmenuOpen) { this.setState({ isSubmenuOpen: false }); } }); _defineProperty(this, "setColor", color => { const { editorView, editorAnalyticsAPI, isCellMenuOpenByKeyboard } = this.props; const { state, dispatch, dom } = editorView; setColorWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.CONTEXT_MENU, color)(state, dispatch); if (!expValEquals('platform_editor_toolbar_submenu_open_click', 'isEnabled', true)) { this.toggleOpen(); } else { toggleContextualMenu()(state, dispatch); this.setState({ isSubmenuOpen: false }); if (isCellMenuOpenByKeyboard) { setFocusToCellMenu(false)(state, dispatch); dom.focus(); } } }); } componentDidMount() { // ArrowKeyNavigationProvider in DropdownMenu expects that menu handle will stay focused // until user pressed ArrowDown. // Behavior above fails the A11Y requirement about first item in menu should be focused immediately. // so here is triggering componentDidUpdate inside dropdown to set focus on first element const { isCellMenuOpenByKeyboard } = this.props; if (isCellMenuOpenByKeyboard) { this.setState({ ...this.state, isOpenAllowed: isCellMenuOpenByKeyboard }); } } componentDidUpdate() { const { isDragAndDropEnabled, isContextualMenuOpen } = getPluginState(this.props.editorView.state); if (isDragAndDropEnabled && this.props.isDragMenuOpen && isContextualMenuOpen) { toggleContextualMenu()(this.props.editorView.state, this.props.editorView.dispatch); } } render() { const { isOpen, offset, boundariesElement, editorView, isCellMenuOpenByKeyboard, api } = this.props; const { isDragAndDropEnabled } = getPluginState(editorView.state); const items = isDragAndDropEnabled ? this.createNewContextMenuItems() : this.createOriginalContextMenuItems(); let isOpenAllowed = false; isOpenAllowed = isCellMenuOpenByKeyboard ? this.state.isOpenAllowed : isOpen; return jsx(UserIntentPopupWrapper, { userIntent: "tableContextualMenuPopupOpen", api: api }, jsx("div", { "data-testid": "table-cell-contextual-menu", onMouseLeave: expValEquals('platform_editor_toolbar_submenu_open_click', 'isEnabled', true) ? undefined : this.closeSubmenu, ref: this.dropdownMenuRef }, jsx(DropdownMenu //This needs be removed when the a11y is completely handled //Disabling key navigation now as it works only partially // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , { arrowKeyNavigationProviderOptions: { type: ArrowKeyNavigationType.MENU, disableArrowKeyNavigation: !isCellMenuOpenByKeyboard || this.state.isSubmenuOpen }, items: items, isOpen: isOpenAllowed, onOpenChange: this.handleOpenChange, onItemActivated: this.onMenuItemActivated, onMouseEnter: this.handleItemMouseEnter, onMouseLeave: this.handleItemMouseLeave, fitHeight: 188, fitWidth: isDragAndDropEnabled ? contextualMenuDropdownWidthDnD : contextualMenuDropdownWidth // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , shouldFocusFirstItem: () => { return Boolean(isCellMenuOpenByKeyboard); }, boundariesElement: boundariesElement, offset: offset, section: isDragAndDropEnabled ? { hasSeparator: true } : undefined, allowEnterDefaultBehavior: this.state.isSubmenuOpen }))); } } _defineProperty(ContextualMenu, "defaultProps", { boundariesElement: typeof document !== 'undefined' ? document.body : undefined }); export default injectIntl(ContextualMenu);