@atlaskit/editor-plugin-table
Version:
Table plugin for the @atlaskit/editor
217 lines (212 loc) • 7.46 kB
JavaScript
import { getFreeSpace } from './column-state';
import { bulkColumnsResize, getTotalWidth } from './resize-state';
export const growColumn = (state, colIndex, amount, selectedColumns) => {
// can't grow if columns don't exist or it's the last column
if (!state.cols[colIndex] || !state.cols[colIndex + 1]) {
return state;
}
const res = moveSpaceFrom(state, colIndex + 1, colIndex, amount);
const remaining = amount - res.amount;
let newState = res.state;
if (remaining > 0) {
newState = stackSpace(newState, colIndex, remaining).state;
}
if (selectedColumns && selectedColumns.length > 1) {
return bulkColumnsResize(newState, selectedColumns, colIndex);
}
return newState;
};
export const shrinkColumn = (state, colIndex, amount, selectedColumns) => {
// can't shrink if columns don't exist
if (!state.cols[colIndex]) {
return state;
}
// try to shrink dragging column by giving from the column to the right first
const res = moveSpaceFrom(state, colIndex, colIndex + 1, -amount);
let newState = res.state;
const isOverflownTable = getTotalWidth(newState) > newState.maxSize;
const isLastColumn = !newState.cols[colIndex + 1];
// stop resizing the last column once table is not overflown
if (isLastColumn && !isOverflownTable) {
return newState;
}
const remaining = amount + res.amount;
if (remaining < 0) {
newState = stackSpace(newState, colIndex + 1, remaining).state;
}
if (selectedColumns && selectedColumns.length > 1) {
return bulkColumnsResize(newState, selectedColumns, colIndex);
}
return newState;
};
export function reduceSpace(state, amount, ignoreCols = []) {
let remaining = amount;
// keep trying to resolve resize request until we run out of free space,
// or nothing to resize
while (remaining > 0) {
// filter candidates only with free space
const candidates = state.cols.filter(column => {
return getFreeSpace(column) && ignoreCols.indexOf(column.index) === -1;
});
if (candidates.length === 0) {
break;
}
const requestedResize = Math.floor(remaining / candidates.length);
if (requestedResize === 0) {
break;
}
candidates.forEach(candidate => {
let newWidth = candidate.width - requestedResize;
if (newWidth < candidate.minWidth) {
// If the new requested width is less than our min
// Calc what width we didn't use, we'll try extract that
// from other cols.
const remainder = candidate.minWidth - newWidth;
newWidth = candidate.minWidth;
remaining = remaining - requestedResize + remainder;
} else {
remaining -= requestedResize;
}
state = {
...state,
cols: [...state.cols.slice(0, candidate.index), {
...candidate,
width: newWidth
}, ...state.cols.slice(candidate.index + 1)]
};
});
}
return state;
}
var ColType = /*#__PURE__*/function (ColType) {
ColType["SOURCE"] = "src";
ColType["DEST"] = "dest";
return ColType;
}(ColType || {}); // TODO: ED-26961 - should handle when destIdx:
// - is beyond the range, and then not give it back
function moveSpaceFrom(state, srcIdx, destIdx, amount, useFreeSpace = true) {
const srcCol = state.cols[srcIdx];
const destCol = state.cols[destIdx];
if (useFreeSpace) {
const freeSpace = getFreeSpace(srcCol);
// if taking more than source column's free space, only take that much
if (amountFor(ColType.DEST)(amount) > freeSpace) {
amount = amount > 0 ? freeSpace : -freeSpace;
}
}
// if the source column shrinks past its min size, don't give the space away
if (amountFor(ColType.SOURCE)(amount) < 0 && widthFor(ColType.SOURCE)(amount, srcCol, destCol) < srcCol.minWidth) {
amount = srcCol.width - srcCol.minWidth;
}
const newDest = destCol ? {
...destCol,
width: widthFor(ColType.DEST)(amount, srcCol, destCol)
} : undefined;
if (!newDest && amountFor(ColType.SOURCE)(amount) < 0) {
// non-zero-sum game, ensure that we're not removing more than the total table width either
const totalWidth = getTotalWidth(state);
if (totalWidth - srcCol.width + widthFor(ColType.SOURCE)(amount, srcCol, destCol) < state.maxSize) {
// would shrink table below max width, stop it
amount = state.maxSize - (totalWidth - srcCol.width) - srcCol.width - 1;
}
}
const newSrc = {
...srcCol,
width: widthFor(ColType.SOURCE)(amount, srcCol, destCol)
};
const newCols = state.cols.map((existingCol, idx) => idx === srcIdx ? newSrc : idx === destIdx ? newDest : existingCol).filter(Boolean);
return {
state: {
...state,
cols: newCols
},
amount
};
}
function stackSpace(state, destIdx, amount) {
let candidates = getCandidates(state, destIdx, amount);
while (candidates.length && amount) {
// search for most (or least) free space in candidates
const candidateIdx = findNextFreeColumn(candidates, amount);
if (candidateIdx === -1) {
// stack to the right -> growing the dragging column and go overflow
if (amount > 0) {
return {
state: {
...state,
cols: [...state.cols.slice(0, destIdx), {
...state.cols[destIdx],
width: state.cols[destIdx].width + amount
}, ...state.cols.slice(destIdx + 1)]
},
remaining: amount
};
}
// stacking to the left, if no free space remains
break;
}
const column = candidates.find(col => col.index === candidateIdx);
if (!column || getFreeSpace(column) <= 0) {
// no more columns with free space remain
break;
}
const res = moveSpaceFrom(state, column.index, destIdx, amount);
state = res.state;
amount -= res.amount;
candidates = candidates.filter(col => col.index !== candidateIdx);
}
return {
state,
remaining: amount
};
}
function findNextFreeColumn(columns, amount) {
if (columns.length === 0) {
return -1;
}
const direction = amount < 0 ? 'left' : 'right';
if (direction === 'left') {
columns = columns.slice().reverse();
}
let freeIndex = -1;
columns.forEach(column => {
if (getFreeSpace(column) && freeIndex === -1) {
freeIndex = column.index;
}
});
if (freeIndex === -1) {
return -1;
}
return freeIndex;
}
function amountFor(colType) {
return amount => colType === ColType.SOURCE ? amount > 0 ? -amount : amount : amount < 0 ? -amount : amount;
}
function widthFor(colType) {
return (amount, srcCol, destCol) => (colType === ColType.SOURCE ? srcCol : destCol).width + amountFor(colType)(amount);
}
function getCandidates(state, destIdx, amount) {
const candidates = state.cols;
// only consider rows after the selected column in the direction of resize
return amount < 0 ? candidates.slice(0, destIdx) : candidates.slice(destIdx + 1);
}
/**
* Update the given column based on resizeAmount, maintaining all other columns
*/
export function updateAffectedColumn(resizeState, colIndex, resizeAmount) {
const updatedCols = resizeState.cols.map((col, index) => {
if (index === colIndex) {
const newWidth = Math.max(col.width + resizeAmount, col.minWidth);
return {
...col,
width: newWidth
};
}
return col;
});
return {
...resizeState,
tableWidth: updatedCols.reduce((acc, col) => acc + col.width, 0),
cols: updatedCols
};
}