@atlaskit/editor-plugin-table
Version:
Table plugin for the @atlaskit/editor
197 lines (190 loc) • 7.98 kB
JavaScript
import { tableCellMinWidth } from '@atlaskit/editor-common/styles';
import { AttrStep } from '@atlaskit/editor-prosemirror/transform';
import { TableMap } from '@atlaskit/editor-tables/table-map';
import { isMinCellWidthTable, hasTableBeenResized } from '../table-resizing/utils/colgroup';
import { getTableContainerElementWidth, getTableElementWidth } from '../table-resizing/utils/misc';
import { getResizeState } from '../table-resizing/utils/resize-state';
import { scaleTableTo } from '../table-resizing/utils/scale-table';
/**
* Given a new ResizeState object, create a transaction that replaces and updates the table node based on new state.
* @param resizeState
* @param table
* @param start
* @returns
*/
export const updateColumnWidths = (resizeState, table, start, api) => tr => {
const map = TableMap.get(table);
const updatedCellsAttrs = {};
const steps = [];
// calculating new attributes for each cell
for (let columnIndex = 0; columnIndex < map.width; columnIndex++) {
for (let rowIndex = 0; rowIndex < map.height; rowIndex++) {
let {
width
} = resizeState.cols[columnIndex];
if (resizeState.isScaled) {
// Ensure that the width is an integer if the table has been scaled
width = Math.floor(width);
}
const mapIndex = rowIndex * map.width + columnIndex;
const cellPos = map.map[mapIndex];
const attrs = updatedCellsAttrs[cellPos] || {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
...table.nodeAt(cellPos).attrs
};
const colspan = attrs.colspan || 1;
if (attrs.colwidth && attrs.colwidth.length > colspan) {
attrs.colwidth = attrs.colwidth.slice(0, colspan);
}
// Rowspanning cell that has already been handled
if (rowIndex && map.map[mapIndex] === map.map[mapIndex - map.width]) {
continue;
}
const colspanIndex = colspan === 1 ? 0 : columnIndex - map.colCount(cellPos);
if (attrs.colwidth && attrs.colwidth[colspanIndex] === width) {
continue;
}
let colwidths = attrs.colwidth ? attrs.colwidth.slice() : Array.from({
length: colspan
}, _ => 0);
colwidths[colspanIndex] = width;
if (colwidths.length > colspan) {
colwidths = colwidths.slice(0, colspan);
}
updatedCellsAttrs[cellPos] = {
...attrs,
colwidth: colwidths.includes(0) ? undefined : colwidths
};
}
}
// updating all cells with new attributes
const seen = {};
for (let rowIndex = 0; rowIndex < map.height; rowIndex++) {
for (let columnIndex = 0; columnIndex < map.width; columnIndex++) {
const mapIndex = rowIndex * map.width + columnIndex;
const pos = map.map[mapIndex];
const cell = table.nodeAt(pos);
if (!seen[pos] && cell) {
if (updatedCellsAttrs[pos]) {
steps.push(new AttrStep(pos + start, 'colwidth', updatedCellsAttrs[pos].colwidth === undefined ? null : updatedCellsAttrs[pos].colwidth));
}
seen[pos] = true;
}
}
}
if (api !== null && api !== void 0 && api.batchAttributeUpdates) {
const batchStep = api.batchAttributeUpdates.actions.batchSteps({
steps,
doc: tr.doc
});
tr.step(batchStep);
} else {
steps.forEach(s => {
tr.step(s);
});
}
return tr;
};
/**
* This function is called when user inserts/deletes a column in a table to;
* - rescale all columns (if the table did not overflow before the insertion)
* - and update column widths.
*
* This is done manually to avoid a multi-dispatch in TableComponent. See [ED-8288].
* @param table
* @param view
* @returns Updated transaction with rescaled columns for a given table
*/
export const rescaleColumns = (isTableScalingEnabled = false, isTableFixedColumnWidthsOptionEnabled = false, shouldUseIncreasedScalingPercent = false, api, isCommentEditor = false) => (table, view) => tr => {
if (!view) {
return tr;
}
const newTable = tr.doc.nodeAt(table.pos);
const domAtPos = view.domAtPos.bind(view);
const maybeTable = domAtPos(table.start).node;
const maybeTableElement = maybeTable instanceof HTMLElement ? maybeTable : null;
const tableRef = maybeTableElement === null || maybeTableElement === void 0 ? void 0 : maybeTableElement.closest('table');
if (!tableRef || !newTable) {
return tr;
}
const isResized = hasTableBeenResized(table.node);
let previousTableInfo = {
width: 0,
possibleMaxWidth: 0,
isResized
};
const tableDepth = view.state.doc.resolve(table.pos).depth;
let shouldScale = isTableScalingEnabled && tableDepth === 0;
if (shouldScale && isTableFixedColumnWidthsOptionEnabled) {
shouldScale = newTable.attrs.displayMode !== 'fixed';
}
if (shouldScale) {
previousTableInfo = {
width: getTableElementWidth(table.node),
possibleMaxWidth: getTableContainerElementWidth(table.node),
isResized
};
} else {
var _tableRef$parentEleme;
previousTableInfo = {
// when table is resized the tableRef client width will be 1px larger than colGroup, which is used in calculations
width: isResized ? tableRef.clientWidth - 1 : tableRef.clientWidth,
/** the is the width the table can reach before overflowing */
possibleMaxWidth: (tableRef === null || tableRef === void 0 ? void 0 : (_tableRef$parentEleme = tableRef.parentElement) === null || _tableRef$parentEleme === void 0 ? void 0 : _tableRef$parentEleme.clientWidth) || 0,
isResized
};
}
// determine the new table, based on new width
const newTableInfo = {
noOfColumns: TableMap.get(newTable).width
};
if (!newTableInfo.noOfColumns || newTableInfo.noOfColumns <= 0) {
return tr;
}
const averageColumnWidth = previousTableInfo.width / newTableInfo.noOfColumns;
// If the table has not been resized (i.e. all columns will have the same width) and every column width is bigger than the minimum column width
// we skip updating the size of columns here.
if (!previousTableInfo.isResized && averageColumnWidth > tableCellMinWidth) {
return tr;
}
const wasTableInOverflow = previousTableInfo.width > previousTableInfo.possibleMaxWidth;
// If the table has not been resized, and each column width is smaller than the minimum column width
// Or if the table has been resized, but each column width is either 48px or null
// we update the table to have 48px for each column
if (!previousTableInfo.isResized && averageColumnWidth <= tableCellMinWidth || previousTableInfo.isResized && isMinCellWidthTable(table.node)) {
const widths = new Array(newTableInfo.noOfColumns).fill(tableCellMinWidth);
const cols = widths.map((_, index) => ({
width: tableCellMinWidth,
minWidth: tableCellMinWidth,
index
}));
const minWidthResizeState = {
cols,
widths,
maxSize: previousTableInfo.possibleMaxWidth,
tableWidth: previousTableInfo.width,
overflow: wasTableInOverflow
};
return updateColumnWidths(minWidthResizeState, table.node, table.start, api)(tr);
}
let resizeState = getResizeState({
minWidth: tableCellMinWidth,
table: table.node,
start: table.start,
tableRef,
domAtPos,
maxSize: previousTableInfo.possibleMaxWidth,
isTableScalingEnabled: shouldScale,
shouldUseIncreasedScalingPercent,
isCommentEditor
});
// Two scenarios that require scaling:
// 1. If the new table width will result in the table going into overflow
// we resize the cells to avoid it (e.g. adding a column)
// 2. If the new table width will be shorter than the parent width, scale columns to fit parent
if (!wasTableInOverflow && resizeState.overflow || resizeState.tableWidth < resizeState.maxSize) {
resizeState = scaleTableTo(resizeState, previousTableInfo.possibleMaxWidth);
}
return updateColumnWidths(resizeState, table.node, table.start, api)(tr);
};