UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

360 lines (348 loc) 21.1 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.createTableView = void 0; var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof")); 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 _get2 = _interopRequireDefault(require("@babel/runtime/helpers/get")); var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _react = _interopRequireDefault(require("react")); var _nodeWidth = require("@atlaskit/editor-common/node-width"); var _reactNodeView = _interopRequireDefault(require("@atlaskit/editor-common/react-node-view")); var _model = require("@atlaskit/editor-prosemirror/model"); var _state = require("@atlaskit/editor-prosemirror/state"); var _editorSharedStyles = require("@atlaskit/editor-shared-styles"); var _tableMap = require("@atlaskit/editor-tables/table-map"); var _platformFeatureFlags = require("@atlaskit/platform-feature-flags"); var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals"); var _createPluginConfig = require("../pm-plugins/create-plugin-config"); var _pluginFactory = require("../pm-plugins/plugin-factory"); var _tableWidth = require("../pm-plugins/table-width"); var _nodes = require("../pm-plugins/utils/nodes"); var _TableComponentWithSharedState = require("./TableComponentWithSharedState"); var _toDOM = require("./toDOM"); 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 _superPropGet(t, o, e, r) { var p = (0, _get2.default)((0, _getPrototypeOf2.default)(1 & r ? t.prototype : t), o, e); return 2 & r && "function" == typeof p ? function (t) { return p.apply(e, t); } : p; } var tableAttributes = function tableAttributes(node) { return { 'data-number-column': node.attrs.isNumberColumnEnabled, 'data-layout': node.attrs.layout, 'data-autosize': node.attrs.__autoSize, 'data-table-local-id': node.attrs.localId || '', 'data-table-width': node.attrs.width || 'inherit', 'data-table-display-mode': node.attrs.displayMode }; }; var getInlineWidth = function getInlineWidth(node, options, state, pos, allowTableResizing) { if (!node.attrs.width && options !== null && options !== void 0 && options.isChromelessEditor || !node.attrs.width && options !== null && options !== void 0 && options.isCommentEditor && allowTableResizing) { return; } // provide a width for tables when custom table width is supported // this is to ensure 'responsive' tables (colgroup widths are undefined) become fixed to // support screen size adjustments var shouldHaveInlineWidth = allowTableResizing && !(0, _nodes.isTableNested)(state, pos); var widthValue = (0, _nodeWidth.getTableContainerWidth)(node); if (node.attrs.isNumberColumnEnabled) { widthValue -= _editorSharedStyles.akEditorTableNumberColumnWidth; } return shouldHaveInlineWidth ? widthValue : undefined; }; var handleInlineTableWidth = function handleInlineTableWidth(table, width) { if (!table || !width) { return; } table.style.setProperty('width', "".concat(width, "px")); }; var TableView = exports.default = /*#__PURE__*/function (_ReactNodeView) { function TableView(props) { var _this; (0, _classCallCheck2.default)(this, TableView); _this = _callSuper(this, TableView, [props.node, props.view, props.getPos, props.portalProviderAPI, props.eventDispatcher, props, undefined, undefined, // @portal-render-immediately true]); (0, _defineProperty2.default)(_this, "getNode", function () { return _this.node; }); (0, _defineProperty2.default)(_this, "hasHoveredRows", false); _this.getPos = props.getPos; _this.eventDispatcher = props.eventDispatcher; _this.options = props.options; _this.getEditorFeatureFlags = props.getEditorFeatureFlags; _this.handleRef = function (node) { return _this._handleTableRef(node); }; return _this; } (0, _inherits2.default)(TableView, _ReactNodeView); return (0, _createClass2.default)(TableView, [{ key: "getContentDOM", value: function getContentDOM() { var _this$reactComponentP, _this$reactComponentP2, _this$reactComponentP3, _this$reactComponentP4; var isNested = (0, _nodes.isTableNested)(this.view.state, this.getPos()); var tableDOMStructure = (0, _toDOM.tableNodeSpecWithFixedToDOM)({ allowColumnResizing: !!this.reactComponentProps.allowColumnResizing, tableResizingEnabled: !!this.reactComponentProps.allowTableResizing, getEditorContainerWidth: this.reactComponentProps.getEditorContainerWidth, isTableScalingEnabled: (_this$reactComponentP = this.reactComponentProps.options) === null || _this$reactComponentP === void 0 ? void 0 : _this$reactComponentP.isTableScalingEnabled, shouldUseIncreasedScalingPercent: (_this$reactComponentP2 = this.reactComponentProps.options) === null || _this$reactComponentP2 === void 0 ? void 0 : _this$reactComponentP2.shouldUseIncreasedScalingPercent, isCommentEditor: (_this$reactComponentP3 = this.reactComponentProps.options) === null || _this$reactComponentP3 === void 0 ? void 0 : _this$reactComponentP3.isCommentEditor, isChromelessEditor: (_this$reactComponentP4 = this.reactComponentProps.options) === null || _this$reactComponentP4 === void 0 ? void 0 : _this$reactComponentP4.isChromelessEditor, isNested: isNested }).toDOM(this.node); var rendered = _model.DOMSerializer.renderSpec(document, tableDOMStructure); if (rendered.dom) { var _this$reactComponentP5, _this$getEditorFeatur, _this$options, _this$options2; var tableElement = rendered.dom.querySelector('table'); this.table = tableElement ? tableElement : rendered.dom; this.renderedDOM = rendered.dom; var allowFixedColumnWidthOption = ((0, _platformFeatureFlags.fg)('platform_editor_table_fixed_column_width_prop') ? (_this$reactComponentP5 = this.reactComponentProps) === null || _this$reactComponentP5 === void 0 ? void 0 : _this$reactComponentP5.allowFixedColumnWidthOption : (_this$getEditorFeatur = this.getEditorFeatureFlags) === null || _this$getEditorFeatur === void 0 ? void 0 : _this$getEditorFeatur.call(this).tableWithFixedColumnWidthsOption) || false; if (!((_this$options = this.options) !== null && _this$options !== void 0 && _this$options.isTableScalingEnabled) || (_this$options2 = this.options) !== null && _this$options2 !== void 0 && _this$options2.isTableScalingEnabled && allowFixedColumnWidthOption && this.node.attrs.displayMode === 'fixed') { var tableInlineWidth = getInlineWidth(this.node, this.reactComponentProps.options, this.reactComponentProps.view.state, this.reactComponentProps.getPos(), this.reactComponentProps.allowTableResizing); if (tableInlineWidth) { handleInlineTableWidth(this.table, tableInlineWidth); } } } return rendered; } /** * Handles moving the table from ProseMirror's DOM structure into a React-rendered table node. * Temporarily disables mutation observers (except for selection changes) during the move, * preserves selection state, and restores it afterwards if mutations occurred and cursor * wasn't at start of node. This prevents duplicate tables and maintains editor state during * the DOM manipulation. */ }, { key: "_handleTableRef", value: function _handleTableRef(node) { var _this2 = this; var oldIgnoreMutation; var selectionBookmark; var mutationsIgnored = false; // Only proceed if we have both a node and table, and the table isn't already inside the node if (node && this.table && !node.contains(this.table)) { // Store the current ignoreMutation handler so we can restore it later oldIgnoreMutation = this.ignoreMutation; // Set up a temporary mutation handler that: // - Ignores all DOM mutations except selection changes // - Tracks when mutations have been ignored via mutationsIgnored flag this.ignoreMutation = function (m) { var isSelectionMutation = m.type === 'selection'; if (!isSelectionMutation) { mutationsIgnored = true; } return !isSelectionMutation; }; // Store the current selection state if there is a visible selection // This lets us restore it after DOM changes var selection = this.view.state.selection; var tablePos = this.getPos(); if (selection.empty && tablePos && _state.TextSelection.near(this.view.state.doc.resolve(tablePos)).from === selection.from) { selectionBookmark = this.view.state.selection.getBookmark(); } if (this.dom) { this.dom.setAttribute('data-ssr-placeholder', "table-nodeview-".concat(this.node.attrs.localId)); this.dom.setAttribute('data-ssr-placeholder-replace', "table-nodeview-".concat(this.node.attrs.localId)); } // Remove the ProseMirror table DOM structure to avoid duplication, as it's replaced with the React table node. if (this.dom && this.renderedDOM) { this.dom.removeChild(this.renderedDOM); } // Move the table from the ProseMirror table structure into the React rendered table node. node.appendChild(this.table); // After the next frame: requestAnimationFrame(function () { // Restore the original mutation handler _this2.ignoreMutation = oldIgnoreMutation; // Restore the selection only if: // - We have a selection bookmark // - Mutations were ignored during the table move // - The bookmarked selection is different from the current selection. if (selectionBookmark && mutationsIgnored) { var _TextSelection$findFr; var resolvedSelection = selectionBookmark.resolve(_this2.view.state.tr.doc); /** * This handles a very specific case only -> insertion by the user of a new * table * Since it's behind a RAF it's possible the user has clicked elsewhere or * it affects collaborative users (which selection changes shouldn't ever) * * This ensures that the selectionBookmark *before* is inside the first * position in the table and that after it is the text position directly * before the table * Ideally we want to remove this RAF entirely but that would require removing * the DOM manipulation and is a more complex effort */ if (!resolvedSelection.eq(_this2.view.state.selection) && resolvedSelection.empty && // Ensure that the *next* valid text position matches the first position // in the table (_TextSelection$findFr = _state.TextSelection.findFrom(_this2.view.state.doc.resolve(_this2.view.state.selection.from + 1), 1, true)) !== null && _TextSelection$findFr !== void 0 && _TextSelection$findFr.eq(resolvedSelection)) { var tr = _this2.view.state.tr.setSelection(resolvedSelection); tr.setMeta('source', 'TableNodeView:_handleTableRef:selection-resync'); _this2.view.dispatch(tr); } } }); } } }, { key: "setDomAttrs", value: function setDomAttrs(node) { var _this3 = this, _this$reactComponentP6, _this$getEditorFeatur2, _this$options3, _this$options4; if (!this.table) { return; // width / attribute application to actual table will happen later when table is set } var attrs = tableAttributes(node); Object.keys(attrs).forEach(function (attr) { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion _this3.table.setAttribute(attr, attrs[attr]); }); var isTableFixedColumnWidthsOptionEnabled = ((0, _platformFeatureFlags.fg)('platform_editor_table_fixed_column_width_prop') ? (_this$reactComponentP6 = this.reactComponentProps) === null || _this$reactComponentP6 === void 0 ? void 0 : _this$reactComponentP6.allowFixedColumnWidthOption : (_this$getEditorFeatur2 = this.getEditorFeatureFlags) === null || _this$getEditorFeatur2 === void 0 ? void 0 : _this$getEditorFeatur2.call(this).tableWithFixedColumnWidthsOption) || false; // Preserve Table Width cannot have inline width set on the table if (!((_this$options3 = this.options) !== null && _this$options3 !== void 0 && _this$options3.isTableScalingEnabled) || (_this$options4 = this.options) !== null && _this$options4 !== void 0 && _this$options4.isTableScalingEnabled && isTableFixedColumnWidthsOptionEnabled && node.attrs.displayMode === 'fixed') { var _tableWidthPluginKey$; // handle inline style when table been resized var tableInlineWidth = getInlineWidth(node, this.reactComponentProps.options, this.view.state, this.getPos(), this.reactComponentProps.allowTableResizing); var isTableResizing = (_tableWidthPluginKey$ = _tableWidth.pluginKey.getState(this.view.state)) === null || _tableWidthPluginKey$ === void 0 ? void 0 : _tableWidthPluginKey$.resizing; if (!isTableResizing && tableInlineWidth) { handleInlineTableWidth(this.table, tableInlineWidth); } } } }, { key: "render", value: function render(props, forwardRef) { return /*#__PURE__*/_react.default.createElement(_TableComponentWithSharedState.TableComponentWithSharedState, { forwardRef: forwardRef, getNode: this.getNode, view: props.view, options: props.options, eventDispatcher: props.eventDispatcher, api: props.pluginInjectionApi, allowColumnResizing: props.allowColumnResizing, allowTableAlignment: props.allowTableAlignment, allowTableResizing: props.allowTableResizing, allowControls: props.allowControls, getPos: props.getPos, getEditorFeatureFlags: props.getEditorFeatureFlags, dispatchAnalyticsEvent: props.dispatchAnalyticsEvent, allowFixedColumnWidthOption: props.allowFixedColumnWidthOption }); } }, { key: "viewShouldUpdate", value: function viewShouldUpdate(nextNode) { var _getPluginState = (0, _pluginFactory.getPluginState)(this.view.state), hoveredRows = _getPluginState.hoveredRows; var hoveredRowsChanged = !!(hoveredRows !== null && hoveredRows !== void 0 && hoveredRows.length) !== this.hasHoveredRows; if (nextNode.attrs.isNumberColumnEnabled && hoveredRowsChanged) { this.hasHoveredRows = !!(hoveredRows !== null && hoveredRows !== void 0 && hoveredRows.length); return true; } var node = this.getNode(); if ((0, _typeof2.default)(node.attrs) !== (0, _typeof2.default)(nextNode.attrs)) { return true; } if ((0, _nodes.tablesHaveDifferentColumnWidths)(node, nextNode) && (0, _expValEquals.expValEquals)('platform_editor_lovability_distribute_column_fix', 'isEnabled', true)) { return true; } var attrKeys = Object.keys(node.attrs); var nextAttrKeys = Object.keys(nextNode.attrs); if (attrKeys.length !== nextAttrKeys.length) { return true; } var tableMap = _tableMap.TableMap.get(node); var nextTableMap = _tableMap.TableMap.get(nextNode); if (tableMap.width !== nextTableMap.width) { return true; } return attrKeys.some(function (key) { return node.attrs[key] !== nextNode.attrs[key]; }); } }, { key: "ignoreMutation", value: function ignoreMutation(mutation) { var type = mutation.type, _mutation$target = mutation.target, nodeName = _mutation$target.nodeName, firstChild = _mutation$target.firstChild; if (type === 'selection' && (nodeName === null || nodeName === void 0 ? void 0 : nodeName.toUpperCase()) === 'DIV' && (firstChild === null || firstChild === void 0 ? void 0 : firstChild.nodeName.toUpperCase()) === 'TABLE') { return false; } // ED-16668 // Do not remove this fixes an issue with windows firefox that relates to // the addition of the shadow sentinels if (type === 'selection' && (nodeName === null || nodeName === void 0 ? void 0 : nodeName.toUpperCase()) === 'TABLE' && ((firstChild === null || firstChild === void 0 ? void 0 : firstChild.nodeName.toUpperCase()) === 'COLGROUP' || (firstChild === null || firstChild === void 0 ? void 0 : firstChild.nodeName.toUpperCase()) === 'SPAN')) { return false; } if (!this.contentDOM) { return true; } return !this.contentDOM.contains(mutation.target) && mutation.type !== 'selection'; } }, { key: "destroy", value: function destroy() { var _this$eventDispatcher; if (this.resizeObserver) { this.resizeObserver.disconnect(); } (_this$eventDispatcher = this.eventDispatcher) === null || _this$eventDispatcher === void 0 || _this$eventDispatcher.emit('TABLE_DELETED', this.node); _superPropGet(TableView, "destroy", this, 3)([]); } }]); }(_reactNodeView.default); var createTableView = exports.createTableView = function createTableView(node, view, getPos, portalProviderAPI, eventDispatcher, getEditorContainerWidth, getEditorFeatureFlags, dispatchAnalyticsEvent, pluginInjectionApi, isCommentEditor, isChromelessEditor, allowFixedColumnWidthOption) { var _pluginInjectionApi$t; var _getPluginState2 = (0, _pluginFactory.getPluginState)(view.state), pluginConfig = _getPluginState2.pluginConfig, isDragAndDropEnabled = _getPluginState2.isDragAndDropEnabled, isTableScalingEnabled = _getPluginState2.isTableScalingEnabled; // Use shared state for isFullWidthModeEnabled and wasFullWidthModeEnabled for most up-to-date values var tableState = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$t = pluginInjectionApi.table) === null || _pluginInjectionApi$t === void 0 ? void 0 : _pluginInjectionApi$t.sharedState.currentState(); var _getPluginConfig = (0, _createPluginConfig.pluginConfig)(pluginConfig), allowColumnResizing = _getPluginConfig.allowColumnResizing, allowControls = _getPluginConfig.allowControls, allowTableResizing = _getPluginConfig.allowTableResizing, allowTableAlignment = _getPluginConfig.allowTableAlignment; var isTableFixedColumnWidthsOptionEnabled = ((0, _platformFeatureFlags.fg)('platform_editor_table_fixed_column_width_prop') ? allowFixedColumnWidthOption : getEditorFeatureFlags === null || getEditorFeatureFlags === void 0 ? void 0 : getEditorFeatureFlags().tableWithFixedColumnWidthsOption) || false; var shouldUseIncreasedScalingPercent = isTableScalingEnabled && (isTableFixedColumnWidthsOptionEnabled || isCommentEditor); return new TableView({ node: node, view: view, allowColumnResizing: allowColumnResizing, allowTableResizing: allowTableResizing, allowTableAlignment: allowTableAlignment, allowControls: allowControls, portalProviderAPI: portalProviderAPI, eventDispatcher: eventDispatcher, getPos: getPos, options: { isFullWidthModeEnabled: tableState === null || tableState === void 0 ? void 0 : tableState.isFullWidthModeEnabled, wasFullWidthModeEnabled: tableState === null || tableState === void 0 ? void 0 : tableState.wasFullWidthModeEnabled, isDragAndDropEnabled: isDragAndDropEnabled, isTableScalingEnabled: isTableScalingEnabled, // same as options.isTableScalingEnabled isCommentEditor: isCommentEditor, isChromelessEditor: isChromelessEditor, shouldUseIncreasedScalingPercent: shouldUseIncreasedScalingPercent }, getEditorContainerWidth: getEditorContainerWidth, getEditorFeatureFlags: getEditorFeatureFlags, dispatchAnalyticsEvent: dispatchAnalyticsEvent, pluginInjectionApi: pluginInjectionApi, allowFixedColumnWidthOption: allowFixedColumnWidthOption }).init(); };