UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

584 lines (577 loc) 25.1 kB
import _slicedToArray from "@babel/runtime/helpers/slicedToArray"; import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray"; /* eslint-disable @atlaskit/design-system/prefer-primitives */ /** * @jsxRuntime classic * @jsx jsx */ /** @jsxFrag */ import React, { useEffect, useState } 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-next'; import { INPUT_METHOD } from '@atlaskit/editor-common/analytics'; 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 } 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 { TableMap } from '@atlaskit/editor-tables/table-map'; import { findCellRectClosestToPos, getSelectionRect, isSelectionType } from '@atlaskit/editor-tables/utils'; import PaintBucketIcon from '@atlaskit/icon/core/paint-bucket'; import { fg } from '@atlaskit/platform-feature-flags'; // 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 Toggle from '@atlaskit/toggle'; import { clearHoverSelection, hoverColumns, hoverRows } from '../../pm-plugins/commands'; import { setColorWithAnalytics, toggleHeaderColumnWithAnalytics, toggleHeaderRowWithAnalytics, toggleNumberColumnWithAnalytics } from '../../pm-plugins/commands/commands-with-analytics'; import { toggleDragMenu } from '../../pm-plugins/drag-and-drop/commands'; import { getPluginState } from '../../pm-plugins/drag-and-drop/plugin-factory'; import { getPluginState as getTablePluginState } from '../../pm-plugins/plugin-factory'; import { getDragMenuConfig } from '../../pm-plugins/utils/drag-menu'; import { checkIfHeaderColumnEnabled, checkIfHeaderRowEnabled, checkIfNumberColumnEnabled } from '../../pm-plugins/utils/nodes'; import { getSelectedColumnIndexes, getSelectedRowIndexes } from '../../pm-plugins/utils/selection'; import { TableCssClassName as ClassName } from '../../types'; import { colorPalletteColumns } from '../consts'; import { DropdownMenu } from './DropdownMenu'; import { cellColourPreviewStyles, dragMenuBackgroundColorStyles, toggleStyles } from './styles'; var MapDragMenuOptionIdToMessage = { add_row_above: { message: messages.addRowAbove, plural: null }, add_row_below: { message: messages.addRowBelow, plural: null }, add_column_left: { message: messages.addColumnLeft, plural: null }, add_column_right: { message: messages.addColumnRight, plural: null }, distribute_columns: { message: messages.distributeColumns, plural: 'noOfCols' }, clear_cells: { message: messages.clearCells, plural: 'noOfCells' }, delete_row: { message: messages.removeRows, plural: 'noOfRows' }, delete_column: { message: messages.removeColumns, plural: 'noOfCols' }, move_column_left: { message: messages.moveColumnLeft, plural: 'noOfCols' }, move_column_right: { message: messages.moveColumnRight, plural: 'noOfCols' }, move_row_up: { message: messages.moveRowUp, plural: 'noOfRows' }, move_row_down: { message: messages.moveRowDown, plural: 'noOfRows' }, sort_column_asc: { message: messages.sortColumnIncreasing, plural: null }, sort_column_desc: { message: messages.sortColumnDecreasing, plural: null } }; var getGroupedDragMenuConfig = function getGroupedDragMenuConfig() { var groupedDragMenuConfig = [['add_row_above', 'add_row_below', 'add_column_left', 'add_column_right', 'distribute_columns', 'clear_cells', 'delete_row', 'delete_column'], ['move_column_left', 'move_column_right', 'move_row_up', 'move_row_down']]; var sortColumnItems = ['sort_column_asc', 'sort_column_desc']; groupedDragMenuConfig.unshift(sortColumnItems); return groupedDragMenuConfig; }; var elementBeforeIconStyles = xcss({ marginRight: 'space.negative.075', display: 'flex' }); var convertToDropdownItems = function convertToDropdownItems(dragMenuConfig, formatMessage, selectionRect) { var groupedDragMenuConfig = getGroupedDragMenuConfig(); var menuItemsArr = _toConsumableArray(Array(groupedDragMenuConfig.length)).map(function () { return []; }); var menuCallback = {}; dragMenuConfig.forEach(function (item) { var _MapDragMenuOptionIdT; var menuGroupIndex = groupedDragMenuConfig.findIndex(function (group) { return group.includes(item.id); }); if (menuGroupIndex === -1) { return; } var isPlural = Boolean((_MapDragMenuOptionIdT = MapDragMenuOptionIdToMessage[item.id]) === null || _MapDragMenuOptionIdT === void 0 ? void 0 : _MapDragMenuOptionIdT.plural); var plural = 0; if (isPlural && selectionRect) { var top = selectionRect.top, bottom = selectionRect.bottom, right = selectionRect.right, left = selectionRect.left; switch (MapDragMenuOptionIdToMessage[item.id].plural) { case 'noOfCols': { plural = right - left; break; } case 'noOfRows': { plural = bottom - top; break; } case 'noOfCells': { plural = Math.max(right - left, bottom - top); break; } } } var options = isPlural ? { 0: plural } : undefined; menuItemsArr[menuGroupIndex].push({ key: item.id, content: formatMessage(MapDragMenuOptionIdToMessage[item.id].message, options), value: { name: item.id }, isDisabled: item.disabled, elemBefore: item.icon ? jsx(Box, { xcss: elementBeforeIconStyles }, jsx(item.icon, { color: "currentColor", spacing: "spacious", label: formatMessage(MapDragMenuOptionIdToMessage[item.id].message, options) })) : undefined, // 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: item.keymap ? jsx("div", { css: shortcutStyle }, item.keymap) : undefined }); item.onClick && (menuCallback[item.id] = item.onClick); }); var menuItems = menuItemsArr.reduce(function (acc, curr) { (curr === null || curr === void 0 ? void 0 : curr.length) > 0 && acc.push({ items: curr }); return acc; }, []); return { menuItems: menuItems, menuCallback: menuCallback }; }; var DragMenu = /*#__PURE__*/React.memo(function (_ref) { var _tableMap$hasMergedCe, _pluginConfig$allowBa, _pluginConfig$allowCo; var _ref$direction = _ref.direction, direction = _ref$direction === void 0 ? 'row' : _ref$direction, index = _ref.index, target = _ref.target, isOpen = _ref.isOpen, editorView = _ref.editorView, tableNode = _ref.tableNode, targetCellPosition = _ref.targetCellPosition, getEditorContainerWidth = _ref.getEditorContainerWidth, api = _ref.api, editorAnalyticsAPI = _ref.editorAnalyticsAPI, pluginConfig = _ref.pluginConfig, formatMessage = _ref.intl.formatMessage, fitHeight = _ref.fitHeight, fitWidth = _ref.fitWidth, mountPoint = _ref.mountPoint, scrollableElement = _ref.scrollableElement, boundariesElement = _ref.boundariesElement, isTableScalingEnabled = _ref.isTableScalingEnabled, shouldUseIncreasedScalingPercent = _ref.shouldUseIncreasedScalingPercent, isTableFixedColumnWidthsOptionEnabled = _ref.isTableFixedColumnWidthsOptionEnabled, ariaNotifyPlugin = _ref.ariaNotifyPlugin, isCommentEditor = _ref.isCommentEditor; var state = editorView.state, dispatch = editorView.dispatch; var selection = state.selection; var tableMap = tableNode ? TableMap.get(tableNode) : undefined; var _useState = useState(false), _useState2 = _slicedToArray(_useState, 2), isSubmenuOpen = _useState2[0], setIsSubmenuOpen = _useState2[1]; var _getPluginState = getPluginState(state), isKeyboardModeActive = _getPluginState.isKeyboardModeActive; var selectionRect = isSelectionType(selection, 'cell') ? // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion getSelectionRect(selection) : findCellRectClosestToPos(selection.$from); var hasMergedCellsInTable = (_tableMap$hasMergedCe = tableMap === null || tableMap === void 0 ? void 0 : tableMap.hasMergedCells()) !== null && _tableMap$hasMergedCe !== void 0 ? _tableMap$hasMergedCe : false; var allowBackgroundColor = (_pluginConfig$allowBa = pluginConfig === null || pluginConfig === void 0 ? void 0 : pluginConfig.allowBackgroundColor) !== null && _pluginConfig$allowBa !== void 0 ? _pluginConfig$allowBa : false; var dragMenuConfig = getDragMenuConfig(direction, getEditorContainerWidth, hasMergedCellsInTable, editorView, api, tableMap, index, targetCellPosition, selectionRect, editorAnalyticsAPI, pluginConfig === null || pluginConfig === void 0 ? void 0 : pluginConfig.isHeaderRowRequired, isTableScalingEnabled, isTableFixedColumnWidthsOptionEnabled, shouldUseIncreasedScalingPercent, ariaNotifyPlugin, isCommentEditor, (_pluginConfig$allowCo = pluginConfig === null || pluginConfig === void 0 ? void 0 : pluginConfig.allowColumnSorting) !== null && _pluginConfig$allowCo !== void 0 ? _pluginConfig$allowCo : true); var _convertToDropdownIte = convertToDropdownItems(dragMenuConfig, formatMessage, selectionRect), menuItems = _convertToDropdownIte.menuItems, menuCallback = _convertToDropdownIte.menuCallback; var handleSubMenuRef = function handleSubMenuRef(ref) { // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting var dom = editorView.dom; var parent = closestElement(dom, '.fabric-editor-popup-scroll-parent') || closestElement(dom, '.ak-editor-content-area'); if (!(parent && ref)) { return; } var boundariesRect = parent.getBoundingClientRect(); var rect = ref.getBoundingClientRect(); if (!!mountPoint) { return; } var offsetParent = ref === null || ref === void 0 ? void 0 : ref.offsetParent; if (!offsetParent) { return; } var offsetParentRect = offsetParent.getBoundingClientRect(); var rightOverflow = offsetParentRect.right + rect.width - boundariesRect.right; var leftOverflow = boundariesRect.left - (offsetParentRect.left - rect.width); if (rightOverflow > leftOverflow) { ref.style.left = "-".concat(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 = "".concat(offsetParentRect.width - rightOverflow, "px"); } else { ref.style.left = "-".concat(rect.width - leftOverflow, "px"); } } }; var setColor = function setColor(color) { var state = editorView.state, dispatch = editorView.dispatch; setColorWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.CONTEXT_MENU, color)(state, dispatch); closeMenu(); setIsSubmenuOpen(false); }; var createBackgroundColorMenuItem = function createBackgroundColorMenuItem() { var _node$attrs; var _getTablePluginState = getTablePluginState(editorView.state), targetCellPosition = _getTablePluginState.targetCellPosition; var node = targetCellPosition ? state.doc.nodeAt(targetCellPosition) : null; var background = hexToEditorBackgroundPaletteColor((node === null || node === void 0 || (_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.background) || '#ffffff'); var _getSelectedRowAndCol = getSelectedRowAndColumnFromPalette(cellBackgroundColorPalette, background, colorPalletteColumns), selectedRowIndex = _getSelectedRowAndCol.selectedRowIndex, selectedColumnIndex = _getSelectedRowAndCol.selectedColumnIndex; return { key: 'background', content: formatMessage(messages.backgroundColor), value: { name: 'background' }, elemBefore: jsx(Box, { xcss: elementBeforeIconStyles }, jsx(PaintBucketIcon, { color: "currentColor", spacing: "spacious", label: formatMessage(messages.backgroundColor) })), elemAfter: jsx("div", { // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766 className: DropdownMenuSharedCssClassName.SUBMENU // 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: dragMenuBackgroundColorStyles() }, 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: ClassName.DRAG_SUBMENU_ICON }), isSubmenuOpen && // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766 jsx("div", { className: ClassName.DRAG_SUBMENU, ref: handleSubMenuRef }, jsx(ArrowKeyNavigationProvider, { type: ArrowKeyNavigationType.COLOR, selectedRowIndex: selectedRowIndex, selectedColumnIndex: selectedColumnIndex // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , handleClose: function handleClose() { var keyboardEvent = new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }); setIsSubmenuOpen(false); // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting target === null || target === void 0 || target.focus(); target === null || target === void 0 || target.dispatchEvent(keyboardEvent); }, isPopupPositioned: true, isOpenedByKeyboard: isKeyboardModeActive }, jsx(ColorPalette, { cols: colorPalletteColumns // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onClick: function onClick(color) { setColor(color); }, selectedColor: background // 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 } })))) }; }; var toggleHeaderColumn = function toggleHeaderColumn() { toggleHeaderColumnWithAnalytics(editorAnalyticsAPI)(state, dispatch); }; var toggleHeaderRow = function toggleHeaderRow() { toggleHeaderRowWithAnalytics(editorAnalyticsAPI)(state, dispatch); }; var toggleRowNumbers = function toggleRowNumbers() { toggleNumberColumnWithAnalytics(editorAnalyticsAPI)(state, dispatch); }; var HeaderColumnToggle = function HeaderColumnToggle() { return ( // 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("div", { css: toggleStyles }, jsx(Toggle, { id: "toggle-header-column", label: formatMessage(messages.headerColumn), onChange: toggleHeaderColumn, isChecked: checkIfHeaderColumnEnabled(selection) })) ); }; var HeaderRowToggle = function HeaderRowToggle() { return ( // 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("div", { css: toggleStyles }, jsx(Toggle, { id: "toggle-header-row", label: formatMessage(messages.headerRow), onChange: toggleHeaderRow, isChecked: checkIfHeaderRowEnabled(selection) })) ); }; var createHeaderRowColumnMenuItemOld = function createHeaderRowColumnMenuItemOld(direction) { return direction === 'column' ? { key: 'header_column', content: formatMessage(messages.headerColumn), value: { name: 'header_column' }, elemAfter: jsx(HeaderColumnToggle, null) } : { key: 'header_row', content: formatMessage(messages.headerRow), value: { name: 'header_row' }, elemAfter: jsx(HeaderRowToggle, null) }; }; var createHeaderRowColumnMenuItem = function createHeaderRowColumnMenuItem(direction) { if (direction === 'column' && (pluginConfig !== null && pluginConfig !== void 0 && pluginConfig.advanced || pluginConfig !== null && pluginConfig !== void 0 && pluginConfig.allowHeaderColumn)) { return { key: 'header_column', content: formatMessage(messages.headerColumn), value: { name: 'header_column' }, elemAfter: jsx(HeaderColumnToggle, null) }; } if (direction === 'row' && (pluginConfig !== null && pluginConfig !== void 0 && pluginConfig.advanced || pluginConfig !== null && pluginConfig !== void 0 && pluginConfig.allowHeaderRow)) { return { key: 'header_row', content: formatMessage(messages.headerRow), value: { name: 'header_row' }, elemAfter: jsx(HeaderRowToggle, null) }; } }; var createRowNumbersMenuItem = function createRowNumbersMenuItem() { return { key: 'row_numbers', content: formatMessage(messages.numberedRows), value: { name: 'row_numbers' }, elemAfter: // 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("div", { css: toggleStyles }, jsx(Toggle, { id: "toggle-row-numbers", label: formatMessage(messages.numberedRows), onChange: toggleRowNumbers, isChecked: checkIfNumberColumnEnabled(selection) })) }; }; /** * This function is to check if the menu should be closed or not. * As when continously clicking on tyle handle on different rows/columns, * should open the menu corresponding to the position of the drag handle. * @returns true when the menu should be closed, false otherwise */ var shouldCloseMenu = function shouldCloseMenu(state) { var _getPluginState2 = getPluginState(state), previousOpenState = _getPluginState2.isDragMenuOpen, previousDragMenuDirection = _getPluginState2.dragMenuDirection, previousDragMenuIndex = _getPluginState2.dragMenuIndex; // menu open but menu direction changed, means user clicked on drag handle of different row/column // menu open menu direction not changed, but index changed, means user clicked on drag handle of same row/column, different cells. // 2 scenarios above , menu should not be closed. if (previousOpenState === true && previousDragMenuDirection !== direction || previousOpenState === true && previousDragMenuDirection === direction && previousDragMenuIndex !== index) { return false; } else { return true; } }; var closeMenu = function closeMenu() { var focusTarget = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'handle'; var state = editorView.state, dispatch = editorView.dispatch; if (shouldCloseMenu(state)) { if (target && focusTarget === 'handle') { target === null || target === void 0 || target.focus(); } else { editorView.focus(); } toggleDragMenu(false, direction, index)(state, dispatch); } }; var handleMenuItemActivated = function handleMenuItemActivated(_ref2) { var _menuCallback$item$va; var item = _ref2.item; (_menuCallback$item$va = menuCallback[item.value.name]) === null || _menuCallback$item$va === void 0 || _menuCallback$item$va.call(menuCallback, state, dispatch); switch (item.value.name) { case 'background': setIsSubmenuOpen(!isSubmenuOpen); break; case 'header_column': toggleHeaderColumn(); break; case 'header_row': toggleHeaderRow(); break; case 'row_numbers': toggleRowNumbers(); break; default: break; } if (['header_column', 'header_row', 'row_numbers', 'background'].indexOf(item.value.name) <= -1) { closeMenu('editor'); } }; var handleItemMouseEnter = function handleItemMouseEnter(_ref3) { var _item$value$name; var item = _ref3.item; if (!selectionRect) { return; } if (item.value.name === 'background' && !isSubmenuOpen) { setIsSubmenuOpen(true); } if (!((_item$value$name = item.value.name) !== null && _item$value$name !== void 0 && _item$value$name.startsWith('delete'))) { return; } (item.value.name === 'delete_column' ? hoverColumns(getSelectedColumnIndexes(selectionRect), true) : hoverRows(getSelectedRowIndexes(selectionRect), true))(state, dispatch); }; // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any var handleItemMouseLeave = function handleItemMouseLeave(_ref4) { var item = _ref4.item; if (item.value.name === 'background' && isSubmenuOpen) { setIsSubmenuOpen(false); } if (['sort_column_asc', 'sort_column_desc', 'delete_column', 'delete_row'].indexOf(item.value.name) > -1) { clearHoverSelection()(state, dispatch); } }; useEffect(function () { // focus on first menu item automatically when menu renders // and user is using keyboard if (isOpen && target && isKeyboardModeActive) { var keyboardEvent = new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }); target.dispatchEvent(keyboardEvent); } }, [isOpen, target, isKeyboardModeActive]); if (!menuItems) { return null; } if (allowBackgroundColor) { menuItems[1].items.unshift(createBackgroundColorMenuItem()); } // If first row, add toggle for Header row, default is true // If first column, add toggle for Header column, default is false if (index === 0) { if (!fg('platform_editor_enable_table_dnd')) { menuItems.push({ items: [createHeaderRowColumnMenuItemOld(direction)] }); } else if ((pluginConfig !== null && pluginConfig !== void 0 && pluginConfig.advanced || pluginConfig !== null && pluginConfig !== void 0 && pluginConfig.allowHeaderColumn || pluginConfig !== null && pluginConfig !== void 0 && pluginConfig.allowHeaderRow) && fg('platform_editor_enable_table_dnd')) { var headerRowColumnMenuItem = createHeaderRowColumnMenuItem(direction); headerRowColumnMenuItem && menuItems.push({ items: [headerRowColumnMenuItem] }); } } // All rows, add toggle for numbered rows, default is false if (direction === 'row' && (fg('platform_editor_enable_table_dnd') ? (pluginConfig === null || pluginConfig === void 0 ? void 0 : pluginConfig.advanced) || (pluginConfig === null || pluginConfig === void 0 ? void 0 : pluginConfig.allowNumberColumn) : true)) { index === 0 ? menuItems[menuItems.length - 1].items.push(createRowNumbersMenuItem()) : menuItems.push({ items: [createRowNumbersMenuItem()] }); } return jsx(UserIntentPopupWrapper, { api: api, userIntent: "tableDragMenuPopupOpen" }, jsx(DropdownMenu, { disableKeyboardHandling: isSubmenuOpen // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , section: { hasSeparator: true }, items: menuItems, onItemActivated: handleMenuItemActivated, onMouseEnter: handleItemMouseEnter, onMouseLeave: handleItemMouseLeave, handleClose: closeMenu, fitHeight: fitHeight, fitWidth: fitWidth, direction: direction, boundariesElement: boundariesElement, scrollableElement: scrollableElement })); }); export default injectIntl(DragMenu);