@neo4j-ndl/react
Version:
React implementation of Neo4j Design System
248 lines (247 loc) • 10.6 kB
JavaScript
/**
*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Modified from here: https://gist.github.com/kottenator/9d936eb3e4e3c3e02598
*
* @param current Current Index
* @param max Max Index
* @returns
*/
export const userFriendlyPagination = (current, max) => {
const last = max;
const delta = 1;
const left = current - delta;
const right = current + delta + 1;
const range = [];
const rangeWithDots = [];
let length;
for (let i = 1; i <= last; i += 1) {
if (i === 1 || i === last || (i >= left && i < right)) {
range.push(i);
}
}
for (let i = 0; i < range.length; i += 1) {
if (length) {
if (range[i] - length === 2) {
rangeWithDots.push(length + 1);
}
else if (range[i] - length !== 1) {
rangeWithDots.push('...');
}
}
rangeWithDots.push(range[i]);
length = range[i];
}
return rangeWithDots;
};
/**
* Simple helper to generate inline grid style
* to be responsive.
*
* Secret sauce:
* The last element takes an "auto" width, so it will
* fill the available space, and the result till be for 4 columns:
* minmax(min column1Sizepx,column1Sizepx) minmax(min column2Sizepx,column2Sizepx) ... minmax(columnNSizepx, auto)
*/
export const gridGenerator = (table) => {
const visibleColumns = table.getVisibleFlatColumns();
const tableDefaultMin = table._getDefaultColumnDef().minSize;
// 20 is the default value that is set in Tanstack. For us the min size is too
// small so we increase it to look better per default.
// https://github.com/TanStack/table/blob/main/docs/guide/column-sizing.md
const defaultMin = tableDefaultMin !== 20 ? tableDefaultMin : 100;
// If there are actions, we want the last column to be fixed sized
// and the rest to be auto.
return visibleColumns
.map((i, idx) => {
var _a;
return visibleColumns.length === idx + 1
? `minmax(${i.getSize()}px ,auto)`
: `minmax(${((_a = table.getColumn(i.id)) === null || _a === void 0 ? void 0 : _a.columnDef.minSize) || defaultMin}px,${i.getSize()}px)`;
})
.join(' ');
};
/**
* Check if the column is size constrained,
* so we can let the column's width grow with `flex-grow: 1`
* @param columnDef
*/
export const isSizeConstrained = (columnDef) => columnDef.size !== undefined && columnDef.size !== 150;
export var ResizeDirection;
(function (ResizeDirection) {
ResizeDirection["LEFT"] = "left";
ResizeDirection["RIGHT"] = "right";
})(ResizeDirection || (ResizeDirection = {}));
const isLastColumnId = (headers, columnId) => {
const columnIndex = headers.findIndex((header) => header.id === columnId);
return columnIndex === headers.length - 1;
};
/**
* Function to resize the column
* @param direction direction to resize
* @param header the header cell that is being resized
*/
export const resizeColumn = (direction, header) => {
var _a, _b, _c;
const RESIZE_AMOUNT = 5; // Pixels to shift
const currentColumnId = header.column.id;
const table = header.getContext().table;
const maxColumnWidth = (_a = header.column.columnDef.maxSize) !== null && _a !== void 0 ? _a : window.innerWidth;
const minColumnWidth = (_b = header.column.columnDef.minSize) !== null && _b !== void 0 ? _b : 0;
const maxTableRowWidth = ((_c = document.querySelector('.ndl-data-grid-thead')) === null || _c === void 0 ? void 0 : _c.getBoundingClientRect().width) || 0;
const adjustColumnWidth = (currentWidth, adjustment) => {
return Math.min(Math.max(currentWidth + adjustment, minColumnWidth), maxColumnWidth);
};
if (direction === ResizeDirection.LEFT) {
table.setColumnSizing((currentSizes) => {
const newWidth = adjustColumnWidth(currentSizes[currentColumnId], -RESIZE_AMOUNT);
return Object.assign(Object.assign({}, currentSizes), { [currentColumnId]: newWidth });
});
}
else if (direction === ResizeDirection.RIGHT) {
table.setColumnSizing((currentSizes) => {
if (isLastColumnId(header.headerGroup.headers, currentColumnId)) {
// Calculate the total width of the current columns combined
const totalCurrentWidth = Object.values(currentSizes).reduce((total, size) => total + (isNaN(size) ? 0 : size), 0);
if (totalCurrentWidth + RESIZE_AMOUNT >= maxTableRowWidth) {
return currentSizes;
}
}
const newWidth = adjustColumnWidth(currentSizes[currentColumnId], RESIZE_AMOUNT);
return Object.assign(Object.assign({}, currentSizes), { [currentColumnId]: newWidth });
});
}
};
export const ACTION_CELL_SIZE = 40;
export const updateColumnWidths = (table, totalWidth) => {
const columns = table.getAllColumns();
const availableWidth = Math.floor(totalWidth);
// Separate pinned and resizable (unpinned) columns
const pinnedColumns = columns.filter((col) => col.getIsPinned());
const resizableColumns = columns.filter((col) => !col.getIsPinned());
// Sum of pinned widths (pinned columns should remain unchanged)
const totalPinnedWidth = pinnedColumns.reduce((sum, col) => sum + col.getSize(), 0);
// Available width for resizable columns
const availableResizableWidth = availableWidth - totalPinnedWidth;
// Calculate min widths for resizable columns
const resizableMinWidths = resizableColumns.map((col) => {
var _a, _b;
return ((_a = col.columnDef.meta) === null || _a === void 0 ? void 0 : _a.isActionCell)
? ACTION_CELL_SIZE
: Math.floor((_b = col.columnDef.minSize) !== null && _b !== void 0 ? _b : 0);
});
// Calculate max widths for resizable columns
const resizableMaxWidths = resizableColumns.map((col) => {
var _a, _b;
return ((_a = col.columnDef.meta) === null || _a === void 0 ? void 0 : _a.isActionCell)
? ACTION_CELL_SIZE
: Math.floor((_b = col.columnDef.maxSize) !== null && _b !== void 0 ? _b : availableResizableWidth);
});
const sumResizableMinWidths = resizableMinWidths.reduce((sum, w) => sum + w, 0);
// If available width for resizable columns is less than or equal to the sum of their minimum widths,
// assign each resizable column its minimum width.
if (sumResizableMinWidths >= availableResizableWidth) {
const newSizing = resizableColumns.reduce((acc, col, index) => (Object.assign(Object.assign({}, acc), { [col.id]: resizableMinWidths[index] })), {});
// Preserve pinned columns unchanged.
pinnedColumns.forEach((col) => {
newSizing[col.id] = col.getSize();
});
table.setColumnSizing(() => newSizing);
return;
}
// Calculate proportional weights for resizable columns based on columnDef.size (default 20),
// but if `isActionCell` is true, use ACTION_CELL_SIZE.
const resizableWeights = resizableColumns.map((col) => {
var _a;
return ((_a = col.columnDef.meta) === null || _a === void 0 ? void 0 : _a.isActionCell)
? ACTION_CELL_SIZE
: typeof col.columnDef.size === 'number'
? col.columnDef.size
: 20;
});
const allocatedResizableWidths = distributeProportional(availableResizableWidth, resizableWeights, resizableMinWidths, resizableMaxWidths);
// Compose the final sizing object.
const finalSizing = {};
// Preserve pinned columns with current sizes.
pinnedColumns.forEach((col) => {
var _a;
if ((_a = col.columnDef.meta) === null || _a === void 0 ? void 0 : _a.isActionCell) {
finalSizing[col.id] = ACTION_CELL_SIZE;
}
else {
finalSizing[col.id] = col.getSize();
}
});
// Assign computed widths to resizable columns.
resizableColumns.forEach((col, index) => {
finalSizing[col.id] = allocatedResizableWidths[index];
});
table.setColumnSizing(() => finalSizing);
};
// Helper function to distribute available width proportionally within min and max bounds.
const distributeProportional = (total, weights, minBounds, maxBounds) => {
const n = weights.length;
const result = new Array(n).fill(0);
const freeIndices = new Set();
for (let i = 0; i < n; i++) {
freeIndices.add(i);
}
let remaining = total;
while (freeIndices.size > 0) {
const free = Array.from(freeIndices);
const totalFreeWeight = free.reduce((sum, i) => sum + weights[i], 0);
let isUpdated = false;
for (const i of free) {
const alloc = remaining * (weights[i] / totalFreeWeight);
if (alloc < minBounds[i]) {
result[i] = minBounds[i];
remaining -= minBounds[i];
freeIndices.delete(i);
isUpdated = true;
}
else if (alloc > maxBounds[i]) {
result[i] = maxBounds[i];
remaining -= maxBounds[i];
freeIndices.delete(i);
isUpdated = true;
}
}
if (!isUpdated) {
for (const i of free) {
result[i] = remaining * (weights[i] / totalFreeWeight);
}
break;
}
}
// Round results and distribute remaining pixels to avoid rounding issues.
const rounded = result.map((v) => Math.floor(v));
const allocatedSum = rounded.reduce((sum, v) => sum + v, 0);
let diff = total - allocatedSum;
for (let i = 0; i < n && diff > 0; i++) {
if (rounded[i] < maxBounds[i]) {
const add = Math.min(diff, maxBounds[i] - rounded[i]);
rounded[i] += add;
diff -= add;
}
}
return rounded;
};
//# sourceMappingURL=helpers.js.map