UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

1,034 lines (1,012 loc) 55.2 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; import React from 'react'; import classnames from 'classnames'; import memoizeOne from 'memoize-one'; import rafSchedule from 'raf-schd'; import { injectIntl } from 'react-intl-next'; import { ACTION_SUBJECT, EVENT_TYPE, TABLE_ACTION } from '@atlaskit/editor-common/analytics'; 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 { tableMarginSides } from '@atlaskit/editor-common/styles'; import { isValidPosition } from '@atlaskit/editor-common/utils'; import { akEditorTableNumberColumnWidth, akEditorTableToolbarSize as tableToolbarSize } from '@atlaskit/editor-shared-styles'; import { findTable, 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 } 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 { META_KEYS } from '../pm-plugins/table-analytics'; 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, ShadowEvent } 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 { OverflowShadowsObserver } from './OverflowShadowsObserver'; import { TableContainer } from './TableContainer'; import { TableStickyScrollbar } from './TableStickyScrollbar'; // When table is inserted via paste, keyboard shortcut or quickInsert, // componentDidUpdate is called multiple times. The isOverflowing value is correct only on the last update. // To make sure we capture the last update, we use setTimeout. const initialOverflowCaptureTimeroutDelay = 300; // This is a hard switch for controlling whether the overflow analytics should be dispatched. There has been 6months of data // already collected which we could use but have not. This has been disabled rather then removed entirely in the event that // the current collected data becomes stale and we want to start collecting fresh data again in future. // PLEASE NOTE: that the current way this alaytics has been configured WILL cause reflows to occur. This is why the has been disabled. const isOverflowAnalyticsEnabled = false; // 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; // Ignored via go/ees005 // eslint-disable-next-line @repo/internal/deprecations/deprecation-ticket-required // @deprecated Please use TableComponentNext instead // eslint-disable-next-line @repo/internal/react/no-class-components class TableComponent extends React.Component { constructor(props) { super(props); _defineProperty(this, "state", { scroll: 0, parentWidth: undefined, [ShadowEvent.SHOW_BEFORE_SHADOW]: false, [ShadowEvent.SHOW_AFTER_SHADOW]: false, 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, "updateShadowState", (shadowKey, value) => { if (this.state[shadowKey] === value) { return; } this.setState({ [shadowKey]: value }); }); _defineProperty(this, "createShadowSentinels", table => { if (table) { const shadowSentinelLeft = document.createElement('span'); shadowSentinelLeft.className = ClassName.TABLE_SHADOW_SENTINEL_LEFT; const shadowSentinelRight = document.createElement('span'); shadowSentinelRight.className = ClassName.TABLE_SHADOW_SENTINEL_RIGHT; table.prepend(shadowSentinelLeft); table.prepend(shadowSentinelRight); } }); _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 }); if (this.overflowShadowsObserver) { this.overflowShadowsObserver.updateStickyShadows(); } } }); // 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'; } } this.setState({ [ShadowEvent.SHOW_BEFORE_SHADOW]: this.wrapper.scrollLeft !== 0 }); }); _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); if (fg('platform_editor_fix_table_resizing_undo')) { // 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, "setTimerToSendInitialOverflowCaptured", isOverflowing => { var _this$state; const { dispatchAnalyticsEvent, containerWidth, allowTableResizing } = this.props; const parentWidth = ((_this$state = this.state) === null || _this$state === void 0 ? void 0 : _this$state.parentWidth) || 0; this.initialOverflowCaptureTimerId = setTimeout(() => { dispatchAnalyticsEvent({ action: TABLE_ACTION.INITIAL_OVERFLOW_CAPTURED, actionSubject: ACTION_SUBJECT.TABLE, actionSubjectId: null, eventType: EVENT_TYPE.TRACK, attributes: { editorWidth: containerWidth.width || 0, isOverflowing, tableResizingEnabled: allowTableResizing || false, width: this.node.attrs.width || 0, parentWidth } }); this.isInitialOverflowSent = true; }, initialOverflowCaptureTimeroutDelay); }); _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)); _defineProperty(this, "updateShadowStateDebounced", rafSchedule(this.updateShadowState)); 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; this.isInitialOverflowSent = 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 }; }); } }); // Disable inline table editing and resizing controls in Firefox // https://github.com/ProseMirror/prosemirror/issues/432 if ('execCommand' in document) { ['enableObjectResizing', 'enableInlineTableEditing'].forEach(cmd => { if (document.queryCommandSupported(cmd)) { document.execCommand(cmd, false, 'false'); } }); } } 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); } } 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); if (isOverflowAnalyticsEnabled) { const initialIsOveflowing = this.state[ShadowEvent.SHOW_BEFORE_SHADOW] || this.state[ShadowEvent.SHOW_AFTER_SHADOW]; this.setTimerToSendInitialOverflowCaptured(initialIsOveflowing); } } 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 (expValEquals('platform_editor_table_drag_handle_hover', 'isEnabled', true)) { 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); if (this.overflowShadowsObserver) { this.overflowShadowsObserver.dispose(); } // 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$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 }); const { tableWithFixedColumnWidthsOption = false } = getEditorFeatureFlags(); 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 componentDidUpdate(_, prevState) { var _this$props$options10, _this$props$options11, _this$wrapper2; const { view, getNode, isMediaFullscreen, allowColumnResizing, allowTableResizing, isResizing, options, isTableScalingEnabled, // we could use options.isTableScalingEnabled here getPos, getEditorFeatureFlags, isInDanger } = this.props; const table = findTable(view.state.selection); let shouldScale = false; let shouldHandleColgroupUpdates = false; const { tableWithFixedColumnWidthsOption = false } = getEditorFeatureFlags(); 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 (this.state.windowResized) { shouldHandleColgroupUpdates = true; } if (shouldHandleColgroupUpdates) { this.handleColgroupUpdates(); } // table is always defined so this never runs if (!expValEquals('platform_editor_table_drag_handle_hover', 'isEnabled', true)) { if (isInDanger && !table) { clearHoverSelection()(view.state, view.dispatch); } } 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(); } if ((_this$wrapper2 = this.wrapper) !== null && _this$wrapper2 !== void 0 && _this$wrapper2.parentElement && this.table && !this.overflowShadowsObserver) { // isDragAndDropEnabled will be false when the editorViewMode is 'live' and so the fix below is never triggered // but the shadow observer must run async so its initial state is correct. // note: when cleaning up platform_editor_table_drag_handle_hover entirely remove this nested if check incl. this.props.isDragAndDropEnabled if (this.props.isDragAndDropEnabled || expValEquals('platform_editor_table_drag_handle_hover', 'isEnabled', true)) { // requestAnimationFrame is used here to fix a race condition issue // that happens when a table is nested in expand and expand's width is // changed via breakout button window.requestAnimationFrame(() => { this.overflowShadowsObserver = new OverflowShadowsObserver(this.updateShadowStateDebounced, // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting this.table, this.wrapper); }); } else { this.overflowShadowsObserver = new OverflowShadowsObserver(this.updateShadowState, this.table, this.wrapper); } } if (this.overflowShadowsObserver) { var _this$state$stickyHea; this.overflowShadowsObserver.observeShadowSentinels((_this$state$stickyHea = this.state.stickyHeader) === null || _this$state$stickyHea === void 0 ? void 0 : _this$state$stickyHea.sticky); } 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(); } if (isOverflowAnalyticsEnabled) { const newIsOverflowing = this.state[ShadowEvent.SHOW_BEFORE_SHADOW] || this.state[ShadowEvent.SHOW_AFTER_SHADOW]; const prevIsOverflowing = prevState[ShadowEvent.SHOW_BEFORE_SHADOW] || prevState[ShadowEvent.SHOW_AFTER_SHADOW]; if (this.initialOverflowCaptureTimerId) { clearTimeout(this.initialOverflowCaptureTimerId); } if (!this.isInitialOverflowSent) { this.setTimerToSendInitialOverflowCaptured(newIsOverflowing); } if (this.isInitialOverflowSent && prevIsOverflowing !== newIsOverflowing) { var _this$state2; const { dispatch, state: { tr } } = this.props.view; dispatch(tr.setMeta(META_KEYS.OVERFLOW_STATE_CHANGED, { isOverflowing: newIsOverflowing, wasOverflowing: prevIsOverflowing, editorWidth: this.props.containerWidth.width || 0, width: this.node.attrs.width || 0, parentWidth: ((_this$state2 = this.state) === null || _this$state2 === void 0 ? void 0 : _this$state2.parentWidth) || 0 })); } } } 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 { showBeforeShadow, showAfterShadow } = this.state; 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, getScrollOffset: () => { var _this$wrapper3; return ((_this$wrapper3 = this.wrapper) === null || _this$wrapper3 === void 0 ? void 0 : _this$wrapper3.scrollLeft) || 0; }, tableWrapperHeight: this.state.tableWrapperHeight, api: pluginInjectionApi, isChromelessEditor: options === null || options === void 0 ? void 0 : options.isChromelessEditor }) : null; const shadowPadding = allowControls && tableActive ? -tableToolbarSize : tableMarginSides; const shadowStyle = memoizeOne(visible => ({ visibility: visible ? 'visible' : 'hidden' })); /** * 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; } catch (e) { tablePos = undefined; } const isNested = isTableNested(view.state, tablePos); const topShadowPadding = isDragAndDropEnabled ? 0 : shadowPadding; const topOffset = 0; const topStickyShadowPosition = !this.props.limitedMode && this.state.stickyHeader && topOffset + this.state.stickyHeader.padding + topShadowPadding + 2; 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, isTableWithFixedColumnWidthsOptionEnabled: 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, getScrollOffset: () => { var _this$wrapper4; return ((_this$wrapper4 = this.wrapper) === null || _this$wrapper4 === void 0 ? void 0 : _this$wrapper4.scrollLeft) || 0; }, getTableWrapperWidth: () => { var _this$wrapper5; return ((_this$wrapper5 = this.wrapper) === null || _this$wrapper5 === void 0 ? void 0 : _this$wrapper5.clientWidth) || 760; } }), /*#__PURE__*/React.createElement("div", { contentEditabl