UNPKG

@revolist/revogrid

Version:

Virtual reactive data grid spreadsheet component - RevoGrid.

240 lines (239 loc) 9.39 kB
/*! * Built by Revolist OU ❤️ */ import { GROUP_COLUMN_PROP } from "../groupingRow/grouping.const"; import { isGrouping } from "../groupingRow/grouping.service"; import { getCellRaw } from "../../utils/column.utils"; /** * Checks whether a sorting map contains at least one active order. * * Empty maps and properties with `undefined` order are treated as inactive. */ export function hasActiveSorting(sorting) { for (const prop of Object.keys(sorting || {})) { if (sorting === null || sorting === void 0 ? void 0 : sorting[prop]) { return true; } } return false; } /** * Compares column properties after object-key coercion. */ function isSameColumnProp(a, b) { return String(a) === String(b); } /** * Returns active sorting properties in explicit priority order. */ function getActiveSortingProps(sorting, sortingOrder) { const activeProps = []; const add = (prop) => { if ((sorting === null || sorting === void 0 ? void 0 : sorting[prop]) && !activeProps.some(active => isSameColumnProp(active, prop))) { activeProps.push(prop); } }; sortingOrder === null || sortingOrder === void 0 ? void 0 : sortingOrder.forEach(add); Object.keys(sorting || {}).forEach(add); return activeProps; } /** * Returns one-based additive sorting rank for a column. * * A single active sort does not need a visible rank, so it returns undefined. */ export function getSortingIndex(sorting, prop, sortingOrder) { const activeProps = getActiveSortingProps(sorting, sortingOrder); if (activeProps.length <= 1) { return undefined; } const index = activeProps.findIndex(active => isSameColumnProp(active, prop)); return index >= 0 ? index + 1 : undefined; } /** * Collects only active comparator functions from a sorting function map. * * This keeps undefined comparator entries from triggering sorting work. */ function activeSortingEntries(sortingFunc = {}, sortingOrder) { const entries = []; const add = (prop) => { const cmp = sortingFunc[prop]; if (typeof cmp === 'function' && !entries.some(([active]) => isSameColumnProp(active, prop))) { entries.push([prop, cmp]); } }; sortingOrder === null || sortingOrder === void 0 ? void 0 : sortingOrder.forEach(add); Object.keys(sortingFunc).forEach(add); return entries; } /** * Reads and normalizes a value for the built-in default comparer. */ function getDefaultCompareValue(item, prop, column) { const aRaw = column ? getCellRaw(item, column) : item === null || item === void 0 ? void 0 : item[prop]; return typeof aRaw === 'number' ? aRaw : aRaw === null || aRaw === void 0 ? void 0 : aRaw.toString().toLowerCase(); } function isEmptyCompareValue(value) { return value === '' || value === null || value === undefined; } /** * Compares two already-normalized default comparer values. */ function compareValues(av, bv) { if (av === bv) { return 0; } const aEmpty = isEmptyCompareValue(av); const bEmpty = isEmptyCompareValue(bv); if (aEmpty || bEmpty) { if (aEmpty && bEmpty) { return 0; } return aEmpty ? -1 : 1; } if (av > bv) { return 1; } return -1; } /** * Sorts indexes by precomputed values for default column comparers. * * This avoids repeatedly parsing the same cell value during large default * sorts while preserving normal multi-column ordering. */ function sortIndexByDefaultComparers(indexes, source, entries, sorting, sortingColumns) { const prepared = entries.map(([prop]) => { const values = []; const column = sortingColumns[prop]; for (const index of indexes) { values[index] = getDefaultCompareValue(source[index], prop, column); } return { order: sorting[prop], values, }; }); return indexes.sort((a, b) => { for (const { order, values } of prepared) { const sorted = compareValues(values[a], values[b]); if (sorted) { return order === 'desc' ? -sorted : sorted; } } return 0; }); } /** * Detects whether the optimized default-comparer path can be used. * * Grouped rows and custom `cellCompare` functions stay on the legacy * comparator path to preserve their exact behavior. */ function canUseDefaultCompareFastPath(entries, indexes, source, sorting, sortingColumns) { return !indexes.some(index => isGrouping(source[index])) && !!sorting && !!sortingColumns && entries.every(([prop]) => { const order = sorting[prop]; const column = sortingColumns[prop]; return !!order && !(column === null || column === void 0 ? void 0 : column.cellCompare); }); } /** * Group placeholder rows are generated for their grouping column. If sorting is * requested for another column, the grouped source must be unwrapped first. */ function hasGroupingRowsForOtherSorting(entries, indexes, source) { return indexes.some(index => { const item = source[index]; return isGrouping(item) && !entries.some(([prop]) => isSameColumnProp(item[GROUP_COLUMN_PROP], prop)); }); } /** * Sorts row indexes against a source collection. * * @param indexes - Current proxy row indexes to sort. * @param source - Full source collection addressed by the indexes. * @param sortingFunc - Comparator functions by column property. * @param sorting - Active sorting order by column property. * @param sortingColumns - Column metadata by property for default-comparer optimization. * @param sortingOrder - Active sorting priority in click/config insertion order. * @returns Sorted proxy indexes. With no sorting function keys, returns source-order indexes. */ export function sortIndexByItems(indexes, source, sortingFunc = {}, sorting, sortingColumns, sortingOrder) { const hasSortingKeys = Object.keys(sortingFunc).length > 0; const sortingEntries = activeSortingEntries(sortingFunc, sortingOrder); // if no sorting - return unsorted indexes if (sortingEntries.length === 0) { // Unsorted indexes return hasSortingKeys ? indexes : [...new Array(indexes.length).keys()]; } if (hasGroupingRowsForOtherSorting(sortingEntries, indexes, source)) { return indexes; } if (canUseDefaultCompareFastPath(sortingEntries, indexes, source, sorting, sortingColumns)) { return sortIndexByDefaultComparers(indexes, source, sortingEntries, sorting, sortingColumns); } // /** * go through all indexes and align in new order * performs a multi-level sorting by applying multiple comparison functions to determine the order of the items based on different properties. */ return indexes.sort((a, b) => { const itemA = source[a]; const itemB = source[b]; for (const [prop, cmp] of sortingEntries) { if (isGrouping(itemA)) { if (!isSameColumnProp(itemA[GROUP_COLUMN_PROP], prop)) { return a - b; } } if (isGrouping(itemB)) { if (!isSameColumnProp(itemB[GROUP_COLUMN_PROP], prop)) { return a - b; } } /** * If the comparison function returns a non-zero value (sorted), it means that the items should be sorted based on the given property. In such a case, the function immediately returns the sorted value, indicating the order in which the items should be arranged. * If none of the comparison functions result in a non-zero value, indicating that the items are equal or should remain in the same order, the function eventually returns 0. */ const sorted = cmp === null || cmp === void 0 ? void 0 : cmp(prop, itemA, itemB); if (sorted) { return sorted; } } return 0; }); } export function defaultCellCompare(prop, a, b) { const aRaw = this.column ? getCellRaw(a, this.column) : a === null || a === void 0 ? void 0 : a[prop]; const bRaw = this.column ? getCellRaw(b, this.column) : b === null || b === void 0 ? void 0 : b[prop]; const av = typeof aRaw === 'number' ? aRaw : aRaw === null || aRaw === void 0 ? void 0 : aRaw.toString().toLowerCase(); const bv = typeof bRaw === 'number' ? bRaw : bRaw === null || bRaw === void 0 ? void 0 : bRaw.toString().toLowerCase(); return compareValues(av, bv); } export function descCellCompare(cmp) { return (prop, a, b) => { return -1 * cmp(prop, a, b); }; } export function getNextOrder(currentOrder) { switch (currentOrder) { case undefined: return 'asc'; case 'asc': return 'desc'; case 'desc': return undefined; } } export function getComparer(column, order) { var _a; const cellCmp = ((_a = column === null || column === void 0 ? void 0 : column.cellCompare) === null || _a === void 0 ? void 0 : _a.bind({ order })) || (defaultCellCompare === null || defaultCellCompare === void 0 ? void 0 : defaultCellCompare.bind({ column, order })); if (order == 'asc') { return cellCmp; } if (order == 'desc') { return descCellCompare(cellCmp); } return undefined; }