@revolist/revogrid
Version:
Virtual reactive data grid spreadsheet component - RevoGrid.
240 lines (239 loc) • 9.39 kB
JavaScript
/*!
* 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;
}