@lilydesignsystem/angular-data-grid 0.0.0-stage → 0.10.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.
@@ -0,0 +1,3598 @@
1
+ import * as i0 from "@angular/core";
2
+ import { ChangeDetectionStrategy, Component, Directive, ElementRef, Injector, afterNextRender, computed, effect, inject, input, model, output, signal, untracked, viewChild } from "@angular/core";
3
+ import { NgTemplateOutlet } from "@angular/common";
4
+ import { DataTable, DataTableBody, DataTableFoot, DataTableHead, DataTableRow, DataTableTD, DataTableTH, PaginationNav, SearchInput } from "@lilydesignsystem/angular-headless";
5
+ function isExportFormula(value) {
6
+ return !!value && typeof value === "object" && !(value instanceof Date) && typeof value.formula === "string" && "result" in value;
7
+ }
8
+ const MIME_TYPES = {
9
+ csv: "text/csv;charset=utf-8",
10
+ tsv: "text/tab-separated-values;charset=utf-8",
11
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
12
+ };
13
+ const FORMULA_START = /^[=+\-@\t\r]/;
14
+ function cellText(value, safe = true) {
15
+ if (value === null || value === void 0) return "";
16
+ if (isExportFormula(value)) return cellText(value.result, safe);
17
+ if (value instanceof Date) {
18
+ if (Number.isNaN(value.getTime())) return "";
19
+ const iso = value.toISOString();
20
+ return iso.endsWith("T00:00:00.000Z") ? iso.slice(0, 10) : iso;
21
+ }
22
+ if (typeof value === "number") return Number.isFinite(value) ? String(value) : "";
23
+ if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
24
+ const text = String(value);
25
+ return safe && FORMULA_START.test(text) ? `'${text}` : text;
26
+ }
27
+ function quoteField(text, delimiter) {
28
+ return text.includes(delimiter) || /["\r\n]/.test(text) ? `"${text.replace(/"/g, "\"\"")}"` : text;
29
+ }
30
+ function toDelimited(table, options = {}) {
31
+ const { delimiter = ",", header = true, bom = false, safe = true, eol = "\r\n" } = options;
32
+ const lines = [];
33
+ if (header) lines.push(table.headers.map((h) => quoteField(cellText(h, safe), delimiter)).join(delimiter));
34
+ for (const row of table.rows) lines.push(table.headers.map((_, i) => quoteField(cellText(row[i], safe), delimiter)).join(delimiter));
35
+ return (bom ? "" : "") + lines.join(eol) + (lines.length > 0 ? eol : "");
36
+ }
37
+ function toCsv(table, options = {}) {
38
+ return toDelimited(table, {
39
+ ...options,
40
+ delimiter: ","
41
+ });
42
+ }
43
+ function toTsv(table, options = {}) {
44
+ return toDelimited(table, {
45
+ ...options,
46
+ delimiter: " "
47
+ });
48
+ }
49
+ const encoder = new TextEncoder();
50
+ function xmlEscape(text) {
51
+ return text.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F￾￿]/g, "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
52
+ }
53
+ function columnName(index) {
54
+ let n = index;
55
+ let name = "";
56
+ do {
57
+ name = String.fromCharCode(65 + n % 26) + name;
58
+ n = Math.floor(n / 26) - 1;
59
+ } while (n >= 0);
60
+ return name;
61
+ }
62
+ function excelSerial(date) {
63
+ return date.getTime() / 864e5 + 25569;
64
+ }
65
+ function sheetNameFor(name) {
66
+ return name.replace(/[[\]:*?/\\]/g, " ").replace(/\s+/g, " ").trim().slice(0, 31) || "Sheet1";
67
+ }
68
+ const STYLES_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><styleSheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"><numFmts count=\"2\"><numFmt numFmtId=\"164\" formatCode=\"yyyy\\-mm\\-dd\"/><numFmt numFmtId=\"165\" formatCode=\"yyyy\\-mm\\-dd\\ hh:mm:ss\"/></numFmts><fonts count=\"2\"><font><sz val=\"11\"/><name val=\"Calibri\"/></font><font><b/><sz val=\"11\"/><name val=\"Calibri\"/></font></fonts><fills count=\"2\"><fill><patternFill patternType=\"none\"/></fill><fill><patternFill patternType=\"gray125\"/></fill></fills><borders count=\"1\"><border><left/><right/><top/><bottom/><diagonal/></border></borders><cellStyleXfs count=\"1\"><xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\"/></cellStyleXfs><cellXfs count=\"4\"><xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\" xfId=\"0\"/><xf numFmtId=\"164\" fontId=\"0\" fillId=\"0\" borderId=\"0\" xfId=\"0\" applyNumberFormat=\"1\"/><xf numFmtId=\"165\" fontId=\"0\" fillId=\"0\" borderId=\"0\" xfId=\"0\" applyNumberFormat=\"1\"/><xf numFmtId=\"0\" fontId=\"1\" fillId=\"0\" borderId=\"0\" xfId=\"0\" applyFont=\"1\"/></cellXfs><cellStyles count=\"1\"><cellStyle name=\"Normal\" xfId=\"0\" builtinId=\"0\"/></cellStyles></styleSheet>";
69
+ function formulaXml(ref, value) {
70
+ const expression = xmlEscape(value.formula.replace(/^=/, ""));
71
+ const result = value.result;
72
+ if (typeof result === "number" && Number.isFinite(result)) return `<c r="${ref}"><f>${expression}</f><v>${result}</v></c>`;
73
+ if (typeof result === "boolean") return `<c r="${ref}" t="b"><f>${expression}</f><v>${result ? 1 : 0}</v></c>`;
74
+ if (result instanceof Date && !Number.isNaN(result.getTime())) return `<c r="${ref}" s="${result.getTime() % 864e5 !== 0 ? 2 : 1}"><f>${expression}</f><v>${excelSerial(result)}</v></c>`;
75
+ if (result === null || result === void 0) return `<c r="${ref}"><f>${expression}</f></c>`;
76
+ return `<c r="${ref}" t="str"><f>${expression}</f><v>${xmlEscape(String(result))}</v></c>`;
77
+ }
78
+ function cellXml(ref, value, headerStyle = false) {
79
+ if (value === null || value === void 0) return "";
80
+ if (isExportFormula(value)) return formulaXml(ref, value);
81
+ const s = headerStyle ? " s=\"3\"" : "";
82
+ if (value instanceof Date) {
83
+ if (Number.isNaN(value.getTime())) return "";
84
+ return `<c r="${ref}" s="${value.getTime() % 864e5 !== 0 ? 2 : 1}"><v>${excelSerial(value)}</v></c>`;
85
+ }
86
+ if (typeof value === "number") return Number.isFinite(value) ? `<c r="${ref}"${s}><v>${value}</v></c>` : "";
87
+ if (typeof value === "boolean") return `<c r="${ref}"${s} t="b"><v>${value ? 1 : 0}</v></c>`;
88
+ return `<c r="${ref}"${s} t="inlineStr"><is><t xml:space="preserve">${xmlEscape(String(value))}</t></is></c>`;
89
+ }
90
+ function sheetXml(table, header) {
91
+ const rows = [];
92
+ let rowNumber = 0;
93
+ if (header) {
94
+ rowNumber += 1;
95
+ rows.push(`<row r="${rowNumber}">${table.headers.map((h, i) => cellXml(`${columnName(i)}${rowNumber}`, h, true)).join("")}</row>`);
96
+ }
97
+ for (const row of table.rows) {
98
+ rowNumber += 1;
99
+ const cells = table.headers.map((_, i) => cellXml(`${columnName(i)}${rowNumber}`, row[i])).join("");
100
+ rows.push(`<row r="${rowNumber}">${cells}</row>`);
101
+ }
102
+ return "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">" + (header && table.headers.length > 0 ? "<sheetViews><sheetView workbookViewId=\"0\"><pane ySplit=\"1\" topLeftCell=\"A2\" activePane=\"bottomLeft\" state=\"frozen\"/></sheetView></sheetViews>" : "") + `<sheetData>${rows.join("")}</sheetData></worksheet>`;
103
+ }
104
+ function toXlsx(table, options = {}) {
105
+ const { sheetName = "Sheet1", header = true } = options;
106
+ const files = {
107
+ "[Content_Types].xml": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\"><Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/><Default Extension=\"xml\" ContentType=\"application/xml\"/><Override PartName=\"/xl/workbook.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml\"/><Override PartName=\"/xl/worksheets/sheet1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml\"/><Override PartName=\"/xl/styles.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml\"/></Types>",
108
+ "_rels/.rels": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\"><Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\" Target=\"xl/workbook.xml\"/></Relationships>",
109
+ "xl/workbook.xml": `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets><sheet name="${xmlEscape(sheetNameFor(sheetName))}" sheetId="1" r:id="rId1"/></sheets></workbook>`,
110
+ "xl/_rels/workbook.xml.rels": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\"><Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet\" Target=\"worksheets/sheet1.xml\"/><Relationship Id=\"rId2\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles\" Target=\"styles.xml\"/></Relationships>",
111
+ "xl/styles.xml": STYLES_XML,
112
+ "xl/worksheets/sheet1.xml": sheetXml(table, header)
113
+ };
114
+ return zipStore(Object.entries(files).map(([name, text]) => ({
115
+ name,
116
+ data: encoder.encode(text)
117
+ })));
118
+ }
119
+ let crcTable;
120
+ function crc32(bytes) {
121
+ if (!crcTable) {
122
+ crcTable = /* @__PURE__ */ new Uint32Array(256);
123
+ for (let n = 0; n < 256; n++) {
124
+ let c = n;
125
+ for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
126
+ crcTable[n] = c >>> 0;
127
+ }
128
+ }
129
+ let crc = 4294967295;
130
+ for (let i = 0; i < bytes.length; i++) crc = crcTable[(crc ^ bytes[i]) & 255] ^ crc >>> 8;
131
+ return (crc ^ 4294967295) >>> 0;
132
+ }
133
+ const DOS_TIME = 0;
134
+ const DOS_DATE = 33;
135
+ function zipStore(entries) {
136
+ const parts = [];
137
+ const central = [];
138
+ let offset = 0;
139
+ for (const { name, data } of entries) {
140
+ const nameBytes = encoder.encode(name);
141
+ const crc = crc32(data);
142
+ const local = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(30));
143
+ local.setUint32(0, 67324752, true);
144
+ local.setUint16(4, 20, true);
145
+ local.setUint16(6, 2048, true);
146
+ local.setUint16(8, 0, true);
147
+ local.setUint16(10, DOS_TIME, true);
148
+ local.setUint16(12, DOS_DATE, true);
149
+ local.setUint32(14, crc, true);
150
+ local.setUint32(18, data.length, true);
151
+ local.setUint32(22, data.length, true);
152
+ local.setUint16(26, nameBytes.length, true);
153
+ local.setUint16(28, 0, true);
154
+ parts.push(new Uint8Array(local.buffer), nameBytes, data);
155
+ const entry = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(46));
156
+ entry.setUint32(0, 33639248, true);
157
+ entry.setUint16(4, 20, true);
158
+ entry.setUint16(6, 20, true);
159
+ entry.setUint16(8, 2048, true);
160
+ entry.setUint16(10, 0, true);
161
+ entry.setUint16(12, DOS_TIME, true);
162
+ entry.setUint16(14, DOS_DATE, true);
163
+ entry.setUint32(16, crc, true);
164
+ entry.setUint32(20, data.length, true);
165
+ entry.setUint32(24, data.length, true);
166
+ entry.setUint16(28, nameBytes.length, true);
167
+ entry.setUint32(42, offset, true);
168
+ central.push(new Uint8Array(entry.buffer), nameBytes);
169
+ offset += 30 + nameBytes.length + data.length;
170
+ }
171
+ const centralSize = central.reduce((n, p) => n + p.length, 0);
172
+ const end = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(22));
173
+ end.setUint32(0, 101010256, true);
174
+ end.setUint16(8, entries.length, true);
175
+ end.setUint16(10, entries.length, true);
176
+ end.setUint32(12, centralSize, true);
177
+ end.setUint32(16, offset, true);
178
+ const all = [
179
+ ...parts,
180
+ ...central,
181
+ new Uint8Array(end.buffer)
182
+ ];
183
+ const out = new Uint8Array(all.reduce((n, p) => n + p.length, 0));
184
+ let at = 0;
185
+ for (const p of all) {
186
+ out.set(p, at);
187
+ at += p.length;
188
+ }
189
+ return out;
190
+ }
191
+ function exportBlob(table, format, options = {}) {
192
+ if (format === "xlsx") return new Blob([toXlsx(table, options)], { type: MIME_TYPES.xlsx });
193
+ const text = toDelimited(table, {
194
+ delimiter: format === "tsv" ? " " : ",",
195
+ header: options.header,
196
+ bom: true
197
+ });
198
+ return new Blob([text], { type: MIME_TYPES[format] });
199
+ }
200
+ function exportFilename(base, format) {
201
+ const cleaned = base.replace(/[\\/:*?"<>|\u0000-\u001F]/g, "-").trim() || "export";
202
+ return cleaned.toLowerCase().endsWith(`.${format}`) ? cleaned : `${cleaned}.${format}`;
203
+ }
204
+ function downloadBlob(blob, filename) {
205
+ if (typeof document === "undefined" || typeof URL === "undefined" || !URL.createObjectURL) return;
206
+ const url = URL.createObjectURL(blob);
207
+ const link = document.createElement("a");
208
+ link.href = url;
209
+ link.download = filename;
210
+ link.style.display = "none";
211
+ document.body.appendChild(link);
212
+ link.click();
213
+ link.remove();
214
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
215
+ }
216
+ function toClipboardText(table) {
217
+ return toTsv(table, { bom: false });
218
+ }
219
+ async function copyToClipboard(text) {
220
+ try {
221
+ if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
222
+ await navigator.clipboard.writeText(text);
223
+ return true;
224
+ }
225
+ } catch {}
226
+ try {
227
+ if (typeof document === "undefined") return false;
228
+ const area = document.createElement("textarea");
229
+ area.value = text;
230
+ area.setAttribute("readonly", "");
231
+ area.setAttribute("aria-hidden", "true");
232
+ area.style.position = "fixed";
233
+ area.style.opacity = "0";
234
+ document.body.appendChild(area);
235
+ area.select();
236
+ const ok = typeof document.execCommand === "function" && document.execCommand("copy");
237
+ area.remove();
238
+ return !!ok;
239
+ } catch {
240
+ return false;
241
+ }
242
+ }
243
+ function foldText(text) {
244
+ return text.normalize("NFD").replace(/\p{M}+/gu, "").toLowerCase();
245
+ }
246
+ function searchTerms(query) {
247
+ return foldText(query).split(/\s+/).filter(Boolean);
248
+ }
249
+ function matchesSearch(text, query) {
250
+ const terms = searchTerms(query);
251
+ if (terms.length === 0) return true;
252
+ const folded = foldText(text);
253
+ return terms.every((term) => folded.includes(term));
254
+ }
255
+ function highlightSegments(text, query) {
256
+ const terms = searchTerms(query);
257
+ if (terms.length === 0 || text === "") return [{
258
+ text,
259
+ match: false
260
+ }];
261
+ const chars = Array.from(text);
262
+ const folded = chars.map((c) => foldText(c));
263
+ if (folded.some((f) => f.length !== 1)) return [{
264
+ text,
265
+ match: false
266
+ }];
267
+ const flat = folded.join("");
268
+ const marked = new Array(chars.length).fill(false);
269
+ for (const term of terms) for (let at = flat.indexOf(term); at !== -1; at = flat.indexOf(term, at + 1)) for (let i = at; i < at + term.length; i++) marked[i] = true;
270
+ const out = [];
271
+ chars.forEach((c, i) => {
272
+ const last = out[out.length - 1];
273
+ if (last && last.match === marked[i]) last.text += c;
274
+ else out.push({
275
+ text: c,
276
+ match: marked[i]
277
+ });
278
+ });
279
+ return out;
280
+ }
281
+ function isRtl(element) {
282
+ if (!element) return false;
283
+ const attr = element.closest("[dir]")?.getAttribute("dir")?.toLowerCase();
284
+ if (attr === "rtl") return true;
285
+ if (attr === "ltr") return false;
286
+ if (typeof getComputedStyle === "function") return getComputedStyle(element).direction === "rtl";
287
+ return false;
288
+ }
289
+ function logicalKey(key, rtl) {
290
+ if (!rtl) return key;
291
+ if (key === "ArrowLeft") return "ArrowRight";
292
+ if (key === "ArrowRight") return "ArrowLeft";
293
+ return key;
294
+ }
295
+ const clamp = (value, low, high) => Math.min(Math.max(value, low), high);
296
+ function gridMove(event, at, nav) {
297
+ const key = logicalKey(event.key, nav.rtl ?? false);
298
+ const corner = !!(event.ctrlKey || event.metaKey);
299
+ let row = at.row;
300
+ let col = at.col;
301
+ switch (key) {
302
+ case "ArrowRight":
303
+ col += 1;
304
+ break;
305
+ case "ArrowLeft":
306
+ col -= 1;
307
+ break;
308
+ case "ArrowDown":
309
+ row += 1;
310
+ break;
311
+ case "ArrowUp":
312
+ row -= 1;
313
+ break;
314
+ case "PageDown":
315
+ row += nav.pageStep;
316
+ break;
317
+ case "PageUp":
318
+ row -= nav.pageStep;
319
+ break;
320
+ case "Home":
321
+ if (corner) [row, col] = [nav.firstRow, 0];
322
+ else if (nav.homeEnd === "column") row = nav.firstRow;
323
+ else col = 0;
324
+ break;
325
+ case "End":
326
+ if (corner) [row, col] = [nav.lastRow, nav.lastCol];
327
+ else if (nav.homeEnd === "column") row = nav.lastRow;
328
+ else col = nav.lastCol;
329
+ break;
330
+ default: return null;
331
+ }
332
+ return {
333
+ row: clamp(row, nav.firstRow, nav.lastRow),
334
+ col: clamp(col, 0, nav.lastCol)
335
+ };
336
+ }
337
+ const CONTROL_LABELS = {
338
+ selection: [
339
+ "selectAll",
340
+ "selectionColumn",
341
+ "selectRow"
342
+ ],
343
+ pagination: [
344
+ "previousPage",
345
+ "nextPage",
346
+ "pageStatus"
347
+ ],
348
+ hidableColumns: ["columnVisibility", "columnVisibilityOption"],
349
+ resizableColumns: ["resizeHandle"],
350
+ editing: ["editCell", "cellEditor"],
351
+ search: ["search"],
352
+ exporting: ["export"]
353
+ };
354
+ const ANNOUNCEMENT_LABELS = {
355
+ sorting: ["sortAnnouncement"],
356
+ search: ["filterAnnouncement"],
357
+ selection: ["selectionAnnouncement"],
358
+ pagination: ["pageAnnouncement"],
359
+ editing: [
360
+ "editAnnouncement",
361
+ "editCancelAnnouncement",
362
+ "editError",
363
+ "undoAnnouncement",
364
+ "redoAnnouncement"
365
+ ],
366
+ exporting: [
367
+ "exportAnnouncement",
368
+ "copyAnnouncement",
369
+ "copyFailed"
370
+ ]
371
+ };
372
+ function requiredLabels(features) {
373
+ const out = [];
374
+ const add = (table, kind) => {
375
+ for (const feature of Object.keys(table)) {
376
+ if (!features[feature]) continue;
377
+ for (const key of table[feature]) if (!out.some((o) => o.key === key)) out.push({
378
+ key,
379
+ kind
380
+ });
381
+ }
382
+ };
383
+ add(CONTROL_LABELS, "control");
384
+ add(ANNOUNCEMENT_LABELS, "announcement");
385
+ return out;
386
+ }
387
+ function missingLabels(features, labels) {
388
+ return requiredLabels(features).filter(({ key }) => {
389
+ const value = labels?.[key];
390
+ return value === void 0 || value === null || value === "";
391
+ });
392
+ }
393
+ const EMPTY_HISTORY = {
394
+ undo: [],
395
+ redo: []
396
+ };
397
+ const HISTORY_LIMIT = 100;
398
+ function recordEdit(history, record, limit = 100) {
399
+ const undo = [...history.undo, record];
400
+ return {
401
+ undo: undo.length > limit ? undo.slice(undo.length - limit) : undo,
402
+ redo: []
403
+ };
404
+ }
405
+ function undoEdit(history) {
406
+ const record = history.undo[history.undo.length - 1];
407
+ if (!record) return {
408
+ history,
409
+ record: void 0
410
+ };
411
+ return {
412
+ history: {
413
+ undo: history.undo.slice(0, -1),
414
+ redo: [...history.redo, record]
415
+ },
416
+ record
417
+ };
418
+ }
419
+ function redoEdit(history) {
420
+ const record = history.redo[history.redo.length - 1];
421
+ if (!record) return {
422
+ history,
423
+ record: void 0
424
+ };
425
+ return {
426
+ history: {
427
+ undo: [...history.undo, record],
428
+ redo: history.redo.slice(0, -1)
429
+ },
430
+ record
431
+ };
432
+ }
433
+ function historyKey(event) {
434
+ if (event.altKey || !(event.ctrlKey || event.metaKey)) return null;
435
+ const key = event.key.toLowerCase();
436
+ if (key === "z") return event.shiftKey ? "redo" : "undo";
437
+ if (key === "y" && !event.shiftKey) return "redo";
438
+ return null;
439
+ }
440
+ const TYPEAHEAD_RESET_MS = 700;
441
+ function isTypeaheadKey(event) {
442
+ return event.key.length === 1 && event.key !== " " && !event.ctrlKey && !event.altKey && !event.metaKey;
443
+ }
444
+ function nextTypeaheadState(previous, key, now, resetMs = 700) {
445
+ return {
446
+ prefix: (previous.prefix !== "" && now - previous.at <= resetMs ? previous.prefix : "") + key,
447
+ at: now
448
+ };
449
+ }
450
+ function findTypeaheadMatch(labels, from, prefix) {
451
+ const folded = foldText(prefix);
452
+ if (folded === "" || labels.length === 0) return -1;
453
+ const needle = folded.length > 1 && [...folded].every((c) => c === folded[0]) ? folded[0] : folded;
454
+ const startOffset = needle.length > 1 ? 0 : 1;
455
+ for (let step = 0; step < labels.length; step++) {
456
+ const index = (from + startOffset + step + labels.length) % labels.length;
457
+ if (foldText(labels[index]).startsWith(needle)) return index;
458
+ }
459
+ return -1;
460
+ }
461
+ const SAFE_SCHEME = /^(https?:|mailto:|tel:)/i;
462
+ const RELATIVE = /^(\/(?!\/)|\.\.?\/|#|\?)/;
463
+ function safeHref(href) {
464
+ if (typeof href !== "string") return null;
465
+ const text = href.trim();
466
+ if (!text) return null;
467
+ if (/[\u0000-\u001F\u007F]/.test(text)) return null;
468
+ return SAFE_SCHEME.test(text) || RELATIVE.test(text) ? text : null;
469
+ }
470
+ function linkParts(value) {
471
+ if (value === null || value === void 0 || value === "") return null;
472
+ if (typeof value === "string") return { href: value };
473
+ if (typeof value === "object" && typeof value.href === "string") return value;
474
+ return null;
475
+ }
476
+ function linkLabel(value) {
477
+ const link = linkParts(value);
478
+ return link ? link.label ?? link.href : "";
479
+ }
480
+ function isCivilDate(text) {
481
+ if (typeof text !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(text)) return false;
482
+ const [y, m, d] = text.split("-").map(Number);
483
+ const date = new Date(Date.UTC(y, m - 1, d));
484
+ return date.getUTCFullYear() === y && date.getUTCMonth() === m - 1 && date.getUTCDate() === d;
485
+ }
486
+ function isCivilTime(text) {
487
+ if (typeof text !== "string") return false;
488
+ const match = /^(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(text);
489
+ return !!match && Number(match[1]) < 24 && Number(match[2]) < 60 && (match[3] === void 0 || Number(match[3]) < 60);
490
+ }
491
+ function civilDateToDate(text) {
492
+ if (text instanceof Date) return Number.isNaN(text.getTime()) ? null : text;
493
+ return isCivilDate(text) ? /* @__PURE__ */ new Date(`${text}T00:00:00Z`) : null;
494
+ }
495
+ const isEmpty = (value) => value === null || value === void 0 || value === "";
496
+ const OFFSET = /^([+-])(\d{2}):?(\d{2})$/;
497
+ function zoneOffsetMinutes(timeZone, instantMs) {
498
+ const bare = OFFSET.exec(timeZone);
499
+ if (bare) return (bare[1] === "-" ? -1 : 1) * (Number(bare[2]) * 60 + Number(bare[3]));
500
+ try {
501
+ const parts = new Intl.DateTimeFormat("en-US", {
502
+ timeZone,
503
+ hourCycle: "h23",
504
+ year: "numeric",
505
+ month: "2-digit",
506
+ day: "2-digit",
507
+ hour: "2-digit",
508
+ minute: "2-digit",
509
+ second: "2-digit"
510
+ }).formatToParts(new Date(instantMs));
511
+ const n = (type) => Number(parts.find((x) => x.type === type)?.value);
512
+ return (Date.UTC(n("year"), n("month") - 1, n("day"), n("hour") % 24, n("minute"), n("second")) - Math.floor(instantMs / 1e3) * 1e3) / 6e4;
513
+ } catch {
514
+ return null;
515
+ }
516
+ }
517
+ function isValidTimeZone(timeZone) {
518
+ return typeof timeZone === "string" && timeZone !== "" && zoneOffsetMinutes(timeZone, 0) !== null;
519
+ }
520
+ function isZonedTime(value) {
521
+ return !!value && typeof value === "object" && isCivilTime(value.time) && isValidTimeZone(value.timeZone);
522
+ }
523
+ function zonedMinutes(value, referenceDate) {
524
+ if (!isZonedTime(value)) return null;
525
+ const [h, m, sec] = value.time.split(":").map(Number);
526
+ const local = h * 60 + m + (sec || 0) / 60;
527
+ if (!referenceDate || !isCivilDate(referenceDate)) return local;
528
+ const [y, mo, d] = referenceDate.split("-").map(Number);
529
+ const guess = Date.UTC(y, mo - 1, d, h, m, sec || 0);
530
+ const offset = zoneOffsetMinutes(value.timeZone, guess);
531
+ if (offset === null) return local;
532
+ return guess / 6e4 - offset;
533
+ }
534
+ function zonedUtcTime(value, referenceDate) {
535
+ if (!referenceDate || !isCivilDate(referenceDate)) return null;
536
+ const minutes = zonedMinutes(value, referenceDate);
537
+ if (minutes === null) return null;
538
+ const day = (Math.round(minutes) % 1440 + 1440) % 1440;
539
+ return `${String(Math.floor(day / 60)).padStart(2, "0")}:${String(day % 60).padStart(2, "0")}`;
540
+ }
541
+ function zoneLabel(timeZone, locale, referenceDate) {
542
+ if (!locale || !referenceDate || !isCivilDate(referenceDate) || OFFSET.test(timeZone)) return timeZone;
543
+ try {
544
+ return new Intl.DateTimeFormat(locale, {
545
+ timeZone,
546
+ timeZoneName: "short"
547
+ }).formatToParts(/* @__PURE__ */ new Date(`${referenceDate}T12:00:00Z`)).find((x) => x.type === "timeZoneName")?.value ?? timeZone;
548
+ } catch {
549
+ return timeZone;
550
+ }
551
+ }
552
+ function peopleOf(value) {
553
+ return (Array.isArray(value) ? value : isEmpty(value) ? [] : [value]).filter((p) => !!p && typeof p === "object" && typeof p.id === "string");
554
+ }
555
+ const personName = (person) => person.name ? person.name : person.id;
556
+ function filesOf(value) {
557
+ return (Array.isArray(value) ? value : []).filter((f) => !!f && typeof f === "object" && typeof f.id === "string" && typeof f.name === "string");
558
+ }
559
+ function aggregateValues(kind, values) {
560
+ const present = values.filter((v) => !isEmpty(v));
561
+ const numbers = present.filter((v) => typeof v === "number" && Number.isFinite(v));
562
+ switch (kind) {
563
+ case "count": return values.length;
564
+ case "countUnique": return present.length === 0 ? null : new Set(present.map((v) => typeof v === "object" ? JSON.stringify(v) : String(v))).size;
565
+ case "sum": return numbers.length === 0 ? null : numbers.reduce((a, b) => a + b, 0);
566
+ case "avg": return numbers.length === 0 ? null : numbers.reduce((a, b) => a + b, 0) / numbers.length;
567
+ case "median": {
568
+ if (numbers.length === 0) return null;
569
+ const sorted = numbers.slice().sort((a, b) => a - b);
570
+ const mid = sorted.length >> 1;
571
+ return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
572
+ }
573
+ case "min":
574
+ case "max": {
575
+ const pool = numbers.length === present.length ? numbers : present.filter((v) => typeof v === "string");
576
+ if (pool.length === 0) return null;
577
+ return pool.reduce((a, b) => (kind === "min" ? b < a : b > a) ? b : a);
578
+ }
579
+ case "range": return numbers.length === 0 ? null : Math.max(...numbers) - Math.min(...numbers);
580
+ case "percentTrue": {
581
+ const booleans = values.filter((v) => typeof v === "boolean");
582
+ return booleans.length === 0 ? null : booleans.filter(Boolean).length / booleans.length * 100;
583
+ }
584
+ case "percentEmpty": return values.length === 0 ? null : (values.length - present.length) / values.length * 100;
585
+ }
586
+ }
587
+ function isInvalidForType(column, value) {
588
+ if (isEmpty(value)) return false;
589
+ switch (column.type) {
590
+ case "number": return typeof value !== "number" || !Number.isFinite(value);
591
+ case "link": return safeHref(linkParts(value)?.href) === null;
592
+ case "date": return !isCivilDate(value);
593
+ case "time": return !isCivilTime(value);
594
+ case "time-zone": return !isZonedTime(value);
595
+ case "select":
596
+ case "radio": return !(column.options ?? []).some((o) => o.id === value);
597
+ case "person": return peopleOf(value).length === 0;
598
+ default: return false;
599
+ }
600
+ }
601
+ const optionLabel = (column, id) => {
602
+ const found = (column.options ?? []).find((o) => o.id === id);
603
+ return found ? found.label : isEmpty(id) ? "" : String(id);
604
+ };
605
+ function resultFormatter(column, locale) {
606
+ const type = column.resultType ?? "text";
607
+ if (type === "number") {
608
+ const formatter = locale || column.numberFormat ? new Intl.NumberFormat(locale, column.numberFormat) : null;
609
+ return (v) => typeof v === "number" && Number.isFinite(v) ? formatter ? formatter.format(v) : String(v) : isEmpty(v) ? "" : String(v);
610
+ }
611
+ if (type === "date") {
612
+ const formatter = locale || column.dateFormat ? new Intl.DateTimeFormat(locale, {
613
+ dateStyle: "medium",
614
+ ...column.dateFormat,
615
+ timeZone: "UTC"
616
+ }) : null;
617
+ return (v) => {
618
+ if (isEmpty(v)) return "";
619
+ const date = civilDateToDate(v);
620
+ return date ? formatter ? formatter.format(date) : date.toISOString().slice(0, 10) : String(v);
621
+ };
622
+ }
623
+ return (v) => isEmpty(v) ? "" : String(v);
624
+ }
625
+ function typeDefaults(column, gridLocale) {
626
+ const locale = column.locale ?? gridLocale;
627
+ switch (column.type) {
628
+ case "text": return {
629
+ align: "start",
630
+ editor: "text",
631
+ ...locale ? { compare: (a, b) => new Intl.Collator(locale).compare(String(a), String(b)) } : {}
632
+ };
633
+ case "number": {
634
+ const formatter = locale || column.numberFormat ? new Intl.NumberFormat(locale, column.numberFormat) : null;
635
+ return {
636
+ align: "end",
637
+ editor: "number",
638
+ ...formatter ? { format: (v) => typeof v === "number" && Number.isFinite(v) ? formatter.format(v) : "" } : {}
639
+ };
640
+ }
641
+ case "link": return {
642
+ align: "start",
643
+ editor: "link",
644
+ format: (v) => linkLabel(v),
645
+ filterText: (v) => {
646
+ const link = linkParts(v);
647
+ return link ? `${link.label ?? ""} ${link.href}`.trim() : "";
648
+ },
649
+ compare: (a, b) => {
650
+ const x = linkLabel(a);
651
+ const y = linkLabel(b);
652
+ return locale ? new Intl.Collator(locale).compare(x, y) : x < y ? -1 : x > y ? 1 : 0;
653
+ },
654
+ exportValue: (row) => linkLabel(column.accessor ? column.accessor(row) : row[column.id]),
655
+ parse: (draft, row) => {
656
+ const previous = linkParts(column.accessor ? column.accessor(row) : row[column.id]);
657
+ const href = draft.trim();
658
+ if (!href) return null;
659
+ return previous && typeof previous === "object" && (previous.label !== void 0 || previous.external !== void 0) ? {
660
+ ...previous,
661
+ href
662
+ } : href;
663
+ }
664
+ };
665
+ case "date": {
666
+ const formatter = locale || column.dateFormat ? new Intl.DateTimeFormat(locale, {
667
+ dateStyle: "medium",
668
+ ...column.dateFormat,
669
+ timeZone: "UTC"
670
+ }) : null;
671
+ return {
672
+ align: "start",
673
+ editor: "date",
674
+ format: (v) => {
675
+ if (isEmpty(v)) return "";
676
+ const date = civilDateToDate(v);
677
+ if (!date) return String(v);
678
+ return formatter ? formatter.format(date) : date.toISOString().slice(0, 10);
679
+ },
680
+ exportValue: (row) => civilDateToDate(column.accessor ? column.accessor(row) : row[column.id])
681
+ };
682
+ }
683
+ case "time": {
684
+ const formatter = locale || column.timeFormat ? new Intl.DateTimeFormat(locale, {
685
+ timeStyle: "short",
686
+ ...column.timeFormat,
687
+ timeZone: "UTC"
688
+ }) : null;
689
+ return {
690
+ align: "start",
691
+ editor: "time",
692
+ format: (v) => {
693
+ if (isEmpty(v)) return "";
694
+ if (!isCivilTime(v)) return String(v);
695
+ return formatter ? formatter.format(/* @__PURE__ */ new Date(`1970-01-01T${v.length === 5 ? `${v}:00` : v}Z`)) : v;
696
+ }
697
+ };
698
+ }
699
+ case "time-zone": {
700
+ const formatter = locale || column.timeFormat ? new Intl.DateTimeFormat(locale, {
701
+ timeStyle: "short",
702
+ ...column.timeFormat,
703
+ timeZone: "UTC"
704
+ }) : null;
705
+ const text = (v) => {
706
+ if (isEmpty(v)) return "";
707
+ if (!isZonedTime(v)) return String(v);
708
+ return `${formatter ? formatter.format(/* @__PURE__ */ new Date(`1970-01-01T${v.time.length === 5 ? `${v.time}:00` : v.time}Z`)) : v.time} ${zoneLabel(v.timeZone, locale, column.referenceDate)}`;
709
+ };
710
+ return {
711
+ align: "start",
712
+ editor: "time-zone",
713
+ format: text,
714
+ compare: (a, b) => (zonedMinutes(a, column.referenceDate) ?? 0) - (zonedMinutes(b, column.referenceDate) ?? 0),
715
+ exportValue: (row) => {
716
+ const v = column.accessor ? column.accessor(row) : row[column.id];
717
+ return isZonedTime(v) ? `${v.time} ${v.timeZone}` : null;
718
+ }
719
+ };
720
+ }
721
+ case "person": {
722
+ const names = (v) => peopleOf(v).map(personName);
723
+ const collator = locale ? new Intl.Collator(locale) : null;
724
+ const order = (a, b) => collator ? collator.compare(a, b) : a < b ? -1 : a > b ? 1 : 0;
725
+ return {
726
+ align: "start",
727
+ editor: "person",
728
+ format: (v) => names(v).join("; "),
729
+ filterText: (v) => peopleOf(v).map((p) => `${personName(p)} ${p.email ?? ""}`.trim()).join(" "),
730
+ compare: (a, b) => {
731
+ const x = names(a);
732
+ const y = names(b);
733
+ return order(x[0] ?? "", y[0] ?? "") || x.length - y.length;
734
+ },
735
+ exportValue: (row) => {
736
+ const value = column.accessor ? column.accessor(row) : row[column.id];
737
+ const joined = (column.exportAs === "email" ? peopleOf(value).map((p) => p.email ?? "") : names(value)).join("; ");
738
+ return joined.replace(/[;\s]/g, "") === "" ? null : joined;
739
+ }
740
+ };
741
+ }
742
+ case "file": {
743
+ const names = (v) => filesOf(v).map((f) => f.name);
744
+ const collator = locale ? new Intl.Collator(locale) : null;
745
+ const order = (a, b) => collator ? collator.compare(a, b) : a < b ? -1 : a > b ? 1 : 0;
746
+ return {
747
+ align: "start",
748
+ format: (v) => names(v).join("; "),
749
+ filterText: (v) => names(v).join(" "),
750
+ compare: (a, b) => {
751
+ const x = names(a);
752
+ const y = names(b);
753
+ return order(x[0] ?? "", y[0] ?? "") || x.length - y.length;
754
+ },
755
+ exportValue: (row) => {
756
+ const joined = names(column.accessor ? column.accessor(row) : row[column.id]).join("; ");
757
+ return joined === "" ? null : joined;
758
+ }
759
+ };
760
+ }
761
+ case "select":
762
+ case "radio": {
763
+ const order = new Map((column.options ?? []).map((o, i) => [o.id, i]));
764
+ return {
765
+ align: "start",
766
+ editor: column.type === "select" ? "select" : "radio",
767
+ format: (v) => optionLabel(column, v),
768
+ filterText: (v) => optionLabel(column, v),
769
+ compare: column.type === "radio" || column.sortBy === "order" ? (a, b) => (order.get(a) ?? Infinity) - (order.get(b) ?? Infinity) : (a, b) => locale ? new Intl.Collator(locale).compare(optionLabel(column, a), optionLabel(column, b)) : optionLabel(column, a) < optionLabel(column, b) ? -1 : optionLabel(column, a) > optionLabel(column, b) ? 1 : 0,
770
+ exportValue: (row) => {
771
+ const id = column.accessor ? column.accessor(row) : row[column.id];
772
+ return column.exportAs === "id" ? isEmpty(id) ? null : id : isEmpty(id) ? null : optionLabel(column, id);
773
+ }
774
+ };
775
+ }
776
+ default: return {};
777
+ }
778
+ }
779
+ function resolveColumn(column, gridLocale, rows = []) {
780
+ if (!column.type) return column;
781
+ const defaults = typeDefaults(column, gridLocale);
782
+ const own = Object.fromEntries(Object.entries(column).filter(([, v]) => v !== void 0));
783
+ const resolved = {
784
+ ...defaults,
785
+ ...own
786
+ };
787
+ const locale = column.locale ?? gridLocale;
788
+ if (column.type === "rollup") {
789
+ const relation = column.relation ?? (column.accessor ? (row) => column.accessor(row) : (row) => row[column.id]);
790
+ const cache = /* @__PURE__ */ new WeakMap();
791
+ const format = resultFormatter(column, locale);
792
+ resolved.accessor = (row) => {
793
+ if (cache.has(row)) return cache.get(row);
794
+ const related = relation(row);
795
+ const values = Array.isArray(related) ? related : [];
796
+ const result = column.reduce ? column.reduce(values, row) : aggregateValues(column.aggregate ?? "count", values);
797
+ cache.set(row, result);
798
+ return result;
799
+ };
800
+ resolved.editable = false;
801
+ resolved.align = column.align ?? (column.resultType === "number" ? "end" : "start");
802
+ if (column.format === void 0) resolved.format = (v) => format(v);
803
+ if (column.exportValue === void 0) resolved.exportValue = (row) => {
804
+ const v = resolved.accessor(row);
805
+ return column.resultType === "date" ? civilDateToDate(v) : isEmpty(v) ? null : v;
806
+ };
807
+ } else if (column.type === "formula") {
808
+ const source = column.accessor ?? ((row) => row[column.id]);
809
+ const cache = /* @__PURE__ */ new WeakMap();
810
+ const format = resultFormatter(column, locale);
811
+ const result = (row) => {
812
+ let found = cache.get(row);
813
+ if (!found) {
814
+ const expression = source(row);
815
+ if (isEmpty(expression)) found = { value: null };
816
+ else if (typeof column.evaluate !== "function") found = { value: expression };
817
+ else try {
818
+ found = column.evaluate(String(expression), {
819
+ row,
820
+ rows,
821
+ columnId: column.id
822
+ });
823
+ } catch (error) {
824
+ found = { error: error instanceof Error ? error.message : String(error) };
825
+ }
826
+ cache.set(row, found);
827
+ }
828
+ return found;
829
+ };
830
+ const valueOf = (row) => {
831
+ const r = result(row);
832
+ return "error" in r ? null : r.value;
833
+ };
834
+ resolved.source = source;
835
+ resolved.errorOf = (row) => {
836
+ const r = result(row);
837
+ return "error" in r ? r.error : void 0;
838
+ };
839
+ resolved.accessor = valueOf;
840
+ resolved.align = column.align ?? (column.resultType === "number" ? "end" : "start");
841
+ if (column.format === void 0) resolved.format = (v, row) => {
842
+ const message = resolved.errorOf(row);
843
+ return message !== void 0 ? message : format(v);
844
+ };
845
+ if (column.exportValue === void 0) resolved.exportValue = (row) => {
846
+ const message = resolved.errorOf(row);
847
+ if (message !== void 0) return message;
848
+ const v = valueOf(row);
849
+ return column.resultType === "date" ? civilDateToDate(v) : isEmpty(v) ? null : v;
850
+ };
851
+ if (column.filterText === void 0) resolved.filterText = (v, row) => resolved.format(v, row);
852
+ }
853
+ return resolved;
854
+ }
855
+ const isMissing = (value) => value === null || value === void 0 || value === "";
856
+ const WIDGET_SELECTOR = "a[href], button, input, select, textarea, summary, [contenteditable=\"true\"]";
857
+ const MIN_COLUMN_WIDTH = 40;
858
+ const RESIZE_STEP = 16;
859
+ let uid = 0;
860
+ function nextDataGridId() {
861
+ uid += 1;
862
+ return `data-grid-${uid}`;
863
+ }
864
+ function defaultAccessor(column, row) {
865
+ return column.accessor ? column.accessor(row) : row[column.id];
866
+ }
867
+ function formatCell(column, row) {
868
+ const value = defaultAccessor(column, row);
869
+ if (column.format) return column.format(value, row);
870
+ return value === null || value === void 0 ? "" : String(value);
871
+ }
872
+ var DataGridAttrs = class DataGridAttrs {
873
+ constructor() {
874
+ this.lilyDataGridAttrs = input(void 0, ...ngDevMode ? [{ debugName: "lilyDataGridAttrs" }] : /* istanbul ignore next */ []);
875
+ this.host = inject(ElementRef);
876
+ this.applied = [];
877
+ effect(() => {
878
+ const next = this.lilyDataGridAttrs() ?? {};
879
+ for (const name of this.applied) if (!(name in next)) this.host.nativeElement.removeAttribute(name);
880
+ for (const [name, value] of Object.entries(next)) this.host.nativeElement.setAttribute(name, value);
881
+ this.applied = Object.keys(next);
882
+ });
883
+ }
884
+ static {
885
+ this.ɵfac = i0.ɵɵngDeclareFactory({
886
+ minVersion: "12.0.0",
887
+ version: "22.2.3",
888
+ ngImport: i0,
889
+ type: DataGridAttrs,
890
+ deps: [],
891
+ target: i0.ɵɵFactoryTarget.Directive
892
+ });
893
+ }
894
+ static {
895
+ this.ɵdir = i0.ɵɵngDeclareDirective({
896
+ minVersion: "17.1.0",
897
+ version: "22.2.3",
898
+ type: DataGridAttrs,
899
+ isStandalone: true,
900
+ selector: "[lilyDataGridAttrs]",
901
+ inputs: { lilyDataGridAttrs: {
902
+ classPropertyName: "lilyDataGridAttrs",
903
+ publicName: "lilyDataGridAttrs",
904
+ isSignal: true,
905
+ isRequired: false,
906
+ transformFunction: null
907
+ } },
908
+ ngImport: i0
909
+ });
910
+ }
911
+ };
912
+ i0.ɵɵngDeclareClassMetadata({
913
+ minVersion: "12.0.0",
914
+ version: "22.2.3",
915
+ ngImport: i0,
916
+ type: DataGridAttrs,
917
+ decorators: [{
918
+ type: Directive,
919
+ args: [{
920
+ selector: "[lilyDataGridAttrs]",
921
+ standalone: true
922
+ }]
923
+ }],
924
+ ctorParameters: () => [],
925
+ propDecorators: { lilyDataGridAttrs: [{
926
+ type: i0.Input,
927
+ args: [{
928
+ isSignal: true,
929
+ alias: "lilyDataGridAttrs",
930
+ required: false
931
+ }]
932
+ }] }
933
+ });
934
+ var DataGrid = class DataGrid {
935
+ cellFor(column, row) {
936
+ const cache = this.cellCache();
937
+ let perRow = cache.get(row);
938
+ if (!perRow) {
939
+ perRow = /* @__PURE__ */ new Map();
940
+ cache.set(row, perRow);
941
+ }
942
+ let ctx = perRow.get(column.id);
943
+ if (!ctx) {
944
+ ctx = {
945
+ value: defaultAccessor(column, row),
946
+ formatted: formatCell(column, row),
947
+ row,
948
+ column
949
+ };
950
+ perRow.set(column.id, ctx);
951
+ }
952
+ return ctx;
953
+ }
954
+ isMovable(column) {
955
+ return column.movable ?? this.movableColumns();
956
+ }
957
+ constructor() {
958
+ this.label = input.required(...ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []);
959
+ this.caption = input(void 0, ...ngDevMode ? [{ debugName: "caption" }] : /* istanbul ignore next */ []);
960
+ this.columns = input.required(...ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []);
961
+ this.rows = input.required(...ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []);
962
+ this.locale = input(void 0, ...ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []);
963
+ this.emptyValue = input(void 0, ...ngDevMode ? [{ debugName: "emptyValue" }] : /* istanbul ignore next */ []);
964
+ this.warnMissingLabels = input(true, ...ngDevMode ? [{ debugName: "warnMissingLabels" }] : /* istanbul ignore next */ []);
965
+ this.nulls = input("last", ...ngDevMode ? [{ debugName: "nulls" }] : /* istanbul ignore next */ []);
966
+ this.movableColumns = input(false, ...ngDevMode ? [{ debugName: "movableColumns" }] : /* istanbul ignore next */ []);
967
+ this.rowFilter = input(void 0, ...ngDevMode ? [{ debugName: "rowFilter" }] : /* istanbul ignore next */ []);
968
+ this.rowId = input((_row, index) => String(index), ...ngDevMode ? [{ debugName: "rowId" }] : /* istanbul ignore next */ []);
969
+ this.selectionMode = input("none", ...ngDevMode ? [{ debugName: "selectionMode" }] : /* istanbul ignore next */ []);
970
+ this.pageSize = input(void 0, ...ngDevMode ? [{ debugName: "pageSize" }] : /* istanbul ignore next */ []);
971
+ this.storageKey = input(void 0, ...ngDevMode ? [{ debugName: "storageKey" }] : /* istanbul ignore next */ []);
972
+ this.labels = input({}, ...ngDevMode ? [{ debugName: "labels" }] : /* istanbul ignore next */ []);
973
+ this.mode = input("grid", ...ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []);
974
+ this.rowClass = input(void 0, ...ngDevMode ? [{ debugName: "rowClass" }] : /* istanbul ignore next */ []);
975
+ this.rowAttrs = input(void 0, ...ngDevMode ? [{ debugName: "rowAttrs" }] : /* istanbul ignore next */ []);
976
+ this.activateRows = input(false, ...ngDevMode ? [{ debugName: "activateRows" }] : /* istanbul ignore next */ []);
977
+ this.empty = input(void 0, ...ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []);
978
+ this.footer = input(void 0, ...ngDevMode ? [{ debugName: "footer" }] : /* istanbul ignore next */ []);
979
+ this.busy = input(false, ...ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []);
980
+ this.scroll = input(true, ...ngDevMode ? [{ debugName: "scroll" }] : /* istanbul ignore next */ []);
981
+ this.exportFilename = input("data-grid", ...ngDevMode ? [{ debugName: "exportFilename" }] : /* istanbul ignore next */ []);
982
+ this.exportScope = input("view", ...ngDevMode ? [{ debugName: "exportScope" }] : /* istanbul ignore next */ []);
983
+ this.exportFormulas = input(false, ...ngDevMode ? [{ debugName: "exportFormulas" }] : /* istanbul ignore next */ []);
984
+ this.exportHandler = input(void 0, ...ngDevMode ? [{ debugName: "exportHandler" }] : /* istanbul ignore next */ []);
985
+ this.copyHandler = input(void 0, ...ngDevMode ? [{ debugName: "copyHandler" }] : /* istanbul ignore next */ []);
986
+ this.virtualize = input(void 0, ...ngDevMode ? [{ debugName: "virtualize" }] : /* istanbul ignore next */ []);
987
+ this.className = input("", ...ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []);
988
+ this.selected = model([], ...ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []);
989
+ this.filter = model("", ...ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []);
990
+ this.filterField = model("", ...ngDevMode ? [{ debugName: "filterField" }] : /* istanbul ignore next */ []);
991
+ this.thenBy = model([], ...ngDevMode ? [{ debugName: "thenBy" }] : /* istanbul ignore next */ []);
992
+ this.sort = model({
993
+ columnId: "",
994
+ direction: "none"
995
+ }, ...ngDevMode ? [{ debugName: "sort" }] : /* istanbul ignore next */ []);
996
+ this.columnOrder = model([], ...ngDevMode ? [{ debugName: "columnOrder" }] : /* istanbul ignore next */ []);
997
+ this.page = model(1, ...ngDevMode ? [{ debugName: "page" }] : /* istanbul ignore next */ []);
998
+ this.cellEdit = output();
999
+ this.filesSelected = output();
1000
+ this.fileRemove = output();
1001
+ this.rowActivate = output();
1002
+ this.exportFile = output();
1003
+ this.copy = output();
1004
+ this.gridId = nextDataGridId();
1005
+ this.minColumnWidth = MIN_COLUMN_WIDTH;
1006
+ this.rootRef = viewChild.required("rootEl", ...ngDevMode ? [{ debugName: "rootRef" }] : /* istanbul ignore next */ []);
1007
+ this.scrollRef = viewChild("scrollEl", ...ngDevMode ? [{ debugName: "scrollRef" }] : /* istanbul ignore next */ []);
1008
+ this.injector = inject(Injector);
1009
+ this.statusMessage = signal("", ...ngDevMode ? [{ debugName: "statusMessage" }] : /* istanbul ignore next */ []);
1010
+ this.hiddenColumnIds = signal(/* @__PURE__ */ new Set(), ...ngDevMode ? [{ debugName: "hiddenColumnIds" }] : /* istanbul ignore next */ []);
1011
+ this.columnWidths = signal({}, ...ngDevMode ? [{ debugName: "columnWidths" }] : /* istanbul ignore next */ []);
1012
+ this.focusedRow = signal(-1, ...ngDevMode ? [{ debugName: "focusedRow" }] : /* istanbul ignore next */ []);
1013
+ this.focusedCol = signal(0, ...ngDevMode ? [{ debugName: "focusedCol" }] : /* istanbul ignore next */ []);
1014
+ this.lastSelectedIndex = -1;
1015
+ this.editingState = signal(null, ...ngDevMode ? [{ debugName: "editingState" }] : /* istanbul ignore next */ []);
1016
+ this.scrollTop = signal(0, ...ngDevMode ? [{ debugName: "scrollTop" }] : /* istanbul ignore next */ []);
1017
+ this.draggingColumnId = null;
1018
+ this.resolvedColumns = computed(() => this.columns().map((c) => resolveColumn(c, this.locale(), this.rows())), ...ngDevMode ? [{ debugName: "resolvedColumns" }] : /* istanbul ignore next */ []);
1019
+ this.isGrid = computed(() => this.mode() === "grid", ...ngDevMode ? [{ debugName: "isGrid" }] : /* istanbul ignore next */ []);
1020
+ this.hasSelection = computed(() => this.selectionMode() !== "none", ...ngDevMode ? [{ debugName: "hasSelection" }] : /* istanbul ignore next */ []);
1021
+ this.colOffset = computed(() => this.hasSelection() ? 1 : 0, ...ngDevMode ? [{ debugName: "colOffset" }] : /* istanbul ignore next */ []);
1022
+ this.orderedColumns = computed(() => {
1023
+ const order = this.columnOrder();
1024
+ const columns = this.resolvedColumns();
1025
+ if (order.length === 0) return columns;
1026
+ const rank = (id) => {
1027
+ const i = order.indexOf(id);
1028
+ return i === -1 ? order.length : i;
1029
+ };
1030
+ return columns.map((c, i) => ({
1031
+ c,
1032
+ i
1033
+ })).sort((a, b) => rank(a.c.id) - rank(b.c.id) || a.i - b.i).map((x) => x.c);
1034
+ }, ...ngDevMode ? [{ debugName: "orderedColumns" }] : /* istanbul ignore next */ []);
1035
+ this.visibleColumns = computed(() => this.orderedColumns().filter((c) => !this.hiddenColumnIds().has(c.id)), ...ngDevMode ? [{ debugName: "visibleColumns" }] : /* istanbul ignore next */ []);
1036
+ this.hidableColumns = computed(() => this.resolvedColumns().filter((c) => c.hidable), ...ngDevMode ? [{ debugName: "hidableColumns" }] : /* istanbul ignore next */ []);
1037
+ this.entries = computed(() => {
1038
+ const rowId = this.rowId();
1039
+ return this.rows().map((row, index) => ({
1040
+ row,
1041
+ id: rowId(row, index)
1042
+ }));
1043
+ }, ...ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []);
1044
+ this.cellCache = computed(() => {
1045
+ this.resolvedColumns();
1046
+ return /* @__PURE__ */ new WeakMap();
1047
+ }, ...ngDevMode ? [{ debugName: "cellCache" }] : /* istanbul ignore next */ []);
1048
+ this.isMissing = isMissing;
1049
+ this.filterableColumns = computed(() => this.resolvedColumns().filter((c) => c.filterable !== false), ...ngDevMode ? [{ debugName: "filterableColumns" }] : /* istanbul ignore next */ []);
1050
+ this.searchIndex = computed(() => this.entries().map((entry) => this.filterableColumns().map((column) => foldText(column.filterText ? column.filterText(this.cellFor(column, entry.row).value, entry.row) : this.cellFor(column, entry.row).formatted))), ...ngDevMode ? [{ debugName: "searchIndex" }] : /* istanbul ignore next */ []);
1051
+ this.filteredEntries = computed(() => {
1052
+ const terms = searchTerms(this.filter());
1053
+ const rowFilter = this.rowFilter();
1054
+ if (terms.length === 0 && !rowFilter) return this.entries();
1055
+ const index = terms.length > 0 ? this.searchIndex() : null;
1056
+ const field = this.filterField();
1057
+ const only = field ? this.filterableColumns().findIndex((c) => c.id === field) : -1;
1058
+ const rowText = (i) => only >= 0 ? index[i][only] : index[i].join(" ");
1059
+ return this.entries().filter((entry, i) => (!rowFilter || rowFilter(entry.row)) && (!index || terms.every((term) => rowText(i).includes(term))));
1060
+ }, ...ngDevMode ? [{ debugName: "filteredEntries" }] : /* istanbul ignore next */ []);
1061
+ this.sortKeys = computed(() => [this.sort(), ...this.thenBy()].map((k) => ({
1062
+ column: this.resolvedColumns().find((c) => c.id === k.columnId),
1063
+ dir: k.direction === "ascending" ? 1 : k.direction === "descending" ? -1 : 0
1064
+ })).filter((k) => !!k.column && k.dir !== 0), ...ngDevMode ? [{ debugName: "sortKeys" }] : /* istanbul ignore next */ []);
1065
+ this.sortedEntries = computed(() => {
1066
+ const keys = this.sortKeys();
1067
+ const filtered = this.filteredEntries();
1068
+ if (keys.length === 0) return filtered;
1069
+ const nulls = this.nulls();
1070
+ const collators = keys.map((k) => {
1071
+ const tag = k.column.locale ?? this.locale();
1072
+ return tag ? new Intl.Collator(tag, { numeric: true }) : null;
1073
+ });
1074
+ const keyed = filtered.map((entry) => ({
1075
+ entry,
1076
+ keys: keys.map((k) => this.cellFor(k.column, entry.row).value)
1077
+ }));
1078
+ keyed.sort((a, b) => {
1079
+ for (let i = 0; i < keys.length; i++) {
1080
+ const { column, dir } = keys[i];
1081
+ const av = a.keys[i];
1082
+ const bv = b.keys[i];
1083
+ if (av === bv) continue;
1084
+ const aMissing = av === null || av === void 0;
1085
+ const bMissing = bv === null || bv === void 0;
1086
+ if (aMissing || bMissing) return aMissing === bMissing ? 0 : (aMissing ? 1 : -1) * (nulls === "first" ? -1 : 1);
1087
+ const collator = collators[i];
1088
+ const result = column.compare ? column.compare(av, bv) * dir : collator && typeof av === "string" && typeof bv === "string" ? collator.compare(av, bv) * dir : av > bv ? dir : -dir;
1089
+ if (result !== 0) return result;
1090
+ }
1091
+ return 0;
1092
+ });
1093
+ return keyed.map((k) => k.entry);
1094
+ }, ...ngDevMode ? [{ debugName: "sortedEntries" }] : /* istanbul ignore next */ []);
1095
+ this.pageCount = computed(() => {
1096
+ const size = this.pageSize();
1097
+ return size ? Math.max(1, Math.ceil(this.sortedEntries().length / size)) : 1;
1098
+ }, ...ngDevMode ? [{ debugName: "pageCount" }] : /* istanbul ignore next */ []);
1099
+ this.clampedPage = computed(() => Math.min(Math.max(this.page(), 1), this.pageCount()), ...ngDevMode ? [{ debugName: "clampedPage" }] : /* istanbul ignore next */ []);
1100
+ this.pageStart = computed(() => {
1101
+ const size = this.pageSize();
1102
+ return size ? (this.clampedPage() - 1) * size : 0;
1103
+ }, ...ngDevMode ? [{ debugName: "pageStart" }] : /* istanbul ignore next */ []);
1104
+ this.pageEntries = computed(() => {
1105
+ const size = this.pageSize();
1106
+ return size ? this.sortedEntries().slice(this.pageStart(), this.pageStart() + size) : this.sortedEntries();
1107
+ }, ...ngDevMode ? [{ debugName: "pageEntries" }] : /* istanbul ignore next */ []);
1108
+ this.lastCol = computed(() => this.colOffset() + this.visibleColumns().length - 1, ...ngDevMode ? [{ debugName: "lastCol" }] : /* istanbul ignore next */ []);
1109
+ this.activeRow = computed(() => Math.max(-1, Math.min(this.focusedRow(), this.pageEntries().length - 1)), ...ngDevMode ? [{ debugName: "activeRow" }] : /* istanbul ignore next */ []);
1110
+ this.activeCol = computed(() => Math.max(0, Math.min(this.focusedCol(), this.lastCol())), ...ngDevMode ? [{ debugName: "activeCol" }] : /* istanbul ignore next */ []);
1111
+ this.indexed = computed(() => !!this.pageSize() || !!this.virtualize(), ...ngDevMode ? [{ debugName: "indexed" }] : /* istanbul ignore next */ []);
1112
+ this.rowCount = computed(() => this.pageSize() ? this.sortedEntries().length + 1 : this.virtualize() ? this.pageEntries().length + 1 : void 0, ...ngDevMode ? [{ debugName: "rowCount" }] : /* istanbul ignore next */ []);
1113
+ this.renderItems = computed(() => {
1114
+ const total = this.pageEntries().length;
1115
+ const v = this.virtualize();
1116
+ if (!v) return this.pageEntries().map((_, index) => ({
1117
+ kind: "row",
1118
+ index
1119
+ }));
1120
+ const overscan = v.overscan ?? 3;
1121
+ const top = this.scrollTop();
1122
+ const start = Math.max(0, Math.floor(top / v.rowHeight) - overscan);
1123
+ const end = Math.min(total, Math.ceil((top + v.height) / v.rowHeight) + overscan);
1124
+ const indices = /* @__PURE__ */ new Set();
1125
+ for (let i = start; i < end; i++) indices.add(i);
1126
+ const active = this.activeRow();
1127
+ if (active >= 0 && active < total) indices.add(active);
1128
+ const items = [];
1129
+ let previous = -1;
1130
+ for (const index of [...indices].sort((a, b) => a - b)) {
1131
+ if (index - previous > 1) items.push({
1132
+ kind: "spacer",
1133
+ rows: index - previous - 1,
1134
+ key: `spacer-${previous}`
1135
+ });
1136
+ items.push({
1137
+ kind: "row",
1138
+ index
1139
+ });
1140
+ previous = index;
1141
+ }
1142
+ if (total - 1 - previous > 0) items.push({
1143
+ kind: "spacer",
1144
+ rows: total - 1 - previous,
1145
+ key: `spacer-${previous}`
1146
+ });
1147
+ return items;
1148
+ }, ...ngDevMode ? [{ debugName: "renderItems" }] : /* istanbul ignore next */ []);
1149
+ this.scrollStyle = computed(() => {
1150
+ const v = this.virtualize();
1151
+ return v ? `height:${v.height}px;overflow:auto` : null;
1152
+ }, ...ngDevMode ? [{ debugName: "scrollStyle" }] : /* istanbul ignore next */ []);
1153
+ this.selectedSet = computed(() => new Set(this.selected()), ...ngDevMode ? [{ debugName: "selectedSet" }] : /* istanbul ignore next */ []);
1154
+ this.allSelected = computed(() => this.sortedEntries().length > 0 && this.sortedEntries().every((e) => this.selectedSet().has(e.id)), ...ngDevMode ? [{ debugName: "allSelected" }] : /* istanbul ignore next */ []);
1155
+ this.someSelected = computed(() => !this.allSelected() && this.sortedEntries().some((e) => this.selectedSet().has(e.id)), ...ngDevMode ? [{ debugName: "someSelected" }] : /* istanbul ignore next */ []);
1156
+ this.footerContext = computed(() => ({
1157
+ columns: this.visibleColumns(),
1158
+ rows: this.pageEntries().map((e) => e.row)
1159
+ }), ...ngDevMode ? [{ debugName: "footerContext" }] : /* istanbul ignore next */ []);
1160
+ this.exportColumns = computed(() => this.visibleColumns().filter((c) => c.exportable !== false), ...ngDevMode ? [{ debugName: "exportColumns" }] : /* istanbul ignore next */ []);
1161
+ this.exportEntries = computed(() => this.exportScope() === "selected" ? this.sortedEntries().filter((e) => this.selectedSet().has(e.id)) : this.sortedEntries(), ...ngDevMode ? [{ debugName: "exportEntries" }] : /* istanbul ignore next */ []);
1162
+ this.hasExportButtons = computed(() => {
1163
+ const l = this.labels();
1164
+ return !!(l.exportCsv || l.exportTsv || l.exportExcel || l.copy);
1165
+ }, ...ngDevMode ? [{ debugName: "hasExportButtons" }] : /* istanbul ignore next */ []);
1166
+ this.history = EMPTY_HISTORY;
1167
+ this.typeahead = {
1168
+ prefix: "",
1169
+ at: 0
1170
+ };
1171
+ effect(() => {
1172
+ const root = this.rootRef().nativeElement;
1173
+ const grid = this.isGrid();
1174
+ const count = this.rowCount();
1175
+ const busy = this.busy();
1176
+ this.sortedEntries();
1177
+ this.pageEntries();
1178
+ this.visibleColumns();
1179
+ this.scroll();
1180
+ afterNextRender(() => {
1181
+ const table = root.querySelector("table.data-table");
1182
+ if (!table) return;
1183
+ table.setAttribute("role", grid ? "grid" : "table");
1184
+ if (grid && count !== void 0) table.setAttribute("aria-rowcount", String(count));
1185
+ else table.removeAttribute("aria-rowcount");
1186
+ if (busy) table.setAttribute("aria-busy", "true");
1187
+ else table.removeAttribute("aria-busy");
1188
+ }, { injector: this.injector });
1189
+ });
1190
+ const reportedLabels = /* @__PURE__ */ new Set();
1191
+ effect(() => {
1192
+ const labels = this.labels();
1193
+ const columns = this.resolvedColumns();
1194
+ const features = {
1195
+ selection: this.selectionMode() !== "none",
1196
+ pagination: !!this.pageSize(),
1197
+ hidableColumns: columns.some((c) => c.hidable),
1198
+ resizableColumns: columns.some((c) => c.resizable),
1199
+ editing: columns.some((c) => c.editable),
1200
+ sorting: columns.some((c) => c.sortable),
1201
+ search: !!labels.search,
1202
+ exporting: !!(labels.exportCsv || labels.exportTsv || labels.exportExcel || labels.copy)
1203
+ };
1204
+ const warn = this.warnMissingLabels();
1205
+ const env = globalThis.process?.env;
1206
+ if (!warn || env?.["NODE_ENV"] === "production") return;
1207
+ const fresh = missingLabels(features, labels).filter((m) => !reportedLabels.has(m.key));
1208
+ if (fresh.length === 0) return;
1209
+ for (const m of fresh) reportedLabels.add(m.key);
1210
+ const controls = fresh.filter((m) => m.kind === "control").map((m) => m.key);
1211
+ const announcements = fresh.filter((m) => m.kind === "announcement").map((m) => m.key);
1212
+ console.warn(`[data-grid] "${this.label()}": labels left out — ` + [controls.length ? `controls with no name: ${controls.join(", ")}` : "", announcements.length ? `silent announcements: ${announcements.join(", ")}` : ""].filter(Boolean).join("; "));
1213
+ });
1214
+ effect(() => {
1215
+ this.pageEntries();
1216
+ this.visibleColumns();
1217
+ this.isGrid();
1218
+ this.scrollTop();
1219
+ this.editingState();
1220
+ afterNextRender(() => this.normaliseWidgets(), { injector: this.injector });
1221
+ });
1222
+ let restored = false;
1223
+ effect(() => {
1224
+ const key = this.storageKey();
1225
+ if (!key || restored) return;
1226
+ restored = true;
1227
+ untracked(() => this.restoreView(key));
1228
+ });
1229
+ effect(() => {
1230
+ const key = this.storageKey();
1231
+ const snapshot = {
1232
+ columnWidths: this.columnWidths(),
1233
+ hiddenColumnIds: [...this.hiddenColumnIds()],
1234
+ sort: this.sort()
1235
+ };
1236
+ if (!key || typeof localStorage === "undefined") return;
1237
+ try {
1238
+ localStorage.setItem(key, JSON.stringify(snapshot));
1239
+ } catch {}
1240
+ });
1241
+ }
1242
+ announce(message) {
1243
+ if (message) this.statusMessage.set(message);
1244
+ }
1245
+ ariaSortFor(column) {
1246
+ if (!column.sortable) return null;
1247
+ if (this.sort().columnId === column.id) return this.sort().direction;
1248
+ return this.thenBy().find((k) => k.columnId === column.id)?.direction ?? "none";
1249
+ }
1250
+ sortOrderFor(column) {
1251
+ const keys = this.sortKeys();
1252
+ if (keys.length < 2) return null;
1253
+ const at = keys.findIndex((k) => k.column.id === column.id);
1254
+ return at === -1 ? null : at + 1;
1255
+ }
1256
+ toggleSort(column, additive = false) {
1257
+ if (!column.sortable) return;
1258
+ const current = this.sort();
1259
+ if (additive && current.direction !== "none" && current.columnId && current.columnId !== column.id) {
1260
+ const thenBy = this.thenBy();
1261
+ const existing = thenBy.find((k) => k.columnId === column.id);
1262
+ const nextThen = !existing ? [...thenBy, {
1263
+ columnId: column.id,
1264
+ direction: "ascending"
1265
+ }] : existing.direction === "ascending" ? thenBy.map((k) => k.columnId === column.id ? {
1266
+ columnId: column.id,
1267
+ direction: "descending"
1268
+ } : k) : thenBy.filter((k) => k.columnId !== column.id);
1269
+ this.thenBy.set(nextThen);
1270
+ this.announce(this.labels().sortAnnouncement?.(column.header, nextThen.find((k) => k.columnId === column.id)?.direction ?? "none"));
1271
+ return;
1272
+ }
1273
+ if (this.thenBy().length > 0) this.thenBy.set([]);
1274
+ const next = current.columnId !== column.id ? {
1275
+ columnId: column.id,
1276
+ direction: "ascending"
1277
+ } : current.direction === "ascending" ? {
1278
+ columnId: column.id,
1279
+ direction: "descending"
1280
+ } : current.direction === "descending" ? {
1281
+ columnId: "",
1282
+ direction: "none"
1283
+ } : {
1284
+ columnId: column.id,
1285
+ direction: "ascending"
1286
+ };
1287
+ this.sort.set(next);
1288
+ this.announce(this.labels().sortAnnouncement?.(column.header, next.direction));
1289
+ }
1290
+ setFilter(value) {
1291
+ this.filter.set(value);
1292
+ this.page.set(1);
1293
+ this.announce(this.labels().filterAnnouncement?.(this.filteredEntries().length, this.rows().length));
1294
+ }
1295
+ onFilterKeydown(event) {
1296
+ const e = event;
1297
+ if (e.key === "Escape" && this.filter() !== "") {
1298
+ e.preventDefault();
1299
+ this.setFilter("");
1300
+ }
1301
+ }
1302
+ onFilterFieldChange(event) {
1303
+ this.filterField.set(event.target.value);
1304
+ this.page.set(1);
1305
+ this.announce(this.labels().filterAnnouncement?.(this.filteredEntries().length, this.rows().length));
1306
+ }
1307
+ isSelected(id) {
1308
+ return this.selectedSet().has(id);
1309
+ }
1310
+ setSelected(next) {
1311
+ this.selected.set(next);
1312
+ this.announce(this.labels().selectionAnnouncement?.(next.length));
1313
+ }
1314
+ onRowSelectClick(index, event) {
1315
+ const e = event;
1316
+ this.toggleRow(index, e);
1317
+ }
1318
+ toggleRow(index, event) {
1319
+ const sorted = this.sortedEntries();
1320
+ const id = sorted[index].id;
1321
+ const selected = this.selected();
1322
+ const mode = this.selectionMode();
1323
+ if (mode === "single") this.setSelected(this.isSelected(id) ? [] : [id]);
1324
+ else if (mode === "multiple") {
1325
+ if (event?.shiftKey && this.lastSelectedIndex >= 0) {
1326
+ const [start, end] = this.lastSelectedIndex < index ? [this.lastSelectedIndex, index] : [index, this.lastSelectedIndex];
1327
+ const range = sorted.slice(start, end + 1).map((e) => e.id);
1328
+ this.setSelected([.../* @__PURE__ */ new Set([...selected, ...range])]);
1329
+ } else if (event?.ctrlKey || event?.metaKey) this.setSelected(this.isSelected(id) ? selected.filter((x) => x !== id) : [...selected, id]);
1330
+ else this.setSelected(this.isSelected(id) && selected.length === 1 ? [] : [id]);
1331
+ }
1332
+ this.lastSelectedIndex = index;
1333
+ }
1334
+ toggleSelectAll() {
1335
+ if (this.allSelected()) this.setSelected([]);
1336
+ else this.setSelected(this.sortedEntries().map((e) => e.id));
1337
+ }
1338
+ widthFor(column) {
1339
+ return this.columnWidths()[column.id] ?? column.width;
1340
+ }
1341
+ cellStyle(column) {
1342
+ const parts = [];
1343
+ const width = this.widthFor(column);
1344
+ if (width) parts.push(`width:${width}px`);
1345
+ if (column.align) parts.push(`text-align:${column.align}`);
1346
+ return parts.length > 0 ? parts.join(";") : null;
1347
+ }
1348
+ setWidth(column, width) {
1349
+ this.columnWidths.update((current) => ({
1350
+ ...current,
1351
+ [column.id]: Math.max(MIN_COLUMN_WIDTH, width)
1352
+ }));
1353
+ }
1354
+ onResizeKeydown(column, event) {
1355
+ const e = event;
1356
+ e.stopPropagation();
1357
+ const current = this.widthFor(column) ?? 120;
1358
+ if (e.key === "ArrowRight") {
1359
+ e.preventDefault();
1360
+ this.setWidth(column, current + RESIZE_STEP);
1361
+ } else if (e.key === "ArrowLeft") {
1362
+ e.preventDefault();
1363
+ this.setWidth(column, current - RESIZE_STEP);
1364
+ }
1365
+ }
1366
+ onResizePointerDown(column, event) {
1367
+ const e = event;
1368
+ e.preventDefault();
1369
+ const startX = e.clientX;
1370
+ const target = e.target;
1371
+ const startWidth = this.widthFor(column) ?? target.closest("th")?.getBoundingClientRect().width ?? 120;
1372
+ target.setPointerCapture?.(e.pointerId);
1373
+ const onMove = (moveEvent) => this.setWidth(column, startWidth + (moveEvent.clientX - startX));
1374
+ const onUp = () => {
1375
+ window.removeEventListener("pointermove", onMove);
1376
+ window.removeEventListener("pointerup", onUp);
1377
+ };
1378
+ window.addEventListener("pointermove", onMove);
1379
+ window.addEventListener("pointerup", onUp);
1380
+ }
1381
+ toggleColumnVisibility(columnId) {
1382
+ this.hiddenColumnIds.update((current) => {
1383
+ const next = new Set(current);
1384
+ if (next.has(columnId)) next.delete(columnId);
1385
+ else next.add(columnId);
1386
+ return next;
1387
+ });
1388
+ }
1389
+ goToPage(next) {
1390
+ const target = Math.min(Math.max(next, 1), this.pageCount());
1391
+ if (target === this.clampedPage()) return;
1392
+ this.page.set(target);
1393
+ this.announce(this.labels().pageAnnouncement?.(target, this.pageCount()));
1394
+ }
1395
+ moveColumn(columnId, toVisibleIndex) {
1396
+ const ids = this.visibleColumns().map((c) => c.id);
1397
+ const from = ids.indexOf(columnId);
1398
+ const to = Math.min(Math.max(toVisibleIndex, 0), ids.length - 1);
1399
+ if (from === -1 || from === to) return false;
1400
+ ids.splice(to, 0, ...ids.splice(from, 1));
1401
+ const visibleSet = new Set(ids);
1402
+ let next = 0;
1403
+ this.columnOrder.set(this.orderedColumns().map((c) => visibleSet.has(c.id) ? ids[next++] : c.id));
1404
+ const column = this.resolvedColumns().find((c) => c.id === columnId);
1405
+ this.announce(this.labels().columnMoveAnnouncement?.(column.header, to + 1, ids.length));
1406
+ return true;
1407
+ }
1408
+ onHeaderDragStart(column, event) {
1409
+ if (!column.movable) return;
1410
+ this.draggingColumnId = column.id;
1411
+ event.dataTransfer?.setData("text/plain", column.id);
1412
+ }
1413
+ onHeaderDragOver(event) {
1414
+ if (this.draggingColumnId) event.preventDefault();
1415
+ }
1416
+ onHeaderDrop(targetId, event) {
1417
+ event.preventDefault();
1418
+ const id = this.draggingColumnId;
1419
+ this.draggingColumnId = null;
1420
+ if (!id || id === targetId) return;
1421
+ const to = this.visibleColumns().findIndex((c) => c.id === targetId);
1422
+ if (to >= 0) this.moveColumn(id, to);
1423
+ }
1424
+ onScroll(event) {
1425
+ if (this.virtualize()) this.scrollTop.set(event.currentTarget.scrollTop);
1426
+ }
1427
+ revealRow(index) {
1428
+ const v = this.virtualize();
1429
+ if (!v || index < 0) return;
1430
+ const top = index * v.rowHeight;
1431
+ const current = this.scrollTop();
1432
+ let next = current;
1433
+ if (top < current) next = top;
1434
+ else if (top + v.rowHeight > current + v.height) next = top + v.rowHeight - v.height;
1435
+ if (next === current) return;
1436
+ this.scrollTop.set(next);
1437
+ const el = this.scrollRef()?.nativeElement;
1438
+ if (el) el.scrollTop = next;
1439
+ }
1440
+ focusActiveCell() {
1441
+ afterNextRender(() => {
1442
+ this.rootRef().nativeElement.querySelector(".data-table-th[tabindex=\"0\"], .data-table-td[tabindex=\"0\"]")?.focus({ preventScroll: true });
1443
+ }, { injector: this.injector });
1444
+ }
1445
+ moveFocus(row, col) {
1446
+ const nextRow = Math.min(Math.max(row, -1), this.pageEntries().length - 1);
1447
+ this.focusedRow.set(nextRow);
1448
+ this.focusedCol.set(Math.min(Math.max(col, 0), this.lastCol()));
1449
+ this.revealRow(nextRow);
1450
+ this.focusActiveCell();
1451
+ }
1452
+ exportCell(column, row) {
1453
+ if (column.exportValue) return column.exportValue(row);
1454
+ const ctx = this.cellFor(column, row);
1455
+ const value = ctx.value;
1456
+ if (value === null || value === void 0) return null;
1457
+ if (typeof value === "number" || typeof value === "boolean" || value instanceof Date) return value;
1458
+ return ctx.formatted;
1459
+ }
1460
+ hasUtcColumn(c) {
1461
+ return c.type === "time-zone" && !!c.exportUtc && !!c.referenceDate && !!this.labels().exportUtcHeader;
1462
+ }
1463
+ buildExportTable(format) {
1464
+ const live = (c) => format === "xlsx" && this.exportFormulas() && c.type === "formula" && !!c.source;
1465
+ const headers = [];
1466
+ for (const c of this.exportColumns()) {
1467
+ headers.push(c.header);
1468
+ if (this.hasUtcColumn(c)) headers.push(this.labels().exportUtcHeader(c.header));
1469
+ }
1470
+ return {
1471
+ headers,
1472
+ rows: this.exportEntries().map((entry) => {
1473
+ const cells = [];
1474
+ for (const c of this.exportColumns()) {
1475
+ const expression = live(c) ? c.source(entry.row) : void 0;
1476
+ cells.push(typeof expression === "string" && expression !== "" && !c.errorOf?.(entry.row) ? {
1477
+ formula: expression,
1478
+ result: this.exportCell(c, entry.row)
1479
+ } : this.exportCell(c, entry.row));
1480
+ if (this.hasUtcColumn(c)) cells.push(zonedUtcTime(this.cellFor(c, entry.row).value, c.referenceDate));
1481
+ }
1482
+ return cells;
1483
+ })
1484
+ };
1485
+ }
1486
+ async runCopy() {
1487
+ const table = this.buildExportTable("tsv");
1488
+ const text = toClipboardText(table);
1489
+ const copied = {
1490
+ text,
1491
+ rowCount: table.rows.length
1492
+ };
1493
+ this.copy.emit(copied);
1494
+ if (this.copyHandler()?.(copied) === false) return;
1495
+ const ok = await copyToClipboard(text);
1496
+ this.announce(ok ? this.labels().copyAnnouncement?.(table.rows.length) : this.labels().copyFailed);
1497
+ }
1498
+ runExport(format) {
1499
+ const table = this.buildExportTable(format);
1500
+ const rows = table.rows;
1501
+ const name = this.exportFilename();
1502
+ const filename = exportFilename(name, format);
1503
+ const blob = exportBlob(table, format, { sheetName: name });
1504
+ const file = {
1505
+ format,
1506
+ filename,
1507
+ rowCount: rows.length,
1508
+ blob
1509
+ };
1510
+ this.exportFile.emit(file);
1511
+ if (this.exportHandler()?.(file) === false) return;
1512
+ downloadBlob(blob, filename);
1513
+ this.announce(this.labels().exportAnnouncement?.(format, rows.length, filename));
1514
+ }
1515
+ onFileDragOver(column, event) {
1516
+ if (column.type !== "file" || !column.editable) return;
1517
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
1518
+ }
1519
+ onFileDrop(entry, column, event) {
1520
+ if (column.type !== "file" || !column.editable) return;
1521
+ const dropped = Array.from(event.dataTransfer?.files ?? []);
1522
+ if (dropped.length === 0) return;
1523
+ event.preventDefault();
1524
+ const files = column.multiple === false ? dropped.slice(0, 1) : dropped;
1525
+ this.filesSelected.emit({
1526
+ rowId: entry.id,
1527
+ row: entry.row,
1528
+ columnId: column.id,
1529
+ files
1530
+ });
1531
+ }
1532
+ onFileInput(entry, column, event) {
1533
+ const input = event.target;
1534
+ const chosen = Array.from(input.files ?? []);
1535
+ if (chosen.length > 0) this.filesSelected.emit({
1536
+ rowId: entry.id,
1537
+ row: entry.row,
1538
+ columnId: column.id,
1539
+ files: chosen
1540
+ });
1541
+ input.value = "";
1542
+ }
1543
+ isEditable(column) {
1544
+ if (!column.editable) return false;
1545
+ if (column.type === "rollup" || column.type === "file") return false;
1546
+ if (column.type === "person" && !column.personEditor) return false;
1547
+ return true;
1548
+ }
1549
+ isEditingCell(entry, column) {
1550
+ const e = this.editingState();
1551
+ return !!e && e.rowId === entry.id && e.columnId === column.id;
1552
+ }
1553
+ startEdit(entry, column) {
1554
+ if (!this.isEditable(column)) return;
1555
+ const raw = column.type === "formula" && column.source ? column.source(entry.row) : defaultAccessor(column, entry.row);
1556
+ if (column.type === "time-zone") {
1557
+ const zoned = isZonedTime(raw) ? raw : null;
1558
+ this.editingState.set({
1559
+ rowId: entry.id,
1560
+ columnId: column.id,
1561
+ draft: zoned?.time ?? "",
1562
+ zone: zoned?.timeZone ?? column.timeZones?.[0]?.id ?? ""
1563
+ });
1564
+ } else {
1565
+ const text = column.type === "link" ? linkParts(raw)?.href ?? "" : raw === null || raw === void 0 ? "" : String(raw);
1566
+ this.editingState.set({
1567
+ rowId: entry.id,
1568
+ columnId: column.id,
1569
+ draft: text
1570
+ });
1571
+ }
1572
+ afterNextRender(() => {
1573
+ const editor = this.rootRef().nativeElement.querySelector(".data-grid-cell-editor");
1574
+ editor?.focus();
1575
+ if (editor instanceof HTMLInputElement && editor.type === "text") editor.select();
1576
+ }, { injector: this.injector });
1577
+ }
1578
+ parseDraft(column, row, draft, zone) {
1579
+ if (column.parse) return column.parse(draft, row);
1580
+ if (column.type === "time-zone") return draft === "" ? null : {
1581
+ time: draft,
1582
+ timeZone: zone ?? ""
1583
+ };
1584
+ if (column.editor === "number") return draft.trim() === "" ? null : Number(draft);
1585
+ if (column.editor === "date" || column.editor === "time" || column.editor === "select") return draft === "" ? null : draft;
1586
+ return draft;
1587
+ }
1588
+ stepHistory(direction) {
1589
+ const step = direction === "undo" ? undoEdit(this.history) : redoEdit(this.history);
1590
+ if (!step.record) return;
1591
+ this.history = step.history;
1592
+ const { ownerId, fieldId, value, previous } = step.record;
1593
+ const entry = this.entries().find((e) => e.id === ownerId);
1594
+ const column = this.resolvedColumns().find((c) => c.id === fieldId);
1595
+ if (!entry || !column) return;
1596
+ const restored = direction === "undo" ? previous : value;
1597
+ const replaced = direction === "undo" ? value : previous;
1598
+ this.cellEdit.emit({
1599
+ rowId: entry.id,
1600
+ row: entry.row,
1601
+ columnId: column.id,
1602
+ value: restored,
1603
+ previous: replaced
1604
+ });
1605
+ const text = column.format ? column.format(restored, entry.row) : String(restored ?? "");
1606
+ this.announce((direction === "undo" ? this.labels().undoAnnouncement : this.labels().redoAnnouncement)?.(column.header, text));
1607
+ }
1608
+ isStructurallyEqual(a, b) {
1609
+ return typeof a === "object" && typeof b === "object" && a !== null && b !== null && JSON.stringify(a) === JSON.stringify(b);
1610
+ }
1611
+ commitValue(entry, column, value) {
1612
+ const message = column.validate?.(value, entry.row);
1613
+ const invalidType = isInvalidForType(column, value);
1614
+ if (message || invalidType) {
1615
+ const text = message ?? this.labels().invalidValue?.(column.header, column.type ?? "") ?? "";
1616
+ const current = this.editingState();
1617
+ if (current) this.editingState.set({
1618
+ ...current,
1619
+ error: text || void 0,
1620
+ invalid: true
1621
+ });
1622
+ this.announce(this.labels().editError?.(column.header, text));
1623
+ return false;
1624
+ }
1625
+ const previous = column.type === "formula" && column.source ? column.source(entry.row) : defaultAccessor(column, entry.row);
1626
+ this.editingState.set(null);
1627
+ if (value !== previous && !this.isStructurallyEqual(value, previous)) {
1628
+ this.history = recordEdit(this.history, {
1629
+ ownerId: entry.id,
1630
+ fieldId: column.id,
1631
+ value,
1632
+ previous
1633
+ });
1634
+ this.cellEdit.emit({
1635
+ rowId: entry.id,
1636
+ row: entry.row,
1637
+ columnId: column.id,
1638
+ value,
1639
+ previous
1640
+ });
1641
+ this.announce(this.labels().editAnnouncement?.(column.header, column.format ? column.format(value, entry.row) : String(value ?? "")));
1642
+ }
1643
+ return true;
1644
+ }
1645
+ commitEdit() {
1646
+ const current = this.editingState();
1647
+ if (!current) return;
1648
+ const entry = this.entries().find((e) => e.id === current.rowId);
1649
+ const column = this.resolvedColumns().find((c) => c.id === current.columnId);
1650
+ if (!entry || !column) {
1651
+ this.editingState.set(null);
1652
+ return;
1653
+ }
1654
+ if (this.commitValue(entry, column, this.parseDraft(column, entry.row, current.draft, current.zone))) this.focusActiveCell();
1655
+ }
1656
+ cancelEdit() {
1657
+ const current = this.editingState();
1658
+ if (!current) return;
1659
+ this.editingState.set(null);
1660
+ const column = this.resolvedColumns().find((c) => c.id === current.columnId);
1661
+ if (column) this.announce(this.labels().editCancelAnnouncement?.(column.header));
1662
+ this.focusActiveCell();
1663
+ }
1664
+ onEditorKeydown(event) {
1665
+ const e = event;
1666
+ e.stopPropagation();
1667
+ if (e.isComposing || e.keyCode === 229) return;
1668
+ if (e.key === "Enter") {
1669
+ e.preventDefault();
1670
+ this.commitEdit();
1671
+ } else if (e.key === "Escape") {
1672
+ e.preventDefault();
1673
+ this.cancelEdit();
1674
+ }
1675
+ }
1676
+ onEditorBlur() {
1677
+ if (this.editingState()) this.commitEdit();
1678
+ }
1679
+ onZonedFocusout(event) {
1680
+ const e = event;
1681
+ if (this.editingState() && !e.currentTarget.contains(e.relatedTarget)) this.commitEdit();
1682
+ }
1683
+ setDraft(event) {
1684
+ const current = this.editingState();
1685
+ if (current) this.editingState.set({
1686
+ ...current,
1687
+ draft: event.target.value,
1688
+ error: void 0,
1689
+ invalid: false
1690
+ });
1691
+ }
1692
+ setZone(event) {
1693
+ const current = this.editingState();
1694
+ if (current) this.editingState.set({
1695
+ ...current,
1696
+ zone: event.target.value,
1697
+ error: void 0,
1698
+ invalid: false
1699
+ });
1700
+ }
1701
+ personEditorContext(entry, column, ctx) {
1702
+ return {
1703
+ value: ctx.value,
1704
+ commit: (v) => void this.commitValue(entry, column, v),
1705
+ cancel: () => this.cancelEdit()
1706
+ };
1707
+ }
1708
+ activateFocusedCell() {
1709
+ if (this.activeCol() === 0 && this.hasSelection()) {
1710
+ if (this.activeRow() === -1) {
1711
+ if (this.selectionMode() === "multiple") this.toggleSelectAll();
1712
+ } else this.toggleRow(this.pageStart() + this.activeRow());
1713
+ return;
1714
+ }
1715
+ const column = this.visibleColumns()[this.activeCol() - this.colOffset()];
1716
+ if (!column) return;
1717
+ if (this.activeRow() === -1) this.toggleSort(column);
1718
+ }
1719
+ widgetsIn(cell) {
1720
+ return Array.from(cell.querySelectorAll(WIDGET_SELECTOR)).filter((el) => !el.closest("[data-grid-own]"));
1721
+ }
1722
+ setWidgetTabindex(cell, value) {
1723
+ for (const widget of this.widgetsIn(cell)) widget.setAttribute("tabindex", value);
1724
+ }
1725
+ normaliseWidgets() {
1726
+ if (!this.isGrid()) return;
1727
+ for (const cell of this.rootRef().nativeElement.querySelectorAll(".data-table-body [data-row][data-col]")) {
1728
+ if (cell.getAttribute("data-grid-entered") === "true") continue;
1729
+ this.setWidgetTabindex(cell, "-1");
1730
+ }
1731
+ }
1732
+ enterCell(cell) {
1733
+ const first = this.widgetsIn(cell)[0];
1734
+ if (!first) return false;
1735
+ cell.setAttribute("data-grid-entered", "true");
1736
+ this.setWidgetTabindex(cell, "0");
1737
+ first.focus();
1738
+ return true;
1739
+ }
1740
+ leaveCell(cell) {
1741
+ if (cell.getAttribute("data-grid-entered") !== "true") return;
1742
+ cell.removeAttribute("data-grid-entered");
1743
+ this.setWidgetTabindex(cell, "-1");
1744
+ }
1745
+ onGridFocusin(event) {
1746
+ if (!this.isGrid()) return;
1747
+ const target = event.target;
1748
+ const cell = target.closest("[data-row][data-col]");
1749
+ if (!cell) return;
1750
+ this.focusedRow.set(Number(cell.dataset["row"]));
1751
+ this.focusedCol.set(Number(cell.dataset["col"]));
1752
+ if (target !== cell && !target.closest("[data-grid-own]") && cell.closest(".data-table-body")) {
1753
+ cell.setAttribute("data-grid-entered", "true");
1754
+ this.setWidgetTabindex(cell, "0");
1755
+ }
1756
+ }
1757
+ onGridFocusout(event) {
1758
+ if (!this.isGrid()) return;
1759
+ const e = event;
1760
+ const cell = e.target.closest("[data-row][data-col]");
1761
+ if (!cell) return;
1762
+ const next = e.relatedTarget;
1763
+ if (next && cell.contains(next)) return;
1764
+ this.leaveCell(cell);
1765
+ }
1766
+ onGridClick(event) {
1767
+ if (!this.activateRows()) return;
1768
+ const target = event.target;
1769
+ const cell = target.closest("[data-row][data-col]");
1770
+ if (!cell || cell.closest(".data-table-head")) return;
1771
+ if (target.closest(WIDGET_SELECTOR) || target.closest("[data-grid-own]")) return;
1772
+ const entry = this.pageEntries()[Number(cell.dataset["row"])];
1773
+ if (entry) this.rowActivate.emit({
1774
+ row: entry.row,
1775
+ id: entry.id
1776
+ });
1777
+ }
1778
+ onGridDblclick(event) {
1779
+ const target = event.target;
1780
+ const cell = target.closest("[data-row][data-col]");
1781
+ if (!cell || cell.closest(".data-table-head") || target.closest("input, button, select, textarea")) return;
1782
+ const entry = this.pageEntries()[Number(cell.dataset["row"])];
1783
+ const column = this.visibleColumns()[Number(cell.dataset["col"]) - this.colOffset()];
1784
+ if (entry && column && this.isEditable(column)) this.startEdit(entry, column);
1785
+ }
1786
+ onGridKeydown(event) {
1787
+ if (!this.isGrid()) return;
1788
+ const e = event;
1789
+ const target = e.target;
1790
+ const cell = target.closest("[data-row][data-col]");
1791
+ if (!cell) return;
1792
+ if (target !== cell && !target.closest("[data-grid-own]")) {
1793
+ if (e.key === "Escape") {
1794
+ e.preventDefault();
1795
+ cell.focus({ preventScroll: true });
1796
+ }
1797
+ return;
1798
+ }
1799
+ const activeRow = this.activeRow();
1800
+ const activeCol = this.activeCol();
1801
+ const colOffset = this.colOffset();
1802
+ const key = logicalKey(e.key, isRtl(cell));
1803
+ if (e.altKey && activeRow === -1 && (key === "ArrowRight" || key === "ArrowLeft")) {
1804
+ const column = this.visibleColumns()[activeCol - colOffset];
1805
+ if (column && this.isMovable(column)) {
1806
+ e.preventDefault();
1807
+ const delta = key === "ArrowRight" ? 1 : -1;
1808
+ if (this.moveColumn(column.id, activeCol - colOffset + delta)) this.moveFocus(-1, activeCol + delta);
1809
+ return;
1810
+ }
1811
+ }
1812
+ const historyAction = target === cell && this.resolvedColumns().some((c) => c.editable) ? historyKey(e) : null;
1813
+ if (historyAction) {
1814
+ e.preventDefault();
1815
+ this.stepHistory(historyAction);
1816
+ return;
1817
+ }
1818
+ if (isTypeaheadKey(e) && target === cell && activeRow >= 0) {
1819
+ const column = this.visibleColumns()[activeCol - colOffset];
1820
+ if (column) {
1821
+ const state = nextTypeaheadState(this.typeahead, e.key, Date.now(), 700);
1822
+ const hit = findTypeaheadMatch(this.pageEntries().map((entry) => this.cellFor(column, entry.row).formatted), activeRow, state.prefix);
1823
+ if (hit !== -1) {
1824
+ this.typeahead = state;
1825
+ e.preventDefault();
1826
+ this.moveFocus(hit, activeCol);
1827
+ return;
1828
+ }
1829
+ }
1830
+ }
1831
+ const move = gridMove(e, {
1832
+ row: activeRow,
1833
+ col: activeCol
1834
+ }, {
1835
+ firstRow: -1,
1836
+ lastRow: this.pageEntries().length - 1,
1837
+ lastCol: this.lastCol(),
1838
+ pageStep: this.pageSize() ?? 10,
1839
+ rtl: isRtl(cell)
1840
+ });
1841
+ if (move) {
1842
+ e.preventDefault();
1843
+ this.moveFocus(move.row, move.col);
1844
+ return;
1845
+ }
1846
+ switch (key) {
1847
+ case "F2": {
1848
+ const entry = this.pageEntries()[Number(cell.dataset["row"])];
1849
+ const column = this.visibleColumns()[Number(cell.dataset["col"]) - colOffset];
1850
+ if (target === cell && entry && column && this.isEditable(column)) {
1851
+ e.preventDefault();
1852
+ this.startEdit(entry, column);
1853
+ }
1854
+ break;
1855
+ }
1856
+ case "Enter":
1857
+ case " ":
1858
+ e.preventDefault();
1859
+ if (e.key === "Enter" && target === cell && cell.closest(".data-table-body")) {
1860
+ const entry = this.pageEntries()[Number(cell.dataset["row"])];
1861
+ const column = this.visibleColumns()[Number(cell.dataset["col"]) - colOffset];
1862
+ if (entry && column && this.isEditable(column)) {
1863
+ this.startEdit(entry, column);
1864
+ break;
1865
+ }
1866
+ }
1867
+ if (target === cell && cell.closest(".data-table-body") && !cell.querySelector("[data-grid-own]")) {
1868
+ if (this.enterCell(cell)) break;
1869
+ if (e.key === "Enter" && this.activateRows()) {
1870
+ const entry = this.pageEntries()[Number(cell.dataset["row"])];
1871
+ if (entry) this.rowActivate.emit({
1872
+ row: entry.row,
1873
+ id: entry.id
1874
+ });
1875
+ break;
1876
+ }
1877
+ }
1878
+ this.activateFocusedCell();
1879
+ }
1880
+ }
1881
+ restoreView(key) {
1882
+ try {
1883
+ const raw = localStorage.getItem(key);
1884
+ if (raw) {
1885
+ const saved = JSON.parse(raw);
1886
+ if (saved.columnWidths) this.columnWidths.set(saved.columnWidths);
1887
+ if (saved.hiddenColumnIds) this.hiddenColumnIds.set(new Set(saved.hiddenColumnIds));
1888
+ if (saved.sort) this.sort.set(saved.sort);
1889
+ }
1890
+ } catch {}
1891
+ }
1892
+ rowLabelOf(entry) {
1893
+ const first = this.visibleColumns()[0];
1894
+ return first ? this.cellFor(first, entry.row).formatted : entry.id;
1895
+ }
1896
+ cellClassOf(column, ctx) {
1897
+ return (typeof column.cellClass === "function" ? column.cellClass(ctx) : column.cellClass) ?? "";
1898
+ }
1899
+ peopleIn(value) {
1900
+ return peopleOf(value);
1901
+ }
1902
+ shownPeople(column, value) {
1903
+ const people = peopleOf(value);
1904
+ return column.maxPeople !== void 0 ? people.slice(0, Math.max(0, column.maxPeople)) : people;
1905
+ }
1906
+ nameOf(person) {
1907
+ return personName(person);
1908
+ }
1909
+ filesIn(value) {
1910
+ return filesOf(value);
1911
+ }
1912
+ linkIn(value) {
1913
+ return linkParts(value);
1914
+ }
1915
+ hrefOf(href) {
1916
+ return href === void 0 ? null : safeHref(href);
1917
+ }
1918
+ stringOf(value) {
1919
+ return String(value);
1920
+ }
1921
+ ungroupedOptions(column) {
1922
+ return (column.options ?? []).filter((o) => !o.group);
1923
+ }
1924
+ groupsOf(column) {
1925
+ return [...new Set((column.options ?? []).filter((o) => o.group).map((o) => o.group))];
1926
+ }
1927
+ optionsInGroup(column, group) {
1928
+ return (column.options ?? []).filter((o) => o.group === group);
1929
+ }
1930
+ static {
1931
+ this.ɵfac = i0.ɵɵngDeclareFactory({
1932
+ minVersion: "12.0.0",
1933
+ version: "22.2.3",
1934
+ ngImport: i0,
1935
+ type: DataGrid,
1936
+ deps: [],
1937
+ target: i0.ɵɵFactoryTarget.Component
1938
+ });
1939
+ }
1940
+ static {
1941
+ this.ɵcmp = i0.ɵɵngDeclareComponent({
1942
+ minVersion: "17.0.0",
1943
+ version: "22.2.3",
1944
+ type: DataGrid,
1945
+ isStandalone: true,
1946
+ selector: "lily-data-grid",
1947
+ inputs: {
1948
+ label: {
1949
+ classPropertyName: "label",
1950
+ publicName: "label",
1951
+ isSignal: true,
1952
+ isRequired: true,
1953
+ transformFunction: null
1954
+ },
1955
+ caption: {
1956
+ classPropertyName: "caption",
1957
+ publicName: "caption",
1958
+ isSignal: true,
1959
+ isRequired: false,
1960
+ transformFunction: null
1961
+ },
1962
+ columns: {
1963
+ classPropertyName: "columns",
1964
+ publicName: "columns",
1965
+ isSignal: true,
1966
+ isRequired: true,
1967
+ transformFunction: null
1968
+ },
1969
+ rows: {
1970
+ classPropertyName: "rows",
1971
+ publicName: "rows",
1972
+ isSignal: true,
1973
+ isRequired: true,
1974
+ transformFunction: null
1975
+ },
1976
+ locale: {
1977
+ classPropertyName: "locale",
1978
+ publicName: "locale",
1979
+ isSignal: true,
1980
+ isRequired: false,
1981
+ transformFunction: null
1982
+ },
1983
+ emptyValue: {
1984
+ classPropertyName: "emptyValue",
1985
+ publicName: "emptyValue",
1986
+ isSignal: true,
1987
+ isRequired: false,
1988
+ transformFunction: null
1989
+ },
1990
+ warnMissingLabels: {
1991
+ classPropertyName: "warnMissingLabels",
1992
+ publicName: "warnMissingLabels",
1993
+ isSignal: true,
1994
+ isRequired: false,
1995
+ transformFunction: null
1996
+ },
1997
+ nulls: {
1998
+ classPropertyName: "nulls",
1999
+ publicName: "nulls",
2000
+ isSignal: true,
2001
+ isRequired: false,
2002
+ transformFunction: null
2003
+ },
2004
+ movableColumns: {
2005
+ classPropertyName: "movableColumns",
2006
+ publicName: "movableColumns",
2007
+ isSignal: true,
2008
+ isRequired: false,
2009
+ transformFunction: null
2010
+ },
2011
+ rowFilter: {
2012
+ classPropertyName: "rowFilter",
2013
+ publicName: "rowFilter",
2014
+ isSignal: true,
2015
+ isRequired: false,
2016
+ transformFunction: null
2017
+ },
2018
+ rowId: {
2019
+ classPropertyName: "rowId",
2020
+ publicName: "rowId",
2021
+ isSignal: true,
2022
+ isRequired: false,
2023
+ transformFunction: null
2024
+ },
2025
+ selectionMode: {
2026
+ classPropertyName: "selectionMode",
2027
+ publicName: "selectionMode",
2028
+ isSignal: true,
2029
+ isRequired: false,
2030
+ transformFunction: null
2031
+ },
2032
+ pageSize: {
2033
+ classPropertyName: "pageSize",
2034
+ publicName: "pageSize",
2035
+ isSignal: true,
2036
+ isRequired: false,
2037
+ transformFunction: null
2038
+ },
2039
+ storageKey: {
2040
+ classPropertyName: "storageKey",
2041
+ publicName: "storageKey",
2042
+ isSignal: true,
2043
+ isRequired: false,
2044
+ transformFunction: null
2045
+ },
2046
+ labels: {
2047
+ classPropertyName: "labels",
2048
+ publicName: "labels",
2049
+ isSignal: true,
2050
+ isRequired: false,
2051
+ transformFunction: null
2052
+ },
2053
+ mode: {
2054
+ classPropertyName: "mode",
2055
+ publicName: "mode",
2056
+ isSignal: true,
2057
+ isRequired: false,
2058
+ transformFunction: null
2059
+ },
2060
+ rowClass: {
2061
+ classPropertyName: "rowClass",
2062
+ publicName: "rowClass",
2063
+ isSignal: true,
2064
+ isRequired: false,
2065
+ transformFunction: null
2066
+ },
2067
+ rowAttrs: {
2068
+ classPropertyName: "rowAttrs",
2069
+ publicName: "rowAttrs",
2070
+ isSignal: true,
2071
+ isRequired: false,
2072
+ transformFunction: null
2073
+ },
2074
+ activateRows: {
2075
+ classPropertyName: "activateRows",
2076
+ publicName: "activateRows",
2077
+ isSignal: true,
2078
+ isRequired: false,
2079
+ transformFunction: null
2080
+ },
2081
+ empty: {
2082
+ classPropertyName: "empty",
2083
+ publicName: "empty",
2084
+ isSignal: true,
2085
+ isRequired: false,
2086
+ transformFunction: null
2087
+ },
2088
+ footer: {
2089
+ classPropertyName: "footer",
2090
+ publicName: "footer",
2091
+ isSignal: true,
2092
+ isRequired: false,
2093
+ transformFunction: null
2094
+ },
2095
+ busy: {
2096
+ classPropertyName: "busy",
2097
+ publicName: "busy",
2098
+ isSignal: true,
2099
+ isRequired: false,
2100
+ transformFunction: null
2101
+ },
2102
+ scroll: {
2103
+ classPropertyName: "scroll",
2104
+ publicName: "scroll",
2105
+ isSignal: true,
2106
+ isRequired: false,
2107
+ transformFunction: null
2108
+ },
2109
+ exportFilename: {
2110
+ classPropertyName: "exportFilename",
2111
+ publicName: "exportFilename",
2112
+ isSignal: true,
2113
+ isRequired: false,
2114
+ transformFunction: null
2115
+ },
2116
+ exportScope: {
2117
+ classPropertyName: "exportScope",
2118
+ publicName: "exportScope",
2119
+ isSignal: true,
2120
+ isRequired: false,
2121
+ transformFunction: null
2122
+ },
2123
+ exportFormulas: {
2124
+ classPropertyName: "exportFormulas",
2125
+ publicName: "exportFormulas",
2126
+ isSignal: true,
2127
+ isRequired: false,
2128
+ transformFunction: null
2129
+ },
2130
+ exportHandler: {
2131
+ classPropertyName: "exportHandler",
2132
+ publicName: "exportHandler",
2133
+ isSignal: true,
2134
+ isRequired: false,
2135
+ transformFunction: null
2136
+ },
2137
+ copyHandler: {
2138
+ classPropertyName: "copyHandler",
2139
+ publicName: "copyHandler",
2140
+ isSignal: true,
2141
+ isRequired: false,
2142
+ transformFunction: null
2143
+ },
2144
+ virtualize: {
2145
+ classPropertyName: "virtualize",
2146
+ publicName: "virtualize",
2147
+ isSignal: true,
2148
+ isRequired: false,
2149
+ transformFunction: null
2150
+ },
2151
+ className: {
2152
+ classPropertyName: "className",
2153
+ publicName: "className",
2154
+ isSignal: true,
2155
+ isRequired: false,
2156
+ transformFunction: null
2157
+ },
2158
+ selected: {
2159
+ classPropertyName: "selected",
2160
+ publicName: "selected",
2161
+ isSignal: true,
2162
+ isRequired: false,
2163
+ transformFunction: null
2164
+ },
2165
+ filter: {
2166
+ classPropertyName: "filter",
2167
+ publicName: "filter",
2168
+ isSignal: true,
2169
+ isRequired: false,
2170
+ transformFunction: null
2171
+ },
2172
+ filterField: {
2173
+ classPropertyName: "filterField",
2174
+ publicName: "filterField",
2175
+ isSignal: true,
2176
+ isRequired: false,
2177
+ transformFunction: null
2178
+ },
2179
+ thenBy: {
2180
+ classPropertyName: "thenBy",
2181
+ publicName: "thenBy",
2182
+ isSignal: true,
2183
+ isRequired: false,
2184
+ transformFunction: null
2185
+ },
2186
+ sort: {
2187
+ classPropertyName: "sort",
2188
+ publicName: "sort",
2189
+ isSignal: true,
2190
+ isRequired: false,
2191
+ transformFunction: null
2192
+ },
2193
+ columnOrder: {
2194
+ classPropertyName: "columnOrder",
2195
+ publicName: "columnOrder",
2196
+ isSignal: true,
2197
+ isRequired: false,
2198
+ transformFunction: null
2199
+ },
2200
+ page: {
2201
+ classPropertyName: "page",
2202
+ publicName: "page",
2203
+ isSignal: true,
2204
+ isRequired: false,
2205
+ transformFunction: null
2206
+ }
2207
+ },
2208
+ outputs: {
2209
+ selected: "selectedChange",
2210
+ filter: "filterChange",
2211
+ filterField: "filterFieldChange",
2212
+ thenBy: "thenByChange",
2213
+ sort: "sortChange",
2214
+ columnOrder: "columnOrderChange",
2215
+ page: "pageChange",
2216
+ cellEdit: "cellEdit",
2217
+ filesSelected: "filesSelected",
2218
+ fileRemove: "fileRemove",
2219
+ rowActivate: "rowActivate",
2220
+ exportFile: "exportFile",
2221
+ copy: "copy"
2222
+ },
2223
+ viewQueries: [{
2224
+ propertyName: "rootRef",
2225
+ first: true,
2226
+ predicate: ["rootEl"],
2227
+ descendants: true,
2228
+ isSignal: true
2229
+ }, {
2230
+ propertyName: "scrollRef",
2231
+ first: true,
2232
+ predicate: ["scrollEl"],
2233
+ descendants: true,
2234
+ isSignal: true
2235
+ }],
2236
+ ngImport: i0,
2237
+ template: `
2238
+ <div #rootEl class="data-grid {{ className() }}">
2239
+ @if (labels().search || hidableColumns().length > 0 || hasExportButtons()) {
2240
+ <div class="data-grid-toolbar">
2241
+ @if (labels().search && labels().searchField && labels().searchAllFields) {
2242
+ <select class="data-grid-search-field" [attr.aria-label]="labels().searchField" [value]="filterField()" (change)="onFilterFieldChange($event)">
2243
+ <option value="">{{ labels().searchAllFields }}</option>
2244
+ @for (c of filterableColumns(); track c.id) {
2245
+ <option [value]="c.id">{{ c.header }}</option>
2246
+ }
2247
+ </select>
2248
+ }
2249
+ @if (labels().search; as searchLabel) {
2250
+ <lily-search-input className="data-grid-search" [label]="searchLabel" [value]="filter()" (valueChange)="setFilter($event)" (keydown)="onFilterKeydown($event)" />
2251
+ }
2252
+ @if (hasExportButtons()) {
2253
+ <div class="data-grid-export" role="group" [attr.aria-label]="labels().export ?? null">
2254
+ @if (labels().copy; as copy) {
2255
+ <button type="button" class="data-grid-export-button" data-grid-export-format="copy" [disabled]="exportEntries().length === 0" (click)="runCopy()">{{ copy }}</button>
2256
+ }
2257
+ @if (labels().exportCsv; as csv) {
2258
+ <button type="button" class="data-grid-export-button" data-grid-export-format="csv" [disabled]="exportEntries().length === 0" (click)="runExport('csv')">{{ csv }}</button>
2259
+ }
2260
+ @if (labels().exportTsv; as tsv) {
2261
+ <button type="button" class="data-grid-export-button" data-grid-export-format="tsv" [disabled]="exportEntries().length === 0" (click)="runExport('tsv')">{{ tsv }}</button>
2262
+ }
2263
+ @if (labels().exportExcel; as excel) {
2264
+ <button type="button" class="data-grid-export-button" data-grid-export-format="xlsx" [disabled]="exportEntries().length === 0" (click)="runExport('xlsx')">{{ excel }}</button>
2265
+ }
2266
+ </div>
2267
+ }
2268
+ @if (hidableColumns().length > 0 && labels().columnVisibility; as visibilityLabel) {
2269
+ <fieldset class="data-grid-column-visibility">
2270
+ <legend>{{ visibilityLabel }}</legend>
2271
+ @for (column of hidableColumns(); track column.id) {
2272
+ <label>
2273
+ <input type="checkbox" [checked]="!hiddenColumnIds().has(column.id)" (change)="toggleColumnVisibility(column.id)" />
2274
+ {{ labels().columnVisibilityOption?.(column.header) ?? column.header }}
2275
+ </label>
2276
+ }
2277
+ </fieldset>
2278
+ }
2279
+ </div>
2280
+ }
2281
+
2282
+ @if (scroll()) {
2283
+ <!-- A scrollable region must be reachable by keyboard (WCAG 2.1.1); in grid mode a roving cell is already a tab stop. -->
2284
+ <div
2285
+ #scrollEl
2286
+ class="data-grid-scroll"
2287
+ role="region"
2288
+ [attr.aria-label]="label()"
2289
+ [attr.tabindex]="isGrid() ? null : 0"
2290
+ [attr.style]="scrollStyle()"
2291
+ (scroll)="onScroll($event)"
2292
+ >
2293
+ <ng-container *ngTemplateOutlet="tableTemplate" />
2294
+ </div>
2295
+ } @else {
2296
+ <ng-container *ngTemplateOutlet="tableTemplate" />
2297
+ }
2298
+
2299
+ @if (pageSize()) {
2300
+ @if (labels().pagination; as pagination) {
2301
+ <lily-pagination-nav className="data-grid-pagination" [label]="pagination">
2302
+ <ng-container *ngTemplateOutlet="pager" />
2303
+ </lily-pagination-nav>
2304
+ } @else {
2305
+ <div class="data-grid-pagination">
2306
+ <ng-container *ngTemplateOutlet="pager" />
2307
+ </div>
2308
+ }
2309
+ }
2310
+
2311
+ <p class="data-grid-status" aria-live="polite">{{ statusMessage() }}</p>
2312
+ </div>
2313
+
2314
+ <ng-template #pager>
2315
+ <button type="button" class="data-grid-page-previous" [disabled]="clampedPage() <= 1" (click)="goToPage(clampedPage() - 1)">{{ labels().previousPage }}</button>
2316
+ <span class="data-grid-page-status">{{ labels().pageStatus?.(clampedPage(), pageCount(), sortedEntries().length) }}</span>
2317
+ <button type="button" class="data-grid-page-next" [disabled]="clampedPage() >= pageCount()" (click)="goToPage(clampedPage() + 1)">{{ labels().nextPage }}</button>
2318
+ </ng-template>
2319
+
2320
+ <ng-template #tableTemplate>
2321
+ <lily-data-table
2322
+ [label]="label()"
2323
+ [className]="''"
2324
+ (keydown)="onGridKeydown($event)"
2325
+ (focusin)="onGridFocusin($event)"
2326
+ (focusout)="onGridFocusout($event)"
2327
+ (click)="onGridClick($event)"
2328
+ (dblclick)="onGridDblclick($event)"
2329
+ >
2330
+ @if (caption()) {
2331
+ <caption>{{ caption() }}</caption>
2332
+ }
2333
+ <thead lily-data-table-head>
2334
+ <tr lily-data-table-row [attr.aria-rowindex]="indexed() ? 1 : null">
2335
+ @if (hasSelection()) {
2336
+ <th
2337
+ lily-data-table-th
2338
+ data-row="-1"
2339
+ data-col="0"
2340
+ [attr.tabindex]="isGrid() ? (activeRow() === -1 && activeCol() === 0 ? 0 : -1) : null"
2341
+ >
2342
+ @if (selectionMode() === 'multiple') {
2343
+ <input
2344
+ type="checkbox"
2345
+ data-grid-own
2346
+ [attr.tabindex]="isGrid() ? -1 : null"
2347
+ [attr.aria-label]="labels().selectAll ?? null"
2348
+ [checked]="allSelected()"
2349
+ [indeterminate]="someSelected()"
2350
+ (click)="toggleSelectAll()"
2351
+ />
2352
+ } @else {
2353
+ <span class="data-grid-selection-header-label">{{ labels().selectionColumn }}</span>
2354
+ }
2355
+ </th>
2356
+ }
2357
+ @for (column of visibleColumns(); track column.id; let i = $index) {
2358
+ <th
2359
+ lily-data-table-th
2360
+ data-row="-1"
2361
+ [attr.data-col]="colOffset() + i"
2362
+ [attr.data-align]="column.align ?? null"
2363
+ [attr.data-cell-type]="column.type ?? null"
2364
+ [attr.data-pinned]="column.pinned ?? null"
2365
+ [attr.tabindex]="isGrid() ? (activeRow() === -1 && activeCol() === colOffset() + i ? 0 : -1) : null"
2366
+ [attr.aria-sort]="ariaSortFor(column)"
2367
+ [attr.data-sort-order]="sortOrderFor(column)"
2368
+ [attr.aria-describedby]="column.type === 'rollup' && labels().rollupDescription ? gridId + '-' + column.id + '-description' : null"
2369
+ [attr.style]="cellStyle(column)"
2370
+ [attr.draggable]="isMovable(column) ? 'true' : null"
2371
+ (dragstart)="onHeaderDragStart(column, $event)"
2372
+ (dragover)="onHeaderDragOver($event)"
2373
+ (drop)="onHeaderDrop(column.id, $event)"
2374
+ >
2375
+ @if (column.sortable) {
2376
+ <button type="button" class="data-grid-sort-button" data-grid-own [attr.tabindex]="isGrid() ? -1 : null" (click)="toggleSort(column, $any($event).shiftKey)">
2377
+ @if (column.headerCell; as headerTemplate) {
2378
+ <ng-container *ngTemplateOutlet="headerTemplate; context: { $implicit: column }" />
2379
+ } @else {<ng-container>{{ column.header }}</ng-container>}
2380
+ </button>
2381
+ } @else if (column.headerCell; as headerTemplate) {
2382
+ <ng-container *ngTemplateOutlet="headerTemplate; context: { $implicit: column }" />
2383
+ } @else {<ng-container>{{ column.header }}</ng-container>}
2384
+ @if (column.type === 'rollup' && labels().rollupDescription; as describe) {
2385
+ <span class="data-grid-visually-hidden" [id]="gridId + '-' + column.id + '-description'">{{ describe(column.header) }}</span>
2386
+ }
2387
+ @if (column.resizable) {
2388
+ <!-- WAI-ARIA "window splitter" pattern: role="separator" plus tabindex/aria-valuenow is how a resize handle becomes focusable and operable. -->
2389
+ <span
2390
+ class="data-grid-resize-handle"
2391
+ role="separator"
2392
+ data-grid-own
2393
+ aria-orientation="vertical"
2394
+ [attr.aria-label]="labels().resizeHandle?.(column.header) ?? null"
2395
+ [attr.aria-valuenow]="widthFor(column) ?? 120"
2396
+ [attr.aria-valuemin]="minColumnWidth"
2397
+ tabindex="0"
2398
+ (keydown)="onResizeKeydown(column, $event)"
2399
+ (pointerdown)="onResizePointerDown(column, $event)"
2400
+ ></span>
2401
+ }
2402
+ </th>
2403
+ }
2404
+ </tr>
2405
+ </thead>
2406
+ <tbody lily-data-table-body>
2407
+ @for (item of renderItems(); track item.kind === 'row' ? pageEntries()[item.index].id : item.key) {
2408
+ @if (item.kind === 'spacer') {
2409
+ <tr class="data-grid-spacer" aria-hidden="true" [attr.style]="'height:' + item.rows * (virtualize()?.rowHeight ?? 0) + 'px'">
2410
+ <td [attr.colspan]="lastCol() + 1"></td>
2411
+ </tr>
2412
+ } @else {
2413
+ @let entry = pageEntries()[item.index];
2414
+ <tr
2415
+ lily-data-table-row
2416
+ [className]="rowClass()?.(entry.row) ?? ''"
2417
+ [attr.aria-selected]="hasSelection() ? isSelected(entry.id) : null"
2418
+ [attr.aria-rowindex]="indexed() ? pageStart() + item.index + 2 : null"
2419
+ [lilyDataGridAttrs]="rowAttrs()?.(entry.row)"
2420
+ >
2421
+ @if (hasSelection()) {
2422
+ <td
2423
+ lily-data-table-td
2424
+ [attr.role]="isGrid() ? 'gridcell' : 'cell'"
2425
+ [attr.data-row]="item.index"
2426
+ data-col="0"
2427
+ [attr.tabindex]="isGrid() ? (activeRow() === item.index && activeCol() === 0 ? 0 : -1) : null"
2428
+ >
2429
+ <input
2430
+ [type]="selectionMode() === 'single' ? 'radio' : 'checkbox'"
2431
+ data-grid-own
2432
+ [attr.tabindex]="isGrid() ? -1 : null"
2433
+ [attr.aria-label]="labels().selectRow?.(rowLabelOf(entry)) ?? null"
2434
+ [checked]="isSelected(entry.id)"
2435
+ (click)="onRowSelectClick(pageStart() + item.index, $event)"
2436
+ />
2437
+ </td>
2438
+ }
2439
+ @for (column of visibleColumns(); track column.id; let colIndex = $index) {
2440
+ @let ctx = cellFor(column, entry.row);
2441
+ @if (column.rowHeader) {
2442
+ <th
2443
+ lily-data-table-th
2444
+ scope="row"
2445
+ role="rowheader"
2446
+ [className]="cellClassOf(column, ctx)"
2447
+ [attr.data-row]="item.index"
2448
+ [attr.data-col]="colOffset() + colIndex"
2449
+ [attr.data-align]="column.align ?? null"
2450
+ [attr.data-cell-type]="column.type ?? null"
2451
+ [attr.data-pinned]="column.pinned ?? null"
2452
+ [attr.data-error]="column.errorOf?.(entry.row) !== undefined ? '' : null"
2453
+ [attr.data-empty]="isMissing(ctx.value) ? '' : null"
2454
+ [attr.tabindex]="isGrid() ? (activeRow() === item.index && activeCol() === colOffset() + colIndex ? 0 : -1) : null"
2455
+ [attr.style]="cellStyle(column)"
2456
+ [lilyDataGridAttrs]="column.cellAttrs?.(ctx)"
2457
+ >
2458
+ <ng-container *ngTemplateOutlet="cellBody; context: { entry: entry, column: column, ctx: ctx }" />
2459
+ </th>
2460
+ } @else {
2461
+ <td
2462
+ lily-data-table-td
2463
+ [className]="cellClassOf(column, ctx)"
2464
+ [attr.role]="isGrid() ? 'gridcell' : 'cell'"
2465
+ [attr.data-row]="item.index"
2466
+ [attr.data-col]="colOffset() + colIndex"
2467
+ [attr.data-align]="column.align ?? null"
2468
+ [attr.data-cell-type]="column.type ?? null"
2469
+ [attr.data-pinned]="column.pinned ?? null"
2470
+ [attr.data-editable]="isEditable(column) ? '' : null"
2471
+ [attr.data-readonly]="column.type === 'rollup' ? '' : null"
2472
+ [attr.aria-readonly]="column.type === 'rollup' ? 'true' : null"
2473
+ [attr.data-error]="column.errorOf?.(entry.row) !== undefined ? '' : null"
2474
+ [attr.data-empty]="isMissing(ctx.value) ? '' : null"
2475
+ [attr.data-editing]="isEditingCell(entry, column) ? '' : null"
2476
+ [attr.tabindex]="isGrid() ? (activeRow() === item.index && activeCol() === colOffset() + colIndex ? 0 : -1) : null"
2477
+ [attr.style]="cellStyle(column)"
2478
+ [lilyDataGridAttrs]="column.cellAttrs?.(ctx)"
2479
+ (dragover)="onFileDragOver(column, $event)"
2480
+ (drop)="onFileDrop(entry, column, $event)"
2481
+ >
2482
+ <ng-container *ngTemplateOutlet="cellBody; context: { entry: entry, column: column, ctx: ctx }" />
2483
+ </td>
2484
+ }
2485
+ }
2486
+ </tr>
2487
+ }
2488
+ }
2489
+ @if (pageEntries().length === 0 && empty(); as emptyTemplate) {
2490
+ <tr lily-data-table-row className="data-grid-empty-row">
2491
+ <td lily-data-table-td className="data-grid-empty" [attr.colspan]="lastCol() + 1" [attr.role]="isGrid() ? 'gridcell' : 'cell'">
2492
+ <ng-container *ngTemplateOutlet="emptyTemplate" />
2493
+ </td>
2494
+ </tr>
2495
+ }
2496
+ </tbody>
2497
+ @if (footer(); as footerTemplate) {
2498
+ <tfoot lily-data-table-foot>
2499
+ <ng-container *ngTemplateOutlet="footerTemplate; context: { $implicit: footerContext() }" />
2500
+ </tfoot>
2501
+ }
2502
+ </lily-data-table>
2503
+ </ng-template>
2504
+
2505
+ <ng-template #cellBody let-entry="entry" let-column="column" let-ctx="ctx">
2506
+ @if (isEditingCell(entry, column)) {
2507
+ @let editing = editingState()!;
2508
+ @if (column.editor === 'person' && column.personEditor) {
2509
+ <ng-container *ngTemplateOutlet="column.personEditor; context: { $implicit: personEditorContext(entry, column, ctx) }" />
2510
+ } @else if (column.editor === 'time-zone') {
2511
+ <span
2512
+ class="data-grid-zoned-editor"
2513
+ role="group"
2514
+ [attr.aria-label]="labels().cellEditor?.(column.header) ?? column.header"
2515
+ (focusout)="onZonedFocusout($event)"
2516
+ >
2517
+ <input
2518
+ class="data-grid-cell-editor"
2519
+ type="time"
2520
+ step="1"
2521
+ [attr.aria-label]="labels().cellEditor?.(column.header) ?? column.header"
2522
+ [attr.aria-invalid]="editing.error || editing.invalid ? 'true' : null"
2523
+ [attr.aria-describedby]="editing.error ? entry.id + '-' + column.id + '-error' : null"
2524
+ [value]="editing.draft"
2525
+ (input)="setDraft($event)"
2526
+ (keydown)="onEditorKeydown($event)"
2527
+ />
2528
+ <select
2529
+ class="data-grid-cell-editor data-grid-zone-editor"
2530
+ [attr.aria-label]="labels().timeZone?.(column.header) ?? column.header"
2531
+ (change)="setZone($event)"
2532
+ (keydown)="onEditorKeydown($event)"
2533
+ >
2534
+ @for (option of column.timeZones ?? []; track option.id) {
2535
+ <option [value]="option.id" [selected]="option.id === editing.zone">{{ option.label }}</option>
2536
+ }
2537
+ </select>
2538
+ </span>
2539
+ } @else if (column.editor === 'select') {
2540
+ <select
2541
+ class="data-grid-cell-editor"
2542
+ [attr.aria-label]="labels().cellEditor?.(column.header) ?? column.header"
2543
+ [attr.aria-invalid]="editing.error || editing.invalid ? 'true' : null"
2544
+ [attr.aria-describedby]="editing.error ? entry.id + '-' + column.id + '-error' : null"
2545
+ (change)="setDraft($event)"
2546
+ (keydown)="onEditorKeydown($event)"
2547
+ (blur)="onEditorBlur()"
2548
+ >
2549
+ <option value="" [selected]="editing.draft === ''"></option>
2550
+ @for (option of ungroupedOptions(column); track option.id) {
2551
+ <option [value]="option.id" [disabled]="option.disabled ?? false" [selected]="option.id === editing.draft">{{ option.label }}</option>
2552
+ }
2553
+ @for (group of groupsOf(column); track group) {
2554
+ <optgroup [label]="group">
2555
+ @for (option of optionsInGroup(column, group); track option.id) {
2556
+ <option [value]="option.id" [disabled]="option.disabled ?? false" [selected]="option.id === editing.draft">{{ option.label }}</option>
2557
+ }
2558
+ </optgroup>
2559
+ }
2560
+ </select>
2561
+ } @else {
2562
+ <input
2563
+ class="data-grid-cell-editor"
2564
+ [type]="column.editor === 'number' ? 'number' : column.editor === 'date' ? 'date' : column.editor === 'time' ? 'time' : 'text'"
2565
+ [attr.step]="column.editor === 'time' ? 1 : null"
2566
+ [attr.inputmode]="column.editor === 'number' ? 'decimal' : null"
2567
+ [attr.aria-label]="(column.type === 'formula' ? labels().formulaEditor?.(column.header) : undefined) ?? labels().cellEditor?.(column.header) ?? column.header"
2568
+ [attr.aria-invalid]="editing.error || editing.invalid ? 'true' : null"
2569
+ [attr.aria-describedby]="editing.error ? entry.id + '-' + column.id + '-error' : null"
2570
+ [value]="editing.draft"
2571
+ (input)="setDraft($event)"
2572
+ (keydown)="onEditorKeydown($event)"
2573
+ (blur)="onEditorBlur()"
2574
+ />
2575
+ }
2576
+ @if (editing.error) {
2577
+ <span class="data-grid-cell-error" [id]="entry.id + '-' + column.id + '-error'">{{ editing.error }}</span>
2578
+ }
2579
+ } @else if (column.type === 'radio' && column.editable) {
2580
+ <fieldset class="data-grid-radio-group" data-grid-own-group>
2581
+ <legend class="data-grid-radio-legend">{{ labels().radioGroup?.(column.header, rowLabelOf(entry)) ?? column.header }}</legend>
2582
+ @for (option of column.options ?? []; track option.id) {
2583
+ <label class="data-grid-radio-option">
2584
+ <input
2585
+ type="radio"
2586
+ [name]="gridId + '-' + entry.id + '-' + column.id"
2587
+ [value]="option.id"
2588
+ [checked]="ctx.value === option.id"
2589
+ [disabled]="option.disabled ?? false"
2590
+ [attr.tabindex]="isGrid() ? -1 : null"
2591
+ (change)="commitValue(entry, column, option.id)"
2592
+ />{{ option.label }}</label>
2593
+ }
2594
+ </fieldset>
2595
+ } @else {
2596
+ @if (column.cell; as cellTemplate) {
2597
+ <ng-container *ngTemplateOutlet="cellTemplate; context: { $implicit: ctx }" />
2598
+ } @else if (isMissing(ctx.value) && (column.emptyValue ?? emptyValue())) {
2599
+ <ng-container>{{ column.emptyValue ?? emptyValue() }}</ng-container>
2600
+ } @else if (column.type === 'person') {
2601
+ @let people = peopleIn(ctx.value);
2602
+ @if (people.length > 0) {
2603
+ @let shown = shownPeople(column, ctx.value);
2604
+ <ul class="data-grid-people">
2605
+ @for (person of shown; track person.id) {
2606
+ <li class="data-grid-person">
2607
+ @if (column.avatar; as avatarTemplate) {
2608
+ <span class="data-grid-avatar" aria-hidden="true"><ng-container *ngTemplateOutlet="avatarTemplate; context: { $implicit: person }" /></span>
2609
+ }
2610
+ <span class="data-grid-person-name">{{ nameOf(person) }}</span>
2611
+ </li>
2612
+ }
2613
+ @if (shown.length < people.length && labels().morePeople; as more) {
2614
+ <li class="data-grid-people-more">{{ more(people.length - shown.length) }}</li>
2615
+ }
2616
+ </ul>
2617
+ }
2618
+ } @else if (column.type === 'file') {
2619
+ @let files = filesIn(ctx.value);
2620
+ @if (files.length > 0) {
2621
+ <ul class="data-grid-files">
2622
+ @for (file of files; track file.id) {
2623
+ <li class="data-grid-file">
2624
+ @if (hrefOf(file.href); as fileHref) {
2625
+ <a class="data-grid-link" [href]="fileHref">{{ file.name }}</a>
2626
+ } @else {
2627
+ <span class="data-grid-file-name">{{ file.name }}</span>
2628
+ }
2629
+ @if (file.size !== undefined && column.formatSize; as sizeText) {
2630
+ {{ ' ' }}<span class="data-grid-file-size">{{ column.formatSize(file.size) }}</span>
2631
+ }
2632
+ @if (column.editable && labels().removeFile; as removeLabel) {
2633
+ <button
2634
+ type="button"
2635
+ class="data-grid-file-remove"
2636
+ data-grid-own
2637
+ [attr.tabindex]="isGrid() ? -1 : null"
2638
+ [attr.aria-label]="removeLabel(file.name)"
2639
+ (click)="fileRemove.emit({ rowId: entry.id, row: entry.row, columnId: column.id, fileId: file.id })"
2640
+ ></button>
2641
+ }
2642
+ </li>
2643
+ }
2644
+ </ul>
2645
+ }
2646
+ @if (column.editable) {
2647
+ <input
2648
+ type="file"
2649
+ class="data-grid-file-input"
2650
+ [attr.aria-label]="labels().fileInput?.(column.header, rowLabelOf(entry)) ?? column.header"
2651
+ [multiple]="column.multiple !== false"
2652
+ [attr.accept]="column.accept ?? null"
2653
+ [attr.tabindex]="isGrid() ? -1 : null"
2654
+ (change)="onFileInput(entry, column, $event)"
2655
+ />
2656
+ }
2657
+ } @else if (column.type === 'link') {
2658
+ @let link = linkIn(ctx.value);
2659
+ @if (link) {
2660
+ @if (hrefOf(link.href); as href) {
2661
+ <a class="data-grid-link" [href]="href" [attr.target]="link.external ? '_blank' : null" [attr.rel]="link.external ? 'noopener noreferrer' : null">{{ link.label ?? link.href }}@if (link.external && labels().opensInNewTab; as newTab) {{{ ' ' }}<span class="data-grid-visually-hidden">{{ newTab(link.label ?? link.href) }}</span>}</a>
2662
+ } @else {
2663
+ <!-- An address with an unsafe scheme is shown as text, never as a link. -->
2664
+ <span class="data-grid-link data-grid-link-unsafe">{{ link.label ?? link.href }}</span>
2665
+ }
2666
+ }
2667
+ } @else if (column.type === 'date' || column.type === 'time') {
2668
+ @if (ctx.formatted !== '') {
2669
+ <time [attr.datetime]="stringOf(ctx.value)">{{ ctx.formatted }}</time>
2670
+ }
2671
+ } @else {<ng-container>{{ ctx.formatted }}</ng-container>}
2672
+ @if (isEditable(column) && column.type !== 'radio' && labels().editCell; as editLabel) {
2673
+ <button
2674
+ type="button"
2675
+ class="data-grid-edit-button"
2676
+ data-grid-own
2677
+ [attr.tabindex]="isGrid() ? -1 : null"
2678
+ [attr.aria-label]="editLabel(column.header, rowLabelOf(entry))"
2679
+ (click)="startEdit(entry, column)"
2680
+ ></button>
2681
+ }
2682
+ }
2683
+ </ng-template>
2684
+ `,
2685
+ isInline: true,
2686
+ dependencies: [
2687
+ {
2688
+ kind: "directive",
2689
+ type: NgTemplateOutlet,
2690
+ selector: "[ngTemplateOutlet]",
2691
+ inputs: [
2692
+ "ngTemplateOutletContext",
2693
+ "ngTemplateOutlet",
2694
+ "ngTemplateOutletInjector"
2695
+ ]
2696
+ },
2697
+ {
2698
+ kind: "directive",
2699
+ type: DataGridAttrs,
2700
+ selector: "[lilyDataGridAttrs]",
2701
+ inputs: ["lilyDataGridAttrs"]
2702
+ },
2703
+ {
2704
+ kind: "component",
2705
+ type: DataTable,
2706
+ selector: "lily-data-table",
2707
+ inputs: ["label", "className"]
2708
+ },
2709
+ {
2710
+ kind: "component",
2711
+ type: DataTableBody,
2712
+ selector: "tbody[lily-data-table-body]",
2713
+ inputs: ["className"]
2714
+ },
2715
+ {
2716
+ kind: "component",
2717
+ type: DataTableFoot,
2718
+ selector: "tfoot[lily-data-table-foot]",
2719
+ inputs: ["className"]
2720
+ },
2721
+ {
2722
+ kind: "component",
2723
+ type: DataTableHead,
2724
+ selector: "thead[lily-data-table-head]",
2725
+ inputs: ["className"]
2726
+ },
2727
+ {
2728
+ kind: "component",
2729
+ type: DataTableRow,
2730
+ selector: "tr[lily-data-table-row]",
2731
+ inputs: ["className"]
2732
+ },
2733
+ {
2734
+ kind: "component",
2735
+ type: DataTableTD,
2736
+ selector: "td[lily-data-table-td]",
2737
+ inputs: ["className"]
2738
+ },
2739
+ {
2740
+ kind: "component",
2741
+ type: DataTableTH,
2742
+ selector: "th[lily-data-table-th]",
2743
+ inputs: ["scope", "className"]
2744
+ },
2745
+ {
2746
+ kind: "component",
2747
+ type: PaginationNav,
2748
+ selector: "lily-pagination-nav",
2749
+ inputs: ["label", "className"]
2750
+ },
2751
+ {
2752
+ kind: "component",
2753
+ type: SearchInput,
2754
+ selector: "lily-search-input",
2755
+ inputs: [
2756
+ "label",
2757
+ "className",
2758
+ "value",
2759
+ "required",
2760
+ "disabled"
2761
+ ],
2762
+ outputs: ["valueChange"]
2763
+ }
2764
+ ],
2765
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
2766
+ });
2767
+ }
2768
+ };
2769
+ i0.ɵɵngDeclareClassMetadata({
2770
+ minVersion: "12.0.0",
2771
+ version: "22.2.3",
2772
+ ngImport: i0,
2773
+ type: DataGrid,
2774
+ decorators: [{
2775
+ type: Component,
2776
+ args: [{
2777
+ selector: "lily-data-grid",
2778
+ standalone: true,
2779
+ imports: [
2780
+ NgTemplateOutlet,
2781
+ DataGridAttrs,
2782
+ DataTable,
2783
+ DataTableBody,
2784
+ DataTableFoot,
2785
+ DataTableHead,
2786
+ DataTableRow,
2787
+ DataTableTD,
2788
+ DataTableTH,
2789
+ PaginationNav,
2790
+ SearchInput
2791
+ ],
2792
+ changeDetection: ChangeDetectionStrategy.OnPush,
2793
+ template: `
2794
+ <div #rootEl class="data-grid {{ className() }}">
2795
+ @if (labels().search || hidableColumns().length > 0 || hasExportButtons()) {
2796
+ <div class="data-grid-toolbar">
2797
+ @if (labels().search && labels().searchField && labels().searchAllFields) {
2798
+ <select class="data-grid-search-field" [attr.aria-label]="labels().searchField" [value]="filterField()" (change)="onFilterFieldChange($event)">
2799
+ <option value="">{{ labels().searchAllFields }}</option>
2800
+ @for (c of filterableColumns(); track c.id) {
2801
+ <option [value]="c.id">{{ c.header }}</option>
2802
+ }
2803
+ </select>
2804
+ }
2805
+ @if (labels().search; as searchLabel) {
2806
+ <lily-search-input className="data-grid-search" [label]="searchLabel" [value]="filter()" (valueChange)="setFilter($event)" (keydown)="onFilterKeydown($event)" />
2807
+ }
2808
+ @if (hasExportButtons()) {
2809
+ <div class="data-grid-export" role="group" [attr.aria-label]="labels().export ?? null">
2810
+ @if (labels().copy; as copy) {
2811
+ <button type="button" class="data-grid-export-button" data-grid-export-format="copy" [disabled]="exportEntries().length === 0" (click)="runCopy()">{{ copy }}</button>
2812
+ }
2813
+ @if (labels().exportCsv; as csv) {
2814
+ <button type="button" class="data-grid-export-button" data-grid-export-format="csv" [disabled]="exportEntries().length === 0" (click)="runExport('csv')">{{ csv }}</button>
2815
+ }
2816
+ @if (labels().exportTsv; as tsv) {
2817
+ <button type="button" class="data-grid-export-button" data-grid-export-format="tsv" [disabled]="exportEntries().length === 0" (click)="runExport('tsv')">{{ tsv }}</button>
2818
+ }
2819
+ @if (labels().exportExcel; as excel) {
2820
+ <button type="button" class="data-grid-export-button" data-grid-export-format="xlsx" [disabled]="exportEntries().length === 0" (click)="runExport('xlsx')">{{ excel }}</button>
2821
+ }
2822
+ </div>
2823
+ }
2824
+ @if (hidableColumns().length > 0 && labels().columnVisibility; as visibilityLabel) {
2825
+ <fieldset class="data-grid-column-visibility">
2826
+ <legend>{{ visibilityLabel }}</legend>
2827
+ @for (column of hidableColumns(); track column.id) {
2828
+ <label>
2829
+ <input type="checkbox" [checked]="!hiddenColumnIds().has(column.id)" (change)="toggleColumnVisibility(column.id)" />
2830
+ {{ labels().columnVisibilityOption?.(column.header) ?? column.header }}
2831
+ </label>
2832
+ }
2833
+ </fieldset>
2834
+ }
2835
+ </div>
2836
+ }
2837
+
2838
+ @if (scroll()) {
2839
+ <!-- A scrollable region must be reachable by keyboard (WCAG 2.1.1); in grid mode a roving cell is already a tab stop. -->
2840
+ <div
2841
+ #scrollEl
2842
+ class="data-grid-scroll"
2843
+ role="region"
2844
+ [attr.aria-label]="label()"
2845
+ [attr.tabindex]="isGrid() ? null : 0"
2846
+ [attr.style]="scrollStyle()"
2847
+ (scroll)="onScroll($event)"
2848
+ >
2849
+ <ng-container *ngTemplateOutlet="tableTemplate" />
2850
+ </div>
2851
+ } @else {
2852
+ <ng-container *ngTemplateOutlet="tableTemplate" />
2853
+ }
2854
+
2855
+ @if (pageSize()) {
2856
+ @if (labels().pagination; as pagination) {
2857
+ <lily-pagination-nav className="data-grid-pagination" [label]="pagination">
2858
+ <ng-container *ngTemplateOutlet="pager" />
2859
+ </lily-pagination-nav>
2860
+ } @else {
2861
+ <div class="data-grid-pagination">
2862
+ <ng-container *ngTemplateOutlet="pager" />
2863
+ </div>
2864
+ }
2865
+ }
2866
+
2867
+ <p class="data-grid-status" aria-live="polite">{{ statusMessage() }}</p>
2868
+ </div>
2869
+
2870
+ <ng-template #pager>
2871
+ <button type="button" class="data-grid-page-previous" [disabled]="clampedPage() <= 1" (click)="goToPage(clampedPage() - 1)">{{ labels().previousPage }}</button>
2872
+ <span class="data-grid-page-status">{{ labels().pageStatus?.(clampedPage(), pageCount(), sortedEntries().length) }}</span>
2873
+ <button type="button" class="data-grid-page-next" [disabled]="clampedPage() >= pageCount()" (click)="goToPage(clampedPage() + 1)">{{ labels().nextPage }}</button>
2874
+ </ng-template>
2875
+
2876
+ <ng-template #tableTemplate>
2877
+ <lily-data-table
2878
+ [label]="label()"
2879
+ [className]="''"
2880
+ (keydown)="onGridKeydown($event)"
2881
+ (focusin)="onGridFocusin($event)"
2882
+ (focusout)="onGridFocusout($event)"
2883
+ (click)="onGridClick($event)"
2884
+ (dblclick)="onGridDblclick($event)"
2885
+ >
2886
+ @if (caption()) {
2887
+ <caption>{{ caption() }}</caption>
2888
+ }
2889
+ <thead lily-data-table-head>
2890
+ <tr lily-data-table-row [attr.aria-rowindex]="indexed() ? 1 : null">
2891
+ @if (hasSelection()) {
2892
+ <th
2893
+ lily-data-table-th
2894
+ data-row="-1"
2895
+ data-col="0"
2896
+ [attr.tabindex]="isGrid() ? (activeRow() === -1 && activeCol() === 0 ? 0 : -1) : null"
2897
+ >
2898
+ @if (selectionMode() === 'multiple') {
2899
+ <input
2900
+ type="checkbox"
2901
+ data-grid-own
2902
+ [attr.tabindex]="isGrid() ? -1 : null"
2903
+ [attr.aria-label]="labels().selectAll ?? null"
2904
+ [checked]="allSelected()"
2905
+ [indeterminate]="someSelected()"
2906
+ (click)="toggleSelectAll()"
2907
+ />
2908
+ } @else {
2909
+ <span class="data-grid-selection-header-label">{{ labels().selectionColumn }}</span>
2910
+ }
2911
+ </th>
2912
+ }
2913
+ @for (column of visibleColumns(); track column.id; let i = $index) {
2914
+ <th
2915
+ lily-data-table-th
2916
+ data-row="-1"
2917
+ [attr.data-col]="colOffset() + i"
2918
+ [attr.data-align]="column.align ?? null"
2919
+ [attr.data-cell-type]="column.type ?? null"
2920
+ [attr.data-pinned]="column.pinned ?? null"
2921
+ [attr.tabindex]="isGrid() ? (activeRow() === -1 && activeCol() === colOffset() + i ? 0 : -1) : null"
2922
+ [attr.aria-sort]="ariaSortFor(column)"
2923
+ [attr.data-sort-order]="sortOrderFor(column)"
2924
+ [attr.aria-describedby]="column.type === 'rollup' && labels().rollupDescription ? gridId + '-' + column.id + '-description' : null"
2925
+ [attr.style]="cellStyle(column)"
2926
+ [attr.draggable]="isMovable(column) ? 'true' : null"
2927
+ (dragstart)="onHeaderDragStart(column, $event)"
2928
+ (dragover)="onHeaderDragOver($event)"
2929
+ (drop)="onHeaderDrop(column.id, $event)"
2930
+ >
2931
+ @if (column.sortable) {
2932
+ <button type="button" class="data-grid-sort-button" data-grid-own [attr.tabindex]="isGrid() ? -1 : null" (click)="toggleSort(column, $any($event).shiftKey)">
2933
+ @if (column.headerCell; as headerTemplate) {
2934
+ <ng-container *ngTemplateOutlet="headerTemplate; context: { $implicit: column }" />
2935
+ } @else {<ng-container>{{ column.header }}</ng-container>}
2936
+ </button>
2937
+ } @else if (column.headerCell; as headerTemplate) {
2938
+ <ng-container *ngTemplateOutlet="headerTemplate; context: { $implicit: column }" />
2939
+ } @else {<ng-container>{{ column.header }}</ng-container>}
2940
+ @if (column.type === 'rollup' && labels().rollupDescription; as describe) {
2941
+ <span class="data-grid-visually-hidden" [id]="gridId + '-' + column.id + '-description'">{{ describe(column.header) }}</span>
2942
+ }
2943
+ @if (column.resizable) {
2944
+ <!-- WAI-ARIA "window splitter" pattern: role="separator" plus tabindex/aria-valuenow is how a resize handle becomes focusable and operable. -->
2945
+ <span
2946
+ class="data-grid-resize-handle"
2947
+ role="separator"
2948
+ data-grid-own
2949
+ aria-orientation="vertical"
2950
+ [attr.aria-label]="labels().resizeHandle?.(column.header) ?? null"
2951
+ [attr.aria-valuenow]="widthFor(column) ?? 120"
2952
+ [attr.aria-valuemin]="minColumnWidth"
2953
+ tabindex="0"
2954
+ (keydown)="onResizeKeydown(column, $event)"
2955
+ (pointerdown)="onResizePointerDown(column, $event)"
2956
+ ></span>
2957
+ }
2958
+ </th>
2959
+ }
2960
+ </tr>
2961
+ </thead>
2962
+ <tbody lily-data-table-body>
2963
+ @for (item of renderItems(); track item.kind === 'row' ? pageEntries()[item.index].id : item.key) {
2964
+ @if (item.kind === 'spacer') {
2965
+ <tr class="data-grid-spacer" aria-hidden="true" [attr.style]="'height:' + item.rows * (virtualize()?.rowHeight ?? 0) + 'px'">
2966
+ <td [attr.colspan]="lastCol() + 1"></td>
2967
+ </tr>
2968
+ } @else {
2969
+ @let entry = pageEntries()[item.index];
2970
+ <tr
2971
+ lily-data-table-row
2972
+ [className]="rowClass()?.(entry.row) ?? ''"
2973
+ [attr.aria-selected]="hasSelection() ? isSelected(entry.id) : null"
2974
+ [attr.aria-rowindex]="indexed() ? pageStart() + item.index + 2 : null"
2975
+ [lilyDataGridAttrs]="rowAttrs()?.(entry.row)"
2976
+ >
2977
+ @if (hasSelection()) {
2978
+ <td
2979
+ lily-data-table-td
2980
+ [attr.role]="isGrid() ? 'gridcell' : 'cell'"
2981
+ [attr.data-row]="item.index"
2982
+ data-col="0"
2983
+ [attr.tabindex]="isGrid() ? (activeRow() === item.index && activeCol() === 0 ? 0 : -1) : null"
2984
+ >
2985
+ <input
2986
+ [type]="selectionMode() === 'single' ? 'radio' : 'checkbox'"
2987
+ data-grid-own
2988
+ [attr.tabindex]="isGrid() ? -1 : null"
2989
+ [attr.aria-label]="labels().selectRow?.(rowLabelOf(entry)) ?? null"
2990
+ [checked]="isSelected(entry.id)"
2991
+ (click)="onRowSelectClick(pageStart() + item.index, $event)"
2992
+ />
2993
+ </td>
2994
+ }
2995
+ @for (column of visibleColumns(); track column.id; let colIndex = $index) {
2996
+ @let ctx = cellFor(column, entry.row);
2997
+ @if (column.rowHeader) {
2998
+ <th
2999
+ lily-data-table-th
3000
+ scope="row"
3001
+ role="rowheader"
3002
+ [className]="cellClassOf(column, ctx)"
3003
+ [attr.data-row]="item.index"
3004
+ [attr.data-col]="colOffset() + colIndex"
3005
+ [attr.data-align]="column.align ?? null"
3006
+ [attr.data-cell-type]="column.type ?? null"
3007
+ [attr.data-pinned]="column.pinned ?? null"
3008
+ [attr.data-error]="column.errorOf?.(entry.row) !== undefined ? '' : null"
3009
+ [attr.data-empty]="isMissing(ctx.value) ? '' : null"
3010
+ [attr.tabindex]="isGrid() ? (activeRow() === item.index && activeCol() === colOffset() + colIndex ? 0 : -1) : null"
3011
+ [attr.style]="cellStyle(column)"
3012
+ [lilyDataGridAttrs]="column.cellAttrs?.(ctx)"
3013
+ >
3014
+ <ng-container *ngTemplateOutlet="cellBody; context: { entry: entry, column: column, ctx: ctx }" />
3015
+ </th>
3016
+ } @else {
3017
+ <td
3018
+ lily-data-table-td
3019
+ [className]="cellClassOf(column, ctx)"
3020
+ [attr.role]="isGrid() ? 'gridcell' : 'cell'"
3021
+ [attr.data-row]="item.index"
3022
+ [attr.data-col]="colOffset() + colIndex"
3023
+ [attr.data-align]="column.align ?? null"
3024
+ [attr.data-cell-type]="column.type ?? null"
3025
+ [attr.data-pinned]="column.pinned ?? null"
3026
+ [attr.data-editable]="isEditable(column) ? '' : null"
3027
+ [attr.data-readonly]="column.type === 'rollup' ? '' : null"
3028
+ [attr.aria-readonly]="column.type === 'rollup' ? 'true' : null"
3029
+ [attr.data-error]="column.errorOf?.(entry.row) !== undefined ? '' : null"
3030
+ [attr.data-empty]="isMissing(ctx.value) ? '' : null"
3031
+ [attr.data-editing]="isEditingCell(entry, column) ? '' : null"
3032
+ [attr.tabindex]="isGrid() ? (activeRow() === item.index && activeCol() === colOffset() + colIndex ? 0 : -1) : null"
3033
+ [attr.style]="cellStyle(column)"
3034
+ [lilyDataGridAttrs]="column.cellAttrs?.(ctx)"
3035
+ (dragover)="onFileDragOver(column, $event)"
3036
+ (drop)="onFileDrop(entry, column, $event)"
3037
+ >
3038
+ <ng-container *ngTemplateOutlet="cellBody; context: { entry: entry, column: column, ctx: ctx }" />
3039
+ </td>
3040
+ }
3041
+ }
3042
+ </tr>
3043
+ }
3044
+ }
3045
+ @if (pageEntries().length === 0 && empty(); as emptyTemplate) {
3046
+ <tr lily-data-table-row className="data-grid-empty-row">
3047
+ <td lily-data-table-td className="data-grid-empty" [attr.colspan]="lastCol() + 1" [attr.role]="isGrid() ? 'gridcell' : 'cell'">
3048
+ <ng-container *ngTemplateOutlet="emptyTemplate" />
3049
+ </td>
3050
+ </tr>
3051
+ }
3052
+ </tbody>
3053
+ @if (footer(); as footerTemplate) {
3054
+ <tfoot lily-data-table-foot>
3055
+ <ng-container *ngTemplateOutlet="footerTemplate; context: { $implicit: footerContext() }" />
3056
+ </tfoot>
3057
+ }
3058
+ </lily-data-table>
3059
+ </ng-template>
3060
+
3061
+ <ng-template #cellBody let-entry="entry" let-column="column" let-ctx="ctx">
3062
+ @if (isEditingCell(entry, column)) {
3063
+ @let editing = editingState()!;
3064
+ @if (column.editor === 'person' && column.personEditor) {
3065
+ <ng-container *ngTemplateOutlet="column.personEditor; context: { $implicit: personEditorContext(entry, column, ctx) }" />
3066
+ } @else if (column.editor === 'time-zone') {
3067
+ <span
3068
+ class="data-grid-zoned-editor"
3069
+ role="group"
3070
+ [attr.aria-label]="labels().cellEditor?.(column.header) ?? column.header"
3071
+ (focusout)="onZonedFocusout($event)"
3072
+ >
3073
+ <input
3074
+ class="data-grid-cell-editor"
3075
+ type="time"
3076
+ step="1"
3077
+ [attr.aria-label]="labels().cellEditor?.(column.header) ?? column.header"
3078
+ [attr.aria-invalid]="editing.error || editing.invalid ? 'true' : null"
3079
+ [attr.aria-describedby]="editing.error ? entry.id + '-' + column.id + '-error' : null"
3080
+ [value]="editing.draft"
3081
+ (input)="setDraft($event)"
3082
+ (keydown)="onEditorKeydown($event)"
3083
+ />
3084
+ <select
3085
+ class="data-grid-cell-editor data-grid-zone-editor"
3086
+ [attr.aria-label]="labels().timeZone?.(column.header) ?? column.header"
3087
+ (change)="setZone($event)"
3088
+ (keydown)="onEditorKeydown($event)"
3089
+ >
3090
+ @for (option of column.timeZones ?? []; track option.id) {
3091
+ <option [value]="option.id" [selected]="option.id === editing.zone">{{ option.label }}</option>
3092
+ }
3093
+ </select>
3094
+ </span>
3095
+ } @else if (column.editor === 'select') {
3096
+ <select
3097
+ class="data-grid-cell-editor"
3098
+ [attr.aria-label]="labels().cellEditor?.(column.header) ?? column.header"
3099
+ [attr.aria-invalid]="editing.error || editing.invalid ? 'true' : null"
3100
+ [attr.aria-describedby]="editing.error ? entry.id + '-' + column.id + '-error' : null"
3101
+ (change)="setDraft($event)"
3102
+ (keydown)="onEditorKeydown($event)"
3103
+ (blur)="onEditorBlur()"
3104
+ >
3105
+ <option value="" [selected]="editing.draft === ''"></option>
3106
+ @for (option of ungroupedOptions(column); track option.id) {
3107
+ <option [value]="option.id" [disabled]="option.disabled ?? false" [selected]="option.id === editing.draft">{{ option.label }}</option>
3108
+ }
3109
+ @for (group of groupsOf(column); track group) {
3110
+ <optgroup [label]="group">
3111
+ @for (option of optionsInGroup(column, group); track option.id) {
3112
+ <option [value]="option.id" [disabled]="option.disabled ?? false" [selected]="option.id === editing.draft">{{ option.label }}</option>
3113
+ }
3114
+ </optgroup>
3115
+ }
3116
+ </select>
3117
+ } @else {
3118
+ <input
3119
+ class="data-grid-cell-editor"
3120
+ [type]="column.editor === 'number' ? 'number' : column.editor === 'date' ? 'date' : column.editor === 'time' ? 'time' : 'text'"
3121
+ [attr.step]="column.editor === 'time' ? 1 : null"
3122
+ [attr.inputmode]="column.editor === 'number' ? 'decimal' : null"
3123
+ [attr.aria-label]="(column.type === 'formula' ? labels().formulaEditor?.(column.header) : undefined) ?? labels().cellEditor?.(column.header) ?? column.header"
3124
+ [attr.aria-invalid]="editing.error || editing.invalid ? 'true' : null"
3125
+ [attr.aria-describedby]="editing.error ? entry.id + '-' + column.id + '-error' : null"
3126
+ [value]="editing.draft"
3127
+ (input)="setDraft($event)"
3128
+ (keydown)="onEditorKeydown($event)"
3129
+ (blur)="onEditorBlur()"
3130
+ />
3131
+ }
3132
+ @if (editing.error) {
3133
+ <span class="data-grid-cell-error" [id]="entry.id + '-' + column.id + '-error'">{{ editing.error }}</span>
3134
+ }
3135
+ } @else if (column.type === 'radio' && column.editable) {
3136
+ <fieldset class="data-grid-radio-group" data-grid-own-group>
3137
+ <legend class="data-grid-radio-legend">{{ labels().radioGroup?.(column.header, rowLabelOf(entry)) ?? column.header }}</legend>
3138
+ @for (option of column.options ?? []; track option.id) {
3139
+ <label class="data-grid-radio-option">
3140
+ <input
3141
+ type="radio"
3142
+ [name]="gridId + '-' + entry.id + '-' + column.id"
3143
+ [value]="option.id"
3144
+ [checked]="ctx.value === option.id"
3145
+ [disabled]="option.disabled ?? false"
3146
+ [attr.tabindex]="isGrid() ? -1 : null"
3147
+ (change)="commitValue(entry, column, option.id)"
3148
+ />{{ option.label }}</label>
3149
+ }
3150
+ </fieldset>
3151
+ } @else {
3152
+ @if (column.cell; as cellTemplate) {
3153
+ <ng-container *ngTemplateOutlet="cellTemplate; context: { $implicit: ctx }" />
3154
+ } @else if (isMissing(ctx.value) && (column.emptyValue ?? emptyValue())) {
3155
+ <ng-container>{{ column.emptyValue ?? emptyValue() }}</ng-container>
3156
+ } @else if (column.type === 'person') {
3157
+ @let people = peopleIn(ctx.value);
3158
+ @if (people.length > 0) {
3159
+ @let shown = shownPeople(column, ctx.value);
3160
+ <ul class="data-grid-people">
3161
+ @for (person of shown; track person.id) {
3162
+ <li class="data-grid-person">
3163
+ @if (column.avatar; as avatarTemplate) {
3164
+ <span class="data-grid-avatar" aria-hidden="true"><ng-container *ngTemplateOutlet="avatarTemplate; context: { $implicit: person }" /></span>
3165
+ }
3166
+ <span class="data-grid-person-name">{{ nameOf(person) }}</span>
3167
+ </li>
3168
+ }
3169
+ @if (shown.length < people.length && labels().morePeople; as more) {
3170
+ <li class="data-grid-people-more">{{ more(people.length - shown.length) }}</li>
3171
+ }
3172
+ </ul>
3173
+ }
3174
+ } @else if (column.type === 'file') {
3175
+ @let files = filesIn(ctx.value);
3176
+ @if (files.length > 0) {
3177
+ <ul class="data-grid-files">
3178
+ @for (file of files; track file.id) {
3179
+ <li class="data-grid-file">
3180
+ @if (hrefOf(file.href); as fileHref) {
3181
+ <a class="data-grid-link" [href]="fileHref">{{ file.name }}</a>
3182
+ } @else {
3183
+ <span class="data-grid-file-name">{{ file.name }}</span>
3184
+ }
3185
+ @if (file.size !== undefined && column.formatSize; as sizeText) {
3186
+ {{ ' ' }}<span class="data-grid-file-size">{{ column.formatSize(file.size) }}</span>
3187
+ }
3188
+ @if (column.editable && labels().removeFile; as removeLabel) {
3189
+ <button
3190
+ type="button"
3191
+ class="data-grid-file-remove"
3192
+ data-grid-own
3193
+ [attr.tabindex]="isGrid() ? -1 : null"
3194
+ [attr.aria-label]="removeLabel(file.name)"
3195
+ (click)="fileRemove.emit({ rowId: entry.id, row: entry.row, columnId: column.id, fileId: file.id })"
3196
+ ></button>
3197
+ }
3198
+ </li>
3199
+ }
3200
+ </ul>
3201
+ }
3202
+ @if (column.editable) {
3203
+ <input
3204
+ type="file"
3205
+ class="data-grid-file-input"
3206
+ [attr.aria-label]="labels().fileInput?.(column.header, rowLabelOf(entry)) ?? column.header"
3207
+ [multiple]="column.multiple !== false"
3208
+ [attr.accept]="column.accept ?? null"
3209
+ [attr.tabindex]="isGrid() ? -1 : null"
3210
+ (change)="onFileInput(entry, column, $event)"
3211
+ />
3212
+ }
3213
+ } @else if (column.type === 'link') {
3214
+ @let link = linkIn(ctx.value);
3215
+ @if (link) {
3216
+ @if (hrefOf(link.href); as href) {
3217
+ <a class="data-grid-link" [href]="href" [attr.target]="link.external ? '_blank' : null" [attr.rel]="link.external ? 'noopener noreferrer' : null">{{ link.label ?? link.href }}@if (link.external && labels().opensInNewTab; as newTab) {{{ ' ' }}<span class="data-grid-visually-hidden">{{ newTab(link.label ?? link.href) }}</span>}</a>
3218
+ } @else {
3219
+ <!-- An address with an unsafe scheme is shown as text, never as a link. -->
3220
+ <span class="data-grid-link data-grid-link-unsafe">{{ link.label ?? link.href }}</span>
3221
+ }
3222
+ }
3223
+ } @else if (column.type === 'date' || column.type === 'time') {
3224
+ @if (ctx.formatted !== '') {
3225
+ <time [attr.datetime]="stringOf(ctx.value)">{{ ctx.formatted }}</time>
3226
+ }
3227
+ } @else {<ng-container>{{ ctx.formatted }}</ng-container>}
3228
+ @if (isEditable(column) && column.type !== 'radio' && labels().editCell; as editLabel) {
3229
+ <button
3230
+ type="button"
3231
+ class="data-grid-edit-button"
3232
+ data-grid-own
3233
+ [attr.tabindex]="isGrid() ? -1 : null"
3234
+ [attr.aria-label]="editLabel(column.header, rowLabelOf(entry))"
3235
+ (click)="startEdit(entry, column)"
3236
+ ></button>
3237
+ }
3238
+ }
3239
+ </ng-template>
3240
+ `
3241
+ }]
3242
+ }],
3243
+ ctorParameters: () => [],
3244
+ propDecorators: {
3245
+ label: [{
3246
+ type: i0.Input,
3247
+ args: [{
3248
+ isSignal: true,
3249
+ alias: "label",
3250
+ required: true
3251
+ }]
3252
+ }],
3253
+ caption: [{
3254
+ type: i0.Input,
3255
+ args: [{
3256
+ isSignal: true,
3257
+ alias: "caption",
3258
+ required: false
3259
+ }]
3260
+ }],
3261
+ columns: [{
3262
+ type: i0.Input,
3263
+ args: [{
3264
+ isSignal: true,
3265
+ alias: "columns",
3266
+ required: true
3267
+ }]
3268
+ }],
3269
+ rows: [{
3270
+ type: i0.Input,
3271
+ args: [{
3272
+ isSignal: true,
3273
+ alias: "rows",
3274
+ required: true
3275
+ }]
3276
+ }],
3277
+ locale: [{
3278
+ type: i0.Input,
3279
+ args: [{
3280
+ isSignal: true,
3281
+ alias: "locale",
3282
+ required: false
3283
+ }]
3284
+ }],
3285
+ emptyValue: [{
3286
+ type: i0.Input,
3287
+ args: [{
3288
+ isSignal: true,
3289
+ alias: "emptyValue",
3290
+ required: false
3291
+ }]
3292
+ }],
3293
+ warnMissingLabels: [{
3294
+ type: i0.Input,
3295
+ args: [{
3296
+ isSignal: true,
3297
+ alias: "warnMissingLabels",
3298
+ required: false
3299
+ }]
3300
+ }],
3301
+ nulls: [{
3302
+ type: i0.Input,
3303
+ args: [{
3304
+ isSignal: true,
3305
+ alias: "nulls",
3306
+ required: false
3307
+ }]
3308
+ }],
3309
+ movableColumns: [{
3310
+ type: i0.Input,
3311
+ args: [{
3312
+ isSignal: true,
3313
+ alias: "movableColumns",
3314
+ required: false
3315
+ }]
3316
+ }],
3317
+ rowFilter: [{
3318
+ type: i0.Input,
3319
+ args: [{
3320
+ isSignal: true,
3321
+ alias: "rowFilter",
3322
+ required: false
3323
+ }]
3324
+ }],
3325
+ rowId: [{
3326
+ type: i0.Input,
3327
+ args: [{
3328
+ isSignal: true,
3329
+ alias: "rowId",
3330
+ required: false
3331
+ }]
3332
+ }],
3333
+ selectionMode: [{
3334
+ type: i0.Input,
3335
+ args: [{
3336
+ isSignal: true,
3337
+ alias: "selectionMode",
3338
+ required: false
3339
+ }]
3340
+ }],
3341
+ pageSize: [{
3342
+ type: i0.Input,
3343
+ args: [{
3344
+ isSignal: true,
3345
+ alias: "pageSize",
3346
+ required: false
3347
+ }]
3348
+ }],
3349
+ storageKey: [{
3350
+ type: i0.Input,
3351
+ args: [{
3352
+ isSignal: true,
3353
+ alias: "storageKey",
3354
+ required: false
3355
+ }]
3356
+ }],
3357
+ labels: [{
3358
+ type: i0.Input,
3359
+ args: [{
3360
+ isSignal: true,
3361
+ alias: "labels",
3362
+ required: false
3363
+ }]
3364
+ }],
3365
+ mode: [{
3366
+ type: i0.Input,
3367
+ args: [{
3368
+ isSignal: true,
3369
+ alias: "mode",
3370
+ required: false
3371
+ }]
3372
+ }],
3373
+ rowClass: [{
3374
+ type: i0.Input,
3375
+ args: [{
3376
+ isSignal: true,
3377
+ alias: "rowClass",
3378
+ required: false
3379
+ }]
3380
+ }],
3381
+ rowAttrs: [{
3382
+ type: i0.Input,
3383
+ args: [{
3384
+ isSignal: true,
3385
+ alias: "rowAttrs",
3386
+ required: false
3387
+ }]
3388
+ }],
3389
+ activateRows: [{
3390
+ type: i0.Input,
3391
+ args: [{
3392
+ isSignal: true,
3393
+ alias: "activateRows",
3394
+ required: false
3395
+ }]
3396
+ }],
3397
+ empty: [{
3398
+ type: i0.Input,
3399
+ args: [{
3400
+ isSignal: true,
3401
+ alias: "empty",
3402
+ required: false
3403
+ }]
3404
+ }],
3405
+ footer: [{
3406
+ type: i0.Input,
3407
+ args: [{
3408
+ isSignal: true,
3409
+ alias: "footer",
3410
+ required: false
3411
+ }]
3412
+ }],
3413
+ busy: [{
3414
+ type: i0.Input,
3415
+ args: [{
3416
+ isSignal: true,
3417
+ alias: "busy",
3418
+ required: false
3419
+ }]
3420
+ }],
3421
+ scroll: [{
3422
+ type: i0.Input,
3423
+ args: [{
3424
+ isSignal: true,
3425
+ alias: "scroll",
3426
+ required: false
3427
+ }]
3428
+ }],
3429
+ exportFilename: [{
3430
+ type: i0.Input,
3431
+ args: [{
3432
+ isSignal: true,
3433
+ alias: "exportFilename",
3434
+ required: false
3435
+ }]
3436
+ }],
3437
+ exportScope: [{
3438
+ type: i0.Input,
3439
+ args: [{
3440
+ isSignal: true,
3441
+ alias: "exportScope",
3442
+ required: false
3443
+ }]
3444
+ }],
3445
+ exportFormulas: [{
3446
+ type: i0.Input,
3447
+ args: [{
3448
+ isSignal: true,
3449
+ alias: "exportFormulas",
3450
+ required: false
3451
+ }]
3452
+ }],
3453
+ exportHandler: [{
3454
+ type: i0.Input,
3455
+ args: [{
3456
+ isSignal: true,
3457
+ alias: "exportHandler",
3458
+ required: false
3459
+ }]
3460
+ }],
3461
+ copyHandler: [{
3462
+ type: i0.Input,
3463
+ args: [{
3464
+ isSignal: true,
3465
+ alias: "copyHandler",
3466
+ required: false
3467
+ }]
3468
+ }],
3469
+ virtualize: [{
3470
+ type: i0.Input,
3471
+ args: [{
3472
+ isSignal: true,
3473
+ alias: "virtualize",
3474
+ required: false
3475
+ }]
3476
+ }],
3477
+ className: [{
3478
+ type: i0.Input,
3479
+ args: [{
3480
+ isSignal: true,
3481
+ alias: "className",
3482
+ required: false
3483
+ }]
3484
+ }],
3485
+ selected: [{
3486
+ type: i0.Input,
3487
+ args: [{
3488
+ isSignal: true,
3489
+ alias: "selected",
3490
+ required: false
3491
+ }]
3492
+ }, {
3493
+ type: i0.Output,
3494
+ args: ["selectedChange"]
3495
+ }],
3496
+ filter: [{
3497
+ type: i0.Input,
3498
+ args: [{
3499
+ isSignal: true,
3500
+ alias: "filter",
3501
+ required: false
3502
+ }]
3503
+ }, {
3504
+ type: i0.Output,
3505
+ args: ["filterChange"]
3506
+ }],
3507
+ filterField: [{
3508
+ type: i0.Input,
3509
+ args: [{
3510
+ isSignal: true,
3511
+ alias: "filterField",
3512
+ required: false
3513
+ }]
3514
+ }, {
3515
+ type: i0.Output,
3516
+ args: ["filterFieldChange"]
3517
+ }],
3518
+ thenBy: [{
3519
+ type: i0.Input,
3520
+ args: [{
3521
+ isSignal: true,
3522
+ alias: "thenBy",
3523
+ required: false
3524
+ }]
3525
+ }, {
3526
+ type: i0.Output,
3527
+ args: ["thenByChange"]
3528
+ }],
3529
+ sort: [{
3530
+ type: i0.Input,
3531
+ args: [{
3532
+ isSignal: true,
3533
+ alias: "sort",
3534
+ required: false
3535
+ }]
3536
+ }, {
3537
+ type: i0.Output,
3538
+ args: ["sortChange"]
3539
+ }],
3540
+ columnOrder: [{
3541
+ type: i0.Input,
3542
+ args: [{
3543
+ isSignal: true,
3544
+ alias: "columnOrder",
3545
+ required: false
3546
+ }]
3547
+ }, {
3548
+ type: i0.Output,
3549
+ args: ["columnOrderChange"]
3550
+ }],
3551
+ page: [{
3552
+ type: i0.Input,
3553
+ args: [{
3554
+ isSignal: true,
3555
+ alias: "page",
3556
+ required: false
3557
+ }]
3558
+ }, {
3559
+ type: i0.Output,
3560
+ args: ["pageChange"]
3561
+ }],
3562
+ cellEdit: [{
3563
+ type: i0.Output,
3564
+ args: ["cellEdit"]
3565
+ }],
3566
+ filesSelected: [{
3567
+ type: i0.Output,
3568
+ args: ["filesSelected"]
3569
+ }],
3570
+ fileRemove: [{
3571
+ type: i0.Output,
3572
+ args: ["fileRemove"]
3573
+ }],
3574
+ rowActivate: [{
3575
+ type: i0.Output,
3576
+ args: ["rowActivate"]
3577
+ }],
3578
+ exportFile: [{
3579
+ type: i0.Output,
3580
+ args: ["exportFile"]
3581
+ }],
3582
+ copy: [{
3583
+ type: i0.Output,
3584
+ args: ["copy"]
3585
+ }],
3586
+ rootRef: [{
3587
+ type: i0.ViewChild,
3588
+ args: ["rootEl", { isSignal: true }]
3589
+ }],
3590
+ scrollRef: [{
3591
+ type: i0.ViewChild,
3592
+ args: ["scrollEl", { isSignal: true }]
3593
+ }]
3594
+ }
3595
+ });
3596
+ export { ANNOUNCEMENT_LABELS, CONTROL_LABELS, DataGrid, DataGridAttrs, EMPTY_HISTORY, HISTORY_LIMIT, MIME_TYPES, TYPEAHEAD_RESET_MS, aggregateValues, cellText, civilDateToDate, columnName, copyToClipboard, crc32, downloadBlob, exportBlob, exportFilename, filesOf, findTypeaheadMatch, foldText, highlightSegments, historyKey, isCivilDate, isCivilTime, isExportFormula, isInvalidForType, isTypeaheadKey, isValidTimeZone, isZonedTime, linkLabel, linkParts, matchesSearch, missingLabels, nextDataGridId, nextTypeaheadState, peopleOf, personName, recordEdit, redoEdit, requiredLabels, resolveColumn, safeHref, searchTerms, toClipboardText, toCsv, toDelimited, toTsv, toXlsx, undoEdit, zipStore, zonedMinutes, zonedUtcTime };
3597
+
3598
+ //# sourceMappingURL=lilydesignsystem-angular-data-grid.mjs.map