| 1 |
import { |
| 2 |
Cell, |
| 3 |
CellElement, |
| 4 |
CellKey, |
| 5 |
ColumnConfigs, |
| 6 |
getCellKey, |
| 7 |
SortableType, |
| 8 |
TableConfig, |
| 9 |
} from "./attributes"; |
| 10 |
import { getElementTextContent } from "./elements"; |
| 11 |
|
| 12 |
export type SortOrder = "asc" | "desc"; |
| 13 |
|
| 14 |
function getCellElements( |
| 15 |
cells: Record<CellKey, Cell>, |
| 16 |
row: number, |
| 17 |
col: number |
| 18 |
): CellElement[] { |
| 19 |
return cells[getCellKey(row, col)]?.elements || []; |
| 20 |
} |
| 21 |
|
| 22 |
export function tableHasMergedCells(cells: Record<CellKey, Cell>): boolean { |
| 23 |
return Object.values(cells).some(cell => { |
| 24 |
const rowSpan = cell.span?.rowSpan || 1; |
| 25 |
const colSpan = cell.span?.colSpan || 1; |
| 26 |
return rowSpan > 1 || colSpan > 1; |
| 27 |
}); |
| 28 |
} |
| 29 |
|
| 30 |
export function columnHasMultipleElements( |
| 31 |
column: number, |
| 32 |
cells: Record<CellKey, Cell> |
| 33 |
): boolean { |
| 34 |
return Object.entries(cells).some(([key, cell]) => { |
| 35 |
const [, col] = key.split(",").map(Number); |
| 36 |
return col === column && (cell.elements?.length || 0) > 1; |
| 37 |
}); |
| 38 |
} |
| 39 |
|
| 40 |
export function isColumnSortable( |
| 41 |
column: number, |
| 42 |
cells: Record<CellKey, Cell> |
| 43 |
): boolean { |
| 44 |
if (tableHasMergedCells(cells)) { |
| 45 |
return false; |
| 46 |
} |
| 47 |
|
| 48 |
if (columnHasMultipleElements(column, cells)) { |
| 49 |
return false; |
| 50 |
} |
| 51 |
|
| 52 |
return true; |
| 53 |
} |
| 54 |
|
| 55 |
export function sortRowsByColumn( |
| 56 |
cells: Record<CellKey, Cell>, |
| 57 |
totalRows: number, |
| 58 |
table: TableConfig, |
| 59 |
column: number, |
| 60 |
sortType: SortableType, |
| 61 |
order: SortOrder |
| 62 |
): number[] { |
| 63 |
const { headerEnabled, footerEnabled } = table; |
| 64 |
|
| 65 |
const headerRow = headerEnabled ? 0 : -1; |
| 66 |
const footerRow = footerEnabled ? totalRows - 1 : -1; |
| 67 |
|
| 68 |
const sortableRows: Array<{ |
| 69 |
index: number; |
| 70 |
value: string | number | Date; |
| 71 |
}> = []; |
| 72 |
|
| 73 |
for (let row = 0; row < totalRows; row++) { |
| 74 |
if (row === headerRow || row === footerRow) { |
| 75 |
continue; |
| 76 |
} |
| 77 |
|
| 78 |
const cellElements = getCellElements(cells, row, column); |
| 79 |
|
| 80 |
let rawValue = ""; |
| 81 |
if (cellElements.length > 0) { |
| 82 |
rawValue = getElementTextContent(cellElements[0]); |
| 83 |
} |
| 84 |
|
| 85 |
let parsedValue: string | number | Date; |
| 86 |
switch (sortType) { |
| 87 |
case "number": { |
| 88 |
const cleaned = rawValue.replace(/[^0-9.-]/g, ""); |
| 89 |
const num = parseFloat(cleaned); |
| 90 |
parsedValue = isNaN(num) ? 0 : num; |
| 91 |
break; |
| 92 |
} |
| 93 |
case "date": { |
| 94 |
const date = new Date(rawValue); |
| 95 |
parsedValue = isNaN(date.getTime()) ? new Date(0) : date; |
| 96 |
break; |
| 97 |
} |
| 98 |
case "text": |
| 99 |
default: |
| 100 |
parsedValue = rawValue.toLowerCase(); |
| 101 |
} |
| 102 |
|
| 103 |
sortableRows.push({ index: row, value: parsedValue }); |
| 104 |
} |
| 105 |
|
| 106 |
sortableRows.sort(({ value: a }, { value: b }) => { |
| 107 |
let result: number; |
| 108 |
|
| 109 |
if (typeof a === "number" && typeof b === "number") { |
| 110 |
result = a - b; |
| 111 |
} else if (a instanceof Date && b instanceof Date) { |
| 112 |
result = a.getTime() - b.getTime(); |
| 113 |
} else { |
| 114 |
result = String(a).localeCompare(String(b)); |
| 115 |
} |
| 116 |
|
| 117 |
return order === "asc" ? result : -result; |
| 118 |
}); |
| 119 |
|
| 120 |
const result: number[] = []; |
| 121 |
|
| 122 |
if (headerEnabled) { |
| 123 |
result.push(0); |
| 124 |
} |
| 125 |
|
| 126 |
for (const { index } of sortableRows) { |
| 127 |
result.push(index); |
| 128 |
} |
| 129 |
|
| 130 |
if (footerEnabled) { |
| 131 |
result.push(totalRows - 1); |
| 132 |
} |
| 133 |
|
| 134 |
return result; |
| 135 |
} |
| 136 |
|
| 137 |
export function hasSortableColumns(columns: ColumnConfigs): boolean { |
| 138 |
return columns.some(config => !!config?.sortable); |
| 139 |
} |
| 140 |
|