feat(dees-table): add multi-column sorting with header menu controls and priority indicators

This commit is contained in:
2026-04-07 12:19:59 +00:00
parent 3601651e10
commit fa56c7cce8
7 changed files with 471 additions and 49 deletions

View File

@@ -1,4 +1,4 @@
import type { Column, TDisplayFunction } from './types.js';
import type { Column, ISortDescriptor, TDisplayFunction } from './types.js';
export function computeColumnsFromDisplayFunction<T>(
displayFunction: TDisplayFunction<T>,
@@ -36,11 +36,31 @@ export function getCellValue<T>(row: T, col: Column<T>, displayFunction?: TDispl
return col.value ? col.value(row) : (row as any)[col.key as any];
}
/**
* Compares two cell values in ascending order. Returns -1, 0, or 1.
* Null/undefined values sort before defined values. Numbers compare numerically;
* everything else compares as case-insensitive strings.
*/
export function compareCellValues(va: any, vb: any): number {
if (va == null && vb == null) return 0;
if (va == null) return -1;
if (vb == null) return 1;
if (typeof va === 'number' && typeof vb === 'number') {
if (va < vb) return -1;
if (va > vb) return 1;
return 0;
}
const sa = String(va).toLowerCase();
const sb = String(vb).toLowerCase();
if (sa < sb) return -1;
if (sa > sb) return 1;
return 0;
}
export function getViewData<T>(
data: T[],
effectiveColumns: Column<T>[],
sortKey?: string,
sortDir?: 'asc' | 'desc' | null,
sortBy: ISortDescriptor[],
filterText?: string,
columnFilters?: Record<string, string>,
filterMode: 'table' | 'data' = 'table',
@@ -94,21 +114,17 @@ export function getViewData<T>(
return true;
});
}
if (!sortKey || !sortDir) return arr;
const col = effectiveColumns.find((c) => String(c.key) === sortKey);
if (!col) return arr;
const dir = sortDir === 'asc' ? 1 : -1;
if (!sortBy || sortBy.length === 0) return arr;
// Pre-resolve descriptors -> columns once for performance.
const resolved = sortBy
.map((desc) => ({ desc, col: effectiveColumns.find((c) => String(c.key) === desc.key) }))
.filter((entry): entry is { desc: ISortDescriptor; col: Column<T> } => !!entry.col);
if (resolved.length === 0) return arr;
arr.sort((a, b) => {
const va = getCellValue(a, col);
const vb = getCellValue(b, col);
if (va == null && vb == null) return 0;
if (va == null) return -1 * dir;
if (vb == null) return 1 * dir;
if (typeof va === 'number' && typeof vb === 'number') return (va - vb) * dir;
const sa = String(va).toLowerCase();
const sb = String(vb).toLowerCase();
if (sa < sb) return -1 * dir;
if (sa > sb) return 1 * dir;
for (const { desc, col } of resolved) {
const cmp = compareCellValues(getCellValue(a, col), getCellValue(b, col));
if (cmp !== 0) return desc.dir === 'asc' ? cmp : -cmp;
}
return 0;
});
return arr;