UNPKG

@neo4j-ndl/react

Version:

React implementation of Neo4j Design System

248 lines (247 loc) 10.6 kB
/** * * 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