@atlaskit/editor-plugin-table
Version:
Table plugin for the @atlaskit/editor
958 lines (937 loc) • 47.9 kB
JavaScript
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import React from 'react';
import classnames from 'classnames';
import rafSchedule from 'raf-schd';
import { injectIntl } from 'react-intl-next';
import { browser as browserLegacy, getBrowserInfo } from '@atlaskit/editor-common/browser';
import { tintDirtyTransaction } from '@atlaskit/editor-common/collab';
import { getParentOfTypeCount } from '@atlaskit/editor-common/nesting';
import { nodeVisibilityManager } from '@atlaskit/editor-common/node-visibility';
import { getParentNodeWidth, getTableContainerWidth } from '@atlaskit/editor-common/node-width';
import { isValidPosition } from '@atlaskit/editor-common/utils';
import { akEditorTableNumberColumnWidth } from '@atlaskit/editor-shared-styles';
import { isTableSelected } from '@atlaskit/editor-tables/utils';
import { fg } from '@atlaskit/platform-feature-flags';
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { autoSizeTable, clearHoverSelection, setTableRef } from '../pm-plugins/commands';
import { autoScrollerFactory } from '../pm-plugins/drag-and-drop/utils/autoscrollers';
import { pluginKey as stickyHeadersPluginKey } from '../pm-plugins/sticky-headers/plugin-key';
import { findStickyHeaderForTable } from '../pm-plugins/sticky-headers/util';
import { insertColgroupFromNode, hasTableBeenResized } from '../pm-plugins/table-resizing/utils/colgroup';
import { COLUMN_MIN_WIDTH, TABLE_EDITOR_MARGIN, TABLE_OFFSET_IN_COMMENT_EDITOR } from '../pm-plugins/table-resizing/utils/consts';
import { updateControls } from '../pm-plugins/table-resizing/utils/dom';
import { getLayoutSize, getScalingPercentForTableWithoutWidth, getTableScalingPercent } from '../pm-plugins/table-resizing/utils/misc';
import { getResizeState, updateColgroup } from '../pm-plugins/table-resizing/utils/resize-state';
import { scaleTable } from '../pm-plugins/table-resizing/utils/scale-table';
import { containsHeaderRow, isTableNested, isTableNestedInMoreThanOneNode, tablesHaveDifferentColumnWidths, tablesHaveDifferentNoOfColumns, tablesHaveDifferentNoOfRows } from '../pm-plugins/utils/nodes';
import { getAssistiveMessage } from '../pm-plugins/utils/table';
import { TableCssClassName as ClassName } from '../types';
import { handleMouseOut, handleMouseOver, isTableInFocus, withCellTracking } from '../ui/event-handlers';
import TableFloatingColumnControls from '../ui/TableFloatingColumnControls';
// Ignored via go/ees005
// eslint-disable-next-line import/no-named-as-default
import TableFloatingControls from '../ui/TableFloatingControls';
import { ExternalDropTargets } from './ExternalDropTargets';
import { TableContainer } from './TableContainer';
import { TableStickyScrollbar } from './TableStickyScrollbar';
// Prevent unnecessary parentWidth updates when table is nested inside of a node that is nested itself.
const NESTED_TABLE_IN_NESTED_PARENT_WIDTH_DIFF_MIN_THRESHOLD = 2;
const NESTED_TABLE_IN_NESTED_PARENT_WIDTH_DIFF_MAX_THRESHOLD = 20;
// eslint-disable-next-line @repo/internal/react/no-class-components
class TableComponent extends React.Component {
constructor(props) {
super(props);
_defineProperty(this, "state", {
parentWidth: undefined,
tableWrapperWidth: undefined,
tableWrapperHeight: undefined,
windowResized: false
});
_defineProperty(this, "handleMouseOut", event => {
if (!isTableInFocus(this.props.view)) {
return false;
}
return handleMouseOut(this.props.view, event);
});
_defineProperty(this, "handleMouseOver", event => {
if (!isTableInFocus(this.props.view)) {
return false;
}
return withCellTracking(handleMouseOver)(this.props.view, event);
});
_defineProperty(this, "handleMouseEnter", () => {
const node = this.props.getNode();
const pos = this.props.getPos();
const tr = this.props.view.state.tr;
const tableId = node.attrs.localId;
tr.setMeta('mouseEnterTable', [tableId, node, pos]);
this.props.view.dispatch(tr);
});
_defineProperty(this, "lastSetTableRef", null);
_defineProperty(this, "onStickyState", state => {
const pos = this.props.getPos();
if (!isValidPosition(pos, this.props.view.state)) {
return;
}
const stickyHeader = findStickyHeaderForTable(state, pos);
if (stickyHeader !== this.state.stickyHeader) {
this.setState({
stickyHeader
});
}
});
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_defineProperty(this, "prevTableState", null);
_defineProperty(this, "handleScroll", event => {
if (!this.wrapper || event.target !== this.wrapper) {
return;
}
if (this.stickyScrollbar) {
this.stickyScrollbar.scrollLeft(this.wrapper.scrollLeft);
}
if (this.table) {
// sync sticky header row to table scroll
const headers = this.table.querySelectorAll('tr[data-header-row]');
for (let i = 0; i < headers.length; i++) {
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
const header = headers[i];
header.scrollLeft = this.wrapper.scrollLeft;
header.style.marginRight = '2px';
}
}
// force update to for table controls to re-align
this.forceUpdate();
});
_defineProperty(this, "handleTableResizing", () => {
const {
getNode,
containerWidth,
options,
allowTableResizing
} = this.props;
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const prevNode = this.node;
const node = getNode();
const prevAttrs = prevNode.attrs;
const isNested = isTableNested(this.props.view.state, this.props.getPos());
let parentWidth = this.getParentNodeWidth();
if (isNested && isTableNestedInMoreThanOneNode(this.props.view.state, this.props.getPos())) {
const resizeObsWrapperWidth = this.wrapperWidth || 0;
const wrapperWidthDiffBetweenRerenders = Math.abs(resizeObsWrapperWidth - (this.state.parentWidth || 0));
const isOusideOfThreshold = wrapperWidthDiffBetweenRerenders <= NESTED_TABLE_IN_NESTED_PARENT_WIDTH_DIFF_MIN_THRESHOLD || wrapperWidthDiffBetweenRerenders > NESTED_TABLE_IN_NESTED_PARENT_WIDTH_DIFF_MAX_THRESHOLD;
// 1. Check isOusideOfThreshold is added to prevent undersired state update.
// When table is nested in the extenstion and the table column is being resized,
// space available within extension can change and cause undesirable state update.
// MIN_THRESNESTED_TABLE_IN_NESTED_PARENT_WIDTH_DIFF_MIN_THRESHOLDHOLD value is required
// as the resizeObsWrapperWidth can differ between page reloads by 2px.
// 2. Check resizeObsWrapperWidth > 1 is added to prevent parentWidth update when table unmounts.
// When a is nested table in a nested expand and the expand collabses, the table unmounts and
// resizeObsWrapperWidth becomes 1.
parentWidth = isOusideOfThreshold && resizeObsWrapperWidth > 1 ? resizeObsWrapperWidth : this.state.parentWidth;
}
const parentWidthChanged = parentWidth && parentWidth !== this.state.parentWidth;
const layoutSize = this.tableNodeLayoutSize(node, containerWidth.width, options);
const hasNumberedColumnChanged = prevAttrs.isNumberColumnEnabled !== node.attrs.isNumberColumnEnabled;
const noOfColumnsChanged = tablesHaveDifferentNoOfColumns(node, prevNode);
if (
// We need to react if our parent changes
// Scales the cols widths relative to the new parent width.
parentWidthChanged ||
// Enabling / disabling this feature reduces or adds size to the table.
hasNumberedColumnChanged ||
// This last check is also to cater for dynamic text sizing changing the 'default' layout width
// Usually happens on window resize.
layoutSize !== this.layoutSize || noOfColumnsChanged) {
const shouldScaleTable = (!allowTableResizing || allowTableResizing && isNested) && !hasNumberedColumnChanged && !noOfColumnsChanged;
// If column has been inserted/deleted avoid multi dispatch
if (shouldScaleTable) {
this.scaleTable({
parentWidth
}, hasNumberedColumnChanged);
}
// only when table resizing is enabled and toggle numbered column to run scaleTable
if (allowTableResizing && hasNumberedColumnChanged) {
if (!hasTableBeenResized(prevNode)) {
this.scaleTable({
parentWidth: node.attrs.width
}, true);
}
}
this.updateParentWidth(parentWidth);
}
this.node = node;
this.containerWidth = containerWidth;
this.layoutSize = layoutSize;
});
// Function gets called when table is nested.
_defineProperty(this, "scaleTable", (scaleOptions, isUserTriggered = false) => {
const {
view,
getNode,
getPos,
containerWidth,
options
} = this.props;
const node = getNode();
const {
state,
dispatch
} = view;
const pos = getPos();
if (typeof pos !== 'number' || !isValidPosition(pos, state)) {
return;
}
const domAtPos = view.domAtPos.bind(view);
const {
width
} = containerWidth;
this.scaleTableDebounced.cancel();
const tr = scaleTable(this.table, {
...scaleOptions,
node,
prevNode: this.node || node,
start: pos + 1,
containerWidth: width,
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
previousContainerWidth: this.containerWidth.width || width,
...options
}, domAtPos, this.props.pluginInjectionApi, false,
// isTableScalingEnabled doesn't change the behavior of nested tables
false // shouldUseIncreasedScalingPercent set to false for nested tables
)(state.tr);
if (!isUserTriggered) {
tintDirtyTransaction(tr);
// Avoid adding this transaction separately to the history as these are automatic updates
// as a consequence of another action
tr.setMeta('addToHistory', false);
}
dispatch(tr);
});
_defineProperty(this, "handleAutoSize", () => {
if (this.table) {
const {
view,
getNode,
getPos,
containerWidth
} = this.props;
const node = getNode();
const pos = getPos();
if (!isValidPosition(pos, view.state)) {
return;
}
autoSizeTable(view, node, this.table, pos, {
containerWidth: containerWidth.width
});
}
});
_defineProperty(this, "handleWindowResize", () => {
const {
getNode,
containerWidth
} = this.props;
const node = getNode();
const layoutSize = this.tableNodeLayoutSize(node);
if (containerWidth.width > layoutSize) {
return;
}
const parentWidth = this.getParentNodeWidth();
this.scaleTableDebounced({
parentWidth: parentWidth
});
});
// This is a new handler for window resize events that sets the windowResized state immediately
// This is needed to update colgroup on window resize, to enforce the table scaling
_defineProperty(this, "handleWindowResizeNew", () => {
// Set resizing to true immediately
if (!this.state.windowResized) {
this.setState({
windowResized: true
});
}
});
_defineProperty(this, "getParentNodeWidth", () => {
const {
getPos,
containerWidth,
options,
view: {
state
}
} = this.props;
const pos = getPos();
if (!isValidPosition(pos, state)) {
return;
}
const parentNodeWith = getParentNodeWidth(pos, state, containerWidth, options && options.isFullWidthModeEnabled);
return parentNodeWith;
});
_defineProperty(this, "updateParentWidth", width => {
this.setState({
parentWidth: width
});
});
_defineProperty(this, "tableNodeLayoutSize", (node, containerWidth, options) => getLayoutSize(node.attrs.layout, containerWidth || this.props.containerWidth.width, options || this.props.options || {}));
_defineProperty(this, "shouldUpdateColgroup", params => {
var _this$props$options, _this$props$options2;
const {
isWindowResized,
isWidthChanged,
isTableWidthChanged,
isColumnsDistributed,
isTableResizedFullWidth,
isTableDisplayModeChanged,
isNumberColumnChanged,
isNumberOfColumnsChanged,
isFullWidthModeAndLineLengthChanged
} = params;
const isFullPageEditor = !((_this$props$options = this.props.options) !== null && _this$props$options !== void 0 && _this$props$options.isCommentEditor) && !((_this$props$options2 = this.props.options) !== null && _this$props$options2 !== void 0 && _this$props$options2.isChromelessEditor);
if (isFullPageEditor) {
return !!isWindowResized || isColumnsDistributed || !!isTableResizedFullWidth || isTableDisplayModeChanged || isNumberColumnChanged || isNumberOfColumnsChanged || !!isFullWidthModeAndLineLengthChanged;
}
return isWidthChanged || isTableWidthChanged || isColumnsDistributed || !!isTableResizedFullWidth || isTableDisplayModeChanged || isNumberColumnChanged || isNumberOfColumnsChanged || !!isFullWidthModeAndLineLengthChanged;
});
_defineProperty(this, "scaleTableDebounced", rafSchedule(this.scaleTable));
_defineProperty(this, "handleTableResizingDebounced", rafSchedule(this.handleTableResizing));
_defineProperty(this, "handleScrollDebounced", rafSchedule(this.handleScroll));
_defineProperty(this, "handleAutoSizeDebounced", rafSchedule(this.handleAutoSize));
_defineProperty(this, "handleWindowResizeDebounced", rafSchedule(this.handleWindowResize));
_defineProperty(this, "handleWindowResizeNewDebounced", rafSchedule(this.handleWindowResizeNew));
const {
options: _options,
containerWidth: _containerWidth,
getNode: _getNode
} = props;
this.node = _getNode();
this.containerWidth = _containerWidth;
const tablePos = props.getPos();
this.isNestedInTable = tablePos ? getParentOfTypeCount(props.view.state.schema.nodes.table)(props.view.state.doc.resolve(tablePos)) > 0 : false;
if (!this.updateColGroupFromFullWidthChange) {
this.updateColGroupFromFullWidthChange = (_options === null || _options === void 0 ? void 0 : _options.isFullWidthModeEnabled) && !(_options !== null && _options !== void 0 && _options.wasFullWidthModeEnabled);
}
// store table size using previous full-width mode so can detect if it has changed.
const isFullWidthModeEnabled = _options ? _options.wasFullWidthModeEnabled : false;
this.layoutSize = this.tableNodeLayoutSize(this.node, _containerWidth.width, {
isFullWidthModeEnabled
});
this.resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
this.setState(prev => {
var _entry$contentRect, _entry$contentRect2;
return (prev === null || prev === void 0 ? void 0 : prev.tableWrapperWidth) === ((_entry$contentRect = entry.contentRect) === null || _entry$contentRect === void 0 ? void 0 : _entry$contentRect.width) && (prev === null || prev === void 0 ? void 0 : prev.tableWrapperHeight) === ((_entry$contentRect2 = entry.contentRect) === null || _entry$contentRect2 === void 0 ? void 0 : _entry$contentRect2.height) ? prev : {
...prev,
tableWrapperWidth: entry.contentRect.width,
tableWrapperHeight: entry.contentRect.height
};
});
}
});
}
componentDidMount() {
const {
observe
} = nodeVisibilityManager(this.props.view.dom);
if (this.table) {
this.nodeVisibilityObserverCleanupFn = observe({
element: this.table,
onFirstVisible: () => {
this.initialiseEventListenersAfterMount();
// force width calculation - missed resize event under firefox when
// table is nested within bodied extension
if (this.wrapper) {
var _this$wrapper;
this.wrapperWidth = (_this$wrapper = this.wrapper) === null || _this$wrapper === void 0 ? void 0 : _this$wrapper.clientWidth;
}
}
});
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
window.addEventListener('resize', this.handleWindowResizeNewDebounced);
}
this.dispatchTableRefUpdate();
}
initialiseEventListenersAfterMount() {
var _this$table, _this$table2, _this$table3;
const {
allowColumnResizing,
allowTableResizing,
eventDispatcher,
isDragAndDropEnabled,
getNode
} = this.props;
const browser = expValEquals('platform_editor_hydratable_ui', 'isEnabled', true) ? getBrowserInfo() : browserLegacy;
const isIE11 = browser.ie_version === 11;
// Ignored via go/ees005
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
this === null || this === void 0 ? void 0 : (_this$table = this.table) === null || _this$table === void 0 ? void 0 : _this$table.addEventListener('mouseenter', this.handleMouseEnter);
// Ignored via go/ees005
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
this === null || this === void 0 ? void 0 : (_this$table2 = this.table) === null || _this$table2 === void 0 ? void 0 : _this$table2.addEventListener('mouseout', this.handleMouseOut);
// Ignored via go/ees005
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
this === null || this === void 0 ? void 0 : (_this$table3 = this.table) === null || _this$table3 === void 0 ? void 0 : _this$table3.addEventListener('mouseover', this.handleMouseOver);
if (this.wrapper) {
this.wrapperReisizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
var _entry$contentRect3;
this.wrapperWidth = (_entry$contentRect3 = entry.contentRect) === null || _entry$contentRect3 === void 0 ? void 0 : _entry$contentRect3.width;
}
});
this.wrapperReisizeObserver.observe(this.wrapper);
}
if (allowColumnResizing && this.wrapper && !isIE11) {
// Ignored via go/ees005
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
this.wrapper.addEventListener('scroll', this.handleScrollDebounced, {
passive: true
});
if (this.table && !this.isNestedInTable) {
this.stickyScrollbar = new TableStickyScrollbar(this.wrapper, this.props.view);
}
if (isDragAndDropEnabled) {
this.dragAndDropCleanupFn = combine(...autoScrollerFactory({
tableWrapper: this.wrapper,
getNode
}));
}
}
if (allowColumnResizing) {
/**
* We no longer use `containerWidth` as a variable to determine an update for table resizing (avoids unnecessary updates).
* Instead we use the resize event to only trigger updates when necessary.
*/
if (!allowTableResizing) {
// Ignored via go/ees005
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
window.addEventListener('resize', this.handleWindowResizeDebounced);
}
this.handleTableResizingDebounced();
}
const currentStickyState = stickyHeadersPluginKey.getState(this.props.view.state);
if (currentStickyState) {
this.onStickyState(currentStickyState);
}
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
eventDispatcher.on(stickyHeadersPluginKey.key, this.onStickyState);
}
componentWillUnmount() {
var _this$resizeObserver, _this$wrapperReisizeO, _this$table4, _this$table5, _this$table6;
const {
allowColumnResizing,
allowTableResizing,
eventDispatcher,
isDragAndDropEnabled,
view,
isInDanger
} = this.props;
const browser = expValEquals('platform_editor_hydratable_ui', 'isEnabled', true) ? getBrowserInfo() : browserLegacy;
const isIE11 = browser.ie_version === 11;
if (this.wrapper && !isIE11) {
// Ignored via go/ees005
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
this.wrapper.removeEventListener('scroll', this.handleScrollDebounced);
}
if (isDragAndDropEnabled && this.dragAndDropCleanupFn) {
this.dragAndDropCleanupFn();
}
if (this.nodeVisibilityObserverCleanupFn) {
this.nodeVisibilityObserverCleanupFn();
}
(_this$resizeObserver = this.resizeObserver) === null || _this$resizeObserver === void 0 ? void 0 : _this$resizeObserver.disconnect();
(_this$wrapperReisizeO = this.wrapperReisizeObserver) === null || _this$wrapperReisizeO === void 0 ? void 0 : _this$wrapperReisizeO.disconnect();
if (this.stickyScrollbar) {
this.stickyScrollbar.dispose();
}
this.handleScrollDebounced.cancel();
this.scaleTableDebounced.cancel();
this.handleTableResizingDebounced.cancel();
this.handleAutoSizeDebounced.cancel();
if (!allowTableResizing) {
this.handleWindowResizeDebounced.cancel();
}
this.handleWindowResizeNewDebounced.cancel();
if (isInDanger) {
clearHoverSelection()(view.state, view.dispatch);
}
if (!allowTableResizing && allowColumnResizing) {
// Ignored via go/ees005
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
window.removeEventListener('resize', this.handleWindowResizeDebounced);
}
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
window.removeEventListener('resize', this.handleWindowResizeNewDebounced);
// Ignored via go/ees005
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
this === null || this === void 0 ? void 0 : (_this$table4 = this.table) === null || _this$table4 === void 0 ? void 0 : _this$table4.removeEventListener('mouseenter', this.handleMouseEnter);
// Ignored via go/ees005
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
this === null || this === void 0 ? void 0 : (_this$table5 = this.table) === null || _this$table5 === void 0 ? void 0 : _this$table5.removeEventListener('mouseout', this.handleMouseOut);
// Ignored via go/ees005
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
this === null || this === void 0 ? void 0 : (_this$table6 = this.table) === null || _this$table6 === void 0 ? void 0 : _this$table6.removeEventListener('mouseover', this.handleMouseOver);
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
eventDispatcher.off(stickyHeadersPluginKey.key, this.onStickyState);
if (this.initialOverflowCaptureTimerId) {
clearTimeout(this.initialOverflowCaptureTimerId);
}
}
// Should be called only when table node width is reset to undefined in Comment Editor
// Maybe replaced by handleColgroupUpdates as we implement new table's support in Comment Editor.
removeInlineTableWidth() {
const {
isResizing,
getNode,
view,
getPos
} = this.props;
if (!this.table) {
return;
}
const tableNode = getNode();
const newTableWidth = tableNode.attrs.width;
const start = getPos() || 0;
const depth = view.state.doc.resolve(start).depth;
if (depth !== 0) {
return;
}
if (!isResizing && newTableWidth === null) {
this.table.style.width = '';
}
}
handleColgroupUpdates(force = false) {
var _this$containerWidth;
const {
getNode,
containerWidth,
isResizing,
view,
getPos,
getEditorFeatureFlags,
options
} = this.props;
if (!this.table) {
return;
}
// Remove any widths styles after resizing preview is completed
this.table.style.width = '';
const tableNode = getNode();
const start = getPos() || 0;
const depth = view.state.doc.resolve(start).depth;
if (depth !== 0) {
return;
}
let tableNodeWidth = getTableContainerWidth(tableNode);
const isTableResizedFullWidth = tableNodeWidth === 1800 && this.wasResizing && !isResizing;
// Needed for undo / redo
const isTableWidthChanged = tableNodeWidth !== this.tableNodeWidth;
const tableRenderWidth = options !== null && options !== void 0 && options.isCommentEditor ? containerWidth.width - (TABLE_OFFSET_IN_COMMENT_EDITOR + 1) // should be the same as this.table.parentElement?.clientWidth - 1, subtract 1 to avoid overflow
: containerWidth.width - TABLE_EDITOR_MARGIN;
tableNodeWidth = options !== null && options !== void 0 && options.isCommentEditor && !tableNode.attrs.width ? tableRenderWidth : tableNodeWidth;
const isTableSquashed = tableRenderWidth < tableNodeWidth;
const isNumberColumnChanged = tableNode.attrs.isNumberColumnEnabled !== this.node.attrs.isNumberColumnEnabled;
const isNumberOfColumnsChanged = tablesHaveDifferentNoOfColumns(tableNode, this.node);
const {
width: containerWidthValue,
lineLength: containerLineLength
} = containerWidth;
const isLineLengthChanged = ((_this$containerWidth = this.containerWidth) === null || _this$containerWidth === void 0 ? void 0 : _this$containerWidth.lineLength) !== containerLineLength;
const isFullWidthModeAndLineLengthChanged = this.updateColGroupFromFullWidthChange && isLineLengthChanged;
const maybeScale = isTableSquashed || isTableWidthChanged || isTableResizedFullWidth && !(options !== null && options !== void 0 && options.isCommentEditor) || isNumberColumnChanged || isNumberOfColumnsChanged || this.state.windowResized;
if (force || maybeScale || isFullWidthModeAndLineLengthChanged) {
var _this$containerWidth2, _this$props, _getEditorFeatureFlag, _this$props$options3, _this$props$options4, _this$props$options5;
const isWidthChanged = ((_this$containerWidth2 = this.containerWidth) === null || _this$containerWidth2 === void 0 ? void 0 : _this$containerWidth2.width) !== containerWidthValue;
const wasTableResized = hasTableBeenResized(this.node);
const isTableResized = hasTableBeenResized(tableNode);
const isColumnsDistributed = wasTableResized && !isTableResized;
const isTableDisplayModeChanged = this.node.attrs.displayMode !== tableNode.attrs.displayMode;
const shouldUpdateColgroup = this.shouldUpdateColgroup({
isWindowResized: this.state.windowResized,
isWidthChanged,
isTableWidthChanged,
isColumnsDistributed,
isTableResizedFullWidth,
isTableDisplayModeChanged,
isNumberColumnChanged,
isNumberOfColumnsChanged,
isFullWidthModeAndLineLengthChanged,
isTableResized
});
const tableWithFixedColumnWidthsOption = (fg('platform_editor_table_fixed_column_width_prop') ? (_this$props = this.props) === null || _this$props === void 0 ? void 0 : _this$props.allowFixedColumnWidthOption : (_getEditorFeatureFlag = getEditorFeatureFlags()) === null || _getEditorFeatureFlag === void 0 ? void 0 : _getEditorFeatureFlag.tableWithFixedColumnWidthsOption) || false;
const isTableScalingWithFixedColumnWidthsOptionEnabled = !!((_this$props$options3 = this.props.options) !== null && _this$props$options3 !== void 0 && _this$props$options3.isTableScalingEnabled) && tableWithFixedColumnWidthsOption;
const shouldUseIncreasedScalingPercent = isTableScalingWithFixedColumnWidthsOptionEnabled || !!((_this$props$options4 = this.props.options) !== null && _this$props$options4 !== void 0 && _this$props$options4.isTableScalingEnabled) && !!((_this$props$options5 = this.props.options) !== null && _this$props$options5 !== void 0 && _this$props$options5.isCommentEditor);
if (force || !isResizing && shouldUpdateColgroup) {
var _this$props$options6, _this$props$options7, _this$props$options8, _this$props$options9;
const resizeState = getResizeState({
minWidth: COLUMN_MIN_WIDTH,
// When numbered column enabled, we need to subtract the width of the numbered column
maxSize: tableNode.attrs.isNumberColumnEnabled ? tableRenderWidth - akEditorTableNumberColumnWidth : tableRenderWidth,
table: tableNode,
tableRef: this.table,
start,
domAtPos: view.domAtPos.bind(view),
isTableScalingEnabled: true,
shouldUseIncreasedScalingPercent,
isCommentEditor: !!((_this$props$options6 = this.props.options) !== null && _this$props$options6 !== void 0 && _this$props$options6.isCommentEditor)
});
let shouldScaleOnColgroupUpdate = false;
if ((_this$props$options7 = this.props.options) !== null && _this$props$options7 !== void 0 && _this$props$options7.isTableScalingEnabled && !tableWithFixedColumnWidthsOption) {
shouldScaleOnColgroupUpdate = true;
}
if (isTableScalingWithFixedColumnWidthsOptionEnabled && tableNode.attrs.displayMode !== 'fixed') {
shouldScaleOnColgroupUpdate = true;
}
if ((_this$props$options8 = this.props.options) !== null && _this$props$options8 !== void 0 && _this$props$options8.isTableScalingEnabled && (_this$props$options9 = this.props.options) !== null && _this$props$options9 !== void 0 && _this$props$options9.isCommentEditor) {
shouldScaleOnColgroupUpdate = true;
}
let scalePercent = 1;
requestAnimationFrame(() => {
var _this$props$options0, _this$props$options1;
// Scaling percent has to be calculated inside requestAnimationFrame, otherwise
// renderWidth calculated as `tableRef?.parentElement?.clientWidth`
// inside of getTableScalingPercent returns 0.
if (!((_this$props$options0 = this.props.options) !== null && _this$props$options0 !== void 0 && _this$props$options0.isCommentEditor) || (_this$props$options1 = this.props.options) !== null && _this$props$options1 !== void 0 && _this$props$options1.isCommentEditor && tableNode.attrs.width) {
scalePercent = getTableScalingPercent(tableNode,
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.table, shouldUseIncreasedScalingPercent);
} else {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
scalePercent = getScalingPercentForTableWithoutWidth(tableNode, this.table);
}
// Request animation frame required for Firefox
updateColgroup(resizeState,
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.table, tableNode, shouldScaleOnColgroupUpdate, scalePercent);
});
}
}
if (isFullWidthModeAndLineLengthChanged) {
this.updateColGroupFromFullWidthChange = false;
}
this.tableNodeWidth = tableNodeWidth;
this.wasResizing = isResizing;
this.containerWidth = containerWidth;
}
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
componentDidUpdate(_, prevState) {
var _getEditorFeatureFlag2, _this$props$options10, _this$props$options11;
const {
getNode,
isMediaFullscreen,
allowColumnResizing,
allowTableResizing,
isResizing,
options,
isTableScalingEnabled,
// we could use options.isTableScalingEnabled here
getPos,
getEditorFeatureFlags,
allowFixedColumnWidthOption
} = this.props;
let shouldScale = false;
let shouldHandleColgroupUpdates = false;
const tableWithFixedColumnWidthsOption = (fg('platform_editor_table_fixed_column_width_prop') ? allowFixedColumnWidthOption : (_getEditorFeatureFlag2 = getEditorFeatureFlags()) === null || _getEditorFeatureFlag2 === void 0 ? void 0 : _getEditorFeatureFlag2.tableWithFixedColumnWidthsOption) || false;
if (isTableScalingEnabled && !tableWithFixedColumnWidthsOption) {
shouldScale = true;
shouldHandleColgroupUpdates = true;
}
const isTableScalingWithFixedColumnWidthsOptionEnabled = !!isTableScalingEnabled && tableWithFixedColumnWidthsOption;
const shouldUseIncreasedScalingPercent = isTableScalingWithFixedColumnWidthsOptionEnabled || !!isTableScalingEnabled && !!((_this$props$options10 = this.props.options) !== null && _this$props$options10 !== void 0 && _this$props$options10.isCommentEditor);
if (isTableScalingWithFixedColumnWidthsOptionEnabled && getNode().attrs.displayMode !== 'fixed') {
shouldScale = true;
shouldHandleColgroupUpdates = true;
}
if (shouldHandleColgroupUpdates) {
this.handleColgroupUpdates();
}
if ((_this$props$options11 = this.props.options) !== null && _this$props$options11 !== void 0 && _this$props$options11.isCommentEditor && allowTableResizing && !(options !== null && options !== void 0 && options.isTableScalingEnabled)) {
this.removeInlineTableWidth();
}
const currentTable = getNode();
const previousTable = this.node;
const isNoOfColumnsChanged = tablesHaveDifferentNoOfColumns(currentTable, previousTable);
const isNoOfRowsChanged = tablesHaveDifferentNoOfRows(currentTable, previousTable);
if (isNoOfColumnsChanged || isNoOfRowsChanged) {
var _this$props$pluginInj, _this$props$pluginInj2;
(_this$props$pluginInj = this.props.pluginInjectionApi) === null || _this$props$pluginInj === void 0 ? void 0 : (_this$props$pluginInj2 = _this$props$pluginInj.accessibilityUtils) === null || _this$props$pluginInj2 === void 0 ? void 0 : _this$props$pluginInj2.actions.ariaNotify(getAssistiveMessage(previousTable, currentTable, this.props.intl), {
priority: 'important'
});
}
if (currentTable.attrs.__autoSize) {
// Wait for next tick to handle auto sizing, gives the browser time to do layout calc etc.
this.handleAutoSizeDebounced();
}
// re-drawing will cause media component get unmounted that will exit fullscreen mode if media is in fullscreen mode
// see https://product-fabric.atlassian.net/browse/MEX-1290
else if (allowColumnResizing && this.table && !isMediaFullscreen) {
// If col widths (e.g. via collab) or number of columns (e.g. delete a column) have changed,
// re-draw colgroup.
if (tablesHaveDifferentColumnWidths(currentTable, previousTable) || isNoOfColumnsChanged) {
const {
view
} = this.props;
const shouldRecreateResizeCols = !allowTableResizing || !isResizing || isNoOfColumnsChanged && isResizing;
if (shouldRecreateResizeCols) {
const start = getPos() || 0;
const depth = view.state.doc.resolve(start).depth;
shouldScale = depth === 0 && shouldScale;
insertColgroupFromNode(this.table, currentTable, shouldScale, undefined, shouldUseIncreasedScalingPercent, options === null || options === void 0 ? void 0 : options.isCommentEditor);
}
updateControls()(view.state);
}
this.handleTableResizingDebounced();
}
this.dispatchTableRefUpdate();
}
dispatchTableRefUpdate() {
if (this.table && this.table !== this.lastSetTableRef && (!expValEquals('platform_editor_table_ref_optimisation', 'isEnabled', true) || this.props.tableActive) && this.props.view && expValEquals('platform_editor_fix_editor_unhandled_type_errors', 'isEnabled', true) && (expValEquals('platform_editor_table_update_table_ref', 'isEnabled', true) || fg('platform_editor_enable_table_update_ref_atlas'))) {
this.lastSetTableRef = this.table;
setTableRef(this.table)(this.props.view.state, this.props.view.dispatch);
}
}
observeTable(table) {
if (table) {
var _this$resizeObserver2;
(_this$resizeObserver2 = this.resizeObserver) === null || _this$resizeObserver2 === void 0 ? void 0 : _this$resizeObserver2.observe(table);
}
}
render() {
var _this$props$options12;
const {
view,
getNode,
isResizing,
allowControls = true,
isHeaderRowEnabled,
ordering,
isHeaderColumnEnabled,
tableActive,
containerWidth,
options,
getPos,
pluginInjectionApi,
isDragAndDropEnabled,
getEditorFeatureFlags,
isTableScalingEnabled,
// here we can use options.isTableScalingEnabled
allowTableResizing,
allowTableAlignment,
selection,
isInDanger,
hoveredRows,
hoveredCell,
isTableHovered,
isWholeTableInDanger
} = this.props;
const node = getNode();
const tableRef = this.table || undefined;
const headerRow = tableRef ? tableRef.querySelector('tr[data-header-row]') : undefined;
const hasHeaderRow = containsHeaderRow(node);
const rowControls = /*#__PURE__*/React.createElement(TableFloatingControls, {
editorView: view,
tableRef: tableRef,
tableNode: node,
tableActive: tableActive,
hoveredRows: hoveredRows,
hoveredCell: hoveredCell,
isTableHovered: isTableHovered,
isInDanger: isInDanger,
isResizing: isResizing,
isNumberColumnEnabled: node.attrs.isNumberColumnEnabled,
isHeaderRowEnabled: isHeaderRowEnabled,
isDragAndDropEnabled: isDragAndDropEnabled,
ordering: ordering,
isHeaderColumnEnabled: isHeaderColumnEnabled,
hasHeaderRow: hasHeaderRow
// pass `selection` and `tableHeight` to control re-render
,
selection: view.state.selection,
headerRowHeight: headerRow ? headerRow.offsetHeight : undefined,
stickyHeader: !this.props.limitedMode ? this.state.stickyHeader : undefined,
tableWrapperWidth: this.state.tableWrapperWidth,
api: pluginInjectionApi,
isChromelessEditor: options === null || options === void 0 ? void 0 : options.isChromelessEditor
});
const tableContainerWidth = getTableContainerWidth(node);
const colControls = isDragAndDropEnabled ? /*#__PURE__*/React.createElement(TableFloatingColumnControls, {
editorView: view,
tableRef: tableRef,
getNode: getNode,
tableActive: tableActive,
isInDanger: isInDanger,
hoveredRows: hoveredRows,
hoveredCell: hoveredCell,
isTableHovered: isTableHovered,
isResizing: isResizing,
ordering: ordering,
hasHeaderRow: hasHeaderRow
// pass `selection` to control re-render
,
selection: view.state.selection,
headerRowHeight: headerRow ? headerRow.offsetHeight : undefined,
stickyHeader: !this.props.limitedMode ? this.state.stickyHeader : undefined,
getEditorFeatureFlags: getEditorFeatureFlags,
tableContainerWidth: tableContainerWidth,
isNumberColumnEnabled: node.attrs.isNumberColumnEnabled
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
getScrollOffset: () => {
var _this$wrapper2;
return ((_this$wrapper2 = this.wrapper) === null || _this$wrapper2 === void 0 ? void 0 : _this$wrapper2.scrollLeft) || 0;
},
tableWrapperHeight: this.state.tableWrapperHeight,
api: pluginInjectionApi,
isChromelessEditor: options === null || options === void 0 ? void 0 : options.isChromelessEditor,
isDragAndDropEnabled: isDragAndDropEnabled
}) : null;
/**
* ED-19838
* There is a getPos issue coming from this code. We need to apply this workaround for now and apply a patch
* before CR6 lands in production
*/
let tablePos;
try {
tablePos = getPos ? getPos() : undefined;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
tablePos = undefined;
}
const isNested = isTableNested(view.state, tablePos);
const {
tableWithFixedColumnWidthsOption = false
} = getEditorFeatureFlags();
const shouldUseIncreasedScalingPercent = !!isTableScalingEnabled && (tableWithFixedColumnWidthsOption || !!((_this$props$options12 = this.props.options) !== null && _this$props$options12 !== void 0 && _this$props$options12.isCommentEditor));
return /*#__PURE__*/React.createElement(TableContainer
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
, {
className: classnames(ClassName.TABLE_CONTAINER, {
[ClassName.WITH_CONTROLS]: allowControls && tableActive,
[ClassName.TABLE_STICKY]: !this.props.limitedMode && this.state.stickyHeader && hasHeaderRow,
[ClassName.HOVERED_DELETE_BUTTON]: isInDanger,
[ClassName.TABLE_SELECTED]: isTableSelected(selection !== null && selection !== void 0 ? selection : view.state.selection),
[ClassName.NESTED_TABLE_WITH_CONTROLS]: tableActive && allowControls && this.isNestedInTable
}),
editorView: view,
getPos: getPos,
node: node
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
,
tableRef: tableRef,
containerWidth: containerWidth,
isNested: isNested,
pluginInjectionApi: pluginInjectionApi,
tableWrapperHeight: this.state.tableWrapperHeight,
isTableResizingEnabled: allowTableResizing,
isResizing: isResizing,
isTableScalingEnabled: isTableScalingEnabled,
allowFixedColumnWidthOption: tableWithFixedColumnWidthsOption,
isWholeTableInDanger: isWholeTableInDanger,
isTableAlignmentEnabled: allowTableAlignment,
shouldUseIncreasedScalingPercent: shouldUseIncreasedScalingPercent,
isCommentEditor: options === null || options === void 0 ? void 0 : options.isCommentEditor,
isChromelessEditor: options === null || options === void 0 ? void 0 : options.isChromelessEditor
}, /*#__PURE__*/React.createElement("div", {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
className: ClassName.TABLE_STICKY_SENTINEL_TOP,
"data-testid": "sticky-sentinel-top"
}), !this.isNestedInTable && /*#__PURE__*/React.createElement("div", {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
className: ClassName.TABLE_STICKY_SCROLLBAR_SENTINEL_TOP,
"data-testid": "sticky-scrollbar-sentinel-top"
}), allowControls && rowControls, isDragAndDropEnabled && /*#__PURE__*/React.createElement(ExternalDropTargets, {
editorView: view,
node: node
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
getScrollOffset: () => {
var _this$wrapper3;
return ((_this$wrapper3 = this.wrapper) === null || _this$wrapper3 === void 0 ? void 0 : _this$wrapper3.scrollLeft) || 0;
}
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
getTableWrapperWidth: () => {
var _this$wrapper4;
return ((_this$wrapper4 = this.wrapper) === null || _this$wrapper4 === void 0 ? void 0 : _this$wrapper4.clientWidth) || 760;
}
}), /*#__PURE__*/React.createElement("div", {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
className: classnames(ClassName.TABLE_NODE_WRAPPER),
ref: elem => {
this.wrapper = elem;
if (elem) {
this.props.contentDOM(elem);
const tableElement = elem.querySelector('table');
if (tableElement !== this.table) {
this.table = tableElement;
this.observeTable(this.table);
// // Update tableRef in plugin state when table is properly mounted
// // At this point, both table and wrapper are in DOM with correct parent-child relationship
if (this.table && (!expValEquals('platform_editor_table_ref_optimisation', 'isEnabled', true) || this.props.tableActive) && this.props.view && !expValEquals('platform_editor_fix_editor_unhandled_type_errors', 'isEnabled', true) && (expValEquals('platform_editor_table_update_table_ref', 'isEnabled', true) || fg('platform_editor_enable_table_update_ref_atlas'))) {
setTableRef(this.table)(this.props.view.state, this.props.view.dispatch);
}
}
}
}
}, allowControls && colControls), !this.isNestedInTable ? /*#__PURE__*/React.createElement("div", {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
className: ClassName.TABLE_STICKY_SCROLLBAR_CONTAINER,
"data-vc-nvs": "true",
style: {
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
height: "var(--ds-space-250, 20px)",
// MAX_BROWSER_SCROLLBAR_HEIGHT
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
display: 'none',
// prevent unwanted scroll during table resize without removing scrollbar container from the dom
width: isResizing ? "var(--ds-space-0, 0px)" : '100%'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: tableRef === null || tableRef === void 0 ? void 0 : tableRef.clientWidth,
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
height: '100%'
},
"data-vc-nvs": "true"
})) : /*#__PURE__*/React.createElement("div", {
style: {
width: tableRef === null || tableRef === void 0 ? void 0 : tableRef.clientWidth,
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
height: '100%'
},
"data-vc-nvs": "true"
}), /*#__PURE__*/React.createElement("div", {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
className: ClassName.TABLE_STICKY_SENTINEL_BOTTOM,
"data-testid": "sticky-sentinel-bottom"
}), !this.isNestedInTable && /*#__PURE__*/React.createElement("div", {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
className: ClassName.TABLE_STICKY_SCROLLBAR_SENTINEL_BOTTOM,
"data-testid": "sticky-scrollbar-sentinel-bottom"
}), /*#__PURE__*/React.createElement("div", {
contentEditable: false
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
,
className: ClassName.TABLE_LEFT_BORDER,
"data-with-numbered-table": node.attrs.isNumberColumnEnabled,
"data-testid": "table-left-border"
}), /*#__PURE__*/React.createElement("div", {
contentEditable: false
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
,
className: ClassName.TABLE_RIGHT_BORDER,
"data-testid": "table-right-border"
}));
}
}
_defineProperty(TableComponent, "displayName", 'TableComponent');
export default injectIntl(TableComponent);