@rivinia/table 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +272 -2
- package/fesm2022/rivinia-table-ui.mjs +867 -0
- package/fesm2022/rivinia-table-ui.mjs.map +1 -0
- package/fesm2022/rivinia-table.mjs +783 -0
- package/fesm2022/rivinia-table.mjs.map +1 -0
- package/package.json +35 -3
- package/types/rivinia-table-ui.d.ts +514 -0
- package/types/rivinia-table.d.ts +494 -0
|
@@ -0,0 +1,783 @@
|
|
|
1
|
+
import { computed, isDevMode, linkedSignal, signal } from "@angular/core";
|
|
2
|
+
function readCellValue(column, row) {
|
|
3
|
+
const accessor = column.accessor;
|
|
4
|
+
if (accessor === void 0) return void 0;
|
|
5
|
+
if (typeof accessor === "string") return row.data[accessor];
|
|
6
|
+
return accessor(row.data);
|
|
7
|
+
}
|
|
8
|
+
const COLLATORS = /* @__PURE__ */ new Map();
|
|
9
|
+
function collatorFor(locale) {
|
|
10
|
+
const key = locale === "" ? "en" : locale;
|
|
11
|
+
const cached = COLLATORS.get(key);
|
|
12
|
+
if (cached !== void 0) return cached;
|
|
13
|
+
let collator;
|
|
14
|
+
try {
|
|
15
|
+
collator = new Intl.Collator(key, {
|
|
16
|
+
numeric: true,
|
|
17
|
+
sensitivity: "base"
|
|
18
|
+
});
|
|
19
|
+
} catch {
|
|
20
|
+
collator = new Intl.Collator(void 0, {
|
|
21
|
+
numeric: true,
|
|
22
|
+
sensitivity: "base"
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
COLLATORS.set(key, collator);
|
|
26
|
+
return collator;
|
|
27
|
+
}
|
|
28
|
+
function normalizeSearchText(value) {
|
|
29
|
+
return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[\u064B-\u065F\u0670]/g, "").replace(/\u0640/g, "").replace(/[أإآٱ]/g, "ا").replace(/ى/g, "ي").replace(/ؤ/g, "و").replace(/ئ/g, "ي").replace(/ة/g, "ه").toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
function isNullish$1(value) {
|
|
32
|
+
return value === null || value === void 0;
|
|
33
|
+
}
|
|
34
|
+
function isActiveFilter(value) {
|
|
35
|
+
if (value === null || value === void 0 || value === "") return false;
|
|
36
|
+
return !Array.isArray(value) || value.length > 0;
|
|
37
|
+
}
|
|
38
|
+
function toNumber(value) {
|
|
39
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
40
|
+
if (typeof value === "bigint") return Number(value);
|
|
41
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
42
|
+
const numeric = Number(value);
|
|
43
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
function toTime(value) {
|
|
48
|
+
if (value instanceof Date) {
|
|
49
|
+
const time = value.getTime();
|
|
50
|
+
return Number.isNaN(time) ? null : time;
|
|
51
|
+
}
|
|
52
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
53
|
+
if (typeof value === "bigint") return Number(value);
|
|
54
|
+
if (typeof value === "string") {
|
|
55
|
+
const trimmed = value.trim();
|
|
56
|
+
if (trimmed === "") return null;
|
|
57
|
+
const numeric = Number(trimmed);
|
|
58
|
+
if (Number.isFinite(numeric)) return numeric;
|
|
59
|
+
const parsed = Date.parse(trimmed);
|
|
60
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
function isRange(value) {
|
|
65
|
+
return typeof value === "object" && value !== null && ("min" in value || "max" in value);
|
|
66
|
+
}
|
|
67
|
+
function matchesRange(cell, range) {
|
|
68
|
+
const min = range.min === null || range.min === void 0 ? null : toTime(range.min);
|
|
69
|
+
const max = range.max === null || range.max === void 0 ? null : toTime(range.max);
|
|
70
|
+
if (min === null && max === null) return true;
|
|
71
|
+
const time = toTime(cell);
|
|
72
|
+
if (time === null) return false;
|
|
73
|
+
return (min === null || time >= min) && (max === null || time <= max);
|
|
74
|
+
}
|
|
75
|
+
function matchesFilter(cell, filter) {
|
|
76
|
+
if (Array.isArray(filter)) return filter.some((item) => Object.is(item, cell));
|
|
77
|
+
if (typeof filter === "boolean") return Boolean(cell) === filter;
|
|
78
|
+
if (filter instanceof Date) {
|
|
79
|
+
const time = toTime(cell);
|
|
80
|
+
return time !== null && time === filter.getTime();
|
|
81
|
+
}
|
|
82
|
+
if (typeof filter === "number") {
|
|
83
|
+
const numeric = toNumber(cell);
|
|
84
|
+
return numeric !== null && numeric === filter;
|
|
85
|
+
}
|
|
86
|
+
if (typeof filter === "bigint") {
|
|
87
|
+
const numeric = toNumber(cell);
|
|
88
|
+
return numeric !== null && numeric === Number(filter);
|
|
89
|
+
}
|
|
90
|
+
if (isRange(filter)) return matchesRange(cell, filter);
|
|
91
|
+
if (typeof filter === "string") return normalizeSearchText(String(cell ?? "")).includes(normalizeSearchText(filter));
|
|
92
|
+
return Object.is(cell, filter);
|
|
93
|
+
}
|
|
94
|
+
function applyFilters(rows, columns, filters) {
|
|
95
|
+
if (filters.length === 0) return rows;
|
|
96
|
+
const byId = new Map(columns.map((column) => [column.id, column]));
|
|
97
|
+
const plan = [];
|
|
98
|
+
for (const filter of filters) {
|
|
99
|
+
if (!isActiveFilter(filter.value)) continue;
|
|
100
|
+
const column = byId.get(filter.column);
|
|
101
|
+
if (column === void 0 || column.accessor === void 0) continue;
|
|
102
|
+
plan.push({
|
|
103
|
+
column,
|
|
104
|
+
value: filter.value
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (plan.length === 0) return rows;
|
|
108
|
+
return rows.filter((row) => {
|
|
109
|
+
for (const entry of plan) if (!matchesFilter(readCellValue(entry.column, row), entry.value)) return false;
|
|
110
|
+
return true;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function applySearch(rows, columns, search) {
|
|
114
|
+
const needle = normalizeSearchText(search.trim());
|
|
115
|
+
if (needle === "") return rows;
|
|
116
|
+
const searchable = columns.filter((column) => column.accessor !== void 0);
|
|
117
|
+
if (searchable.length === 0) return [];
|
|
118
|
+
return rows.filter((row) => {
|
|
119
|
+
for (const column of searchable) {
|
|
120
|
+
const value = readCellValue(column, row);
|
|
121
|
+
if (value === null || value === void 0) continue;
|
|
122
|
+
if (normalizeSearchText(String(value)).includes(needle)) return true;
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function comparePresent(left, right, comparator, collator) {
|
|
128
|
+
if (comparator !== void 0) return comparator(left, right);
|
|
129
|
+
if (left instanceof Date && right instanceof Date) {
|
|
130
|
+
const a = left.getTime();
|
|
131
|
+
const b = right.getTime();
|
|
132
|
+
if (!Number.isNaN(a) && !Number.isNaN(b)) return a < b ? -1 : a > b ? 1 : 0;
|
|
133
|
+
}
|
|
134
|
+
if (typeof left === "number" && typeof right === "number") return left < right ? -1 : left > right ? 1 : 0;
|
|
135
|
+
if (typeof left === "bigint" && typeof right === "bigint") return left < right ? -1 : left > right ? 1 : 0;
|
|
136
|
+
if (typeof left === "boolean" && typeof right === "boolean") return left === right ? 0 : left ? 1 : -1;
|
|
137
|
+
return collator.compare(String(left), String(right));
|
|
138
|
+
}
|
|
139
|
+
function applySort(rows, columns, sorting, locale = "en") {
|
|
140
|
+
if (sorting.length === 0) return rows;
|
|
141
|
+
const byId = new Map(columns.map((column) => [column.id, column]));
|
|
142
|
+
const plan = [];
|
|
143
|
+
for (const state of sorting) {
|
|
144
|
+
const column = byId.get(state.column);
|
|
145
|
+
if (column === void 0 || column.accessor === void 0) continue;
|
|
146
|
+
plan.push({
|
|
147
|
+
state,
|
|
148
|
+
column
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
if (plan.length === 0) return rows;
|
|
152
|
+
const collator = collatorFor(locale);
|
|
153
|
+
const keyed = rows.map((row) => ({
|
|
154
|
+
row,
|
|
155
|
+
keys: plan.map((entry) => readCellValue(entry.column, row))
|
|
156
|
+
}));
|
|
157
|
+
keyed.sort((a, b) => {
|
|
158
|
+
for (let index = 0; index < plan.length; index++) {
|
|
159
|
+
const left = a.keys[index];
|
|
160
|
+
const right = b.keys[index];
|
|
161
|
+
const leftMissing = isNullish$1(left);
|
|
162
|
+
const rightMissing = isNullish$1(right);
|
|
163
|
+
if (leftMissing || rightMissing) {
|
|
164
|
+
if (leftMissing && rightMissing) continue;
|
|
165
|
+
return leftMissing ? 1 : -1;
|
|
166
|
+
}
|
|
167
|
+
const entry = plan[index];
|
|
168
|
+
const result = comparePresent(left, right, entry.column.comparator, collator);
|
|
169
|
+
if (result !== 0) return entry.state.direction === "desc" ? -result : result;
|
|
170
|
+
}
|
|
171
|
+
return a.row.index - b.row.index;
|
|
172
|
+
});
|
|
173
|
+
return keyed.map((entry) => entry.row);
|
|
174
|
+
}
|
|
175
|
+
function paginate(rows, page, pageSize) {
|
|
176
|
+
const total = rows.length;
|
|
177
|
+
const size = Number.isFinite(pageSize) ? Math.floor(pageSize) : 0;
|
|
178
|
+
const currentPage = Number.isFinite(page) ? Math.max(0, Math.floor(page)) : 0;
|
|
179
|
+
if (size <= 0) return {
|
|
180
|
+
pageRows: rows,
|
|
181
|
+
total,
|
|
182
|
+
pageCount: total > 0 ? 1 : 0,
|
|
183
|
+
start: total > 0 ? 1 : 0,
|
|
184
|
+
end: total
|
|
185
|
+
};
|
|
186
|
+
const pageCount = Math.ceil(total / size);
|
|
187
|
+
const offset = currentPage * size;
|
|
188
|
+
const pageRows = offset >= total ? [] : rows.slice(offset, offset + size);
|
|
189
|
+
return {
|
|
190
|
+
pageRows,
|
|
191
|
+
total,
|
|
192
|
+
pageCount,
|
|
193
|
+
start: pageRows.length === 0 ? 0 : offset + 1,
|
|
194
|
+
end: pageRows.length === 0 ? 0 : offset + pageRows.length
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
const NO_CELLS = Object.freeze([]);
|
|
198
|
+
function defaultWarn(message) {
|
|
199
|
+
if (isDevMode()) console.warn(`[rivinia/table] ${message}`);
|
|
200
|
+
}
|
|
201
|
+
function isNullish(value) {
|
|
202
|
+
return value === null || value === void 0;
|
|
203
|
+
}
|
|
204
|
+
function clamp(value, min, max) {
|
|
205
|
+
if (!Number.isFinite(value)) return min;
|
|
206
|
+
return value < min ? min : value > max ? max : value;
|
|
207
|
+
}
|
|
208
|
+
function checkColumns(columns, warn, strict) {
|
|
209
|
+
for (const column of columns) {
|
|
210
|
+
const conflicting = column.merge !== void 0 ? "merge" : column.colSpan !== void 0 ? "colSpan" : null;
|
|
211
|
+
if (column.span !== void 0 && conflicting !== null) {
|
|
212
|
+
const message = `column "${column.id}" declares both span and ${conflicting}; use span alone for full control, or merge/colSpan for the simple path`;
|
|
213
|
+
if (strict) throw new Error(`[rivinia/table] ${message}`);
|
|
214
|
+
warn(`${message}; span wins`);
|
|
215
|
+
}
|
|
216
|
+
if (column.merge !== void 0 && column.accessor === void 0) warn(`column "${column.id}" declares merge but has no accessor; merge is ignored`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function assertSpanColumns(columns, options) {
|
|
220
|
+
checkColumns(columns, options?.warn ?? defaultWarn, true);
|
|
221
|
+
}
|
|
222
|
+
function buildSpanModel(rows, columns, options) {
|
|
223
|
+
const warn = options?.warn ?? defaultWarn;
|
|
224
|
+
checkColumns(columns, warn, options?.strict ?? true);
|
|
225
|
+
const rowCount = rows.length;
|
|
226
|
+
const columnCount = columns.length;
|
|
227
|
+
if (!columns.some((column) => column.span !== void 0 || column.colSpan !== void 0 || column.merge !== void 0 && column.accessor !== void 0) || rowCount === 0 || columnCount === 0) return plainSpanModel(rowCount, columnCount);
|
|
228
|
+
const claimed = new Uint8Array(rowCount * columnCount);
|
|
229
|
+
const owners = new Int32Array(rowCount * columnCount).fill(-1);
|
|
230
|
+
const cells = [];
|
|
231
|
+
const resolve = (definition, row, column) => {
|
|
232
|
+
const current = rows[row];
|
|
233
|
+
const value = readCellValue(definition, current);
|
|
234
|
+
const spanResolver = definition.span;
|
|
235
|
+
if (spanResolver !== void 0) {
|
|
236
|
+
const explicit = spanResolver(contextOf(current, definition, value, row, column));
|
|
237
|
+
if (explicit !== null && explicit !== void 0) return {
|
|
238
|
+
rowSpan: explicit.rowSpan ?? 1,
|
|
239
|
+
colSpan: explicit.colSpan ?? 1
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
let rowSpan = 1;
|
|
243
|
+
let colSpan = 1;
|
|
244
|
+
const merge = definition.merge;
|
|
245
|
+
if (merge !== void 0 && definition.accessor !== void 0) {
|
|
246
|
+
if (merge === "equal") {
|
|
247
|
+
if (!isNullish(value)) {
|
|
248
|
+
let end = row;
|
|
249
|
+
while (end + 1 < rowCount) {
|
|
250
|
+
const candidate = readCellValue(definition, rows[end + 1]);
|
|
251
|
+
if (isNullish(candidate) || !Object.is(candidate, value)) break;
|
|
252
|
+
end++;
|
|
253
|
+
}
|
|
254
|
+
rowSpan = end - row + 1;
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
const anchor = current.data;
|
|
258
|
+
let end = row;
|
|
259
|
+
while (end + 1 < rowCount && merge(rows[end + 1].data, anchor)) end++;
|
|
260
|
+
rowSpan = end - row + 1;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const colSpanDefinition = definition.colSpan;
|
|
264
|
+
if (colSpanDefinition !== void 0) colSpan = typeof colSpanDefinition === "function" ? colSpanDefinition(contextOf(current, definition, value, row, column)) : colSpanDefinition;
|
|
265
|
+
return {
|
|
266
|
+
rowSpan,
|
|
267
|
+
colSpan
|
|
268
|
+
};
|
|
269
|
+
};
|
|
270
|
+
for (let row = 0; row < rowCount; row++) for (let column = 0; column < columnCount; column++) {
|
|
271
|
+
if (claimed[row * columnCount + column] !== 0) continue;
|
|
272
|
+
const definition = columns[column];
|
|
273
|
+
const requested = resolve(definition, row, column);
|
|
274
|
+
let rowSpan = requested.rowSpan ?? 1;
|
|
275
|
+
let colSpan = requested.colSpan ?? 1;
|
|
276
|
+
if (colSpan === Number.POSITIVE_INFINITY) colSpan = columnCount - column;
|
|
277
|
+
if (!Number.isInteger(rowSpan) || rowSpan < 1) {
|
|
278
|
+
warn(`column "${definition.id}" returned rowSpan ${rowSpan}; clamped to 1`);
|
|
279
|
+
rowSpan = 1;
|
|
280
|
+
}
|
|
281
|
+
if (!Number.isInteger(colSpan) || colSpan < 1) {
|
|
282
|
+
warn(`column "${definition.id}" returned colSpan ${colSpan}; clamped to 1`);
|
|
283
|
+
colSpan = 1;
|
|
284
|
+
}
|
|
285
|
+
rowSpan = Math.min(rowSpan, rowCount - row);
|
|
286
|
+
colSpan = Math.min(colSpan, columnCount - column);
|
|
287
|
+
const pinning = definition.pinned ?? null;
|
|
288
|
+
for (let step = 1; step < colSpan; step++) if ((columns[column + step].pinned ?? null) !== pinning) {
|
|
289
|
+
colSpan = step;
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
const wantedRowSpan = rowSpan;
|
|
293
|
+
const wantedColSpan = colSpan;
|
|
294
|
+
while (colSpan > 1 && !isFree(claimed, columnCount, row, column, rowSpan, colSpan)) colSpan--;
|
|
295
|
+
while (rowSpan > 1 && !isFree(claimed, columnCount, row, column, rowSpan, colSpan)) rowSpan--;
|
|
296
|
+
if (rowSpan !== wantedRowSpan || colSpan !== wantedColSpan) warn(`column "${definition.id}" at row ${row} overlaps an existing span; shrunk from ${wantedRowSpan}x${wantedColSpan} to ${rowSpan}x${colSpan}`);
|
|
297
|
+
const anchor = cells.length;
|
|
298
|
+
cells.push({
|
|
299
|
+
row,
|
|
300
|
+
column,
|
|
301
|
+
rowSpan,
|
|
302
|
+
colSpan
|
|
303
|
+
});
|
|
304
|
+
for (let r = row; r < row + rowSpan; r++) for (let c = column; c < column + colSpan; c++) {
|
|
305
|
+
claimed[r * columnCount + c] = 1;
|
|
306
|
+
owners[r * columnCount + c] = anchor;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (cells.length === rowCount * columnCount) return plainSpanModel(rowCount, columnCount);
|
|
310
|
+
const ownerAt = (row, column) => {
|
|
311
|
+
if (row < 0 || row >= rowCount || column < 0 || column >= columnCount) return null;
|
|
312
|
+
const index = owners[row * columnCount + column];
|
|
313
|
+
return index < 0 ? null : cells[index];
|
|
314
|
+
};
|
|
315
|
+
return {
|
|
316
|
+
cells,
|
|
317
|
+
at(row, column) {
|
|
318
|
+
const cell = ownerAt(row, column);
|
|
319
|
+
return cell !== null && cell.row === row && cell.column === column ? cell : null;
|
|
320
|
+
},
|
|
321
|
+
owner: ownerAt,
|
|
322
|
+
move(row, column, dRow, dColumn) {
|
|
323
|
+
if (dRow === 0 && dColumn === 0) return {
|
|
324
|
+
row: clamp(row, 0, rowCount - 1),
|
|
325
|
+
column: clamp(column, 0, columnCount - 1)
|
|
326
|
+
};
|
|
327
|
+
let currentRow = clamp(row, 0, rowCount - 1);
|
|
328
|
+
let currentColumn = clamp(column, 0, columnCount - 1);
|
|
329
|
+
for (;;) {
|
|
330
|
+
const nextRow = currentRow + dRow;
|
|
331
|
+
const nextColumn = currentColumn + dColumn;
|
|
332
|
+
if (nextRow < 0 || nextRow >= rowCount || nextColumn < 0 || nextColumn >= columnCount) return {
|
|
333
|
+
row: currentRow,
|
|
334
|
+
column: currentColumn
|
|
335
|
+
};
|
|
336
|
+
const span = ownerAt(nextRow, nextColumn);
|
|
337
|
+
if (span === null) return {
|
|
338
|
+
row: nextRow,
|
|
339
|
+
column: nextColumn
|
|
340
|
+
};
|
|
341
|
+
if (!(currentRow >= span.row && currentRow < span.row + span.rowSpan && currentColumn >= span.column && currentColumn < span.column + span.colSpan)) return {
|
|
342
|
+
row: span.row,
|
|
343
|
+
column: span.column
|
|
344
|
+
};
|
|
345
|
+
const jumpRow = dRow === 0 ? nextRow : dRow > 0 ? span.row + span.rowSpan : span.row - 1;
|
|
346
|
+
const jumpColumn = dColumn === 0 ? nextColumn : dColumn > 0 ? span.column + span.colSpan : span.column - 1;
|
|
347
|
+
if (jumpRow < 0 || jumpRow >= rowCount || jumpColumn < 0 || jumpColumn >= columnCount) return {
|
|
348
|
+
row: currentRow,
|
|
349
|
+
column: currentColumn
|
|
350
|
+
};
|
|
351
|
+
const jumped = ownerAt(jumpRow, jumpColumn);
|
|
352
|
+
if (jumped === null || jumped.row === jumpRow && jumped.column === jumpColumn) return {
|
|
353
|
+
row: jumpRow,
|
|
354
|
+
column: jumpColumn
|
|
355
|
+
};
|
|
356
|
+
currentRow = jumpRow;
|
|
357
|
+
currentColumn = jumpColumn;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
function contextOf(row, column, value, rowIndex, columnIndex) {
|
|
363
|
+
return {
|
|
364
|
+
row,
|
|
365
|
+
column,
|
|
366
|
+
value,
|
|
367
|
+
rowIndex,
|
|
368
|
+
columnIndex
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function isFree(claimed, columnCount, row, column, rowSpan, colSpan) {
|
|
372
|
+
for (let r = row; r < row + rowSpan; r++) for (let c = column; c < column + colSpan; c++) {
|
|
373
|
+
if (r === row && c === column) continue;
|
|
374
|
+
if (claimed[r * columnCount + c] !== 0) return false;
|
|
375
|
+
}
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
function plainSpanModel(rowCount, columnCount) {
|
|
379
|
+
return {
|
|
380
|
+
cells: NO_CELLS,
|
|
381
|
+
at: () => null,
|
|
382
|
+
owner: () => null,
|
|
383
|
+
move(row, column, dRow, dColumn) {
|
|
384
|
+
if (rowCount === 0 || columnCount === 0) return {
|
|
385
|
+
row: 0,
|
|
386
|
+
column: 0
|
|
387
|
+
};
|
|
388
|
+
return {
|
|
389
|
+
row: clamp(row + dRow, 0, rowCount - 1),
|
|
390
|
+
column: clamp(column + dColumn, 0, columnCount - 1)
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function tableColumn() {
|
|
396
|
+
return Object.freeze({
|
|
397
|
+
accessor(key, definition) {
|
|
398
|
+
return Object.freeze({
|
|
399
|
+
header: key,
|
|
400
|
+
...definition,
|
|
401
|
+
id: key,
|
|
402
|
+
accessor: (row) => row[key]
|
|
403
|
+
});
|
|
404
|
+
},
|
|
405
|
+
display(id, definition) {
|
|
406
|
+
return Object.freeze({
|
|
407
|
+
header: id,
|
|
408
|
+
...definition,
|
|
409
|
+
id
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
const DEFAULT_PAGE_SIZE = 25;
|
|
415
|
+
const NO_IDS = /* @__PURE__ */ new Set();
|
|
416
|
+
const EMPTY_SELECTION = {
|
|
417
|
+
mode: "include",
|
|
418
|
+
ids: NO_IDS
|
|
419
|
+
};
|
|
420
|
+
function pruneSelection(state, rows) {
|
|
421
|
+
if (state.ids.size === 0) return state;
|
|
422
|
+
const present = /* @__PURE__ */ new Set();
|
|
423
|
+
for (const row of rows) present.add(row.id);
|
|
424
|
+
let dropped = false;
|
|
425
|
+
const next = /* @__PURE__ */ new Set();
|
|
426
|
+
for (const id of state.ids) if (present.has(id)) next.add(id);
|
|
427
|
+
else dropped = true;
|
|
428
|
+
return dropped ? {
|
|
429
|
+
mode: state.mode,
|
|
430
|
+
ids: next
|
|
431
|
+
} : state;
|
|
432
|
+
}
|
|
433
|
+
function withId(ids, id) {
|
|
434
|
+
if (ids.has(id)) return ids;
|
|
435
|
+
const next = new Set(ids);
|
|
436
|
+
next.add(id);
|
|
437
|
+
return next;
|
|
438
|
+
}
|
|
439
|
+
function withoutId(ids, id) {
|
|
440
|
+
if (!ids.has(id)) return ids;
|
|
441
|
+
const next = new Set(ids);
|
|
442
|
+
next.delete(id);
|
|
443
|
+
return next;
|
|
444
|
+
}
|
|
445
|
+
var RiviniaSelection = class {
|
|
446
|
+
enabled;
|
|
447
|
+
multiple;
|
|
448
|
+
mode;
|
|
449
|
+
ids;
|
|
450
|
+
count;
|
|
451
|
+
rows;
|
|
452
|
+
pageRows;
|
|
453
|
+
defaultScope;
|
|
454
|
+
initial;
|
|
455
|
+
state;
|
|
456
|
+
constructor(config) {
|
|
457
|
+
this.rows = config.rows;
|
|
458
|
+
this.pageRows = config.pageRows;
|
|
459
|
+
this.defaultScope = config.defaultScope;
|
|
460
|
+
this.initial = {
|
|
461
|
+
mode: config.initial.mode,
|
|
462
|
+
ids: new Set(config.initial.ids)
|
|
463
|
+
};
|
|
464
|
+
this.enabled = config.kind !== "none";
|
|
465
|
+
this.multiple = config.kind === "multi";
|
|
466
|
+
this.state = linkedSignal({
|
|
467
|
+
...ngDevMode ? { debugName: "state" } : /* istanbul ignore next */ {},
|
|
468
|
+
source: config.rows,
|
|
469
|
+
computation: (rows, previous) => pruneSelection(previous?.value ?? this.initial, rows)
|
|
470
|
+
});
|
|
471
|
+
this.mode = computed(() => this.state().mode, ...ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []);
|
|
472
|
+
this.ids = computed(() => this.selectedIds(), ...ngDevMode ? [{ debugName: "ids" }] : /* istanbul ignore next */ []);
|
|
473
|
+
this.count = computed(() => this.ids().length, ...ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []);
|
|
474
|
+
}
|
|
475
|
+
select(id) {
|
|
476
|
+
if (!this.enabled) return;
|
|
477
|
+
const state = this.state();
|
|
478
|
+
if (!this.multiple) {
|
|
479
|
+
if (state.mode === "include" && state.ids.size === 1 && state.ids.has(id)) return;
|
|
480
|
+
this.state.set({
|
|
481
|
+
mode: "include",
|
|
482
|
+
ids: /* @__PURE__ */ new Set([id])
|
|
483
|
+
});
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
if (state.mode === "include") {
|
|
487
|
+
const ids = withId(state.ids, id);
|
|
488
|
+
if (ids !== state.ids) this.state.set({
|
|
489
|
+
mode: "include",
|
|
490
|
+
ids
|
|
491
|
+
});
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const ids = withoutId(state.ids, id);
|
|
495
|
+
if (ids !== state.ids) this.state.set({
|
|
496
|
+
mode: "exclude",
|
|
497
|
+
ids
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
deselect(id) {
|
|
501
|
+
if (!this.enabled) return;
|
|
502
|
+
const state = this.state();
|
|
503
|
+
if (state.mode === "include") {
|
|
504
|
+
const ids = withoutId(state.ids, id);
|
|
505
|
+
if (ids !== state.ids) this.state.set({
|
|
506
|
+
mode: "include",
|
|
507
|
+
ids
|
|
508
|
+
});
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
const ids = withId(state.ids, id);
|
|
512
|
+
if (ids !== state.ids) this.state.set({
|
|
513
|
+
mode: "exclude",
|
|
514
|
+
ids
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
toggle(id) {
|
|
518
|
+
if (this.isSelected(id)) this.deselect(id);
|
|
519
|
+
else this.select(id);
|
|
520
|
+
}
|
|
521
|
+
clear() {
|
|
522
|
+
if (!this.enabled) return;
|
|
523
|
+
this.state.set(EMPTY_SELECTION);
|
|
524
|
+
}
|
|
525
|
+
isSelected(id) {
|
|
526
|
+
if (!this.enabled) return false;
|
|
527
|
+
const state = this.state();
|
|
528
|
+
return state.mode === "include" ? state.ids.has(id) : !state.ids.has(id);
|
|
529
|
+
}
|
|
530
|
+
selectAllOnPage() {
|
|
531
|
+
if (!this.enabled || !this.multiple) return;
|
|
532
|
+
const page = this.pageRows();
|
|
533
|
+
if (page.length === 0) return;
|
|
534
|
+
const state = this.state();
|
|
535
|
+
if (state.mode === "include") {
|
|
536
|
+
let ids = state.ids;
|
|
537
|
+
for (const row of page) ids = withId(ids, row.id);
|
|
538
|
+
if (ids !== state.ids) this.state.set({
|
|
539
|
+
mode: "include",
|
|
540
|
+
ids
|
|
541
|
+
});
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
let ids = state.ids;
|
|
545
|
+
for (const row of page) ids = withoutId(ids, row.id);
|
|
546
|
+
if (ids !== state.ids) this.state.set({
|
|
547
|
+
mode: "exclude",
|
|
548
|
+
ids
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
selectAll(scope) {
|
|
552
|
+
if (!this.enabled || !this.multiple) return;
|
|
553
|
+
if ((scope ?? this.defaultScope) === "page") {
|
|
554
|
+
this.selectAllOnPage();
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (this.rows().length === 0) return;
|
|
558
|
+
this.state.set({
|
|
559
|
+
mode: "exclude",
|
|
560
|
+
ids: NO_IDS
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
toState() {
|
|
564
|
+
const state = this.state();
|
|
565
|
+
if (state.mode === "exclude") return {
|
|
566
|
+
selected: [],
|
|
567
|
+
excluded: [...state.ids]
|
|
568
|
+
};
|
|
569
|
+
return {
|
|
570
|
+
selected: [...state.ids],
|
|
571
|
+
excluded: []
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
restore(selection) {
|
|
575
|
+
if (!this.enabled) return;
|
|
576
|
+
this.state.set(pruneSelection({
|
|
577
|
+
mode: selection.mode,
|
|
578
|
+
ids: new Set(selection.ids)
|
|
579
|
+
}, this.rows()));
|
|
580
|
+
}
|
|
581
|
+
selectedIds() {
|
|
582
|
+
const state = this.state();
|
|
583
|
+
const ids = [];
|
|
584
|
+
for (const row of this.rows()) {
|
|
585
|
+
const listed = state.ids.has(row.id);
|
|
586
|
+
if (state.mode === "include" ? listed : !listed) ids.push(row.id);
|
|
587
|
+
}
|
|
588
|
+
return ids;
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
function buildRows(data, getId) {
|
|
592
|
+
const rows = new Array(data.length);
|
|
593
|
+
for (let index = 0; index < data.length; index++) {
|
|
594
|
+
const item = data[index];
|
|
595
|
+
rows[index] = {
|
|
596
|
+
id: getId(item),
|
|
597
|
+
data: item,
|
|
598
|
+
index
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
return rows;
|
|
602
|
+
}
|
|
603
|
+
function reindex(rows) {
|
|
604
|
+
for (let index = 0; index < rows.length; index++) if (rows[index].index !== index) return rows.map((row, position) => ({
|
|
605
|
+
id: row.id,
|
|
606
|
+
data: row.data,
|
|
607
|
+
index: position
|
|
608
|
+
}));
|
|
609
|
+
return rows;
|
|
610
|
+
}
|
|
611
|
+
function normalizePage(value) {
|
|
612
|
+
return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
613
|
+
}
|
|
614
|
+
function normalizePageSize(value) {
|
|
615
|
+
return Number.isFinite(value) && value > 0 ? Math.floor(value) : DEFAULT_PAGE_SIZE;
|
|
616
|
+
}
|
|
617
|
+
var RiviniaTable = class {
|
|
618
|
+
columns;
|
|
619
|
+
visibleColumns;
|
|
620
|
+
rows;
|
|
621
|
+
pageRows;
|
|
622
|
+
spans;
|
|
623
|
+
total;
|
|
624
|
+
pageCount;
|
|
625
|
+
sorting;
|
|
626
|
+
filters;
|
|
627
|
+
globalSearch;
|
|
628
|
+
page;
|
|
629
|
+
pageSize;
|
|
630
|
+
selection;
|
|
631
|
+
request;
|
|
632
|
+
state;
|
|
633
|
+
options;
|
|
634
|
+
initialState;
|
|
635
|
+
defaultPageSize;
|
|
636
|
+
warn;
|
|
637
|
+
warned = /* @__PURE__ */ new Set();
|
|
638
|
+
generatedIds = /* @__PURE__ */ new WeakMap();
|
|
639
|
+
selectionImpl;
|
|
640
|
+
generatedIdSeq = 0;
|
|
641
|
+
constructor(options) {
|
|
642
|
+
this.options = options;
|
|
643
|
+
this.initialState = options().initialState ?? {};
|
|
644
|
+
const warnOption = options().warn;
|
|
645
|
+
this.warn = (message) => {
|
|
646
|
+
if (this.warned.has(message)) return;
|
|
647
|
+
this.warned.add(message);
|
|
648
|
+
if (warnOption !== void 0) warnOption(message);
|
|
649
|
+
else if (isDevMode()) console.warn(`[rivinia/table] ${message}`);
|
|
650
|
+
};
|
|
651
|
+
if (options().server !== void 0) this.warn("server mode is not implemented yet; nothing will be fetched. Pass `data` and let the app own the request for now");
|
|
652
|
+
assertSpanColumns(options().columns(), { warn: this.warn });
|
|
653
|
+
const initial = this.initialState;
|
|
654
|
+
const selectionOptions = options().selection;
|
|
655
|
+
this.columns = computed(() => this.options().columns(), ...ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []);
|
|
656
|
+
this.visibleColumns = computed(() => this.columns().filter((column) => column.hidden !== true), ...ngDevMode ? [{ debugName: "visibleColumns" }] : /* istanbul ignore next */ []);
|
|
657
|
+
this.sorting = signal(initial.sort ?? [], ...ngDevMode ? [{ debugName: "sorting" }] : /* istanbul ignore next */ []);
|
|
658
|
+
this.filters = signal(initial.filters ?? [], ...ngDevMode ? [{ debugName: "filters" }] : /* istanbul ignore next */ []);
|
|
659
|
+
this.globalSearch = signal(initial.search ?? "", ...ngDevMode ? [{ debugName: "globalSearch" }] : /* istanbul ignore next */ []);
|
|
660
|
+
this.defaultPageSize = normalizePageSize(initial.pageSize ?? options().pageSize ?? DEFAULT_PAGE_SIZE);
|
|
661
|
+
this.pageSize = signal(this.defaultPageSize, ...ngDevMode ? [{ debugName: "pageSize" }] : /* istanbul ignore next */ []);
|
|
662
|
+
this.page = linkedSignal({
|
|
663
|
+
...ngDevMode ? { debugName: "page" } : /* istanbul ignore next */ {},
|
|
664
|
+
source: () => JSON.stringify([
|
|
665
|
+
this.filters(),
|
|
666
|
+
this.sorting(),
|
|
667
|
+
this.globalSearch()
|
|
668
|
+
]),
|
|
669
|
+
computation: (_signature, previous) => previous === void 0 ? normalizePage(initial.page ?? 0) : 0
|
|
670
|
+
});
|
|
671
|
+
this.rows = computed(() => {
|
|
672
|
+
const current = this.options();
|
|
673
|
+
const data = current.data?.() ?? [];
|
|
674
|
+
const getId = current.getRowId ?? this.defaultRowId;
|
|
675
|
+
const columns = this.visibleColumns();
|
|
676
|
+
const all = buildRows(data, getId);
|
|
677
|
+
const filtered = current.manualFiltering ? all : applySearch(applyFilters(all, columns, this.filters()), columns, this.globalSearch());
|
|
678
|
+
return reindex(current.manualSorting ? filtered : applySort(filtered, columns, this.sorting(), current.locale?.() ?? "en"));
|
|
679
|
+
}, ...ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []);
|
|
680
|
+
this.total = computed(() => this.options().rowCount?.() ?? this.rows().length, ...ngDevMode ? [{ debugName: "total" }] : /* istanbul ignore next */ []);
|
|
681
|
+
this.pageRows = computed(() => {
|
|
682
|
+
if (this.options().manualPagination) return this.rows();
|
|
683
|
+
return paginate(this.rows(), this.page(), this.pageSize()).pageRows;
|
|
684
|
+
}, ...ngDevMode ? [{ debugName: "pageRows" }] : /* istanbul ignore next */ []);
|
|
685
|
+
this.pageCount = computed(() => {
|
|
686
|
+
const total = this.total();
|
|
687
|
+
const size = this.pageSize();
|
|
688
|
+
return size > 0 ? Math.ceil(total / size) : total > 0 ? 1 : 0;
|
|
689
|
+
}, ...ngDevMode ? [{ debugName: "pageCount" }] : /* istanbul ignore next */ []);
|
|
690
|
+
const selection = new RiviniaSelection({
|
|
691
|
+
rows: this.rows,
|
|
692
|
+
pageRows: this.pageRows,
|
|
693
|
+
kind: selectionOptions?.mode ?? "none",
|
|
694
|
+
defaultScope: selectionOptions?.selectAllScope ?? "filtered",
|
|
695
|
+
initial: {
|
|
696
|
+
mode: (initial.excluded?.length ?? 0) > 0 ? "exclude" : "include",
|
|
697
|
+
ids: (initial.excluded?.length ?? 0) > 0 ? initial.excluded ?? [] : initial.selected ?? []
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
this.selection = selection;
|
|
701
|
+
this.selectionImpl = selection;
|
|
702
|
+
this.spans = computed(() => buildSpanModel(this.pageRows(), this.visibleColumns(), {
|
|
703
|
+
warn: this.warn,
|
|
704
|
+
strict: false
|
|
705
|
+
}), ...ngDevMode ? [{ debugName: "spans" }] : /* istanbul ignore next */ []);
|
|
706
|
+
this.request = computed(() => ({
|
|
707
|
+
page: this.page(),
|
|
708
|
+
pageSize: this.pageSize(),
|
|
709
|
+
sort: this.sorting(),
|
|
710
|
+
filters: this.filters(),
|
|
711
|
+
search: this.globalSearch()
|
|
712
|
+
}), ...ngDevMode ? [{ debugName: "request" }] : /* istanbul ignore next */ []);
|
|
713
|
+
this.state = computed(() => {
|
|
714
|
+
const columns = this.columns();
|
|
715
|
+
const selected = selection.toState();
|
|
716
|
+
return {
|
|
717
|
+
sort: this.sorting(),
|
|
718
|
+
filters: this.filters(),
|
|
719
|
+
search: this.globalSearch(),
|
|
720
|
+
page: this.page(),
|
|
721
|
+
pageSize: this.pageSize(),
|
|
722
|
+
order: columns.map((column) => column.id),
|
|
723
|
+
hidden: columns.filter((column) => column.hidden === true).map((column) => column.id),
|
|
724
|
+
pinnedStart: columns.filter((column) => column.pinned === "start").map((column) => column.id),
|
|
725
|
+
pinnedEnd: columns.filter((column) => column.pinned === "end").map((column) => column.id),
|
|
726
|
+
selected: selected.selected,
|
|
727
|
+
excluded: selected.excluded
|
|
728
|
+
};
|
|
729
|
+
}, ...ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []);
|
|
730
|
+
}
|
|
731
|
+
setState(patch) {
|
|
732
|
+
if (patch.sort !== void 0) this.sorting.set(patch.sort);
|
|
733
|
+
if (patch.filters !== void 0) this.filters.set(patch.filters);
|
|
734
|
+
if (patch.search !== void 0) this.globalSearch.set(patch.search);
|
|
735
|
+
if (patch.pageSize !== void 0) this.pageSize.set(normalizePageSize(patch.pageSize));
|
|
736
|
+
if (patch.page !== void 0) this.page.set(normalizePage(patch.page));
|
|
737
|
+
if (patch.selected !== void 0 || patch.excluded !== void 0) {
|
|
738
|
+
const excluded = patch.excluded ?? [];
|
|
739
|
+
this.selectionImpl.restore(excluded.length > 0 ? {
|
|
740
|
+
mode: "exclude",
|
|
741
|
+
ids: excluded
|
|
742
|
+
} : {
|
|
743
|
+
mode: "include",
|
|
744
|
+
ids: patch.selected ?? []
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
resetState() {
|
|
749
|
+
const initial = this.initialState;
|
|
750
|
+
this.sorting.set(initial.sort ?? []);
|
|
751
|
+
this.filters.set(initial.filters ?? []);
|
|
752
|
+
this.globalSearch.set(initial.search ?? "");
|
|
753
|
+
this.pageSize.set(this.defaultPageSize);
|
|
754
|
+
this.page.set(normalizePage(initial.page ?? 0));
|
|
755
|
+
const excluded = initial.excluded ?? [];
|
|
756
|
+
this.selectionImpl.restore(excluded.length > 0 ? {
|
|
757
|
+
mode: "exclude",
|
|
758
|
+
ids: excluded
|
|
759
|
+
} : {
|
|
760
|
+
mode: "include",
|
|
761
|
+
ids: initial.selected ?? []
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
defaultRowId = (row) => {
|
|
765
|
+
if (typeof row === "object" && row !== null) {
|
|
766
|
+
const candidate = row.id;
|
|
767
|
+
if (typeof candidate === "string" || typeof candidate === "number") return candidate;
|
|
768
|
+
let generated = this.generatedIds.get(row);
|
|
769
|
+
if (generated === void 0) {
|
|
770
|
+
generated = `rv-row-${++this.generatedIdSeq}`;
|
|
771
|
+
this.generatedIds.set(row, generated);
|
|
772
|
+
}
|
|
773
|
+
return generated;
|
|
774
|
+
}
|
|
775
|
+
return String(row);
|
|
776
|
+
};
|
|
777
|
+
};
|
|
778
|
+
function createTable(options) {
|
|
779
|
+
return new RiviniaTable(options);
|
|
780
|
+
}
|
|
781
|
+
export { RiviniaTable, applyFilters, applySearch, applySort, assertSpanColumns, buildSpanModel, createTable, paginate, readCellValue, tableColumn };
|
|
782
|
+
|
|
783
|
+
//# sourceMappingURL=rivinia-table.mjs.map
|