@lilydesignsystem/angular-data-grid 0.0.0-stage → 0.9.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,3016 @@
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 } 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
+ const SAFE_SCHEME = /^(https?:|mailto:|tel:)/i;
217
+ const RELATIVE = /^(\/(?!\/)|\.\.?\/|#|\?)/;
218
+ function safeHref(href) {
219
+ if (typeof href !== "string") return null;
220
+ const text = href.trim();
221
+ if (!text) return null;
222
+ if (/[\u0000-\u001F\u007F]/.test(text)) return null;
223
+ return SAFE_SCHEME.test(text) || RELATIVE.test(text) ? text : null;
224
+ }
225
+ function linkParts(value) {
226
+ if (value === null || value === void 0 || value === "") return null;
227
+ if (typeof value === "string") return { href: value };
228
+ if (typeof value === "object" && typeof value.href === "string") return value;
229
+ return null;
230
+ }
231
+ function linkLabel(value) {
232
+ const link = linkParts(value);
233
+ return link ? link.label ?? link.href : "";
234
+ }
235
+ function isCivilDate(text) {
236
+ if (typeof text !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(text)) return false;
237
+ const [y, m, d] = text.split("-").map(Number);
238
+ const date = new Date(Date.UTC(y, m - 1, d));
239
+ return date.getUTCFullYear() === y && date.getUTCMonth() === m - 1 && date.getUTCDate() === d;
240
+ }
241
+ function isCivilTime(text) {
242
+ if (typeof text !== "string") return false;
243
+ const match = /^(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(text);
244
+ return !!match && Number(match[1]) < 24 && Number(match[2]) < 60 && (match[3] === void 0 || Number(match[3]) < 60);
245
+ }
246
+ function civilDateToDate(text) {
247
+ if (text instanceof Date) return Number.isNaN(text.getTime()) ? null : text;
248
+ return isCivilDate(text) ? /* @__PURE__ */ new Date(`${text}T00:00:00Z`) : null;
249
+ }
250
+ const isEmpty = (value) => value === null || value === void 0 || value === "";
251
+ const OFFSET = /^([+-])(\d{2}):?(\d{2})$/;
252
+ function zoneOffsetMinutes(timeZone, instantMs) {
253
+ const bare = OFFSET.exec(timeZone);
254
+ if (bare) return (bare[1] === "-" ? -1 : 1) * (Number(bare[2]) * 60 + Number(bare[3]));
255
+ try {
256
+ const parts = new Intl.DateTimeFormat("en-US", {
257
+ timeZone,
258
+ hourCycle: "h23",
259
+ year: "numeric",
260
+ month: "2-digit",
261
+ day: "2-digit",
262
+ hour: "2-digit",
263
+ minute: "2-digit",
264
+ second: "2-digit"
265
+ }).formatToParts(new Date(instantMs));
266
+ const n = (type) => Number(parts.find((x) => x.type === type)?.value);
267
+ return (Date.UTC(n("year"), n("month") - 1, n("day"), n("hour") % 24, n("minute"), n("second")) - Math.floor(instantMs / 1e3) * 1e3) / 6e4;
268
+ } catch {
269
+ return null;
270
+ }
271
+ }
272
+ function isValidTimeZone(timeZone) {
273
+ return typeof timeZone === "string" && timeZone !== "" && zoneOffsetMinutes(timeZone, 0) !== null;
274
+ }
275
+ function isZonedTime(value) {
276
+ return !!value && typeof value === "object" && isCivilTime(value.time) && isValidTimeZone(value.timeZone);
277
+ }
278
+ function zonedMinutes(value, referenceDate) {
279
+ if (!isZonedTime(value)) return null;
280
+ const [h, m, sec] = value.time.split(":").map(Number);
281
+ const local = h * 60 + m + (sec || 0) / 60;
282
+ if (!referenceDate || !isCivilDate(referenceDate)) return local;
283
+ const [y, mo, d] = referenceDate.split("-").map(Number);
284
+ const guess = Date.UTC(y, mo - 1, d, h, m, sec || 0);
285
+ const offset = zoneOffsetMinutes(value.timeZone, guess);
286
+ if (offset === null) return local;
287
+ return guess / 6e4 - offset;
288
+ }
289
+ function zonedUtcTime(value, referenceDate) {
290
+ if (!referenceDate || !isCivilDate(referenceDate)) return null;
291
+ const minutes = zonedMinutes(value, referenceDate);
292
+ if (minutes === null) return null;
293
+ const day = (Math.round(minutes) % 1440 + 1440) % 1440;
294
+ return `${String(Math.floor(day / 60)).padStart(2, "0")}:${String(day % 60).padStart(2, "0")}`;
295
+ }
296
+ function zoneLabel(timeZone, locale, referenceDate) {
297
+ if (!locale || !referenceDate || !isCivilDate(referenceDate) || OFFSET.test(timeZone)) return timeZone;
298
+ try {
299
+ return new Intl.DateTimeFormat(locale, {
300
+ timeZone,
301
+ timeZoneName: "short"
302
+ }).formatToParts(/* @__PURE__ */ new Date(`${referenceDate}T12:00:00Z`)).find((x) => x.type === "timeZoneName")?.value ?? timeZone;
303
+ } catch {
304
+ return timeZone;
305
+ }
306
+ }
307
+ function peopleOf(value) {
308
+ return (Array.isArray(value) ? value : isEmpty(value) ? [] : [value]).filter((p) => !!p && typeof p === "object" && typeof p.id === "string");
309
+ }
310
+ const personName = (person) => person.name ? person.name : person.id;
311
+ function filesOf(value) {
312
+ return (Array.isArray(value) ? value : []).filter((f) => !!f && typeof f === "object" && typeof f.id === "string" && typeof f.name === "string");
313
+ }
314
+ function aggregateValues(kind, values) {
315
+ const present = values.filter((v) => !isEmpty(v));
316
+ const numbers = present.filter((v) => typeof v === "number" && Number.isFinite(v));
317
+ switch (kind) {
318
+ case "count": return values.length;
319
+ case "countUnique": return present.length === 0 ? null : new Set(present.map((v) => typeof v === "object" ? JSON.stringify(v) : String(v))).size;
320
+ case "sum": return numbers.length === 0 ? null : numbers.reduce((a, b) => a + b, 0);
321
+ case "avg": return numbers.length === 0 ? null : numbers.reduce((a, b) => a + b, 0) / numbers.length;
322
+ case "median": {
323
+ if (numbers.length === 0) return null;
324
+ const sorted = numbers.slice().sort((a, b) => a - b);
325
+ const mid = sorted.length >> 1;
326
+ return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
327
+ }
328
+ case "min":
329
+ case "max": {
330
+ const pool = numbers.length === present.length ? numbers : present.filter((v) => typeof v === "string");
331
+ if (pool.length === 0) return null;
332
+ return pool.reduce((a, b) => (kind === "min" ? b < a : b > a) ? b : a);
333
+ }
334
+ case "range": return numbers.length === 0 ? null : Math.max(...numbers) - Math.min(...numbers);
335
+ case "percentTrue": {
336
+ const booleans = values.filter((v) => typeof v === "boolean");
337
+ return booleans.length === 0 ? null : booleans.filter(Boolean).length / booleans.length * 100;
338
+ }
339
+ case "percentEmpty": return values.length === 0 ? null : (values.length - present.length) / values.length * 100;
340
+ }
341
+ }
342
+ function isInvalidForType(column, value) {
343
+ if (isEmpty(value)) return false;
344
+ switch (column.type) {
345
+ case "number": return typeof value !== "number" || !Number.isFinite(value);
346
+ case "link": return safeHref(linkParts(value)?.href) === null;
347
+ case "date": return !isCivilDate(value);
348
+ case "time": return !isCivilTime(value);
349
+ case "time-zone": return !isZonedTime(value);
350
+ case "select":
351
+ case "radio": return !(column.options ?? []).some((o) => o.id === value);
352
+ case "person": return peopleOf(value).length === 0;
353
+ default: return false;
354
+ }
355
+ }
356
+ const optionLabel = (column, id) => {
357
+ const found = (column.options ?? []).find((o) => o.id === id);
358
+ return found ? found.label : isEmpty(id) ? "" : String(id);
359
+ };
360
+ function resultFormatter(column, locale) {
361
+ const type = column.resultType ?? "text";
362
+ if (type === "number") {
363
+ const formatter = locale || column.numberFormat ? new Intl.NumberFormat(locale, column.numberFormat) : null;
364
+ return (v) => typeof v === "number" && Number.isFinite(v) ? formatter ? formatter.format(v) : String(v) : isEmpty(v) ? "" : String(v);
365
+ }
366
+ if (type === "date") {
367
+ const formatter = locale || column.dateFormat ? new Intl.DateTimeFormat(locale, {
368
+ dateStyle: "medium",
369
+ ...column.dateFormat,
370
+ timeZone: "UTC"
371
+ }) : null;
372
+ return (v) => {
373
+ if (isEmpty(v)) return "";
374
+ const date = civilDateToDate(v);
375
+ return date ? formatter ? formatter.format(date) : date.toISOString().slice(0, 10) : String(v);
376
+ };
377
+ }
378
+ return (v) => isEmpty(v) ? "" : String(v);
379
+ }
380
+ function typeDefaults(column, gridLocale) {
381
+ const locale = column.locale ?? gridLocale;
382
+ switch (column.type) {
383
+ case "text": return {
384
+ align: "start",
385
+ editor: "text",
386
+ ...locale ? { compare: (a, b) => new Intl.Collator(locale).compare(String(a), String(b)) } : {}
387
+ };
388
+ case "number": {
389
+ const formatter = locale || column.numberFormat ? new Intl.NumberFormat(locale, column.numberFormat) : null;
390
+ return {
391
+ align: "end",
392
+ editor: "number",
393
+ ...formatter ? { format: (v) => typeof v === "number" && Number.isFinite(v) ? formatter.format(v) : "" } : {}
394
+ };
395
+ }
396
+ case "link": return {
397
+ align: "start",
398
+ editor: "link",
399
+ format: (v) => linkLabel(v),
400
+ filterText: (v) => {
401
+ const link = linkParts(v);
402
+ return link ? `${link.label ?? ""} ${link.href}`.trim() : "";
403
+ },
404
+ compare: (a, b) => {
405
+ const x = linkLabel(a);
406
+ const y = linkLabel(b);
407
+ return locale ? new Intl.Collator(locale).compare(x, y) : x < y ? -1 : x > y ? 1 : 0;
408
+ },
409
+ exportValue: (row) => linkLabel(column.accessor ? column.accessor(row) : row[column.id]),
410
+ parse: (draft, row) => {
411
+ const previous = linkParts(column.accessor ? column.accessor(row) : row[column.id]);
412
+ const href = draft.trim();
413
+ if (!href) return null;
414
+ return previous && typeof previous === "object" && (previous.label !== void 0 || previous.external !== void 0) ? {
415
+ ...previous,
416
+ href
417
+ } : href;
418
+ }
419
+ };
420
+ case "date": {
421
+ const formatter = locale || column.dateFormat ? new Intl.DateTimeFormat(locale, {
422
+ dateStyle: "medium",
423
+ ...column.dateFormat,
424
+ timeZone: "UTC"
425
+ }) : null;
426
+ return {
427
+ align: "start",
428
+ editor: "date",
429
+ format: (v) => {
430
+ if (isEmpty(v)) return "";
431
+ const date = civilDateToDate(v);
432
+ if (!date) return String(v);
433
+ return formatter ? formatter.format(date) : date.toISOString().slice(0, 10);
434
+ },
435
+ exportValue: (row) => civilDateToDate(column.accessor ? column.accessor(row) : row[column.id])
436
+ };
437
+ }
438
+ case "time": {
439
+ const formatter = locale || column.timeFormat ? new Intl.DateTimeFormat(locale, {
440
+ timeStyle: "short",
441
+ ...column.timeFormat,
442
+ timeZone: "UTC"
443
+ }) : null;
444
+ return {
445
+ align: "start",
446
+ editor: "time",
447
+ format: (v) => {
448
+ if (isEmpty(v)) return "";
449
+ if (!isCivilTime(v)) return String(v);
450
+ return formatter ? formatter.format(/* @__PURE__ */ new Date(`1970-01-01T${v.length === 5 ? `${v}:00` : v}Z`)) : v;
451
+ }
452
+ };
453
+ }
454
+ case "time-zone": {
455
+ const formatter = locale || column.timeFormat ? new Intl.DateTimeFormat(locale, {
456
+ timeStyle: "short",
457
+ ...column.timeFormat,
458
+ timeZone: "UTC"
459
+ }) : null;
460
+ const text = (v) => {
461
+ if (isEmpty(v)) return "";
462
+ if (!isZonedTime(v)) return String(v);
463
+ 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)}`;
464
+ };
465
+ return {
466
+ align: "start",
467
+ editor: "time-zone",
468
+ format: text,
469
+ compare: (a, b) => (zonedMinutes(a, column.referenceDate) ?? 0) - (zonedMinutes(b, column.referenceDate) ?? 0),
470
+ exportValue: (row) => {
471
+ const v = column.accessor ? column.accessor(row) : row[column.id];
472
+ return isZonedTime(v) ? `${v.time} ${v.timeZone}` : null;
473
+ }
474
+ };
475
+ }
476
+ case "person": {
477
+ const names = (v) => peopleOf(v).map(personName);
478
+ const collator = locale ? new Intl.Collator(locale) : null;
479
+ const order = (a, b) => collator ? collator.compare(a, b) : a < b ? -1 : a > b ? 1 : 0;
480
+ return {
481
+ align: "start",
482
+ editor: "person",
483
+ format: (v) => names(v).join("; "),
484
+ filterText: (v) => peopleOf(v).map((p) => `${personName(p)} ${p.email ?? ""}`.trim()).join(" "),
485
+ compare: (a, b) => {
486
+ const x = names(a);
487
+ const y = names(b);
488
+ return order(x[0] ?? "", y[0] ?? "") || x.length - y.length;
489
+ },
490
+ exportValue: (row) => {
491
+ const value = column.accessor ? column.accessor(row) : row[column.id];
492
+ const joined = (column.exportAs === "email" ? peopleOf(value).map((p) => p.email ?? "") : names(value)).join("; ");
493
+ return joined.replace(/[;\s]/g, "") === "" ? null : joined;
494
+ }
495
+ };
496
+ }
497
+ case "file": {
498
+ const names = (v) => filesOf(v).map((f) => f.name);
499
+ const collator = locale ? new Intl.Collator(locale) : null;
500
+ const order = (a, b) => collator ? collator.compare(a, b) : a < b ? -1 : a > b ? 1 : 0;
501
+ return {
502
+ align: "start",
503
+ format: (v) => names(v).join("; "),
504
+ filterText: (v) => names(v).join(" "),
505
+ compare: (a, b) => {
506
+ const x = names(a);
507
+ const y = names(b);
508
+ return order(x[0] ?? "", y[0] ?? "") || x.length - y.length;
509
+ },
510
+ exportValue: (row) => {
511
+ const joined = names(column.accessor ? column.accessor(row) : row[column.id]).join("; ");
512
+ return joined === "" ? null : joined;
513
+ }
514
+ };
515
+ }
516
+ case "select":
517
+ case "radio": {
518
+ const order = new Map((column.options ?? []).map((o, i) => [o.id, i]));
519
+ return {
520
+ align: "start",
521
+ editor: column.type === "select" ? "select" : "radio",
522
+ format: (v) => optionLabel(column, v),
523
+ filterText: (v) => optionLabel(column, v),
524
+ 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,
525
+ exportValue: (row) => {
526
+ const id = column.accessor ? column.accessor(row) : row[column.id];
527
+ return column.exportAs === "id" ? isEmpty(id) ? null : id : isEmpty(id) ? null : optionLabel(column, id);
528
+ }
529
+ };
530
+ }
531
+ default: return {};
532
+ }
533
+ }
534
+ function resolveColumn(column, gridLocale, rows = []) {
535
+ if (!column.type) return column;
536
+ const defaults = typeDefaults(column, gridLocale);
537
+ const own = Object.fromEntries(Object.entries(column).filter(([, v]) => v !== void 0));
538
+ const resolved = {
539
+ ...defaults,
540
+ ...own
541
+ };
542
+ const locale = column.locale ?? gridLocale;
543
+ if (column.type === "rollup") {
544
+ const relation = column.relation ?? (column.accessor ? (row) => column.accessor(row) : (row) => row[column.id]);
545
+ const cache = /* @__PURE__ */ new WeakMap();
546
+ const format = resultFormatter(column, locale);
547
+ resolved.accessor = (row) => {
548
+ if (cache.has(row)) return cache.get(row);
549
+ const related = relation(row);
550
+ const values = Array.isArray(related) ? related : [];
551
+ const result = column.reduce ? column.reduce(values, row) : aggregateValues(column.aggregate ?? "count", values);
552
+ cache.set(row, result);
553
+ return result;
554
+ };
555
+ resolved.editable = false;
556
+ resolved.align = column.align ?? (column.resultType === "number" ? "end" : "start");
557
+ if (column.format === void 0) resolved.format = (v) => format(v);
558
+ if (column.exportValue === void 0) resolved.exportValue = (row) => {
559
+ const v = resolved.accessor(row);
560
+ return column.resultType === "date" ? civilDateToDate(v) : isEmpty(v) ? null : v;
561
+ };
562
+ } else if (column.type === "formula") {
563
+ const source = column.accessor ?? ((row) => row[column.id]);
564
+ const cache = /* @__PURE__ */ new WeakMap();
565
+ const format = resultFormatter(column, locale);
566
+ const result = (row) => {
567
+ let found = cache.get(row);
568
+ if (!found) {
569
+ const expression = source(row);
570
+ if (isEmpty(expression)) found = { value: null };
571
+ else if (typeof column.evaluate !== "function") found = { value: expression };
572
+ else try {
573
+ found = column.evaluate(String(expression), {
574
+ row,
575
+ rows,
576
+ columnId: column.id
577
+ });
578
+ } catch (error) {
579
+ found = { error: error instanceof Error ? error.message : String(error) };
580
+ }
581
+ cache.set(row, found);
582
+ }
583
+ return found;
584
+ };
585
+ const valueOf = (row) => {
586
+ const r = result(row);
587
+ return "error" in r ? null : r.value;
588
+ };
589
+ resolved.source = source;
590
+ resolved.errorOf = (row) => {
591
+ const r = result(row);
592
+ return "error" in r ? r.error : void 0;
593
+ };
594
+ resolved.accessor = valueOf;
595
+ resolved.align = column.align ?? (column.resultType === "number" ? "end" : "start");
596
+ if (column.format === void 0) resolved.format = (v, row) => {
597
+ const message = resolved.errorOf(row);
598
+ return message !== void 0 ? message : format(v);
599
+ };
600
+ if (column.exportValue === void 0) resolved.exportValue = (row) => {
601
+ const message = resolved.errorOf(row);
602
+ if (message !== void 0) return message;
603
+ const v = valueOf(row);
604
+ return column.resultType === "date" ? civilDateToDate(v) : isEmpty(v) ? null : v;
605
+ };
606
+ if (column.filterText === void 0) resolved.filterText = (v, row) => resolved.format(v, row);
607
+ }
608
+ return resolved;
609
+ }
610
+ const WIDGET_SELECTOR = "a[href], button, input, select, textarea, summary, [contenteditable=\"true\"]";
611
+ const MIN_COLUMN_WIDTH = 40;
612
+ const RESIZE_STEP = 16;
613
+ let uid = 0;
614
+ function nextDataGridId() {
615
+ uid += 1;
616
+ return `data-grid-${uid}`;
617
+ }
618
+ function defaultAccessor(column, row) {
619
+ return column.accessor ? column.accessor(row) : row[column.id];
620
+ }
621
+ function formatCell(column, row) {
622
+ const value = defaultAccessor(column, row);
623
+ if (column.format) return column.format(value, row);
624
+ return value === null || value === void 0 ? "" : String(value);
625
+ }
626
+ var DataGridAttrs = class DataGridAttrs {
627
+ constructor() {
628
+ this.lilyDataGridAttrs = input(void 0, ...ngDevMode ? [{ debugName: "lilyDataGridAttrs" }] : /* istanbul ignore next */ []);
629
+ this.host = inject(ElementRef);
630
+ this.applied = [];
631
+ effect(() => {
632
+ const next = this.lilyDataGridAttrs() ?? {};
633
+ for (const name of this.applied) if (!(name in next)) this.host.nativeElement.removeAttribute(name);
634
+ for (const [name, value] of Object.entries(next)) this.host.nativeElement.setAttribute(name, value);
635
+ this.applied = Object.keys(next);
636
+ });
637
+ }
638
+ static {
639
+ this.ɵfac = i0.ɵɵngDeclareFactory({
640
+ minVersion: "12.0.0",
641
+ version: "22.2.3",
642
+ ngImport: i0,
643
+ type: DataGridAttrs,
644
+ deps: [],
645
+ target: i0.ɵɵFactoryTarget.Directive
646
+ });
647
+ }
648
+ static {
649
+ this.ɵdir = i0.ɵɵngDeclareDirective({
650
+ minVersion: "17.1.0",
651
+ version: "22.2.3",
652
+ type: DataGridAttrs,
653
+ isStandalone: true,
654
+ selector: "[lilyDataGridAttrs]",
655
+ inputs: { lilyDataGridAttrs: {
656
+ classPropertyName: "lilyDataGridAttrs",
657
+ publicName: "lilyDataGridAttrs",
658
+ isSignal: true,
659
+ isRequired: false,
660
+ transformFunction: null
661
+ } },
662
+ ngImport: i0
663
+ });
664
+ }
665
+ };
666
+ i0.ɵɵngDeclareClassMetadata({
667
+ minVersion: "12.0.0",
668
+ version: "22.2.3",
669
+ ngImport: i0,
670
+ type: DataGridAttrs,
671
+ decorators: [{
672
+ type: Directive,
673
+ args: [{
674
+ selector: "[lilyDataGridAttrs]",
675
+ standalone: true
676
+ }]
677
+ }],
678
+ ctorParameters: () => [],
679
+ propDecorators: { lilyDataGridAttrs: [{
680
+ type: i0.Input,
681
+ args: [{
682
+ isSignal: true,
683
+ alias: "lilyDataGridAttrs",
684
+ required: false
685
+ }]
686
+ }] }
687
+ });
688
+ var DataGrid = class DataGrid {
689
+ cellFor(column, row) {
690
+ const cache = this.cellCache();
691
+ let perRow = cache.get(row);
692
+ if (!perRow) {
693
+ perRow = /* @__PURE__ */ new Map();
694
+ cache.set(row, perRow);
695
+ }
696
+ let ctx = perRow.get(column.id);
697
+ if (!ctx) {
698
+ ctx = {
699
+ value: defaultAccessor(column, row),
700
+ formatted: formatCell(column, row),
701
+ row,
702
+ column
703
+ };
704
+ perRow.set(column.id, ctx);
705
+ }
706
+ return ctx;
707
+ }
708
+ constructor() {
709
+ this.label = input.required(...ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []);
710
+ this.caption = input(void 0, ...ngDevMode ? [{ debugName: "caption" }] : /* istanbul ignore next */ []);
711
+ this.columns = input.required(...ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []);
712
+ this.rows = input.required(...ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []);
713
+ this.locale = input(void 0, ...ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []);
714
+ this.rowId = input((_row, index) => String(index), ...ngDevMode ? [{ debugName: "rowId" }] : /* istanbul ignore next */ []);
715
+ this.selectionMode = input("none", ...ngDevMode ? [{ debugName: "selectionMode" }] : /* istanbul ignore next */ []);
716
+ this.pageSize = input(void 0, ...ngDevMode ? [{ debugName: "pageSize" }] : /* istanbul ignore next */ []);
717
+ this.storageKey = input(void 0, ...ngDevMode ? [{ debugName: "storageKey" }] : /* istanbul ignore next */ []);
718
+ this.labels = input({}, ...ngDevMode ? [{ debugName: "labels" }] : /* istanbul ignore next */ []);
719
+ this.mode = input("grid", ...ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []);
720
+ this.rowClass = input(void 0, ...ngDevMode ? [{ debugName: "rowClass" }] : /* istanbul ignore next */ []);
721
+ this.rowAttrs = input(void 0, ...ngDevMode ? [{ debugName: "rowAttrs" }] : /* istanbul ignore next */ []);
722
+ this.activateRows = input(false, ...ngDevMode ? [{ debugName: "activateRows" }] : /* istanbul ignore next */ []);
723
+ this.empty = input(void 0, ...ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []);
724
+ this.footer = input(void 0, ...ngDevMode ? [{ debugName: "footer" }] : /* istanbul ignore next */ []);
725
+ this.busy = input(false, ...ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []);
726
+ this.scroll = input(true, ...ngDevMode ? [{ debugName: "scroll" }] : /* istanbul ignore next */ []);
727
+ this.exportFilename = input("data-grid", ...ngDevMode ? [{ debugName: "exportFilename" }] : /* istanbul ignore next */ []);
728
+ this.exportScope = input("view", ...ngDevMode ? [{ debugName: "exportScope" }] : /* istanbul ignore next */ []);
729
+ this.exportFormulas = input(false, ...ngDevMode ? [{ debugName: "exportFormulas" }] : /* istanbul ignore next */ []);
730
+ this.exportHandler = input(void 0, ...ngDevMode ? [{ debugName: "exportHandler" }] : /* istanbul ignore next */ []);
731
+ this.virtualize = input(void 0, ...ngDevMode ? [{ debugName: "virtualize" }] : /* istanbul ignore next */ []);
732
+ this.className = input("", ...ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []);
733
+ this.selected = model([], ...ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []);
734
+ this.filter = model("", ...ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []);
735
+ this.sort = model({
736
+ columnId: "",
737
+ direction: "none"
738
+ }, ...ngDevMode ? [{ debugName: "sort" }] : /* istanbul ignore next */ []);
739
+ this.columnOrder = model([], ...ngDevMode ? [{ debugName: "columnOrder" }] : /* istanbul ignore next */ []);
740
+ this.page = model(1, ...ngDevMode ? [{ debugName: "page" }] : /* istanbul ignore next */ []);
741
+ this.cellEdit = output();
742
+ this.filesSelected = output();
743
+ this.fileRemove = output();
744
+ this.rowActivate = output();
745
+ this.exportFile = output();
746
+ this.gridId = nextDataGridId();
747
+ this.minColumnWidth = MIN_COLUMN_WIDTH;
748
+ this.rootRef = viewChild.required("rootEl", ...ngDevMode ? [{ debugName: "rootRef" }] : /* istanbul ignore next */ []);
749
+ this.scrollRef = viewChild("scrollEl", ...ngDevMode ? [{ debugName: "scrollRef" }] : /* istanbul ignore next */ []);
750
+ this.injector = inject(Injector);
751
+ this.statusMessage = signal("", ...ngDevMode ? [{ debugName: "statusMessage" }] : /* istanbul ignore next */ []);
752
+ this.hiddenColumnIds = signal(/* @__PURE__ */ new Set(), ...ngDevMode ? [{ debugName: "hiddenColumnIds" }] : /* istanbul ignore next */ []);
753
+ this.columnWidths = signal({}, ...ngDevMode ? [{ debugName: "columnWidths" }] : /* istanbul ignore next */ []);
754
+ this.focusedRow = signal(-1, ...ngDevMode ? [{ debugName: "focusedRow" }] : /* istanbul ignore next */ []);
755
+ this.focusedCol = signal(0, ...ngDevMode ? [{ debugName: "focusedCol" }] : /* istanbul ignore next */ []);
756
+ this.lastSelectedIndex = -1;
757
+ this.editingState = signal(null, ...ngDevMode ? [{ debugName: "editingState" }] : /* istanbul ignore next */ []);
758
+ this.scrollTop = signal(0, ...ngDevMode ? [{ debugName: "scrollTop" }] : /* istanbul ignore next */ []);
759
+ this.draggingColumnId = null;
760
+ this.resolvedColumns = computed(() => this.columns().map((c) => resolveColumn(c, this.locale(), this.rows())), ...ngDevMode ? [{ debugName: "resolvedColumns" }] : /* istanbul ignore next */ []);
761
+ this.isGrid = computed(() => this.mode() === "grid", ...ngDevMode ? [{ debugName: "isGrid" }] : /* istanbul ignore next */ []);
762
+ this.hasSelection = computed(() => this.selectionMode() !== "none", ...ngDevMode ? [{ debugName: "hasSelection" }] : /* istanbul ignore next */ []);
763
+ this.colOffset = computed(() => this.hasSelection() ? 1 : 0, ...ngDevMode ? [{ debugName: "colOffset" }] : /* istanbul ignore next */ []);
764
+ this.orderedColumns = computed(() => {
765
+ const order = this.columnOrder();
766
+ const columns = this.resolvedColumns();
767
+ if (order.length === 0) return columns;
768
+ const rank = (id) => {
769
+ const i = order.indexOf(id);
770
+ return i === -1 ? order.length : i;
771
+ };
772
+ return columns.map((c, i) => ({
773
+ c,
774
+ i
775
+ })).sort((a, b) => rank(a.c.id) - rank(b.c.id) || a.i - b.i).map((x) => x.c);
776
+ }, ...ngDevMode ? [{ debugName: "orderedColumns" }] : /* istanbul ignore next */ []);
777
+ this.visibleColumns = computed(() => this.orderedColumns().filter((c) => !this.hiddenColumnIds().has(c.id)), ...ngDevMode ? [{ debugName: "visibleColumns" }] : /* istanbul ignore next */ []);
778
+ this.hidableColumns = computed(() => this.resolvedColumns().filter((c) => c.hidable), ...ngDevMode ? [{ debugName: "hidableColumns" }] : /* istanbul ignore next */ []);
779
+ this.entries = computed(() => {
780
+ const rowId = this.rowId();
781
+ return this.rows().map((row, index) => ({
782
+ row,
783
+ id: rowId(row, index)
784
+ }));
785
+ }, ...ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []);
786
+ this.cellCache = computed(() => {
787
+ this.resolvedColumns();
788
+ return /* @__PURE__ */ new WeakMap();
789
+ }, ...ngDevMode ? [{ debugName: "cellCache" }] : /* istanbul ignore next */ []);
790
+ this.searchIndex = computed(() => {
791
+ const filterable = this.resolvedColumns().filter((c) => c.filterable !== false);
792
+ return this.entries().map((entry) => filterable.map((column) => (column.filterText ? column.filterText(this.cellFor(column, entry.row).value, entry.row) : this.cellFor(column, entry.row).formatted).toLowerCase()));
793
+ }, ...ngDevMode ? [{ debugName: "searchIndex" }] : /* istanbul ignore next */ []);
794
+ this.filteredEntries = computed(() => {
795
+ const text = this.filter().trim().toLowerCase();
796
+ if (!text) return this.entries();
797
+ const index = this.searchIndex();
798
+ return this.entries().filter((_entry, i) => index[i].some((value) => value.includes(text)));
799
+ }, ...ngDevMode ? [{ debugName: "filteredEntries" }] : /* istanbul ignore next */ []);
800
+ this.sortedEntries = computed(() => {
801
+ const sort = this.sort();
802
+ const filtered = this.filteredEntries();
803
+ if (sort.direction === "none" || !sort.columnId) return filtered;
804
+ const column = this.resolvedColumns().find((c) => c.id === sort.columnId);
805
+ if (!column) return filtered;
806
+ const dir = sort.direction === "ascending" ? 1 : -1;
807
+ const compare = column.compare;
808
+ const keyed = filtered.map((entry) => ({
809
+ entry,
810
+ key: this.cellFor(column, entry.row).value
811
+ }));
812
+ keyed.sort((a, b) => {
813
+ const av = a.key;
814
+ const bv = b.key;
815
+ if (av === bv) return 0;
816
+ if (av === null || av === void 0) return 1;
817
+ if (bv === null || bv === void 0) return -1;
818
+ if (compare) return compare(av, bv) * dir;
819
+ return av > bv ? dir : -dir;
820
+ });
821
+ return keyed.map((k) => k.entry);
822
+ }, ...ngDevMode ? [{ debugName: "sortedEntries" }] : /* istanbul ignore next */ []);
823
+ this.pageCount = computed(() => {
824
+ const size = this.pageSize();
825
+ return size ? Math.max(1, Math.ceil(this.sortedEntries().length / size)) : 1;
826
+ }, ...ngDevMode ? [{ debugName: "pageCount" }] : /* istanbul ignore next */ []);
827
+ this.clampedPage = computed(() => Math.min(Math.max(this.page(), 1), this.pageCount()), ...ngDevMode ? [{ debugName: "clampedPage" }] : /* istanbul ignore next */ []);
828
+ this.pageStart = computed(() => {
829
+ const size = this.pageSize();
830
+ return size ? (this.clampedPage() - 1) * size : 0;
831
+ }, ...ngDevMode ? [{ debugName: "pageStart" }] : /* istanbul ignore next */ []);
832
+ this.pageEntries = computed(() => {
833
+ const size = this.pageSize();
834
+ return size ? this.sortedEntries().slice(this.pageStart(), this.pageStart() + size) : this.sortedEntries();
835
+ }, ...ngDevMode ? [{ debugName: "pageEntries" }] : /* istanbul ignore next */ []);
836
+ this.lastCol = computed(() => this.colOffset() + this.visibleColumns().length - 1, ...ngDevMode ? [{ debugName: "lastCol" }] : /* istanbul ignore next */ []);
837
+ this.activeRow = computed(() => Math.max(-1, Math.min(this.focusedRow(), this.pageEntries().length - 1)), ...ngDevMode ? [{ debugName: "activeRow" }] : /* istanbul ignore next */ []);
838
+ this.activeCol = computed(() => Math.max(0, Math.min(this.focusedCol(), this.lastCol())), ...ngDevMode ? [{ debugName: "activeCol" }] : /* istanbul ignore next */ []);
839
+ this.indexed = computed(() => !!this.pageSize() || !!this.virtualize(), ...ngDevMode ? [{ debugName: "indexed" }] : /* istanbul ignore next */ []);
840
+ this.rowCount = computed(() => this.pageSize() ? this.sortedEntries().length + 1 : this.virtualize() ? this.pageEntries().length + 1 : void 0, ...ngDevMode ? [{ debugName: "rowCount" }] : /* istanbul ignore next */ []);
841
+ this.renderItems = computed(() => {
842
+ const total = this.pageEntries().length;
843
+ const v = this.virtualize();
844
+ if (!v) return this.pageEntries().map((_, index) => ({
845
+ kind: "row",
846
+ index
847
+ }));
848
+ const overscan = v.overscan ?? 3;
849
+ const top = this.scrollTop();
850
+ const start = Math.max(0, Math.floor(top / v.rowHeight) - overscan);
851
+ const end = Math.min(total, Math.ceil((top + v.height) / v.rowHeight) + overscan);
852
+ const indices = /* @__PURE__ */ new Set();
853
+ for (let i = start; i < end; i++) indices.add(i);
854
+ const active = this.activeRow();
855
+ if (active >= 0 && active < total) indices.add(active);
856
+ const items = [];
857
+ let previous = -1;
858
+ for (const index of [...indices].sort((a, b) => a - b)) {
859
+ if (index - previous > 1) items.push({
860
+ kind: "spacer",
861
+ rows: index - previous - 1,
862
+ key: `spacer-${previous}`
863
+ });
864
+ items.push({
865
+ kind: "row",
866
+ index
867
+ });
868
+ previous = index;
869
+ }
870
+ if (total - 1 - previous > 0) items.push({
871
+ kind: "spacer",
872
+ rows: total - 1 - previous,
873
+ key: `spacer-${previous}`
874
+ });
875
+ return items;
876
+ }, ...ngDevMode ? [{ debugName: "renderItems" }] : /* istanbul ignore next */ []);
877
+ this.scrollStyle = computed(() => {
878
+ const v = this.virtualize();
879
+ return v ? `height:${v.height}px;overflow:auto` : null;
880
+ }, ...ngDevMode ? [{ debugName: "scrollStyle" }] : /* istanbul ignore next */ []);
881
+ this.selectedSet = computed(() => new Set(this.selected()), ...ngDevMode ? [{ debugName: "selectedSet" }] : /* istanbul ignore next */ []);
882
+ this.allSelected = computed(() => this.sortedEntries().length > 0 && this.sortedEntries().every((e) => this.selectedSet().has(e.id)), ...ngDevMode ? [{ debugName: "allSelected" }] : /* istanbul ignore next */ []);
883
+ this.someSelected = computed(() => !this.allSelected() && this.sortedEntries().some((e) => this.selectedSet().has(e.id)), ...ngDevMode ? [{ debugName: "someSelected" }] : /* istanbul ignore next */ []);
884
+ this.footerContext = computed(() => ({
885
+ columns: this.visibleColumns(),
886
+ rows: this.pageEntries().map((e) => e.row)
887
+ }), ...ngDevMode ? [{ debugName: "footerContext" }] : /* istanbul ignore next */ []);
888
+ this.exportColumns = computed(() => this.visibleColumns().filter((c) => c.exportable !== false), ...ngDevMode ? [{ debugName: "exportColumns" }] : /* istanbul ignore next */ []);
889
+ this.exportEntries = computed(() => this.exportScope() === "selected" ? this.sortedEntries().filter((e) => this.selectedSet().has(e.id)) : this.sortedEntries(), ...ngDevMode ? [{ debugName: "exportEntries" }] : /* istanbul ignore next */ []);
890
+ this.hasExportButtons = computed(() => {
891
+ const l = this.labels();
892
+ return !!(l.exportCsv || l.exportTsv || l.exportExcel);
893
+ }, ...ngDevMode ? [{ debugName: "hasExportButtons" }] : /* istanbul ignore next */ []);
894
+ effect(() => {
895
+ const root = this.rootRef().nativeElement;
896
+ const grid = this.isGrid();
897
+ const count = this.rowCount();
898
+ const busy = this.busy();
899
+ this.sortedEntries();
900
+ this.pageEntries();
901
+ this.visibleColumns();
902
+ this.scroll();
903
+ afterNextRender(() => {
904
+ const table = root.querySelector("table.data-table");
905
+ if (!table) return;
906
+ table.setAttribute("role", grid ? "grid" : "table");
907
+ if (grid && count !== void 0) table.setAttribute("aria-rowcount", String(count));
908
+ else table.removeAttribute("aria-rowcount");
909
+ if (busy) table.setAttribute("aria-busy", "true");
910
+ else table.removeAttribute("aria-busy");
911
+ }, { injector: this.injector });
912
+ });
913
+ effect(() => {
914
+ this.pageEntries();
915
+ this.visibleColumns();
916
+ this.isGrid();
917
+ this.scrollTop();
918
+ this.editingState();
919
+ afterNextRender(() => this.normaliseWidgets(), { injector: this.injector });
920
+ });
921
+ let restored = false;
922
+ effect(() => {
923
+ const key = this.storageKey();
924
+ if (!key || restored) return;
925
+ restored = true;
926
+ untracked(() => this.restoreView(key));
927
+ });
928
+ effect(() => {
929
+ const key = this.storageKey();
930
+ const snapshot = {
931
+ columnWidths: this.columnWidths(),
932
+ hiddenColumnIds: [...this.hiddenColumnIds()],
933
+ sort: this.sort()
934
+ };
935
+ if (!key || typeof localStorage === "undefined") return;
936
+ try {
937
+ localStorage.setItem(key, JSON.stringify(snapshot));
938
+ } catch {}
939
+ });
940
+ }
941
+ announce(message) {
942
+ if (message) this.statusMessage.set(message);
943
+ }
944
+ ariaSortFor(column) {
945
+ if (!column.sortable) return null;
946
+ return this.sort().columnId === column.id ? this.sort().direction : "none";
947
+ }
948
+ toggleSort(column) {
949
+ if (!column.sortable) return;
950
+ const current = this.sort();
951
+ const next = current.columnId !== column.id ? {
952
+ columnId: column.id,
953
+ direction: "ascending"
954
+ } : current.direction === "ascending" ? {
955
+ columnId: column.id,
956
+ direction: "descending"
957
+ } : current.direction === "descending" ? {
958
+ columnId: "",
959
+ direction: "none"
960
+ } : {
961
+ columnId: column.id,
962
+ direction: "ascending"
963
+ };
964
+ this.sort.set(next);
965
+ this.announce(this.labels().sortAnnouncement?.(column.header, next.direction));
966
+ }
967
+ onFilterInput(event) {
968
+ this.filter.set(event.target.value);
969
+ this.page.set(1);
970
+ this.announce(this.labels().filterAnnouncement?.(this.filteredEntries().length, this.rows().length));
971
+ }
972
+ isSelected(id) {
973
+ return this.selectedSet().has(id);
974
+ }
975
+ setSelected(next) {
976
+ this.selected.set(next);
977
+ this.announce(this.labels().selectionAnnouncement?.(next.length));
978
+ }
979
+ onRowSelectClick(index, event) {
980
+ const e = event;
981
+ this.toggleRow(index, e);
982
+ }
983
+ toggleRow(index, event) {
984
+ const sorted = this.sortedEntries();
985
+ const id = sorted[index].id;
986
+ const selected = this.selected();
987
+ const mode = this.selectionMode();
988
+ if (mode === "single") this.setSelected(this.isSelected(id) ? [] : [id]);
989
+ else if (mode === "multiple") {
990
+ if (event?.shiftKey && this.lastSelectedIndex >= 0) {
991
+ const [start, end] = this.lastSelectedIndex < index ? [this.lastSelectedIndex, index] : [index, this.lastSelectedIndex];
992
+ const range = sorted.slice(start, end + 1).map((e) => e.id);
993
+ this.setSelected([.../* @__PURE__ */ new Set([...selected, ...range])]);
994
+ } else if (event?.ctrlKey || event?.metaKey) this.setSelected(this.isSelected(id) ? selected.filter((x) => x !== id) : [...selected, id]);
995
+ else this.setSelected(this.isSelected(id) && selected.length === 1 ? [] : [id]);
996
+ }
997
+ this.lastSelectedIndex = index;
998
+ }
999
+ toggleSelectAll() {
1000
+ if (this.allSelected()) this.setSelected([]);
1001
+ else this.setSelected(this.sortedEntries().map((e) => e.id));
1002
+ }
1003
+ widthFor(column) {
1004
+ return this.columnWidths()[column.id] ?? column.width;
1005
+ }
1006
+ cellStyle(column) {
1007
+ const parts = [];
1008
+ const width = this.widthFor(column);
1009
+ if (width) parts.push(`width:${width}px`);
1010
+ if (column.align) parts.push(`text-align:${column.align}`);
1011
+ return parts.length > 0 ? parts.join(";") : null;
1012
+ }
1013
+ setWidth(column, width) {
1014
+ this.columnWidths.update((current) => ({
1015
+ ...current,
1016
+ [column.id]: Math.max(MIN_COLUMN_WIDTH, width)
1017
+ }));
1018
+ }
1019
+ onResizeKeydown(column, event) {
1020
+ const e = event;
1021
+ e.stopPropagation();
1022
+ const current = this.widthFor(column) ?? 120;
1023
+ if (e.key === "ArrowRight") {
1024
+ e.preventDefault();
1025
+ this.setWidth(column, current + RESIZE_STEP);
1026
+ } else if (e.key === "ArrowLeft") {
1027
+ e.preventDefault();
1028
+ this.setWidth(column, current - RESIZE_STEP);
1029
+ }
1030
+ }
1031
+ onResizePointerDown(column, event) {
1032
+ const e = event;
1033
+ e.preventDefault();
1034
+ const startX = e.clientX;
1035
+ const target = e.target;
1036
+ const startWidth = this.widthFor(column) ?? target.closest("th")?.getBoundingClientRect().width ?? 120;
1037
+ target.setPointerCapture?.(e.pointerId);
1038
+ const onMove = (moveEvent) => this.setWidth(column, startWidth + (moveEvent.clientX - startX));
1039
+ const onUp = () => {
1040
+ window.removeEventListener("pointermove", onMove);
1041
+ window.removeEventListener("pointerup", onUp);
1042
+ };
1043
+ window.addEventListener("pointermove", onMove);
1044
+ window.addEventListener("pointerup", onUp);
1045
+ }
1046
+ toggleColumnVisibility(columnId) {
1047
+ this.hiddenColumnIds.update((current) => {
1048
+ const next = new Set(current);
1049
+ if (next.has(columnId)) next.delete(columnId);
1050
+ else next.add(columnId);
1051
+ return next;
1052
+ });
1053
+ }
1054
+ goToPage(next) {
1055
+ const target = Math.min(Math.max(next, 1), this.pageCount());
1056
+ if (target === this.clampedPage()) return;
1057
+ this.page.set(target);
1058
+ this.announce(this.labels().pageAnnouncement?.(target, this.pageCount()));
1059
+ }
1060
+ moveColumn(columnId, toVisibleIndex) {
1061
+ const ids = this.visibleColumns().map((c) => c.id);
1062
+ const from = ids.indexOf(columnId);
1063
+ const to = Math.min(Math.max(toVisibleIndex, 0), ids.length - 1);
1064
+ if (from === -1 || from === to) return false;
1065
+ ids.splice(to, 0, ...ids.splice(from, 1));
1066
+ const visibleSet = new Set(ids);
1067
+ let next = 0;
1068
+ this.columnOrder.set(this.orderedColumns().map((c) => visibleSet.has(c.id) ? ids[next++] : c.id));
1069
+ const column = this.resolvedColumns().find((c) => c.id === columnId);
1070
+ this.announce(this.labels().columnMoveAnnouncement?.(column.header, to + 1, ids.length));
1071
+ return true;
1072
+ }
1073
+ onHeaderDragStart(column, event) {
1074
+ if (!column.movable) return;
1075
+ this.draggingColumnId = column.id;
1076
+ event.dataTransfer?.setData("text/plain", column.id);
1077
+ }
1078
+ onHeaderDragOver(event) {
1079
+ if (this.draggingColumnId) event.preventDefault();
1080
+ }
1081
+ onHeaderDrop(targetId, event) {
1082
+ event.preventDefault();
1083
+ const id = this.draggingColumnId;
1084
+ this.draggingColumnId = null;
1085
+ if (!id || id === targetId) return;
1086
+ const to = this.visibleColumns().findIndex((c) => c.id === targetId);
1087
+ if (to >= 0) this.moveColumn(id, to);
1088
+ }
1089
+ onScroll(event) {
1090
+ if (this.virtualize()) this.scrollTop.set(event.currentTarget.scrollTop);
1091
+ }
1092
+ revealRow(index) {
1093
+ const v = this.virtualize();
1094
+ if (!v || index < 0) return;
1095
+ const top = index * v.rowHeight;
1096
+ const current = this.scrollTop();
1097
+ let next = current;
1098
+ if (top < current) next = top;
1099
+ else if (top + v.rowHeight > current + v.height) next = top + v.rowHeight - v.height;
1100
+ if (next === current) return;
1101
+ this.scrollTop.set(next);
1102
+ const el = this.scrollRef()?.nativeElement;
1103
+ if (el) el.scrollTop = next;
1104
+ }
1105
+ focusActiveCell() {
1106
+ afterNextRender(() => {
1107
+ this.rootRef().nativeElement.querySelector(".data-table-th[tabindex=\"0\"], .data-table-td[tabindex=\"0\"]")?.focus({ preventScroll: true });
1108
+ }, { injector: this.injector });
1109
+ }
1110
+ moveFocus(row, col) {
1111
+ const nextRow = Math.min(Math.max(row, -1), this.pageEntries().length - 1);
1112
+ this.focusedRow.set(nextRow);
1113
+ this.focusedCol.set(Math.min(Math.max(col, 0), this.lastCol()));
1114
+ this.revealRow(nextRow);
1115
+ this.focusActiveCell();
1116
+ }
1117
+ exportCell(column, row) {
1118
+ if (column.exportValue) return column.exportValue(row);
1119
+ const ctx = this.cellFor(column, row);
1120
+ const value = ctx.value;
1121
+ if (value === null || value === void 0) return null;
1122
+ if (typeof value === "number" || typeof value === "boolean" || value instanceof Date) return value;
1123
+ return ctx.formatted;
1124
+ }
1125
+ hasUtcColumn(c) {
1126
+ return c.type === "time-zone" && !!c.exportUtc && !!c.referenceDate && !!this.labels().exportUtcHeader;
1127
+ }
1128
+ runExport(format) {
1129
+ const live = (c) => format === "xlsx" && this.exportFormulas() && c.type === "formula" && !!c.source;
1130
+ const headers = [];
1131
+ for (const c of this.exportColumns()) {
1132
+ headers.push(c.header);
1133
+ if (this.hasUtcColumn(c)) headers.push(this.labels().exportUtcHeader(c.header));
1134
+ }
1135
+ const rows = this.exportEntries().map((entry) => {
1136
+ const cells = [];
1137
+ for (const c of this.exportColumns()) {
1138
+ const expression = live(c) ? c.source(entry.row) : void 0;
1139
+ cells.push(typeof expression === "string" && expression !== "" && !c.errorOf?.(entry.row) ? {
1140
+ formula: expression,
1141
+ result: this.exportCell(c, entry.row)
1142
+ } : this.exportCell(c, entry.row));
1143
+ if (this.hasUtcColumn(c)) cells.push(zonedUtcTime(this.cellFor(c, entry.row).value, c.referenceDate));
1144
+ }
1145
+ return cells;
1146
+ });
1147
+ const table = {
1148
+ headers,
1149
+ rows
1150
+ };
1151
+ const name = this.exportFilename();
1152
+ const filename = exportFilename(name, format);
1153
+ const blob = exportBlob(table, format, { sheetName: name });
1154
+ const file = {
1155
+ format,
1156
+ filename,
1157
+ rowCount: rows.length,
1158
+ blob
1159
+ };
1160
+ this.exportFile.emit(file);
1161
+ if (this.exportHandler()?.(file) === false) return;
1162
+ downloadBlob(blob, filename);
1163
+ this.announce(this.labels().exportAnnouncement?.(format, rows.length, filename));
1164
+ }
1165
+ onFileDragOver(column, event) {
1166
+ if (column.type !== "file" || !column.editable) return;
1167
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
1168
+ }
1169
+ onFileDrop(entry, column, event) {
1170
+ if (column.type !== "file" || !column.editable) return;
1171
+ const dropped = Array.from(event.dataTransfer?.files ?? []);
1172
+ if (dropped.length === 0) return;
1173
+ event.preventDefault();
1174
+ const files = column.multiple === false ? dropped.slice(0, 1) : dropped;
1175
+ this.filesSelected.emit({
1176
+ rowId: entry.id,
1177
+ row: entry.row,
1178
+ columnId: column.id,
1179
+ files
1180
+ });
1181
+ }
1182
+ onFileInput(entry, column, event) {
1183
+ const input = event.target;
1184
+ const chosen = Array.from(input.files ?? []);
1185
+ if (chosen.length > 0) this.filesSelected.emit({
1186
+ rowId: entry.id,
1187
+ row: entry.row,
1188
+ columnId: column.id,
1189
+ files: chosen
1190
+ });
1191
+ input.value = "";
1192
+ }
1193
+ isEditable(column) {
1194
+ if (!column.editable) return false;
1195
+ if (column.type === "rollup" || column.type === "file") return false;
1196
+ if (column.type === "person" && !column.personEditor) return false;
1197
+ return true;
1198
+ }
1199
+ isEditingCell(entry, column) {
1200
+ const e = this.editingState();
1201
+ return !!e && e.rowId === entry.id && e.columnId === column.id;
1202
+ }
1203
+ startEdit(entry, column) {
1204
+ if (!this.isEditable(column)) return;
1205
+ const raw = column.type === "formula" && column.source ? column.source(entry.row) : defaultAccessor(column, entry.row);
1206
+ if (column.type === "time-zone") {
1207
+ const zoned = isZonedTime(raw) ? raw : null;
1208
+ this.editingState.set({
1209
+ rowId: entry.id,
1210
+ columnId: column.id,
1211
+ draft: zoned?.time ?? "",
1212
+ zone: zoned?.timeZone ?? column.timeZones?.[0]?.id ?? ""
1213
+ });
1214
+ } else {
1215
+ const text = column.type === "link" ? linkParts(raw)?.href ?? "" : raw === null || raw === void 0 ? "" : String(raw);
1216
+ this.editingState.set({
1217
+ rowId: entry.id,
1218
+ columnId: column.id,
1219
+ draft: text
1220
+ });
1221
+ }
1222
+ afterNextRender(() => {
1223
+ const editor = this.rootRef().nativeElement.querySelector(".data-grid-cell-editor");
1224
+ editor?.focus();
1225
+ if (editor instanceof HTMLInputElement && editor.type === "text") editor.select();
1226
+ }, { injector: this.injector });
1227
+ }
1228
+ parseDraft(column, row, draft, zone) {
1229
+ if (column.parse) return column.parse(draft, row);
1230
+ if (column.type === "time-zone") return draft === "" ? null : {
1231
+ time: draft,
1232
+ timeZone: zone ?? ""
1233
+ };
1234
+ if (column.editor === "number") return draft.trim() === "" ? null : Number(draft);
1235
+ if (column.editor === "date" || column.editor === "time" || column.editor === "select") return draft === "" ? null : draft;
1236
+ return draft;
1237
+ }
1238
+ isStructurallyEqual(a, b) {
1239
+ return typeof a === "object" && typeof b === "object" && a !== null && b !== null && JSON.stringify(a) === JSON.stringify(b);
1240
+ }
1241
+ commitValue(entry, column, value) {
1242
+ const message = column.validate?.(value, entry.row);
1243
+ const invalidType = isInvalidForType(column, value);
1244
+ if (message || invalidType) {
1245
+ const text = message ?? this.labels().invalidValue?.(column.header, column.type ?? "") ?? "";
1246
+ const current = this.editingState();
1247
+ if (current) this.editingState.set({
1248
+ ...current,
1249
+ error: text || void 0,
1250
+ invalid: true
1251
+ });
1252
+ this.announce(this.labels().editError?.(column.header, text));
1253
+ return false;
1254
+ }
1255
+ const previous = column.type === "formula" && column.source ? column.source(entry.row) : defaultAccessor(column, entry.row);
1256
+ this.editingState.set(null);
1257
+ if (value !== previous && !this.isStructurallyEqual(value, previous)) {
1258
+ this.cellEdit.emit({
1259
+ rowId: entry.id,
1260
+ row: entry.row,
1261
+ columnId: column.id,
1262
+ value,
1263
+ previous
1264
+ });
1265
+ this.announce(this.labels().editAnnouncement?.(column.header, column.format ? column.format(value, entry.row) : String(value ?? "")));
1266
+ }
1267
+ return true;
1268
+ }
1269
+ commitEdit() {
1270
+ const current = this.editingState();
1271
+ if (!current) return;
1272
+ const entry = this.entries().find((e) => e.id === current.rowId);
1273
+ const column = this.resolvedColumns().find((c) => c.id === current.columnId);
1274
+ if (!entry || !column) {
1275
+ this.editingState.set(null);
1276
+ return;
1277
+ }
1278
+ if (this.commitValue(entry, column, this.parseDraft(column, entry.row, current.draft, current.zone))) this.focusActiveCell();
1279
+ }
1280
+ cancelEdit() {
1281
+ const current = this.editingState();
1282
+ if (!current) return;
1283
+ this.editingState.set(null);
1284
+ const column = this.resolvedColumns().find((c) => c.id === current.columnId);
1285
+ if (column) this.announce(this.labels().editCancelAnnouncement?.(column.header));
1286
+ this.focusActiveCell();
1287
+ }
1288
+ onEditorKeydown(event) {
1289
+ const e = event;
1290
+ e.stopPropagation();
1291
+ if (e.isComposing || e.keyCode === 229) return;
1292
+ if (e.key === "Enter") {
1293
+ e.preventDefault();
1294
+ this.commitEdit();
1295
+ } else if (e.key === "Escape") {
1296
+ e.preventDefault();
1297
+ this.cancelEdit();
1298
+ }
1299
+ }
1300
+ onEditorBlur() {
1301
+ if (this.editingState()) this.commitEdit();
1302
+ }
1303
+ onZonedFocusout(event) {
1304
+ const e = event;
1305
+ if (this.editingState() && !e.currentTarget.contains(e.relatedTarget)) this.commitEdit();
1306
+ }
1307
+ setDraft(event) {
1308
+ const current = this.editingState();
1309
+ if (current) this.editingState.set({
1310
+ ...current,
1311
+ draft: event.target.value,
1312
+ error: void 0,
1313
+ invalid: false
1314
+ });
1315
+ }
1316
+ setZone(event) {
1317
+ const current = this.editingState();
1318
+ if (current) this.editingState.set({
1319
+ ...current,
1320
+ zone: event.target.value,
1321
+ error: void 0,
1322
+ invalid: false
1323
+ });
1324
+ }
1325
+ personEditorContext(entry, column, ctx) {
1326
+ return {
1327
+ value: ctx.value,
1328
+ commit: (v) => void this.commitValue(entry, column, v),
1329
+ cancel: () => this.cancelEdit()
1330
+ };
1331
+ }
1332
+ activateFocusedCell() {
1333
+ if (this.activeCol() === 0 && this.hasSelection()) {
1334
+ if (this.activeRow() === -1) {
1335
+ if (this.selectionMode() === "multiple") this.toggleSelectAll();
1336
+ } else this.toggleRow(this.pageStart() + this.activeRow());
1337
+ return;
1338
+ }
1339
+ const column = this.visibleColumns()[this.activeCol() - this.colOffset()];
1340
+ if (!column) return;
1341
+ if (this.activeRow() === -1) this.toggleSort(column);
1342
+ }
1343
+ widgetsIn(cell) {
1344
+ return Array.from(cell.querySelectorAll(WIDGET_SELECTOR)).filter((el) => !el.closest("[data-grid-own]"));
1345
+ }
1346
+ setWidgetTabindex(cell, value) {
1347
+ for (const widget of this.widgetsIn(cell)) widget.setAttribute("tabindex", value);
1348
+ }
1349
+ normaliseWidgets() {
1350
+ if (!this.isGrid()) return;
1351
+ for (const cell of this.rootRef().nativeElement.querySelectorAll(".data-table-body [data-row][data-col]")) {
1352
+ if (cell.getAttribute("data-grid-entered") === "true") continue;
1353
+ this.setWidgetTabindex(cell, "-1");
1354
+ }
1355
+ }
1356
+ enterCell(cell) {
1357
+ const first = this.widgetsIn(cell)[0];
1358
+ if (!first) return false;
1359
+ cell.setAttribute("data-grid-entered", "true");
1360
+ this.setWidgetTabindex(cell, "0");
1361
+ first.focus();
1362
+ return true;
1363
+ }
1364
+ leaveCell(cell) {
1365
+ if (cell.getAttribute("data-grid-entered") !== "true") return;
1366
+ cell.removeAttribute("data-grid-entered");
1367
+ this.setWidgetTabindex(cell, "-1");
1368
+ }
1369
+ onGridFocusin(event) {
1370
+ if (!this.isGrid()) return;
1371
+ const target = event.target;
1372
+ const cell = target.closest("[data-row][data-col]");
1373
+ if (!cell) return;
1374
+ this.focusedRow.set(Number(cell.dataset["row"]));
1375
+ this.focusedCol.set(Number(cell.dataset["col"]));
1376
+ if (target !== cell && !target.closest("[data-grid-own]") && cell.closest(".data-table-body")) {
1377
+ cell.setAttribute("data-grid-entered", "true");
1378
+ this.setWidgetTabindex(cell, "0");
1379
+ }
1380
+ }
1381
+ onGridFocusout(event) {
1382
+ if (!this.isGrid()) return;
1383
+ const e = event;
1384
+ const cell = e.target.closest("[data-row][data-col]");
1385
+ if (!cell) return;
1386
+ const next = e.relatedTarget;
1387
+ if (next && cell.contains(next)) return;
1388
+ this.leaveCell(cell);
1389
+ }
1390
+ onGridClick(event) {
1391
+ if (!this.activateRows()) return;
1392
+ const target = event.target;
1393
+ const cell = target.closest("[data-row][data-col]");
1394
+ if (!cell || cell.closest(".data-table-head")) return;
1395
+ if (target.closest(WIDGET_SELECTOR) || target.closest("[data-grid-own]")) return;
1396
+ const entry = this.pageEntries()[Number(cell.dataset["row"])];
1397
+ if (entry) this.rowActivate.emit({
1398
+ row: entry.row,
1399
+ id: entry.id
1400
+ });
1401
+ }
1402
+ onGridDblclick(event) {
1403
+ const target = event.target;
1404
+ const cell = target.closest("[data-row][data-col]");
1405
+ if (!cell || cell.closest(".data-table-head") || target.closest("input, button, select, textarea")) return;
1406
+ const entry = this.pageEntries()[Number(cell.dataset["row"])];
1407
+ const column = this.visibleColumns()[Number(cell.dataset["col"]) - this.colOffset()];
1408
+ if (entry && column && this.isEditable(column)) this.startEdit(entry, column);
1409
+ }
1410
+ onGridKeydown(event) {
1411
+ if (!this.isGrid()) return;
1412
+ const e = event;
1413
+ const target = e.target;
1414
+ const cell = target.closest("[data-row][data-col]");
1415
+ if (!cell) return;
1416
+ if (target !== cell && !target.closest("[data-grid-own]")) {
1417
+ if (e.key === "Escape") {
1418
+ e.preventDefault();
1419
+ cell.focus({ preventScroll: true });
1420
+ }
1421
+ return;
1422
+ }
1423
+ const activeRow = this.activeRow();
1424
+ const activeCol = this.activeCol();
1425
+ const colOffset = this.colOffset();
1426
+ if (e.altKey && activeRow === -1 && (e.key === "ArrowRight" || e.key === "ArrowLeft")) {
1427
+ const column = this.visibleColumns()[activeCol - colOffset];
1428
+ if (column?.movable) {
1429
+ e.preventDefault();
1430
+ const delta = e.key === "ArrowRight" ? 1 : -1;
1431
+ if (this.moveColumn(column.id, activeCol - colOffset + delta)) this.moveFocus(-1, activeCol + delta);
1432
+ return;
1433
+ }
1434
+ }
1435
+ const size = this.pageSize();
1436
+ switch (e.key) {
1437
+ case "ArrowRight":
1438
+ e.preventDefault();
1439
+ this.moveFocus(activeRow, activeCol + 1);
1440
+ break;
1441
+ case "ArrowLeft":
1442
+ e.preventDefault();
1443
+ this.moveFocus(activeRow, activeCol - 1);
1444
+ break;
1445
+ case "ArrowDown":
1446
+ e.preventDefault();
1447
+ this.moveFocus(activeRow + 1, activeCol);
1448
+ break;
1449
+ case "ArrowUp":
1450
+ e.preventDefault();
1451
+ this.moveFocus(activeRow - 1, activeCol);
1452
+ break;
1453
+ case "Home":
1454
+ e.preventDefault();
1455
+ if (e.ctrlKey || e.metaKey) this.moveFocus(-1, 0);
1456
+ else this.moveFocus(activeRow, 0);
1457
+ break;
1458
+ case "End":
1459
+ e.preventDefault();
1460
+ if (e.ctrlKey || e.metaKey) this.moveFocus(this.pageEntries().length - 1, this.lastCol());
1461
+ else this.moveFocus(activeRow, this.lastCol());
1462
+ break;
1463
+ case "PageDown":
1464
+ e.preventDefault();
1465
+ this.moveFocus(activeRow + (size ?? 10), activeCol);
1466
+ break;
1467
+ case "PageUp":
1468
+ e.preventDefault();
1469
+ this.moveFocus(activeRow - (size ?? 10), activeCol);
1470
+ break;
1471
+ case "F2": {
1472
+ const entry = this.pageEntries()[Number(cell.dataset["row"])];
1473
+ const column = this.visibleColumns()[Number(cell.dataset["col"]) - colOffset];
1474
+ if (target === cell && entry && column && this.isEditable(column)) {
1475
+ e.preventDefault();
1476
+ this.startEdit(entry, column);
1477
+ }
1478
+ break;
1479
+ }
1480
+ case "Enter":
1481
+ case " ":
1482
+ e.preventDefault();
1483
+ if (e.key === "Enter" && target === cell && cell.closest(".data-table-body")) {
1484
+ const entry = this.pageEntries()[Number(cell.dataset["row"])];
1485
+ const column = this.visibleColumns()[Number(cell.dataset["col"]) - colOffset];
1486
+ if (entry && column && this.isEditable(column)) {
1487
+ this.startEdit(entry, column);
1488
+ break;
1489
+ }
1490
+ }
1491
+ if (target === cell && cell.closest(".data-table-body") && !cell.querySelector("[data-grid-own]")) {
1492
+ if (this.enterCell(cell)) break;
1493
+ if (e.key === "Enter" && this.activateRows()) {
1494
+ const entry = this.pageEntries()[Number(cell.dataset["row"])];
1495
+ if (entry) this.rowActivate.emit({
1496
+ row: entry.row,
1497
+ id: entry.id
1498
+ });
1499
+ break;
1500
+ }
1501
+ }
1502
+ this.activateFocusedCell();
1503
+ }
1504
+ }
1505
+ restoreView(key) {
1506
+ try {
1507
+ const raw = localStorage.getItem(key);
1508
+ if (raw) {
1509
+ const saved = JSON.parse(raw);
1510
+ if (saved.columnWidths) this.columnWidths.set(saved.columnWidths);
1511
+ if (saved.hiddenColumnIds) this.hiddenColumnIds.set(new Set(saved.hiddenColumnIds));
1512
+ if (saved.sort) this.sort.set(saved.sort);
1513
+ }
1514
+ } catch {}
1515
+ }
1516
+ rowLabelOf(entry) {
1517
+ const first = this.visibleColumns()[0];
1518
+ return first ? this.cellFor(first, entry.row).formatted : entry.id;
1519
+ }
1520
+ cellClassOf(column, ctx) {
1521
+ return (typeof column.cellClass === "function" ? column.cellClass(ctx) : column.cellClass) ?? "";
1522
+ }
1523
+ peopleIn(value) {
1524
+ return peopleOf(value);
1525
+ }
1526
+ shownPeople(column, value) {
1527
+ const people = peopleOf(value);
1528
+ return column.maxPeople !== void 0 ? people.slice(0, Math.max(0, column.maxPeople)) : people;
1529
+ }
1530
+ nameOf(person) {
1531
+ return personName(person);
1532
+ }
1533
+ filesIn(value) {
1534
+ return filesOf(value);
1535
+ }
1536
+ linkIn(value) {
1537
+ return linkParts(value);
1538
+ }
1539
+ hrefOf(href) {
1540
+ return href === void 0 ? null : safeHref(href);
1541
+ }
1542
+ stringOf(value) {
1543
+ return String(value);
1544
+ }
1545
+ ungroupedOptions(column) {
1546
+ return (column.options ?? []).filter((o) => !o.group);
1547
+ }
1548
+ groupsOf(column) {
1549
+ return [...new Set((column.options ?? []).filter((o) => o.group).map((o) => o.group))];
1550
+ }
1551
+ optionsInGroup(column, group) {
1552
+ return (column.options ?? []).filter((o) => o.group === group);
1553
+ }
1554
+ static {
1555
+ this.ɵfac = i0.ɵɵngDeclareFactory({
1556
+ minVersion: "12.0.0",
1557
+ version: "22.2.3",
1558
+ ngImport: i0,
1559
+ type: DataGrid,
1560
+ deps: [],
1561
+ target: i0.ɵɵFactoryTarget.Component
1562
+ });
1563
+ }
1564
+ static {
1565
+ this.ɵcmp = i0.ɵɵngDeclareComponent({
1566
+ minVersion: "17.0.0",
1567
+ version: "22.2.3",
1568
+ type: DataGrid,
1569
+ isStandalone: true,
1570
+ selector: "lily-data-grid",
1571
+ inputs: {
1572
+ label: {
1573
+ classPropertyName: "label",
1574
+ publicName: "label",
1575
+ isSignal: true,
1576
+ isRequired: true,
1577
+ transformFunction: null
1578
+ },
1579
+ caption: {
1580
+ classPropertyName: "caption",
1581
+ publicName: "caption",
1582
+ isSignal: true,
1583
+ isRequired: false,
1584
+ transformFunction: null
1585
+ },
1586
+ columns: {
1587
+ classPropertyName: "columns",
1588
+ publicName: "columns",
1589
+ isSignal: true,
1590
+ isRequired: true,
1591
+ transformFunction: null
1592
+ },
1593
+ rows: {
1594
+ classPropertyName: "rows",
1595
+ publicName: "rows",
1596
+ isSignal: true,
1597
+ isRequired: true,
1598
+ transformFunction: null
1599
+ },
1600
+ locale: {
1601
+ classPropertyName: "locale",
1602
+ publicName: "locale",
1603
+ isSignal: true,
1604
+ isRequired: false,
1605
+ transformFunction: null
1606
+ },
1607
+ rowId: {
1608
+ classPropertyName: "rowId",
1609
+ publicName: "rowId",
1610
+ isSignal: true,
1611
+ isRequired: false,
1612
+ transformFunction: null
1613
+ },
1614
+ selectionMode: {
1615
+ classPropertyName: "selectionMode",
1616
+ publicName: "selectionMode",
1617
+ isSignal: true,
1618
+ isRequired: false,
1619
+ transformFunction: null
1620
+ },
1621
+ pageSize: {
1622
+ classPropertyName: "pageSize",
1623
+ publicName: "pageSize",
1624
+ isSignal: true,
1625
+ isRequired: false,
1626
+ transformFunction: null
1627
+ },
1628
+ storageKey: {
1629
+ classPropertyName: "storageKey",
1630
+ publicName: "storageKey",
1631
+ isSignal: true,
1632
+ isRequired: false,
1633
+ transformFunction: null
1634
+ },
1635
+ labels: {
1636
+ classPropertyName: "labels",
1637
+ publicName: "labels",
1638
+ isSignal: true,
1639
+ isRequired: false,
1640
+ transformFunction: null
1641
+ },
1642
+ mode: {
1643
+ classPropertyName: "mode",
1644
+ publicName: "mode",
1645
+ isSignal: true,
1646
+ isRequired: false,
1647
+ transformFunction: null
1648
+ },
1649
+ rowClass: {
1650
+ classPropertyName: "rowClass",
1651
+ publicName: "rowClass",
1652
+ isSignal: true,
1653
+ isRequired: false,
1654
+ transformFunction: null
1655
+ },
1656
+ rowAttrs: {
1657
+ classPropertyName: "rowAttrs",
1658
+ publicName: "rowAttrs",
1659
+ isSignal: true,
1660
+ isRequired: false,
1661
+ transformFunction: null
1662
+ },
1663
+ activateRows: {
1664
+ classPropertyName: "activateRows",
1665
+ publicName: "activateRows",
1666
+ isSignal: true,
1667
+ isRequired: false,
1668
+ transformFunction: null
1669
+ },
1670
+ empty: {
1671
+ classPropertyName: "empty",
1672
+ publicName: "empty",
1673
+ isSignal: true,
1674
+ isRequired: false,
1675
+ transformFunction: null
1676
+ },
1677
+ footer: {
1678
+ classPropertyName: "footer",
1679
+ publicName: "footer",
1680
+ isSignal: true,
1681
+ isRequired: false,
1682
+ transformFunction: null
1683
+ },
1684
+ busy: {
1685
+ classPropertyName: "busy",
1686
+ publicName: "busy",
1687
+ isSignal: true,
1688
+ isRequired: false,
1689
+ transformFunction: null
1690
+ },
1691
+ scroll: {
1692
+ classPropertyName: "scroll",
1693
+ publicName: "scroll",
1694
+ isSignal: true,
1695
+ isRequired: false,
1696
+ transformFunction: null
1697
+ },
1698
+ exportFilename: {
1699
+ classPropertyName: "exportFilename",
1700
+ publicName: "exportFilename",
1701
+ isSignal: true,
1702
+ isRequired: false,
1703
+ transformFunction: null
1704
+ },
1705
+ exportScope: {
1706
+ classPropertyName: "exportScope",
1707
+ publicName: "exportScope",
1708
+ isSignal: true,
1709
+ isRequired: false,
1710
+ transformFunction: null
1711
+ },
1712
+ exportFormulas: {
1713
+ classPropertyName: "exportFormulas",
1714
+ publicName: "exportFormulas",
1715
+ isSignal: true,
1716
+ isRequired: false,
1717
+ transformFunction: null
1718
+ },
1719
+ exportHandler: {
1720
+ classPropertyName: "exportHandler",
1721
+ publicName: "exportHandler",
1722
+ isSignal: true,
1723
+ isRequired: false,
1724
+ transformFunction: null
1725
+ },
1726
+ virtualize: {
1727
+ classPropertyName: "virtualize",
1728
+ publicName: "virtualize",
1729
+ isSignal: true,
1730
+ isRequired: false,
1731
+ transformFunction: null
1732
+ },
1733
+ className: {
1734
+ classPropertyName: "className",
1735
+ publicName: "className",
1736
+ isSignal: true,
1737
+ isRequired: false,
1738
+ transformFunction: null
1739
+ },
1740
+ selected: {
1741
+ classPropertyName: "selected",
1742
+ publicName: "selected",
1743
+ isSignal: true,
1744
+ isRequired: false,
1745
+ transformFunction: null
1746
+ },
1747
+ filter: {
1748
+ classPropertyName: "filter",
1749
+ publicName: "filter",
1750
+ isSignal: true,
1751
+ isRequired: false,
1752
+ transformFunction: null
1753
+ },
1754
+ sort: {
1755
+ classPropertyName: "sort",
1756
+ publicName: "sort",
1757
+ isSignal: true,
1758
+ isRequired: false,
1759
+ transformFunction: null
1760
+ },
1761
+ columnOrder: {
1762
+ classPropertyName: "columnOrder",
1763
+ publicName: "columnOrder",
1764
+ isSignal: true,
1765
+ isRequired: false,
1766
+ transformFunction: null
1767
+ },
1768
+ page: {
1769
+ classPropertyName: "page",
1770
+ publicName: "page",
1771
+ isSignal: true,
1772
+ isRequired: false,
1773
+ transformFunction: null
1774
+ }
1775
+ },
1776
+ outputs: {
1777
+ selected: "selectedChange",
1778
+ filter: "filterChange",
1779
+ sort: "sortChange",
1780
+ columnOrder: "columnOrderChange",
1781
+ page: "pageChange",
1782
+ cellEdit: "cellEdit",
1783
+ filesSelected: "filesSelected",
1784
+ fileRemove: "fileRemove",
1785
+ rowActivate: "rowActivate",
1786
+ exportFile: "exportFile"
1787
+ },
1788
+ viewQueries: [{
1789
+ propertyName: "rootRef",
1790
+ first: true,
1791
+ predicate: ["rootEl"],
1792
+ descendants: true,
1793
+ isSignal: true
1794
+ }, {
1795
+ propertyName: "scrollRef",
1796
+ first: true,
1797
+ predicate: ["scrollEl"],
1798
+ descendants: true,
1799
+ isSignal: true
1800
+ }],
1801
+ ngImport: i0,
1802
+ template: `
1803
+ <div #rootEl class="data-grid {{ className() }}">
1804
+ @if (labels().search || hidableColumns().length > 0 || hasExportButtons()) {
1805
+ <div class="data-grid-toolbar">
1806
+ @if (labels().search; as searchLabel) {
1807
+ <input class="data-grid-search" type="search" [attr.aria-label]="searchLabel" [value]="filter()" (input)="onFilterInput($event)" />
1808
+ }
1809
+ @if (hasExportButtons()) {
1810
+ <div class="data-grid-export" role="group" [attr.aria-label]="labels().export ?? null">
1811
+ @if (labels().exportCsv; as csv) {
1812
+ <button type="button" class="data-grid-export-button" data-grid-export-format="csv" [disabled]="exportEntries().length === 0" (click)="runExport('csv')">{{ csv }}</button>
1813
+ }
1814
+ @if (labels().exportTsv; as tsv) {
1815
+ <button type="button" class="data-grid-export-button" data-grid-export-format="tsv" [disabled]="exportEntries().length === 0" (click)="runExport('tsv')">{{ tsv }}</button>
1816
+ }
1817
+ @if (labels().exportExcel; as excel) {
1818
+ <button type="button" class="data-grid-export-button" data-grid-export-format="xlsx" [disabled]="exportEntries().length === 0" (click)="runExport('xlsx')">{{ excel }}</button>
1819
+ }
1820
+ </div>
1821
+ }
1822
+ @if (hidableColumns().length > 0 && labels().columnVisibility; as visibilityLabel) {
1823
+ <fieldset class="data-grid-column-visibility">
1824
+ <legend>{{ visibilityLabel }}</legend>
1825
+ @for (column of hidableColumns(); track column.id) {
1826
+ <label>
1827
+ <input type="checkbox" [checked]="!hiddenColumnIds().has(column.id)" (change)="toggleColumnVisibility(column.id)" />
1828
+ {{ labels().columnVisibilityOption?.(column.header) ?? column.header }}
1829
+ </label>
1830
+ }
1831
+ </fieldset>
1832
+ }
1833
+ </div>
1834
+ }
1835
+
1836
+ @if (scroll()) {
1837
+ <!-- A scrollable region must be reachable by keyboard (WCAG 2.1.1); in grid mode a roving cell is already a tab stop. -->
1838
+ <div
1839
+ #scrollEl
1840
+ class="data-grid-scroll"
1841
+ role="region"
1842
+ [attr.aria-label]="label()"
1843
+ [attr.tabindex]="isGrid() ? null : 0"
1844
+ [attr.style]="scrollStyle()"
1845
+ (scroll)="onScroll($event)"
1846
+ >
1847
+ <ng-container *ngTemplateOutlet="tableTemplate" />
1848
+ </div>
1849
+ } @else {
1850
+ <ng-container *ngTemplateOutlet="tableTemplate" />
1851
+ }
1852
+
1853
+ @if (pageSize()) {
1854
+ <div class="data-grid-pagination">
1855
+ <button type="button" class="data-grid-page-previous" [disabled]="clampedPage() <= 1" (click)="goToPage(clampedPage() - 1)">{{ labels().previousPage }}</button>
1856
+ <span class="data-grid-page-status">{{ labels().pageStatus?.(clampedPage(), pageCount(), sortedEntries().length) }}</span>
1857
+ <button type="button" class="data-grid-page-next" [disabled]="clampedPage() >= pageCount()" (click)="goToPage(clampedPage() + 1)">{{ labels().nextPage }}</button>
1858
+ </div>
1859
+ }
1860
+
1861
+ <p class="data-grid-status" aria-live="polite">{{ statusMessage() }}</p>
1862
+ </div>
1863
+
1864
+ <ng-template #tableTemplate>
1865
+ <lily-data-table
1866
+ [label]="label()"
1867
+ [className]="''"
1868
+ (keydown)="onGridKeydown($event)"
1869
+ (focusin)="onGridFocusin($event)"
1870
+ (focusout)="onGridFocusout($event)"
1871
+ (click)="onGridClick($event)"
1872
+ (dblclick)="onGridDblclick($event)"
1873
+ >
1874
+ @if (caption()) {
1875
+ <caption>{{ caption() }}</caption>
1876
+ }
1877
+ <thead lily-data-table-head>
1878
+ <tr lily-data-table-row [attr.aria-rowindex]="indexed() ? 1 : null">
1879
+ @if (hasSelection()) {
1880
+ <th
1881
+ lily-data-table-th
1882
+ data-row="-1"
1883
+ data-col="0"
1884
+ [attr.tabindex]="isGrid() ? (activeRow() === -1 && activeCol() === 0 ? 0 : -1) : null"
1885
+ >
1886
+ @if (selectionMode() === 'multiple') {
1887
+ <input
1888
+ type="checkbox"
1889
+ data-grid-own
1890
+ [attr.tabindex]="isGrid() ? -1 : null"
1891
+ [attr.aria-label]="labels().selectAll ?? null"
1892
+ [checked]="allSelected()"
1893
+ [indeterminate]="someSelected()"
1894
+ (click)="toggleSelectAll()"
1895
+ />
1896
+ } @else {
1897
+ <span class="data-grid-selection-header-label">{{ labels().selectionColumn }}</span>
1898
+ }
1899
+ </th>
1900
+ }
1901
+ @for (column of visibleColumns(); track column.id; let i = $index) {
1902
+ <th
1903
+ lily-data-table-th
1904
+ data-row="-1"
1905
+ [attr.data-col]="colOffset() + i"
1906
+ [attr.data-align]="column.align ?? null"
1907
+ [attr.data-cell-type]="column.type ?? null"
1908
+ [attr.data-pinned]="column.pinned ?? null"
1909
+ [attr.tabindex]="isGrid() ? (activeRow() === -1 && activeCol() === colOffset() + i ? 0 : -1) : null"
1910
+ [attr.aria-sort]="ariaSortFor(column)"
1911
+ [attr.aria-describedby]="column.type === 'rollup' && labels().rollupDescription ? gridId + '-' + column.id + '-description' : null"
1912
+ [attr.style]="cellStyle(column)"
1913
+ [attr.draggable]="column.movable ? 'true' : null"
1914
+ (dragstart)="onHeaderDragStart(column, $event)"
1915
+ (dragover)="onHeaderDragOver($event)"
1916
+ (drop)="onHeaderDrop(column.id, $event)"
1917
+ >
1918
+ @if (column.sortable) {
1919
+ <button type="button" class="data-grid-sort-button" data-grid-own [attr.tabindex]="isGrid() ? -1 : null" (click)="toggleSort(column)">
1920
+ @if (column.headerCell; as headerTemplate) {
1921
+ <ng-container *ngTemplateOutlet="headerTemplate; context: { $implicit: column }" />
1922
+ } @else {<ng-container>{{ column.header }}</ng-container>}
1923
+ </button>
1924
+ } @else if (column.headerCell; as headerTemplate) {
1925
+ <ng-container *ngTemplateOutlet="headerTemplate; context: { $implicit: column }" />
1926
+ } @else {<ng-container>{{ column.header }}</ng-container>}
1927
+ @if (column.type === 'rollup' && labels().rollupDescription; as describe) {
1928
+ <span class="data-grid-visually-hidden" [id]="gridId + '-' + column.id + '-description'">{{ describe(column.header) }}</span>
1929
+ }
1930
+ @if (column.resizable) {
1931
+ <!-- WAI-ARIA "window splitter" pattern: role="separator" plus tabindex/aria-valuenow is how a resize handle becomes focusable and operable. -->
1932
+ <span
1933
+ class="data-grid-resize-handle"
1934
+ role="separator"
1935
+ data-grid-own
1936
+ aria-orientation="vertical"
1937
+ [attr.aria-label]="labels().resizeHandle?.(column.header) ?? null"
1938
+ [attr.aria-valuenow]="widthFor(column) ?? 120"
1939
+ [attr.aria-valuemin]="minColumnWidth"
1940
+ tabindex="0"
1941
+ (keydown)="onResizeKeydown(column, $event)"
1942
+ (pointerdown)="onResizePointerDown(column, $event)"
1943
+ ></span>
1944
+ }
1945
+ </th>
1946
+ }
1947
+ </tr>
1948
+ </thead>
1949
+ <tbody lily-data-table-body>
1950
+ @for (item of renderItems(); track item.kind === 'row' ? pageEntries()[item.index].id : item.key) {
1951
+ @if (item.kind === 'spacer') {
1952
+ <tr class="data-grid-spacer" aria-hidden="true" [attr.style]="'height:' + item.rows * (virtualize()?.rowHeight ?? 0) + 'px'">
1953
+ <td [attr.colspan]="lastCol() + 1"></td>
1954
+ </tr>
1955
+ } @else {
1956
+ @let entry = pageEntries()[item.index];
1957
+ <tr
1958
+ lily-data-table-row
1959
+ [className]="rowClass()?.(entry.row) ?? ''"
1960
+ [attr.aria-selected]="hasSelection() ? isSelected(entry.id) : null"
1961
+ [attr.aria-rowindex]="indexed() ? pageStart() + item.index + 2 : null"
1962
+ [lilyDataGridAttrs]="rowAttrs()?.(entry.row)"
1963
+ >
1964
+ @if (hasSelection()) {
1965
+ <td
1966
+ lily-data-table-td
1967
+ [attr.role]="isGrid() ? 'gridcell' : 'cell'"
1968
+ [attr.data-row]="item.index"
1969
+ data-col="0"
1970
+ [attr.tabindex]="isGrid() ? (activeRow() === item.index && activeCol() === 0 ? 0 : -1) : null"
1971
+ >
1972
+ <input
1973
+ [type]="selectionMode() === 'single' ? 'radio' : 'checkbox'"
1974
+ data-grid-own
1975
+ [attr.tabindex]="isGrid() ? -1 : null"
1976
+ [attr.aria-label]="labels().selectRow?.(rowLabelOf(entry)) ?? null"
1977
+ [checked]="isSelected(entry.id)"
1978
+ (click)="onRowSelectClick(pageStart() + item.index, $event)"
1979
+ />
1980
+ </td>
1981
+ }
1982
+ @for (column of visibleColumns(); track column.id; let colIndex = $index) {
1983
+ @let ctx = cellFor(column, entry.row);
1984
+ @if (column.rowHeader) {
1985
+ <th
1986
+ lily-data-table-th
1987
+ scope="row"
1988
+ role="rowheader"
1989
+ [className]="cellClassOf(column, ctx)"
1990
+ [attr.data-row]="item.index"
1991
+ [attr.data-col]="colOffset() + colIndex"
1992
+ [attr.data-align]="column.align ?? null"
1993
+ [attr.data-cell-type]="column.type ?? null"
1994
+ [attr.data-pinned]="column.pinned ?? null"
1995
+ [attr.data-error]="column.errorOf?.(entry.row) !== undefined ? '' : null"
1996
+ [attr.tabindex]="isGrid() ? (activeRow() === item.index && activeCol() === colOffset() + colIndex ? 0 : -1) : null"
1997
+ [attr.style]="cellStyle(column)"
1998
+ [lilyDataGridAttrs]="column.cellAttrs?.(ctx)"
1999
+ >
2000
+ <ng-container *ngTemplateOutlet="cellBody; context: { entry: entry, column: column, ctx: ctx }" />
2001
+ </th>
2002
+ } @else {
2003
+ <td
2004
+ lily-data-table-td
2005
+ [className]="cellClassOf(column, ctx)"
2006
+ [attr.role]="isGrid() ? 'gridcell' : 'cell'"
2007
+ [attr.data-row]="item.index"
2008
+ [attr.data-col]="colOffset() + colIndex"
2009
+ [attr.data-align]="column.align ?? null"
2010
+ [attr.data-cell-type]="column.type ?? null"
2011
+ [attr.data-pinned]="column.pinned ?? null"
2012
+ [attr.data-editable]="isEditable(column) ? '' : null"
2013
+ [attr.data-readonly]="column.type === 'rollup' ? '' : null"
2014
+ [attr.aria-readonly]="column.type === 'rollup' ? 'true' : null"
2015
+ [attr.data-error]="column.errorOf?.(entry.row) !== undefined ? '' : null"
2016
+ [attr.data-editing]="isEditingCell(entry, column) ? '' : null"
2017
+ [attr.tabindex]="isGrid() ? (activeRow() === item.index && activeCol() === colOffset() + colIndex ? 0 : -1) : null"
2018
+ [attr.style]="cellStyle(column)"
2019
+ [lilyDataGridAttrs]="column.cellAttrs?.(ctx)"
2020
+ (dragover)="onFileDragOver(column, $event)"
2021
+ (drop)="onFileDrop(entry, column, $event)"
2022
+ >
2023
+ <ng-container *ngTemplateOutlet="cellBody; context: { entry: entry, column: column, ctx: ctx }" />
2024
+ </td>
2025
+ }
2026
+ }
2027
+ </tr>
2028
+ }
2029
+ }
2030
+ @if (pageEntries().length === 0 && empty(); as emptyTemplate) {
2031
+ <tr lily-data-table-row className="data-grid-empty-row">
2032
+ <td lily-data-table-td className="data-grid-empty" [attr.colspan]="lastCol() + 1" [attr.role]="isGrid() ? 'gridcell' : 'cell'">
2033
+ <ng-container *ngTemplateOutlet="emptyTemplate" />
2034
+ </td>
2035
+ </tr>
2036
+ }
2037
+ </tbody>
2038
+ @if (footer(); as footerTemplate) {
2039
+ <tfoot lily-data-table-foot>
2040
+ <ng-container *ngTemplateOutlet="footerTemplate; context: { $implicit: footerContext() }" />
2041
+ </tfoot>
2042
+ }
2043
+ </lily-data-table>
2044
+ </ng-template>
2045
+
2046
+ <ng-template #cellBody let-entry="entry" let-column="column" let-ctx="ctx">
2047
+ @if (isEditingCell(entry, column)) {
2048
+ @let editing = editingState()!;
2049
+ @if (column.editor === 'person' && column.personEditor) {
2050
+ <ng-container *ngTemplateOutlet="column.personEditor; context: { $implicit: personEditorContext(entry, column, ctx) }" />
2051
+ } @else if (column.editor === 'time-zone') {
2052
+ <span
2053
+ class="data-grid-zoned-editor"
2054
+ role="group"
2055
+ [attr.aria-label]="labels().cellEditor?.(column.header) ?? column.header"
2056
+ (focusout)="onZonedFocusout($event)"
2057
+ >
2058
+ <input
2059
+ class="data-grid-cell-editor"
2060
+ type="time"
2061
+ step="1"
2062
+ [attr.aria-label]="labels().cellEditor?.(column.header) ?? column.header"
2063
+ [attr.aria-invalid]="editing.error || editing.invalid ? 'true' : null"
2064
+ [attr.aria-describedby]="editing.error ? entry.id + '-' + column.id + '-error' : null"
2065
+ [value]="editing.draft"
2066
+ (input)="setDraft($event)"
2067
+ (keydown)="onEditorKeydown($event)"
2068
+ />
2069
+ <select
2070
+ class="data-grid-cell-editor data-grid-zone-editor"
2071
+ [attr.aria-label]="labels().timeZone?.(column.header) ?? column.header"
2072
+ (change)="setZone($event)"
2073
+ (keydown)="onEditorKeydown($event)"
2074
+ >
2075
+ @for (option of column.timeZones ?? []; track option.id) {
2076
+ <option [value]="option.id" [selected]="option.id === editing.zone">{{ option.label }}</option>
2077
+ }
2078
+ </select>
2079
+ </span>
2080
+ } @else if (column.editor === 'select') {
2081
+ <select
2082
+ class="data-grid-cell-editor"
2083
+ [attr.aria-label]="labels().cellEditor?.(column.header) ?? column.header"
2084
+ [attr.aria-invalid]="editing.error || editing.invalid ? 'true' : null"
2085
+ [attr.aria-describedby]="editing.error ? entry.id + '-' + column.id + '-error' : null"
2086
+ (change)="setDraft($event)"
2087
+ (keydown)="onEditorKeydown($event)"
2088
+ (blur)="onEditorBlur()"
2089
+ >
2090
+ <option value="" [selected]="editing.draft === ''"></option>
2091
+ @for (option of ungroupedOptions(column); track option.id) {
2092
+ <option [value]="option.id" [disabled]="option.disabled ?? false" [selected]="option.id === editing.draft">{{ option.label }}</option>
2093
+ }
2094
+ @for (group of groupsOf(column); track group) {
2095
+ <optgroup [label]="group">
2096
+ @for (option of optionsInGroup(column, group); track option.id) {
2097
+ <option [value]="option.id" [disabled]="option.disabled ?? false" [selected]="option.id === editing.draft">{{ option.label }}</option>
2098
+ }
2099
+ </optgroup>
2100
+ }
2101
+ </select>
2102
+ } @else {
2103
+ <input
2104
+ class="data-grid-cell-editor"
2105
+ [type]="column.editor === 'number' ? 'number' : column.editor === 'date' ? 'date' : column.editor === 'time' ? 'time' : 'text'"
2106
+ [attr.step]="column.editor === 'time' ? 1 : null"
2107
+ [attr.inputmode]="column.editor === 'number' ? 'decimal' : null"
2108
+ [attr.aria-label]="(column.type === 'formula' ? labels().formulaEditor?.(column.header) : undefined) ?? labels().cellEditor?.(column.header) ?? column.header"
2109
+ [attr.aria-invalid]="editing.error || editing.invalid ? 'true' : null"
2110
+ [attr.aria-describedby]="editing.error ? entry.id + '-' + column.id + '-error' : null"
2111
+ [value]="editing.draft"
2112
+ (input)="setDraft($event)"
2113
+ (keydown)="onEditorKeydown($event)"
2114
+ (blur)="onEditorBlur()"
2115
+ />
2116
+ }
2117
+ @if (editing.error) {
2118
+ <span class="data-grid-cell-error" [id]="entry.id + '-' + column.id + '-error'">{{ editing.error }}</span>
2119
+ }
2120
+ } @else if (column.type === 'radio' && column.editable) {
2121
+ <fieldset class="data-grid-radio-group" data-grid-own-group>
2122
+ <legend class="data-grid-radio-legend">{{ labels().radioGroup?.(column.header, rowLabelOf(entry)) ?? column.header }}</legend>
2123
+ @for (option of column.options ?? []; track option.id) {
2124
+ <label class="data-grid-radio-option">
2125
+ <input
2126
+ type="radio"
2127
+ [name]="gridId + '-' + entry.id + '-' + column.id"
2128
+ [value]="option.id"
2129
+ [checked]="ctx.value === option.id"
2130
+ [disabled]="option.disabled ?? false"
2131
+ [attr.tabindex]="isGrid() ? -1 : null"
2132
+ (change)="commitValue(entry, column, option.id)"
2133
+ />{{ option.label }}</label>
2134
+ }
2135
+ </fieldset>
2136
+ } @else {
2137
+ @if (column.cell; as cellTemplate) {
2138
+ <ng-container *ngTemplateOutlet="cellTemplate; context: { $implicit: ctx }" />
2139
+ } @else if (column.type === 'person') {
2140
+ @let people = peopleIn(ctx.value);
2141
+ @if (people.length > 0) {
2142
+ @let shown = shownPeople(column, ctx.value);
2143
+ <ul class="data-grid-people">
2144
+ @for (person of shown; track person.id) {
2145
+ <li class="data-grid-person">
2146
+ @if (column.avatar; as avatarTemplate) {
2147
+ <span class="data-grid-avatar" aria-hidden="true"><ng-container *ngTemplateOutlet="avatarTemplate; context: { $implicit: person }" /></span>
2148
+ }
2149
+ <span class="data-grid-person-name">{{ nameOf(person) }}</span>
2150
+ </li>
2151
+ }
2152
+ @if (shown.length < people.length && labels().morePeople; as more) {
2153
+ <li class="data-grid-people-more">{{ more(people.length - shown.length) }}</li>
2154
+ }
2155
+ </ul>
2156
+ }
2157
+ } @else if (column.type === 'file') {
2158
+ @let files = filesIn(ctx.value);
2159
+ @if (files.length > 0) {
2160
+ <ul class="data-grid-files">
2161
+ @for (file of files; track file.id) {
2162
+ <li class="data-grid-file">
2163
+ @if (hrefOf(file.href); as fileHref) {
2164
+ <a class="data-grid-link" [href]="fileHref">{{ file.name }}</a>
2165
+ } @else {
2166
+ <span class="data-grid-file-name">{{ file.name }}</span>
2167
+ }
2168
+ @if (file.size !== undefined && column.formatSize; as sizeText) {
2169
+ {{ ' ' }}<span class="data-grid-file-size">{{ column.formatSize(file.size) }}</span>
2170
+ }
2171
+ @if (column.editable && labels().removeFile; as removeLabel) {
2172
+ <button
2173
+ type="button"
2174
+ class="data-grid-file-remove"
2175
+ data-grid-own
2176
+ [attr.tabindex]="isGrid() ? -1 : null"
2177
+ [attr.aria-label]="removeLabel(file.name)"
2178
+ (click)="fileRemove.emit({ rowId: entry.id, row: entry.row, columnId: column.id, fileId: file.id })"
2179
+ ></button>
2180
+ }
2181
+ </li>
2182
+ }
2183
+ </ul>
2184
+ }
2185
+ @if (column.editable) {
2186
+ <input
2187
+ type="file"
2188
+ class="data-grid-file-input"
2189
+ [attr.aria-label]="labels().fileInput?.(column.header, rowLabelOf(entry)) ?? column.header"
2190
+ [multiple]="column.multiple !== false"
2191
+ [attr.accept]="column.accept ?? null"
2192
+ [attr.tabindex]="isGrid() ? -1 : null"
2193
+ (change)="onFileInput(entry, column, $event)"
2194
+ />
2195
+ }
2196
+ } @else if (column.type === 'link') {
2197
+ @let link = linkIn(ctx.value);
2198
+ @if (link) {
2199
+ @if (hrefOf(link.href); as href) {
2200
+ <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>
2201
+ } @else {
2202
+ <!-- An address with an unsafe scheme is shown as text, never as a link. -->
2203
+ <span class="data-grid-link data-grid-link-unsafe">{{ link.label ?? link.href }}</span>
2204
+ }
2205
+ }
2206
+ } @else if (column.type === 'date' || column.type === 'time') {
2207
+ @if (ctx.formatted !== '') {
2208
+ <time [attr.datetime]="stringOf(ctx.value)">{{ ctx.formatted }}</time>
2209
+ }
2210
+ } @else {<ng-container>{{ ctx.formatted }}</ng-container>}
2211
+ @if (isEditable(column) && column.type !== 'radio' && labels().editCell; as editLabel) {
2212
+ <button
2213
+ type="button"
2214
+ class="data-grid-edit-button"
2215
+ data-grid-own
2216
+ [attr.tabindex]="isGrid() ? -1 : null"
2217
+ [attr.aria-label]="editLabel(column.header, rowLabelOf(entry))"
2218
+ (click)="startEdit(entry, column)"
2219
+ ></button>
2220
+ }
2221
+ }
2222
+ </ng-template>
2223
+ `,
2224
+ isInline: true,
2225
+ dependencies: [
2226
+ {
2227
+ kind: "directive",
2228
+ type: NgTemplateOutlet,
2229
+ selector: "[ngTemplateOutlet]",
2230
+ inputs: [
2231
+ "ngTemplateOutletContext",
2232
+ "ngTemplateOutlet",
2233
+ "ngTemplateOutletInjector"
2234
+ ]
2235
+ },
2236
+ {
2237
+ kind: "directive",
2238
+ type: DataGridAttrs,
2239
+ selector: "[lilyDataGridAttrs]",
2240
+ inputs: ["lilyDataGridAttrs"]
2241
+ },
2242
+ {
2243
+ kind: "component",
2244
+ type: DataTable,
2245
+ selector: "lily-data-table",
2246
+ inputs: ["label", "className"]
2247
+ },
2248
+ {
2249
+ kind: "component",
2250
+ type: DataTableBody,
2251
+ selector: "tbody[lily-data-table-body]",
2252
+ inputs: ["className"]
2253
+ },
2254
+ {
2255
+ kind: "component",
2256
+ type: DataTableFoot,
2257
+ selector: "tfoot[lily-data-table-foot]",
2258
+ inputs: ["className"]
2259
+ },
2260
+ {
2261
+ kind: "component",
2262
+ type: DataTableHead,
2263
+ selector: "thead[lily-data-table-head]",
2264
+ inputs: ["className"]
2265
+ },
2266
+ {
2267
+ kind: "component",
2268
+ type: DataTableRow,
2269
+ selector: "tr[lily-data-table-row]",
2270
+ inputs: ["className"]
2271
+ },
2272
+ {
2273
+ kind: "component",
2274
+ type: DataTableTD,
2275
+ selector: "td[lily-data-table-td]",
2276
+ inputs: ["className"]
2277
+ },
2278
+ {
2279
+ kind: "component",
2280
+ type: DataTableTH,
2281
+ selector: "th[lily-data-table-th]",
2282
+ inputs: ["scope", "className"]
2283
+ }
2284
+ ],
2285
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
2286
+ });
2287
+ }
2288
+ };
2289
+ i0.ɵɵngDeclareClassMetadata({
2290
+ minVersion: "12.0.0",
2291
+ version: "22.2.3",
2292
+ ngImport: i0,
2293
+ type: DataGrid,
2294
+ decorators: [{
2295
+ type: Component,
2296
+ args: [{
2297
+ selector: "lily-data-grid",
2298
+ standalone: true,
2299
+ imports: [
2300
+ NgTemplateOutlet,
2301
+ DataGridAttrs,
2302
+ DataTable,
2303
+ DataTableBody,
2304
+ DataTableFoot,
2305
+ DataTableHead,
2306
+ DataTableRow,
2307
+ DataTableTD,
2308
+ DataTableTH
2309
+ ],
2310
+ changeDetection: ChangeDetectionStrategy.OnPush,
2311
+ template: `
2312
+ <div #rootEl class="data-grid {{ className() }}">
2313
+ @if (labels().search || hidableColumns().length > 0 || hasExportButtons()) {
2314
+ <div class="data-grid-toolbar">
2315
+ @if (labels().search; as searchLabel) {
2316
+ <input class="data-grid-search" type="search" [attr.aria-label]="searchLabel" [value]="filter()" (input)="onFilterInput($event)" />
2317
+ }
2318
+ @if (hasExportButtons()) {
2319
+ <div class="data-grid-export" role="group" [attr.aria-label]="labels().export ?? null">
2320
+ @if (labels().exportCsv; as csv) {
2321
+ <button type="button" class="data-grid-export-button" data-grid-export-format="csv" [disabled]="exportEntries().length === 0" (click)="runExport('csv')">{{ csv }}</button>
2322
+ }
2323
+ @if (labels().exportTsv; as tsv) {
2324
+ <button type="button" class="data-grid-export-button" data-grid-export-format="tsv" [disabled]="exportEntries().length === 0" (click)="runExport('tsv')">{{ tsv }}</button>
2325
+ }
2326
+ @if (labels().exportExcel; as excel) {
2327
+ <button type="button" class="data-grid-export-button" data-grid-export-format="xlsx" [disabled]="exportEntries().length === 0" (click)="runExport('xlsx')">{{ excel }}</button>
2328
+ }
2329
+ </div>
2330
+ }
2331
+ @if (hidableColumns().length > 0 && labels().columnVisibility; as visibilityLabel) {
2332
+ <fieldset class="data-grid-column-visibility">
2333
+ <legend>{{ visibilityLabel }}</legend>
2334
+ @for (column of hidableColumns(); track column.id) {
2335
+ <label>
2336
+ <input type="checkbox" [checked]="!hiddenColumnIds().has(column.id)" (change)="toggleColumnVisibility(column.id)" />
2337
+ {{ labels().columnVisibilityOption?.(column.header) ?? column.header }}
2338
+ </label>
2339
+ }
2340
+ </fieldset>
2341
+ }
2342
+ </div>
2343
+ }
2344
+
2345
+ @if (scroll()) {
2346
+ <!-- A scrollable region must be reachable by keyboard (WCAG 2.1.1); in grid mode a roving cell is already a tab stop. -->
2347
+ <div
2348
+ #scrollEl
2349
+ class="data-grid-scroll"
2350
+ role="region"
2351
+ [attr.aria-label]="label()"
2352
+ [attr.tabindex]="isGrid() ? null : 0"
2353
+ [attr.style]="scrollStyle()"
2354
+ (scroll)="onScroll($event)"
2355
+ >
2356
+ <ng-container *ngTemplateOutlet="tableTemplate" />
2357
+ </div>
2358
+ } @else {
2359
+ <ng-container *ngTemplateOutlet="tableTemplate" />
2360
+ }
2361
+
2362
+ @if (pageSize()) {
2363
+ <div class="data-grid-pagination">
2364
+ <button type="button" class="data-grid-page-previous" [disabled]="clampedPage() <= 1" (click)="goToPage(clampedPage() - 1)">{{ labels().previousPage }}</button>
2365
+ <span class="data-grid-page-status">{{ labels().pageStatus?.(clampedPage(), pageCount(), sortedEntries().length) }}</span>
2366
+ <button type="button" class="data-grid-page-next" [disabled]="clampedPage() >= pageCount()" (click)="goToPage(clampedPage() + 1)">{{ labels().nextPage }}</button>
2367
+ </div>
2368
+ }
2369
+
2370
+ <p class="data-grid-status" aria-live="polite">{{ statusMessage() }}</p>
2371
+ </div>
2372
+
2373
+ <ng-template #tableTemplate>
2374
+ <lily-data-table
2375
+ [label]="label()"
2376
+ [className]="''"
2377
+ (keydown)="onGridKeydown($event)"
2378
+ (focusin)="onGridFocusin($event)"
2379
+ (focusout)="onGridFocusout($event)"
2380
+ (click)="onGridClick($event)"
2381
+ (dblclick)="onGridDblclick($event)"
2382
+ >
2383
+ @if (caption()) {
2384
+ <caption>{{ caption() }}</caption>
2385
+ }
2386
+ <thead lily-data-table-head>
2387
+ <tr lily-data-table-row [attr.aria-rowindex]="indexed() ? 1 : null">
2388
+ @if (hasSelection()) {
2389
+ <th
2390
+ lily-data-table-th
2391
+ data-row="-1"
2392
+ data-col="0"
2393
+ [attr.tabindex]="isGrid() ? (activeRow() === -1 && activeCol() === 0 ? 0 : -1) : null"
2394
+ >
2395
+ @if (selectionMode() === 'multiple') {
2396
+ <input
2397
+ type="checkbox"
2398
+ data-grid-own
2399
+ [attr.tabindex]="isGrid() ? -1 : null"
2400
+ [attr.aria-label]="labels().selectAll ?? null"
2401
+ [checked]="allSelected()"
2402
+ [indeterminate]="someSelected()"
2403
+ (click)="toggleSelectAll()"
2404
+ />
2405
+ } @else {
2406
+ <span class="data-grid-selection-header-label">{{ labels().selectionColumn }}</span>
2407
+ }
2408
+ </th>
2409
+ }
2410
+ @for (column of visibleColumns(); track column.id; let i = $index) {
2411
+ <th
2412
+ lily-data-table-th
2413
+ data-row="-1"
2414
+ [attr.data-col]="colOffset() + i"
2415
+ [attr.data-align]="column.align ?? null"
2416
+ [attr.data-cell-type]="column.type ?? null"
2417
+ [attr.data-pinned]="column.pinned ?? null"
2418
+ [attr.tabindex]="isGrid() ? (activeRow() === -1 && activeCol() === colOffset() + i ? 0 : -1) : null"
2419
+ [attr.aria-sort]="ariaSortFor(column)"
2420
+ [attr.aria-describedby]="column.type === 'rollup' && labels().rollupDescription ? gridId + '-' + column.id + '-description' : null"
2421
+ [attr.style]="cellStyle(column)"
2422
+ [attr.draggable]="column.movable ? 'true' : null"
2423
+ (dragstart)="onHeaderDragStart(column, $event)"
2424
+ (dragover)="onHeaderDragOver($event)"
2425
+ (drop)="onHeaderDrop(column.id, $event)"
2426
+ >
2427
+ @if (column.sortable) {
2428
+ <button type="button" class="data-grid-sort-button" data-grid-own [attr.tabindex]="isGrid() ? -1 : null" (click)="toggleSort(column)">
2429
+ @if (column.headerCell; as headerTemplate) {
2430
+ <ng-container *ngTemplateOutlet="headerTemplate; context: { $implicit: column }" />
2431
+ } @else {<ng-container>{{ column.header }}</ng-container>}
2432
+ </button>
2433
+ } @else if (column.headerCell; as headerTemplate) {
2434
+ <ng-container *ngTemplateOutlet="headerTemplate; context: { $implicit: column }" />
2435
+ } @else {<ng-container>{{ column.header }}</ng-container>}
2436
+ @if (column.type === 'rollup' && labels().rollupDescription; as describe) {
2437
+ <span class="data-grid-visually-hidden" [id]="gridId + '-' + column.id + '-description'">{{ describe(column.header) }}</span>
2438
+ }
2439
+ @if (column.resizable) {
2440
+ <!-- WAI-ARIA "window splitter" pattern: role="separator" plus tabindex/aria-valuenow is how a resize handle becomes focusable and operable. -->
2441
+ <span
2442
+ class="data-grid-resize-handle"
2443
+ role="separator"
2444
+ data-grid-own
2445
+ aria-orientation="vertical"
2446
+ [attr.aria-label]="labels().resizeHandle?.(column.header) ?? null"
2447
+ [attr.aria-valuenow]="widthFor(column) ?? 120"
2448
+ [attr.aria-valuemin]="minColumnWidth"
2449
+ tabindex="0"
2450
+ (keydown)="onResizeKeydown(column, $event)"
2451
+ (pointerdown)="onResizePointerDown(column, $event)"
2452
+ ></span>
2453
+ }
2454
+ </th>
2455
+ }
2456
+ </tr>
2457
+ </thead>
2458
+ <tbody lily-data-table-body>
2459
+ @for (item of renderItems(); track item.kind === 'row' ? pageEntries()[item.index].id : item.key) {
2460
+ @if (item.kind === 'spacer') {
2461
+ <tr class="data-grid-spacer" aria-hidden="true" [attr.style]="'height:' + item.rows * (virtualize()?.rowHeight ?? 0) + 'px'">
2462
+ <td [attr.colspan]="lastCol() + 1"></td>
2463
+ </tr>
2464
+ } @else {
2465
+ @let entry = pageEntries()[item.index];
2466
+ <tr
2467
+ lily-data-table-row
2468
+ [className]="rowClass()?.(entry.row) ?? ''"
2469
+ [attr.aria-selected]="hasSelection() ? isSelected(entry.id) : null"
2470
+ [attr.aria-rowindex]="indexed() ? pageStart() + item.index + 2 : null"
2471
+ [lilyDataGridAttrs]="rowAttrs()?.(entry.row)"
2472
+ >
2473
+ @if (hasSelection()) {
2474
+ <td
2475
+ lily-data-table-td
2476
+ [attr.role]="isGrid() ? 'gridcell' : 'cell'"
2477
+ [attr.data-row]="item.index"
2478
+ data-col="0"
2479
+ [attr.tabindex]="isGrid() ? (activeRow() === item.index && activeCol() === 0 ? 0 : -1) : null"
2480
+ >
2481
+ <input
2482
+ [type]="selectionMode() === 'single' ? 'radio' : 'checkbox'"
2483
+ data-grid-own
2484
+ [attr.tabindex]="isGrid() ? -1 : null"
2485
+ [attr.aria-label]="labels().selectRow?.(rowLabelOf(entry)) ?? null"
2486
+ [checked]="isSelected(entry.id)"
2487
+ (click)="onRowSelectClick(pageStart() + item.index, $event)"
2488
+ />
2489
+ </td>
2490
+ }
2491
+ @for (column of visibleColumns(); track column.id; let colIndex = $index) {
2492
+ @let ctx = cellFor(column, entry.row);
2493
+ @if (column.rowHeader) {
2494
+ <th
2495
+ lily-data-table-th
2496
+ scope="row"
2497
+ role="rowheader"
2498
+ [className]="cellClassOf(column, ctx)"
2499
+ [attr.data-row]="item.index"
2500
+ [attr.data-col]="colOffset() + colIndex"
2501
+ [attr.data-align]="column.align ?? null"
2502
+ [attr.data-cell-type]="column.type ?? null"
2503
+ [attr.data-pinned]="column.pinned ?? null"
2504
+ [attr.data-error]="column.errorOf?.(entry.row) !== undefined ? '' : null"
2505
+ [attr.tabindex]="isGrid() ? (activeRow() === item.index && activeCol() === colOffset() + colIndex ? 0 : -1) : null"
2506
+ [attr.style]="cellStyle(column)"
2507
+ [lilyDataGridAttrs]="column.cellAttrs?.(ctx)"
2508
+ >
2509
+ <ng-container *ngTemplateOutlet="cellBody; context: { entry: entry, column: column, ctx: ctx }" />
2510
+ </th>
2511
+ } @else {
2512
+ <td
2513
+ lily-data-table-td
2514
+ [className]="cellClassOf(column, ctx)"
2515
+ [attr.role]="isGrid() ? 'gridcell' : 'cell'"
2516
+ [attr.data-row]="item.index"
2517
+ [attr.data-col]="colOffset() + colIndex"
2518
+ [attr.data-align]="column.align ?? null"
2519
+ [attr.data-cell-type]="column.type ?? null"
2520
+ [attr.data-pinned]="column.pinned ?? null"
2521
+ [attr.data-editable]="isEditable(column) ? '' : null"
2522
+ [attr.data-readonly]="column.type === 'rollup' ? '' : null"
2523
+ [attr.aria-readonly]="column.type === 'rollup' ? 'true' : null"
2524
+ [attr.data-error]="column.errorOf?.(entry.row) !== undefined ? '' : null"
2525
+ [attr.data-editing]="isEditingCell(entry, column) ? '' : null"
2526
+ [attr.tabindex]="isGrid() ? (activeRow() === item.index && activeCol() === colOffset() + colIndex ? 0 : -1) : null"
2527
+ [attr.style]="cellStyle(column)"
2528
+ [lilyDataGridAttrs]="column.cellAttrs?.(ctx)"
2529
+ (dragover)="onFileDragOver(column, $event)"
2530
+ (drop)="onFileDrop(entry, column, $event)"
2531
+ >
2532
+ <ng-container *ngTemplateOutlet="cellBody; context: { entry: entry, column: column, ctx: ctx }" />
2533
+ </td>
2534
+ }
2535
+ }
2536
+ </tr>
2537
+ }
2538
+ }
2539
+ @if (pageEntries().length === 0 && empty(); as emptyTemplate) {
2540
+ <tr lily-data-table-row className="data-grid-empty-row">
2541
+ <td lily-data-table-td className="data-grid-empty" [attr.colspan]="lastCol() + 1" [attr.role]="isGrid() ? 'gridcell' : 'cell'">
2542
+ <ng-container *ngTemplateOutlet="emptyTemplate" />
2543
+ </td>
2544
+ </tr>
2545
+ }
2546
+ </tbody>
2547
+ @if (footer(); as footerTemplate) {
2548
+ <tfoot lily-data-table-foot>
2549
+ <ng-container *ngTemplateOutlet="footerTemplate; context: { $implicit: footerContext() }" />
2550
+ </tfoot>
2551
+ }
2552
+ </lily-data-table>
2553
+ </ng-template>
2554
+
2555
+ <ng-template #cellBody let-entry="entry" let-column="column" let-ctx="ctx">
2556
+ @if (isEditingCell(entry, column)) {
2557
+ @let editing = editingState()!;
2558
+ @if (column.editor === 'person' && column.personEditor) {
2559
+ <ng-container *ngTemplateOutlet="column.personEditor; context: { $implicit: personEditorContext(entry, column, ctx) }" />
2560
+ } @else if (column.editor === 'time-zone') {
2561
+ <span
2562
+ class="data-grid-zoned-editor"
2563
+ role="group"
2564
+ [attr.aria-label]="labels().cellEditor?.(column.header) ?? column.header"
2565
+ (focusout)="onZonedFocusout($event)"
2566
+ >
2567
+ <input
2568
+ class="data-grid-cell-editor"
2569
+ type="time"
2570
+ step="1"
2571
+ [attr.aria-label]="labels().cellEditor?.(column.header) ?? column.header"
2572
+ [attr.aria-invalid]="editing.error || editing.invalid ? 'true' : null"
2573
+ [attr.aria-describedby]="editing.error ? entry.id + '-' + column.id + '-error' : null"
2574
+ [value]="editing.draft"
2575
+ (input)="setDraft($event)"
2576
+ (keydown)="onEditorKeydown($event)"
2577
+ />
2578
+ <select
2579
+ class="data-grid-cell-editor data-grid-zone-editor"
2580
+ [attr.aria-label]="labels().timeZone?.(column.header) ?? column.header"
2581
+ (change)="setZone($event)"
2582
+ (keydown)="onEditorKeydown($event)"
2583
+ >
2584
+ @for (option of column.timeZones ?? []; track option.id) {
2585
+ <option [value]="option.id" [selected]="option.id === editing.zone">{{ option.label }}</option>
2586
+ }
2587
+ </select>
2588
+ </span>
2589
+ } @else if (column.editor === 'select') {
2590
+ <select
2591
+ class="data-grid-cell-editor"
2592
+ [attr.aria-label]="labels().cellEditor?.(column.header) ?? column.header"
2593
+ [attr.aria-invalid]="editing.error || editing.invalid ? 'true' : null"
2594
+ [attr.aria-describedby]="editing.error ? entry.id + '-' + column.id + '-error' : null"
2595
+ (change)="setDraft($event)"
2596
+ (keydown)="onEditorKeydown($event)"
2597
+ (blur)="onEditorBlur()"
2598
+ >
2599
+ <option value="" [selected]="editing.draft === ''"></option>
2600
+ @for (option of ungroupedOptions(column); track option.id) {
2601
+ <option [value]="option.id" [disabled]="option.disabled ?? false" [selected]="option.id === editing.draft">{{ option.label }}</option>
2602
+ }
2603
+ @for (group of groupsOf(column); track group) {
2604
+ <optgroup [label]="group">
2605
+ @for (option of optionsInGroup(column, group); track option.id) {
2606
+ <option [value]="option.id" [disabled]="option.disabled ?? false" [selected]="option.id === editing.draft">{{ option.label }}</option>
2607
+ }
2608
+ </optgroup>
2609
+ }
2610
+ </select>
2611
+ } @else {
2612
+ <input
2613
+ class="data-grid-cell-editor"
2614
+ [type]="column.editor === 'number' ? 'number' : column.editor === 'date' ? 'date' : column.editor === 'time' ? 'time' : 'text'"
2615
+ [attr.step]="column.editor === 'time' ? 1 : null"
2616
+ [attr.inputmode]="column.editor === 'number' ? 'decimal' : null"
2617
+ [attr.aria-label]="(column.type === 'formula' ? labels().formulaEditor?.(column.header) : undefined) ?? labels().cellEditor?.(column.header) ?? column.header"
2618
+ [attr.aria-invalid]="editing.error || editing.invalid ? 'true' : null"
2619
+ [attr.aria-describedby]="editing.error ? entry.id + '-' + column.id + '-error' : null"
2620
+ [value]="editing.draft"
2621
+ (input)="setDraft($event)"
2622
+ (keydown)="onEditorKeydown($event)"
2623
+ (blur)="onEditorBlur()"
2624
+ />
2625
+ }
2626
+ @if (editing.error) {
2627
+ <span class="data-grid-cell-error" [id]="entry.id + '-' + column.id + '-error'">{{ editing.error }}</span>
2628
+ }
2629
+ } @else if (column.type === 'radio' && column.editable) {
2630
+ <fieldset class="data-grid-radio-group" data-grid-own-group>
2631
+ <legend class="data-grid-radio-legend">{{ labels().radioGroup?.(column.header, rowLabelOf(entry)) ?? column.header }}</legend>
2632
+ @for (option of column.options ?? []; track option.id) {
2633
+ <label class="data-grid-radio-option">
2634
+ <input
2635
+ type="radio"
2636
+ [name]="gridId + '-' + entry.id + '-' + column.id"
2637
+ [value]="option.id"
2638
+ [checked]="ctx.value === option.id"
2639
+ [disabled]="option.disabled ?? false"
2640
+ [attr.tabindex]="isGrid() ? -1 : null"
2641
+ (change)="commitValue(entry, column, option.id)"
2642
+ />{{ option.label }}</label>
2643
+ }
2644
+ </fieldset>
2645
+ } @else {
2646
+ @if (column.cell; as cellTemplate) {
2647
+ <ng-container *ngTemplateOutlet="cellTemplate; context: { $implicit: ctx }" />
2648
+ } @else if (column.type === 'person') {
2649
+ @let people = peopleIn(ctx.value);
2650
+ @if (people.length > 0) {
2651
+ @let shown = shownPeople(column, ctx.value);
2652
+ <ul class="data-grid-people">
2653
+ @for (person of shown; track person.id) {
2654
+ <li class="data-grid-person">
2655
+ @if (column.avatar; as avatarTemplate) {
2656
+ <span class="data-grid-avatar" aria-hidden="true"><ng-container *ngTemplateOutlet="avatarTemplate; context: { $implicit: person }" /></span>
2657
+ }
2658
+ <span class="data-grid-person-name">{{ nameOf(person) }}</span>
2659
+ </li>
2660
+ }
2661
+ @if (shown.length < people.length && labels().morePeople; as more) {
2662
+ <li class="data-grid-people-more">{{ more(people.length - shown.length) }}</li>
2663
+ }
2664
+ </ul>
2665
+ }
2666
+ } @else if (column.type === 'file') {
2667
+ @let files = filesIn(ctx.value);
2668
+ @if (files.length > 0) {
2669
+ <ul class="data-grid-files">
2670
+ @for (file of files; track file.id) {
2671
+ <li class="data-grid-file">
2672
+ @if (hrefOf(file.href); as fileHref) {
2673
+ <a class="data-grid-link" [href]="fileHref">{{ file.name }}</a>
2674
+ } @else {
2675
+ <span class="data-grid-file-name">{{ file.name }}</span>
2676
+ }
2677
+ @if (file.size !== undefined && column.formatSize; as sizeText) {
2678
+ {{ ' ' }}<span class="data-grid-file-size">{{ column.formatSize(file.size) }}</span>
2679
+ }
2680
+ @if (column.editable && labels().removeFile; as removeLabel) {
2681
+ <button
2682
+ type="button"
2683
+ class="data-grid-file-remove"
2684
+ data-grid-own
2685
+ [attr.tabindex]="isGrid() ? -1 : null"
2686
+ [attr.aria-label]="removeLabel(file.name)"
2687
+ (click)="fileRemove.emit({ rowId: entry.id, row: entry.row, columnId: column.id, fileId: file.id })"
2688
+ ></button>
2689
+ }
2690
+ </li>
2691
+ }
2692
+ </ul>
2693
+ }
2694
+ @if (column.editable) {
2695
+ <input
2696
+ type="file"
2697
+ class="data-grid-file-input"
2698
+ [attr.aria-label]="labels().fileInput?.(column.header, rowLabelOf(entry)) ?? column.header"
2699
+ [multiple]="column.multiple !== false"
2700
+ [attr.accept]="column.accept ?? null"
2701
+ [attr.tabindex]="isGrid() ? -1 : null"
2702
+ (change)="onFileInput(entry, column, $event)"
2703
+ />
2704
+ }
2705
+ } @else if (column.type === 'link') {
2706
+ @let link = linkIn(ctx.value);
2707
+ @if (link) {
2708
+ @if (hrefOf(link.href); as href) {
2709
+ <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>
2710
+ } @else {
2711
+ <!-- An address with an unsafe scheme is shown as text, never as a link. -->
2712
+ <span class="data-grid-link data-grid-link-unsafe">{{ link.label ?? link.href }}</span>
2713
+ }
2714
+ }
2715
+ } @else if (column.type === 'date' || column.type === 'time') {
2716
+ @if (ctx.formatted !== '') {
2717
+ <time [attr.datetime]="stringOf(ctx.value)">{{ ctx.formatted }}</time>
2718
+ }
2719
+ } @else {<ng-container>{{ ctx.formatted }}</ng-container>}
2720
+ @if (isEditable(column) && column.type !== 'radio' && labels().editCell; as editLabel) {
2721
+ <button
2722
+ type="button"
2723
+ class="data-grid-edit-button"
2724
+ data-grid-own
2725
+ [attr.tabindex]="isGrid() ? -1 : null"
2726
+ [attr.aria-label]="editLabel(column.header, rowLabelOf(entry))"
2727
+ (click)="startEdit(entry, column)"
2728
+ ></button>
2729
+ }
2730
+ }
2731
+ </ng-template>
2732
+ `
2733
+ }]
2734
+ }],
2735
+ ctorParameters: () => [],
2736
+ propDecorators: {
2737
+ label: [{
2738
+ type: i0.Input,
2739
+ args: [{
2740
+ isSignal: true,
2741
+ alias: "label",
2742
+ required: true
2743
+ }]
2744
+ }],
2745
+ caption: [{
2746
+ type: i0.Input,
2747
+ args: [{
2748
+ isSignal: true,
2749
+ alias: "caption",
2750
+ required: false
2751
+ }]
2752
+ }],
2753
+ columns: [{
2754
+ type: i0.Input,
2755
+ args: [{
2756
+ isSignal: true,
2757
+ alias: "columns",
2758
+ required: true
2759
+ }]
2760
+ }],
2761
+ rows: [{
2762
+ type: i0.Input,
2763
+ args: [{
2764
+ isSignal: true,
2765
+ alias: "rows",
2766
+ required: true
2767
+ }]
2768
+ }],
2769
+ locale: [{
2770
+ type: i0.Input,
2771
+ args: [{
2772
+ isSignal: true,
2773
+ alias: "locale",
2774
+ required: false
2775
+ }]
2776
+ }],
2777
+ rowId: [{
2778
+ type: i0.Input,
2779
+ args: [{
2780
+ isSignal: true,
2781
+ alias: "rowId",
2782
+ required: false
2783
+ }]
2784
+ }],
2785
+ selectionMode: [{
2786
+ type: i0.Input,
2787
+ args: [{
2788
+ isSignal: true,
2789
+ alias: "selectionMode",
2790
+ required: false
2791
+ }]
2792
+ }],
2793
+ pageSize: [{
2794
+ type: i0.Input,
2795
+ args: [{
2796
+ isSignal: true,
2797
+ alias: "pageSize",
2798
+ required: false
2799
+ }]
2800
+ }],
2801
+ storageKey: [{
2802
+ type: i0.Input,
2803
+ args: [{
2804
+ isSignal: true,
2805
+ alias: "storageKey",
2806
+ required: false
2807
+ }]
2808
+ }],
2809
+ labels: [{
2810
+ type: i0.Input,
2811
+ args: [{
2812
+ isSignal: true,
2813
+ alias: "labels",
2814
+ required: false
2815
+ }]
2816
+ }],
2817
+ mode: [{
2818
+ type: i0.Input,
2819
+ args: [{
2820
+ isSignal: true,
2821
+ alias: "mode",
2822
+ required: false
2823
+ }]
2824
+ }],
2825
+ rowClass: [{
2826
+ type: i0.Input,
2827
+ args: [{
2828
+ isSignal: true,
2829
+ alias: "rowClass",
2830
+ required: false
2831
+ }]
2832
+ }],
2833
+ rowAttrs: [{
2834
+ type: i0.Input,
2835
+ args: [{
2836
+ isSignal: true,
2837
+ alias: "rowAttrs",
2838
+ required: false
2839
+ }]
2840
+ }],
2841
+ activateRows: [{
2842
+ type: i0.Input,
2843
+ args: [{
2844
+ isSignal: true,
2845
+ alias: "activateRows",
2846
+ required: false
2847
+ }]
2848
+ }],
2849
+ empty: [{
2850
+ type: i0.Input,
2851
+ args: [{
2852
+ isSignal: true,
2853
+ alias: "empty",
2854
+ required: false
2855
+ }]
2856
+ }],
2857
+ footer: [{
2858
+ type: i0.Input,
2859
+ args: [{
2860
+ isSignal: true,
2861
+ alias: "footer",
2862
+ required: false
2863
+ }]
2864
+ }],
2865
+ busy: [{
2866
+ type: i0.Input,
2867
+ args: [{
2868
+ isSignal: true,
2869
+ alias: "busy",
2870
+ required: false
2871
+ }]
2872
+ }],
2873
+ scroll: [{
2874
+ type: i0.Input,
2875
+ args: [{
2876
+ isSignal: true,
2877
+ alias: "scroll",
2878
+ required: false
2879
+ }]
2880
+ }],
2881
+ exportFilename: [{
2882
+ type: i0.Input,
2883
+ args: [{
2884
+ isSignal: true,
2885
+ alias: "exportFilename",
2886
+ required: false
2887
+ }]
2888
+ }],
2889
+ exportScope: [{
2890
+ type: i0.Input,
2891
+ args: [{
2892
+ isSignal: true,
2893
+ alias: "exportScope",
2894
+ required: false
2895
+ }]
2896
+ }],
2897
+ exportFormulas: [{
2898
+ type: i0.Input,
2899
+ args: [{
2900
+ isSignal: true,
2901
+ alias: "exportFormulas",
2902
+ required: false
2903
+ }]
2904
+ }],
2905
+ exportHandler: [{
2906
+ type: i0.Input,
2907
+ args: [{
2908
+ isSignal: true,
2909
+ alias: "exportHandler",
2910
+ required: false
2911
+ }]
2912
+ }],
2913
+ virtualize: [{
2914
+ type: i0.Input,
2915
+ args: [{
2916
+ isSignal: true,
2917
+ alias: "virtualize",
2918
+ required: false
2919
+ }]
2920
+ }],
2921
+ className: [{
2922
+ type: i0.Input,
2923
+ args: [{
2924
+ isSignal: true,
2925
+ alias: "className",
2926
+ required: false
2927
+ }]
2928
+ }],
2929
+ selected: [{
2930
+ type: i0.Input,
2931
+ args: [{
2932
+ isSignal: true,
2933
+ alias: "selected",
2934
+ required: false
2935
+ }]
2936
+ }, {
2937
+ type: i0.Output,
2938
+ args: ["selectedChange"]
2939
+ }],
2940
+ filter: [{
2941
+ type: i0.Input,
2942
+ args: [{
2943
+ isSignal: true,
2944
+ alias: "filter",
2945
+ required: false
2946
+ }]
2947
+ }, {
2948
+ type: i0.Output,
2949
+ args: ["filterChange"]
2950
+ }],
2951
+ sort: [{
2952
+ type: i0.Input,
2953
+ args: [{
2954
+ isSignal: true,
2955
+ alias: "sort",
2956
+ required: false
2957
+ }]
2958
+ }, {
2959
+ type: i0.Output,
2960
+ args: ["sortChange"]
2961
+ }],
2962
+ columnOrder: [{
2963
+ type: i0.Input,
2964
+ args: [{
2965
+ isSignal: true,
2966
+ alias: "columnOrder",
2967
+ required: false
2968
+ }]
2969
+ }, {
2970
+ type: i0.Output,
2971
+ args: ["columnOrderChange"]
2972
+ }],
2973
+ page: [{
2974
+ type: i0.Input,
2975
+ args: [{
2976
+ isSignal: true,
2977
+ alias: "page",
2978
+ required: false
2979
+ }]
2980
+ }, {
2981
+ type: i0.Output,
2982
+ args: ["pageChange"]
2983
+ }],
2984
+ cellEdit: [{
2985
+ type: i0.Output,
2986
+ args: ["cellEdit"]
2987
+ }],
2988
+ filesSelected: [{
2989
+ type: i0.Output,
2990
+ args: ["filesSelected"]
2991
+ }],
2992
+ fileRemove: [{
2993
+ type: i0.Output,
2994
+ args: ["fileRemove"]
2995
+ }],
2996
+ rowActivate: [{
2997
+ type: i0.Output,
2998
+ args: ["rowActivate"]
2999
+ }],
3000
+ exportFile: [{
3001
+ type: i0.Output,
3002
+ args: ["exportFile"]
3003
+ }],
3004
+ rootRef: [{
3005
+ type: i0.ViewChild,
3006
+ args: ["rootEl", { isSignal: true }]
3007
+ }],
3008
+ scrollRef: [{
3009
+ type: i0.ViewChild,
3010
+ args: ["scrollEl", { isSignal: true }]
3011
+ }]
3012
+ }
3013
+ });
3014
+ export { DataGrid, DataGridAttrs, MIME_TYPES, aggregateValues, cellText, civilDateToDate, columnName, crc32, downloadBlob, exportBlob, exportFilename, filesOf, isCivilDate, isCivilTime, isExportFormula, isInvalidForType, isValidTimeZone, isZonedTime, linkLabel, linkParts, nextDataGridId, peopleOf, personName, resolveColumn, safeHref, toCsv, toDelimited, toTsv, toXlsx, zipStore, zonedMinutes, zonedUtcTime };
3015
+
3016
+ //# sourceMappingURL=lilydesignsystem-angular-data-grid.mjs.map