UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

293 lines (285 loc) • 15 kB
import _extends from "@babel/runtime/helpers/extends"; import _classCallCheck from "@babel/runtime/helpers/classCallCheck"; import _createClass from "@babel/runtime/helpers/createClass"; import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn"; import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf"; import _inherits from "@babel/runtime/helpers/inherits"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); } function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } import React from 'react'; import { injectIntl } from 'react-intl'; import { ACTION, ACTION_SUBJECT, CONTENT_COMPONENT, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics/types/enums'; import Popup from '@atlaskit/editor-common/Popup'; import { closestElement } from '@atlaskit/editor-common/utils/dom'; import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils'; import { CellSelection } from '@atlaskit/editor-tables/cell-selection'; import { TableMap } from '@atlaskit/editor-tables/table-map'; import { findTable } from '@atlaskit/editor-tables/utils'; import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { insertColumnWithAnalytics, insertRowWithAnalytics } from '../../pm-plugins/commands/commands-with-analytics'; import { checkIfNumberColumnEnabled } from '../../pm-plugins/utils/nodes'; import { isFullRowOrColumnSelected } from '../../pm-plugins/utils/selection'; import { TableCssClassName as ClassName } from '../../types'; import { getTableZIndexes } from '../sticky-header-z-index'; import getPopupOptions from './getPopupOptions'; import { DragAndDropInsertButton } from './InsertButton'; /** * Distance from the table top to the top of the row containing `cell`, summed from row heights. * Viewport rects cannot be used: a stuck header row is fixed to the viewport while the table scrolls. */ var getRowOffsetFromTableTop = function getRowOffsetFromTableTop(cell, table) { var anchorRow = closestElement(cell, 'tr'); if (!(table instanceof HTMLTableElement) || !anchorRow) { return 0; } var offset = 0; var _iterator = _createForOfIteratorHelper(table.rows), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var row = _step.value; if (row === anchorRow) { return offset; } offset += row.offsetHeight; } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return 0; }; // Ignored via go/ees005 // eslint-disable-next-line @repo/internal/react/no-class-components, @typescript-eslint/no-explicit-any export var FloatingInsertButton = /*#__PURE__*/function (_React$Component) { function FloatingInsertButton(props) { var _this; _classCallCheck(this, FloatingInsertButton); _this = _callSuper(this, FloatingInsertButton, [props]); _this.insertColumn = _this.insertColumn.bind(_this); _this.insertRow = _this.insertRow.bind(_this); return _this; } _inherits(FloatingInsertButton, _React$Component); return _createClass(FloatingInsertButton, [{ key: "render", value: function render() { var _this$props = this.props, tableNode = _this$props.tableNode, editorView = _this$props.editorView, insertColumnButtonIndex = _this$props.insertColumnButtonIndex, insertRowButtonIndex = _this$props.insertRowButtonIndex, tableRef = _this$props.tableRef, mountPoint = _this$props.mountPoint, boundariesElement = _this$props.boundariesElement, isHeaderRowEnabled = _this$props.isHeaderRowEnabled, dispatchAnalyticsEvent = _this$props.dispatchAnalyticsEvent, isChromelessEditor = _this$props.isChromelessEditor; // ED-26961 - disable insert button for first row. if (insertRowButtonIndex === 0) { return null; } var type = typeof insertColumnButtonIndex !== 'undefined' ? 'column' : typeof insertRowButtonIndex !== 'undefined' ? 'row' : null; if (!tableNode || !tableRef || !type) { return null; } // We can't display the insert button for row index 0 // when the header row is enabled. if (type === 'row' && isHeaderRowEnabled && insertRowButtonIndex === 0) { return null; } var tr = editorView.state.tr; if (expValEquals('platform_editor_table_menu_updates', 'isEnabled', true)) { if (isFullRowOrColumnSelected(tr.selection)) { return null; } } else { if (tr.selection instanceof CellSelection && (tr.selection.isColSelection() || tr.selection.isRowSelection())) { return null; } } var tablePos = findTable(tr.selection); if (!tablePos) { return null; } // the tableNode props is not always latest (when you type some text in a cell, it's not updated yet) // we need to get the latest one by calling findTable(tr.selection) var cellPosition = this.getCellPosition(type, tablePos === null || tablePos === void 0 ? void 0 : tablePos.node); if (!cellPosition) { return null; } var domAtPos = editorView.domAtPos.bind(editorView); var pos = cellPosition + tablePos.start + 1; var target; try { target = findDomRefAtPos(pos, domAtPos); } catch (error) { // eslint-disable-next-line no-console console.warn(error); if (dispatchAnalyticsEvent) { var payload = { action: ACTION.ERRORED, actionSubject: ACTION_SUBJECT.CONTENT_COMPONENT, eventType: EVENT_TYPE.OPERATIONAL, attributes: { component: CONTENT_COMPONENT.FLOATING_INSERT_BUTTON, selection: editorView.state.selection.toJSON(), position: pos, docSize: editorView.state.doc.nodeSize, // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any error: error === null || error === void 0 ? void 0 : error.toString() } }; dispatchAnalyticsEvent(payload); } } if (!target || !(target instanceof HTMLElement)) { return null; } var targetCellRef = type === 'row' ? closestElement(target, 'tr') : closestElement(target, 'td, th'); if (!targetCellRef) { return null; } var tableContainerWrapper = closestElement(targetCellRef, ".".concat(ClassName.TABLE_CONTAINER)); var tableWrapper = closestElement(targetCellRef, ".".concat(ClassName.TABLE_NODE_WRAPPER)); // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion var index = type === 'column' ? insertColumnButtonIndex : insertRowButtonIndex; var hasNumberedColumns = checkIfNumberColumnEnabled(editorView.state.selection); // If row 0 has a colspan, anchor to a lower row for the real column boundary (X), // then move back up to the table top (Y): // row 0: [ colspan=2 ] // row 1: [ col 1 ][ col 2 ] ← anchor here for X // ↑ button should render at row 0/table top var verticalOffsetCorrection = 0; if (type === 'column' && expValEquals('platform_editor_table_menu_updates', 'isEnabled', true)) { if (isExperimentEnabled('platform_editor_sticky_header_insert_column_exp')) { verticalOffsetCorrection = getRowOffsetFromTableTop(targetCellRef, tableRef); } else { verticalOffsetCorrection = Math.max(0, targetCellRef.getBoundingClientRect().top - tableRef.getBoundingClientRect().top); } } // Insert buttons sit above the sticky header. // Set the layer on the Popup because its children share its stacking context. var zIndex = getTableZIndexes().headerButton; return /*#__PURE__*/React.createElement(Popup, _extends({ target: targetCellRef, mountTo: tableContainerWrapper || mountPoint, boundariesElement: tableContainerWrapper || boundariesElement // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion , scrollableElement: tableWrapper, forcePlacement: true, allowOutOfBounds: true // Ignored via go/ees005 // eslint-disable-next-line react/jsx-props-no-spreading }, getPopupOptions(type, index, hasNumberedColumns, tableContainerWrapper, verticalOffsetCorrection), { zIndex: zIndex }), /*#__PURE__*/React.createElement(DragAndDropInsertButton, { type: type, tableRef: tableRef, onMouseDown: type === 'column' ? this.insertColumn : this.insertRow, hasStickyHeaders: this.props.hasStickyHeaders || false, isChromelessEditor: isChromelessEditor })); } // Finds a row where `columnIndex` has real left/right cell boundaries. }, { key: "findRowWithUnmergedColumn", value: function findRowWithUnmergedColumn(tableMap, columnIndex) { for (var row = 0; row < tableMap.height; row++) { var pos = tableMap.map[row * tableMap.width + columnIndex]; var rect = tableMap.findCell(pos); if (rect.left === columnIndex && rect.right === columnIndex + 1) { return row; } } return null; } }, { key: "getCellPosition", value: function getCellPosition(type, tableNode) { var _this$props2 = this.props, insertColumnButtonIndex = _this$props2.insertColumnButtonIndex, insertRowButtonIndex = _this$props2.insertRowButtonIndex; var tableMap = TableMap.get(tableNode); if (type === 'column') { // This condition is to make typescript happy. // Previously insertColumnButtonIndex - 1 would produce NaN and return null anyway. if (insertColumnButtonIndex === undefined) { return null; } var columnIndex = insertColumnButtonIndex === 0 ? 0 : insertColumnButtonIndex - 1; if (columnIndex > tableMap.width - 1) { return null; } if (expValEquals('platform_editor_table_menu_updates', 'isEnabled', true)) { var rowWithRealColumn = this.findRowWithUnmergedColumn(tableMap, columnIndex); return rowWithRealColumn === null ? null : tableMap.positionAt(rowWithRealColumn, columnIndex, tableNode); } return tableMap.positionAt(0, columnIndex, tableNode); } else { // This condition is to make typescript happy. // Previously insertRowButtonIndex - 1 would produce NaN and return null anyway. if (insertRowButtonIndex === undefined) { return null; } var rowIndex = insertRowButtonIndex === 0 ? 0 : insertRowButtonIndex - 1; if (rowIndex > tableMap.height - 1) { return null; } return tableMap.positionAt(rowIndex, 0, tableNode); } } }, { key: "insertRow", value: function insertRow(event) { var _this$props3 = this.props, editorView = _this$props3.editorView, insertRowButtonIndex = _this$props3.insertRowButtonIndex, editorAnalyticsAPI = _this$props3.editorAnalyticsAPI; if (typeof insertRowButtonIndex !== 'undefined') { event.preventDefault(); var state = editorView.state, dispatch = editorView.dispatch; insertRowWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.BUTTON, { index: insertRowButtonIndex, moveCursorToInsertedRow: true })(state, dispatch); } } }, { key: "insertColumn", value: function insertColumn(event) { var _this$props4 = this.props, editorView = _this$props4.editorView, insertColumnButtonIndex = _this$props4.insertColumnButtonIndex, editorAnalyticsAPI = _this$props4.editorAnalyticsAPI, getEditorFeatureFlags = _this$props4.getEditorFeatureFlags, isTableScalingEnabled = _this$props4.isTableScalingEnabled, isCommentEditor = _this$props4.isCommentEditor; if (typeof insertColumnButtonIndex !== 'undefined') { event.preventDefault(); var _ref = getEditorFeatureFlags ? getEditorFeatureFlags() : {}, _ref$tableWithFixedCo = _ref.tableWithFixedColumnWidthsOption, tableWithFixedColumnWidthsOption = _ref$tableWithFixedCo === void 0 ? false : _ref$tableWithFixedCo; var shouldUseIncreasedScalingPercent = isTableScalingEnabled && (tableWithFixedColumnWidthsOption || isCommentEditor); var state = editorView.state, dispatch = editorView.dispatch; insertColumnWithAnalytics(this.props.api, editorAnalyticsAPI, isTableScalingEnabled, tableWithFixedColumnWidthsOption, shouldUseIncreasedScalingPercent, isCommentEditor)(INPUT_METHOD.BUTTON, insertColumnButtonIndex)(state, dispatch, editorView); } } }]); }(React.Component); // eslint-disable-next-line @typescript-eslint/no-restricted-types _defineProperty(FloatingInsertButton, "displayName", 'FloatingInsertButton'); var _default_1 = injectIntl(FloatingInsertButton); export default _default_1;