@atlaskit/editor-plugin-table
Version:
Table plugin for the @atlaskit/editor
298 lines (290 loc) • 15.9 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.FloatingInsertButton = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _react = _interopRequireDefault(require("react"));
var _reactIntl = require("react-intl");
var _enums = require("@atlaskit/editor-common/analytics/types/enums");
var _Popup = _interopRequireDefault(require("@atlaskit/editor-common/Popup"));
var _dom = require("@atlaskit/editor-common/utils/dom");
var _utils = require("@atlaskit/editor-prosemirror/utils");
var _cellSelection = require("@atlaskit/editor-tables/cell-selection");
var _tableMap = require("@atlaskit/editor-tables/table-map");
var _utils2 = require("@atlaskit/editor-tables/utils");
var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
var _commandsWithAnalytics = require("../../pm-plugins/commands/commands-with-analytics");
var _nodes = require("../../pm-plugins/utils/nodes");
var _selection = require("../../pm-plugins/utils/selection");
var _types = require("../../types");
var _stickyHeaderZIndex = require("../sticky-header-z-index");
var _getPopupOptions = _interopRequireDefault(require("./getPopupOptions"));
var _InsertButton = require("./InsertButton");
function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2.default)(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; }
/**
* 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 = (0, _dom.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
var FloatingInsertButton = exports.FloatingInsertButton = /*#__PURE__*/function (_React$Component) {
function FloatingInsertButton(props) {
var _this;
(0, _classCallCheck2.default)(this, FloatingInsertButton);
_this = _callSuper(this, FloatingInsertButton, [props]);
_this.insertColumn = _this.insertColumn.bind(_this);
_this.insertRow = _this.insertRow.bind(_this);
return _this;
}
(0, _inherits2.default)(FloatingInsertButton, _React$Component);
return (0, _createClass2.default)(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 ((0, _expValEquals.expValEquals)('platform_editor_table_menu_updates', 'isEnabled', true)) {
if ((0, _selection.isFullRowOrColumnSelected)(tr.selection)) {
return null;
}
} else {
if (tr.selection instanceof _cellSelection.CellSelection && (tr.selection.isColSelection() || tr.selection.isRowSelection())) {
return null;
}
}
var tablePos = (0, _utils2.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 = (0, _utils.findDomRefAtPos)(pos, domAtPos);
} catch (error) {
// eslint-disable-next-line no-console
console.warn(error);
if (dispatchAnalyticsEvent) {
var payload = {
action: _enums.ACTION.ERRORED,
actionSubject: _enums.ACTION_SUBJECT.CONTENT_COMPONENT,
eventType: _enums.EVENT_TYPE.OPERATIONAL,
attributes: {
component: _enums.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' ? (0, _dom.closestElement)(target, 'tr') : (0, _dom.closestElement)(target, 'td, th');
if (!targetCellRef) {
return null;
}
var tableContainerWrapper = (0, _dom.closestElement)(targetCellRef, ".".concat(_types.TableCssClassName.TABLE_CONTAINER));
var tableWrapper = (0, _dom.closestElement)(targetCellRef, ".".concat(_types.TableCssClassName.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 = (0, _nodes.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' && (0, _expValEquals.expValEquals)('platform_editor_table_menu_updates', 'isEnabled', true)) {
if ((0, _isExperimentEnabled.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 = (0, _stickyHeaderZIndex.getTableZIndexes)().headerButton;
return /*#__PURE__*/_react.default.createElement(_Popup.default, (0, _extends2.default)({
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
}, (0, _getPopupOptions.default)(type, index, hasNumberedColumns, tableContainerWrapper, verticalOffsetCorrection), {
zIndex: zIndex
}), /*#__PURE__*/_react.default.createElement(_InsertButton.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.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 ((0, _expValEquals.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;
(0, _commandsWithAnalytics.insertRowWithAnalytics)(editorAnalyticsAPI)(_enums.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;
(0, _commandsWithAnalytics.insertColumnWithAnalytics)(this.props.api, editorAnalyticsAPI, isTableScalingEnabled, tableWithFixedColumnWidthsOption, shouldUseIncreasedScalingPercent, isCommentEditor)(_enums.INPUT_METHOD.BUTTON, insertColumnButtonIndex)(state, dispatch, editorView);
}
}
}]);
}(_react.default.Component); // eslint-disable-next-line @typescript-eslint/no-restricted-types
(0, _defineProperty2.default)(FloatingInsertButton, "displayName", 'FloatingInsertButton');
var _default_1 = (0, _reactIntl.injectIntl)(FloatingInsertButton);
var _default = exports.default = _default_1;