UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

175 lines (167 loc) 7.36 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; import { getCellAttrs, getCellDomAttrs } from '@atlaskit/adf-schema/tableNodes'; import { ACTION_SUBJECT, EVENT_TYPE, TABLE_ACTION } from '@atlaskit/editor-common/analytics'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { getRoundedTableCellEdgeState } from './rounded-table-edges'; import TableNodeView from './TableNodeViewBase'; const DEFAULT_COL_SPAN = 1; const DEFAULT_ROW_SPAN = 1; /** * For performance reasons we do this in the background - it shouldn't block the main thread */ function delayUntilIdle(cb) { if (typeof window === 'undefined') { return; } // eslint-disable-next-line compat/compat if (window.requestIdleCallback !== undefined) { // eslint-disable-next-line compat/compat return window.requestIdleCallback(() => cb(), { timeout: 500 }); } return window.requestAnimationFrame(() => cb()); } // Ignored via go/ees005 // eslint-disable-next-line require-unicode-regexp const cssVariablePattern = /^VAR\(--.*\)$/; const getCellDomAttrsForTableMenuUpdates = node => { const attrs = getCellDomAttrs(node); if (expValEquals('platform_editor_table_menu_updates', 'isEnabled', true) && node.attrs.valign) { attrs['data-valign'] = node.attrs.valign; } return attrs; }; export default class TableCell extends TableNodeView { constructor(node, view, getPos, eventDispatcher, editorAnalyticsAPI) { super(node, view, getPos, eventDispatcher); _defineProperty(this, "destroy", () => { if (this.delayHandle && typeof window !== 'undefined') { var _window, _window$cancelIdleCal, _window2, _window2$cancelAnimat; // eslint-disable-next-line compat/compat (_window = window) === null || _window === void 0 ? void 0 : (_window$cancelIdleCal = _window.cancelIdleCallback) === null || _window$cancelIdleCal === void 0 ? void 0 : _window$cancelIdleCal.call(_window, this.delayHandle); (_window2 = window) === null || _window2 === void 0 ? void 0 : (_window2$cancelAnimat = _window2.cancelAnimationFrame) === null || _window2$cancelAnimat === void 0 ? void 0 : _window2$cancelAnimat.call(_window2, this.delayHandle); } }); if (expValEquals('platform_editor_table_q4_loveability', 'isEnabled', true)) { this.updateTableEdgeAttrs(); } // CONFCLOUD-78239: Previously we had a bug which tried to invert the heading colour of a table // Obviously design tokens can't be inverted and so it would result in `VAR(--DS-BACKGROUND-ACCENT-GRAY-SUBTLEST, #F4F5F7)` // which is not a valid CSS variable. // // We should follow-up and remove this in TODO-xx in 6 months once we're confident // that this has fixed most of the cases in the wild. // // This is a workaround to fix those cases on the fly. Note it is super specific on purpose // so that it just fixes the heading token (other tokens should be unaffected) // At some point in the future if (cssVariablePattern.test(node.attrs.background)) { this.delayHandle = delayUntilIdle(() => { const pos = getPos(); if (pos) { const tr = view.state.tr; tr.setNodeAttribute(pos, 'background', node.attrs.background.toLowerCase()) // Ensures dispatch does not contribute to undo history (otherwise user requires multiple undo's to revert table) .setMeta('addToHistory', false); editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({ action: TABLE_ACTION.TABLE_CELL_BACKGROUND_FIXED, actionSubject: ACTION_SUBJECT.TABLE, actionSubjectId: null, eventType: EVENT_TYPE.TRACK })(tr); view.dispatch(tr); } }); } } update(node) { const didUpdate = this.updateNodeView(node); if (didUpdate) { this.node = node; } return didUpdate; } ignoreMutation(mutation) { var _mutation$attributeNa; // `data-reaches-*` are presentation-only attrs written imperatively for the rounded-corner // masks. Ignore them so ProseMirror doesn't rebuild the table (and drop the header row's // sticky class) on every column insert. if (mutation.type === 'attributes' && (_mutation$attributeNa = mutation.attributeName) !== null && _mutation$attributeNa !== void 0 && _mutation$attributeNa.startsWith('data-reaches-') && expValEquals('platform_editor_table_q4_loveability', 'isEnabled', true)) { return true; } return false; } /** * Detects whether this cell visually reaches the bottom or right edge of the table * (accounting for rowspan/colspan) and sets data attributes so CSS can apply * rounded corners and transparent borders to merged cells that span to the table edges. */ updateTableEdgeAttrs() { const pos = this.getPos(); if (pos === undefined) { return; } try { const resolvedPos = this.view.state.doc.resolve(pos); // Cell → row → table: depth-1 is the table, depth is the row const tableDepth = resolvedPos.depth - 1; const rowDepth = resolvedPos.depth; if (tableDepth < 0 || rowDepth < 0) { return; } const tableNode = resolvedPos.node(tableDepth); if (tableNode.type.name !== 'table') { return; } const cellStartInTable = pos - resolvedPos.start(tableDepth); const edgeState = getRoundedTableCellEdgeState(tableNode, cellStartInTable); if (!edgeState) { return; } this.setDataAttr('data-reaches-top', edgeState.reachesTop); this.setDataAttr('data-reaches-bottom', edgeState.reachesBottom); this.setDataAttr('data-reaches-left', edgeState.reachesLeft); this.setDataAttr('data-reaches-right', edgeState.reachesRight); } catch { // Position may be stale during document mutations; silently ignore. } } setDataAttr(attr, value) { const hasAttr = this.dom.hasAttribute(attr); if (value && !hasAttr) { this.dom.setAttribute(attr, 'true'); } else if (!value && hasAttr) { this.dom.removeAttribute(attr); } } updateNodeView(node) { if (this.node.type !== node.type) { return false; } const attrs = getCellDomAttrsForTableMenuUpdates(this.node); const nextAttrs = getCellDomAttrsForTableMenuUpdates(node); const { colspan, rowspan } = getCellAttrs(this.dom); // need to rerender when colspan/rowspan in dom are different from the node attrs // this can happen when undoing merge cells if (colspan !== (node.attrs.colspan || DEFAULT_COL_SPAN) || rowspan !== (node.attrs.rowspan || DEFAULT_ROW_SPAN)) { return false; } // added + changed attributes const addedAttrs = Object.entries(nextAttrs).filter(([key, value]) => attrs[key] !== value); const removedAttrs = Object.keys(attrs).filter(key => !nextAttrs.hasOwnProperty(key)); if (addedAttrs.length || removedAttrs.length) { addedAttrs.forEach(([key, value]) => this.dom.setAttribute(key, value || '')); removedAttrs.forEach(key => this.dom.removeAttribute(key)); return true; } // Return true to not re-render this node view if (this.node.sameMarkup(node)) { return true; } return false; } }