@atlaskit/editor-plugin-table
Version:
Table plugin for the @atlaskit/editor
333 lines (321 loc) • 18.4 kB
JavaScript
"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 _editorSharedStyles = require("@atlaskit/editor-shared-styles");
var _tableMap = require("@atlaskit/editor-tables/table-map");
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$options, _this$options2, _this$getEditorFeatur;
var tableElement = rendered.dom.querySelector('table');
this.table = tableElement ? tableElement : rendered.dom;
this.renderedDOM = rendered.dom;
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 && (_this$getEditorFeatur = this.getEditorFeatureFlags) !== null && _this$getEditorFeatur !== void 0 && _this$getEditorFeatur.call(this).tableWithFixedColumnWidthsOption && 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
if (this.view.state.selection.visible) {
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 resolvedSelection = selectionBookmark.resolve(_this2.view.state.tr.doc);
// Don't set the selection if it's the same as the current selection.
if (!resolvedSelection.eq(_this2.view.state.selection)) {
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$options3,
_this$options4,
_this$getEditorFeatur2;
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]);
});
// 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 && (_this$getEditorFeatur2 = this.getEditorFeatureFlags) !== null && _this$getEditorFeatur2 !== void 0 && _this$getEditorFeatur2.call(this).tableWithFixedColumnWidthsOption && 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
});
}
}, {
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;
}
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) {
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 = (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
}).init();
};