@atlaskit/editor-plugin-table
Version:
Table plugin for the @atlaskit/editor
881 lines (879 loc) • 34.6 kB
JavaScript
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);