@gridnexa/angular 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,693 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+
12
+ // src/index.ts
13
+ import {
14
+ Component,
15
+ EventEmitter,
16
+ Input,
17
+ Output,
18
+ ViewChild
19
+ } from "@angular/core";
20
+ export * from "@gridnexa/core";
21
+ function rawValue(row, column) {
22
+ return column.valueGetter ? column.valueGetter(row) : row[column.field];
23
+ }
24
+ function evaluateFormula(row, columns, expression) {
25
+ const formula = expression.trim().slice(1);
26
+ if (!/^[\w\s.+\-*/()%]+$/.test(formula)) return expression;
27
+ const resolved = columns.reduce((current, column) => {
28
+ const value2 = Number(rawValue(row, column));
29
+ return current.replace(
30
+ new RegExp(`\\b${column.id}\\b|\\b${String(column.field)}\\b`, "g"),
31
+ Number.isFinite(value2) ? String(value2) : "0"
32
+ );
33
+ }, formula);
34
+ try {
35
+ return Function(`"use strict"; return (${resolved});`)();
36
+ } catch {
37
+ return expression;
38
+ }
39
+ }
40
+ function value(row, column, columns) {
41
+ const current = rawValue(row, column);
42
+ return typeof current === "string" && current.trim().startsWith("=") ? evaluateFormula(row, columns, current) : current;
43
+ }
44
+ function format(row, column, columns) {
45
+ const current = value(row, column, columns);
46
+ return column.valueFormatter ? column.valueFormatter(current) : String(current ?? "");
47
+ }
48
+ function matches(row, column, filter, columns) {
49
+ const raw = value(row, column, columns);
50
+ const text = String(raw ?? "").toLowerCase();
51
+ const needle = String(filter.value ?? "").toLowerCase();
52
+ const numeric = Number(raw);
53
+ const from = Number(filter.value);
54
+ const to = Number(filter.valueTo);
55
+ if (filter.operator === "blank") return !text.trim();
56
+ if (filter.operator === "notBlank") return Boolean(text.trim());
57
+ if (filter.operator === "in") return (filter.values ?? []).map(String).includes(String(raw ?? ""));
58
+ if (!needle && filter.operator !== "between") return true;
59
+ if (filter.operator === "equals") return text === needle;
60
+ if (filter.operator === "startsWith") return text.startsWith(needle);
61
+ if (filter.operator === "endsWith") return text.endsWith(needle);
62
+ if (filter.operator === "gt") return numeric > from;
63
+ if (filter.operator === "gte") return numeric >= from;
64
+ if (filter.operator === "lt") return numeric < from;
65
+ if (filter.operator === "lte") return numeric <= from;
66
+ if (filter.operator === "between") return numeric >= from && numeric <= to;
67
+ return text.includes(needle);
68
+ }
69
+ function activeAdvanced(model) {
70
+ if (!model) return false;
71
+ if (model.kind === "group") return model.conditions.some(activeAdvanced);
72
+ if (model.operator === "blank" || model.operator === "notBlank") return true;
73
+ if (model.operator === "in") return Boolean(model.values?.length);
74
+ return model.value != null && String(model.value).trim() !== "";
75
+ }
76
+ function matchesAdvanced(row, columns, model) {
77
+ if (!model || !activeAdvanced(model)) return true;
78
+ if (model.kind === "group") {
79
+ const active = model.conditions.filter(activeAdvanced);
80
+ return model.joinOperator === "or" ? active.some((condition) => matchesAdvanced(row, columns, condition)) : active.every((condition) => matchesAdvanced(row, columns, condition));
81
+ }
82
+ const column = columns.find((entry) => entry.id === model.columnId);
83
+ return column ? matches(row, column, { operator: model.operator, value: model.value, valueTo: model.valueTo, values: model.values }, columns) : true;
84
+ }
85
+ function aggregate(values, aggregation) {
86
+ if (aggregation === "count") return values.length;
87
+ const numbers = values.map(Number).filter((entry) => !Number.isNaN(entry));
88
+ if (!numbers.length) return 0;
89
+ if (aggregation === "avg") return numbers.reduce((sum, entry) => sum + entry, 0) / numbers.length;
90
+ if (aggregation === "min") return Math.min(...numbers);
91
+ if (aggregation === "max") return Math.max(...numbers);
92
+ return numbers.reduce((sum, entry) => sum + entry, 0);
93
+ }
94
+ function buildPivot(rows, columns, groupBy, pivotBy, pivotValueColumns, pivotAggregation = "sum") {
95
+ if (!pivotBy) return { columns, rows, active: false };
96
+ const valueFields = pivotValueColumns?.length ? pivotValueColumns : columns.filter((column) => rows.some((row) => typeof value(row, column, columns) === "number")).map((column) => column.field);
97
+ const pivotLabels = Array.from(new Set(rows.map((row) => String(row[pivotBy] ?? "Blank"))));
98
+ const groupLabels = Array.from(new Set(rows.map((row) => String(groupBy ? row[groupBy] : "Total"))));
99
+ const pivotColumns = [
100
+ { id: "__group", field: "__group", headerName: groupBy ?? "Group", width: 180 },
101
+ ...pivotLabels.flatMap(
102
+ (label) => valueFields.map((field) => ({
103
+ id: `${label}_${String(field)}`,
104
+ field: `${label}_${String(field)}`,
105
+ headerName: `${label} ${String(field)}`,
106
+ width: 140
107
+ }))
108
+ )
109
+ ];
110
+ const pivotRows = groupLabels.map((group) => {
111
+ const output = { __group: group };
112
+ pivotLabels.forEach((pivot) => {
113
+ const bucket = rows.filter((row) => String(row[pivotBy] ?? "Blank") === pivot && String(groupBy ? row[groupBy] : "Total") === group);
114
+ valueFields.forEach((field) => {
115
+ output[`${pivot}_${String(field)}`] = aggregate(bucket.map((row) => row[field]), pivotAggregation);
116
+ });
117
+ });
118
+ return output;
119
+ });
120
+ return { columns: pivotColumns, rows: pivotRows, active: true };
121
+ }
122
+ function cell(text, tag = "td") {
123
+ const element = document.createElement(tag);
124
+ element.textContent = text;
125
+ return element;
126
+ }
127
+ var GridNexaAngularComponent = class {
128
+ columns = [];
129
+ rows = [];
130
+ mergedHeaders;
131
+ rowNumbers = false;
132
+ checkboxSelection = false;
133
+ enableRangeSelection = true;
134
+ enableFillHandle = true;
135
+ enableUndoRedo = true;
136
+ quickFilterText = "";
137
+ columnFilters;
138
+ externalFilter;
139
+ advancedFilter;
140
+ advancedFilterModel;
141
+ pageSize;
142
+ groupBy;
143
+ pivotBy;
144
+ pivotValueColumns;
145
+ pivotAggregation = "sum";
146
+ getRowId;
147
+ getTreeDataPath;
148
+ masterDetailRenderer;
149
+ transaction;
150
+ localeText;
151
+ rowSelectionChange = new EventEmitter();
152
+ cellClick = new EventEmitter();
153
+ cellValueChange = new EventEmitter();
154
+ advancedFilterModelChange = new EventEmitter();
155
+ pivotModelChange = new EventEmitter();
156
+ serverSideOperation = new EventEmitter();
157
+ host;
158
+ selected = /* @__PURE__ */ new Set();
159
+ pageIndex = 0;
160
+ sortState = null;
161
+ hiddenColumnIds = /* @__PURE__ */ new Set();
162
+ activeCell = null;
163
+ findText = "";
164
+ toolsOpen = false;
165
+ draggedColumnId = null;
166
+ undoStack = [];
167
+ redoStack = [];
168
+ ngAfterViewInit() {
169
+ this.render();
170
+ }
171
+ ngOnChanges() {
172
+ this.hiddenColumnIds = new Set(this.columns.filter((column) => column.hidden).map((column) => column.id));
173
+ this.applyTransaction();
174
+ this.render();
175
+ }
176
+ rowId(row, index) {
177
+ return this.getRowId?.(row, index) ?? index;
178
+ }
179
+ applyTransaction() {
180
+ if (!this.transaction) return;
181
+ const removeIds = new Set((this.transaction.remove ?? []).map((row, index) => this.rowId(row, index)));
182
+ const updates = new Map((this.transaction.update ?? []).map((row, index) => [this.rowId(row, index), row]));
183
+ this.rows = [
184
+ ...this.rows.filter((row, index) => !removeIds.has(this.rowId(row, index))).map((row, index) => updates.get(this.rowId(row, index)) ?? row),
185
+ ...this.transaction.add ?? []
186
+ ];
187
+ this.transaction = void 0;
188
+ }
189
+ visibleRows() {
190
+ const query = this.quickFilterText.trim().toLowerCase();
191
+ const rows = this.rows.filter((row) => {
192
+ if (this.externalFilter && !this.externalFilter(row)) return false;
193
+ if (this.advancedFilter && !this.advancedFilter(row)) return false;
194
+ if (!matchesAdvanced(row, this.columns, this.advancedFilterModel)) return false;
195
+ if (query && !this.columns.some((column2) => String(value(row, column2, this.columns) ?? "").toLowerCase().includes(query))) return false;
196
+ return Object.entries(this.columnFilters ?? {}).every(([columnId, filter]) => {
197
+ const column2 = this.columns.find((entry) => entry.id === columnId);
198
+ return column2 ? matches(row, column2, filter, this.columns) : true;
199
+ });
200
+ });
201
+ if (!this.sortState) return rows;
202
+ const column = this.columns.find((entry) => entry.id === this.sortState?.columnId);
203
+ if (!column) return rows;
204
+ return [...rows].sort((left, right) => {
205
+ const comparison = String(value(left, column, this.columns) ?? "").localeCompare(String(value(right, column, this.columns) ?? ""), void 0, { numeric: true });
206
+ return this.sortState?.direction === "asc" ? comparison : -comparison;
207
+ });
208
+ }
209
+ exportRows(columns, rows, excel = false) {
210
+ const content = excel ? `<table><tr>${columns.map((column) => `<th>${column.headerName}</th>`).join("")}</tr>${rows.map((row) => `<tr>${columns.map((column) => `<td>${format(row, column, this.columns)}</td>`).join("")}</tr>`).join("")}</table>` : [columns.map((column) => JSON.stringify(column.headerName)).join(","), ...rows.map((row) => columns.map((column) => JSON.stringify(format(row, column, this.columns))).join(","))].join("\n");
211
+ const blob = new Blob([content], { type: excel ? "application/vnd.ms-excel" : "text/csv;charset=utf-8" });
212
+ const link = document.createElement("a");
213
+ link.href = URL.createObjectURL(blob);
214
+ link.download = excel ? "gridnexa-export.xls" : "gridnexa-export.csv";
215
+ link.click();
216
+ URL.revokeObjectURL(link.href);
217
+ }
218
+ setCellValue(row, rowIndex, column, nextValue, trackHistory = true) {
219
+ const oldValue = rawValue(row, column);
220
+ const newValue = typeof oldValue === "number" ? Number(nextValue) : nextValue;
221
+ if (trackHistory && this.enableUndoRedo) {
222
+ this.undoStack.push({ row, rowIndex, column, oldValue, newValue });
223
+ this.redoStack = [];
224
+ }
225
+ row[column.field] = newValue;
226
+ this.cellValueChange.emit({ row, rowIndex, column, oldValue, newValue });
227
+ }
228
+ undo() {
229
+ const edit = this.undoStack.pop();
230
+ if (!edit) return;
231
+ this.setCellValue(edit.row, edit.rowIndex, edit.column, edit.oldValue, false);
232
+ this.redoStack.push(edit);
233
+ this.render();
234
+ }
235
+ redo() {
236
+ const edit = this.redoStack.pop();
237
+ if (!edit) return;
238
+ this.setCellValue(edit.row, edit.rowIndex, edit.column, edit.newValue, false);
239
+ this.undoStack.push(edit);
240
+ this.render();
241
+ }
242
+ fillDown() {
243
+ if (!this.activeCell || !this.enableFillHandle) return;
244
+ const sourceRow = this.rows[this.activeCell.rowIndex];
245
+ const targetRow = this.rows[this.activeCell.rowIndex + 1];
246
+ const column = this.columns.find((entry) => entry.id === this.activeCell?.columnId);
247
+ if (!sourceRow || !targetRow || !column || column.editable === false) return;
248
+ this.setCellValue(targetRow, this.activeCell.rowIndex + 1, column, rawValue(sourceRow, column));
249
+ this.render();
250
+ }
251
+ async copyActiveCell() {
252
+ if (!this.activeCell || typeof navigator === "undefined") return;
253
+ const row = this.rows[this.activeCell.rowIndex];
254
+ const column = this.columns.find((entry) => entry.id === this.activeCell?.columnId);
255
+ if (!row || !column) return;
256
+ await navigator.clipboard?.writeText(format(row, column, this.columns));
257
+ }
258
+ async pasteActiveCell() {
259
+ if (!this.activeCell || typeof navigator === "undefined") return;
260
+ const row = this.rows[this.activeCell.rowIndex];
261
+ const column = this.columns.find((entry) => entry.id === this.activeCell?.columnId);
262
+ if (!row || !column || column.editable === false) return;
263
+ const text = await navigator.clipboard?.readText();
264
+ this.setCellValue(row, this.activeCell.rowIndex, column, text);
265
+ this.render();
266
+ }
267
+ moveRow(rowIndex, direction) {
268
+ const nextIndex = rowIndex + direction;
269
+ if (nextIndex < 0 || nextIndex >= this.rows.length) return;
270
+ const rows = [...this.rows];
271
+ const [row] = rows.splice(rowIndex, 1);
272
+ rows.splice(nextIndex, 0, row);
273
+ this.rows = rows;
274
+ this.render();
275
+ }
276
+ moveColumn(sourceId, targetId) {
277
+ if (sourceId === targetId) return;
278
+ const columns = [...this.columns];
279
+ const sourceIndex = columns.findIndex((column2) => column2.id === sourceId);
280
+ const targetIndex = columns.findIndex((column2) => column2.id === targetId);
281
+ if (sourceIndex < 0 || targetIndex < 0) return;
282
+ const [column] = columns.splice(sourceIndex, 1);
283
+ columns.splice(targetIndex, 0, column);
284
+ this.columns = columns;
285
+ this.render();
286
+ }
287
+ updateAdvancedFilter(columnId, operator, filterValue) {
288
+ const model = {
289
+ kind: "group",
290
+ joinOperator: "and",
291
+ conditions: [{ kind: "rule", columnId, operator, value: filterValue }]
292
+ };
293
+ this.advancedFilterModel = model;
294
+ this.advancedFilterModelChange.emit(model);
295
+ this.render();
296
+ }
297
+ updatePivot(next) {
298
+ if ("groupBy" in next) this.groupBy = next.groupBy;
299
+ if ("pivotBy" in next) this.pivotBy = next.pivotBy;
300
+ if ("pivotValueColumns" in next) this.pivotValueColumns = next.pivotValueColumns;
301
+ if ("pivotAggregation" in next && next.pivotAggregation) this.pivotAggregation = next.pivotAggregation;
302
+ this.pivotModelChange.emit({
303
+ groupBy: this.groupBy,
304
+ pivotBy: this.pivotBy,
305
+ pivotValueColumns: this.pivotValueColumns ?? [],
306
+ pivotAggregation: this.pivotAggregation
307
+ });
308
+ this.pageIndex = 0;
309
+ this.render();
310
+ }
311
+ render() {
312
+ if (!this.host) return;
313
+ const sourceRows = this.visibleRows();
314
+ const pivot = buildPivot(sourceRows, this.columns, this.groupBy, this.pivotBy, this.pivotValueColumns, this.pivotAggregation);
315
+ const columns = pivot.columns.filter((column) => !this.hiddenColumnIds.has(column.id) && !column.hidden);
316
+ const pageRows = this.pageSize ? pivot.rows.slice(this.pageIndex * this.pageSize, this.pageIndex * this.pageSize + this.pageSize) : pivot.rows;
317
+ const root = document.createElement("div");
318
+ root.className = "gridnexa-angular-grid";
319
+ root.append(this.renderToolbar(columns, pivot.rows), this.renderTable(columns, pageRows));
320
+ if (this.toolsOpen) root.appendChild(this.renderToolsPanel());
321
+ root.appendChild(this.renderStatus(pivot.rows.length));
322
+ this.host.nativeElement.replaceChildren(root);
323
+ this.serverSideOperation.emit({
324
+ sortModel: this.sortState ? [this.sortState] : [],
325
+ filterModel: this.columnFilters,
326
+ advancedFilterModel: this.advancedFilterModel,
327
+ selectedRowIds: Array.from(this.selected),
328
+ pageIndex: this.pageIndex,
329
+ pageSize: this.pageSize,
330
+ groupBy: this.groupBy,
331
+ pivotBy: this.pivotBy,
332
+ pivotValueColumns: this.pivotValueColumns,
333
+ pivotAggregation: this.pivotAggregation,
334
+ treeData: Boolean(this.getTreeDataPath),
335
+ masterDetail: Boolean(this.masterDetailRenderer)
336
+ });
337
+ }
338
+ renderToolbar(columns, rows) {
339
+ const toolbar = document.createElement("div");
340
+ toolbar.style.cssText = "display:flex;gap:8px;justify-content:space-between;align-items:center;padding:10px;background:#f8fbff;border:1px solid #dbe3ef";
341
+ toolbar.append(`${rows.length} rows`);
342
+ const actions = document.createElement("div");
343
+ actions.style.cssText = "display:flex;gap:6px;flex-wrap:wrap";
344
+ const find = document.createElement("input");
345
+ find.type = "search";
346
+ find.placeholder = "Find cell";
347
+ find.value = this.findText;
348
+ find.style.cssText = "min-height:32px;padding:0 10px;border:1px solid #bfdbfe;border-radius:8px";
349
+ find.addEventListener("input", () => {
350
+ this.findText = find.value;
351
+ this.render();
352
+ });
353
+ const pageCount = this.pageSize ? Math.max(1, Math.ceil(rows.length / this.pageSize)) : 1;
354
+ if (this.pageSize) {
355
+ const prev = this.button("Prev", () => {
356
+ this.pageIndex = Math.max(0, this.pageIndex - 1);
357
+ this.render();
358
+ });
359
+ prev.disabled = this.pageIndex <= 0;
360
+ const next = this.button("Next", () => {
361
+ this.pageIndex = Math.min(pageCount - 1, this.pageIndex + 1);
362
+ this.render();
363
+ });
364
+ next.disabled = this.pageIndex >= pageCount - 1;
365
+ actions.append(prev, ` Page ${this.pageIndex + 1} `, next);
366
+ }
367
+ actions.appendChild(find);
368
+ if (this.enableUndoRedo) {
369
+ const undo = this.button("Undo", () => this.undo());
370
+ undo.disabled = !this.undoStack.length;
371
+ const redo = this.button("Redo", () => this.redo());
372
+ redo.disabled = !this.redoStack.length;
373
+ actions.append(undo, redo);
374
+ }
375
+ if (this.enableFillHandle) actions.appendChild(this.button("Fill", () => this.fillDown()));
376
+ actions.append(
377
+ this.button("Copy", () => void this.copyActiveCell()),
378
+ this.button("Paste", () => void this.pasteActiveCell()),
379
+ this.button(this.toolsOpen ? "Hide tools" : "Tools", () => {
380
+ this.toolsOpen = !this.toolsOpen;
381
+ this.render();
382
+ }),
383
+ this.button("Export CSV", () => this.exportRows(columns, rows)),
384
+ this.button("Export Excel", () => this.exportRows(columns, rows, true))
385
+ );
386
+ toolbar.appendChild(actions);
387
+ return toolbar;
388
+ }
389
+ renderTable(columns, rows) {
390
+ const table = document.createElement("table");
391
+ table.style.cssText = "width:100%;border-collapse:collapse";
392
+ const thead = document.createElement("thead");
393
+ const leading = Number(this.checkboxSelection) + Number(this.rowNumbers);
394
+ if (this.mergedHeaders?.length) thead.appendChild(this.renderMergedHeaders(columns, leading));
395
+ const header = document.createElement("tr");
396
+ if (this.checkboxSelection) header.appendChild(cell("", "th"));
397
+ if (this.rowNumbers) header.appendChild(cell("#", "th"));
398
+ columns.forEach((column) => {
399
+ const th = cell(`${column.headerName}${this.sortState?.columnId === column.id ? this.sortState.direction === "asc" ? " \u2191" : " \u2193" : ""}`, "th");
400
+ th.style.cssText = "padding:10px;border:1px solid #dbe3ef;background:#f8fbff;text-align:left";
401
+ th.draggable = true;
402
+ th.addEventListener("dragstart", () => {
403
+ this.draggedColumnId = column.id;
404
+ });
405
+ th.addEventListener("dragover", (event) => event.preventDefault());
406
+ th.addEventListener("drop", (event) => {
407
+ event.preventDefault();
408
+ if (this.draggedColumnId) this.moveColumn(this.draggedColumnId, column.id);
409
+ this.draggedColumnId = null;
410
+ });
411
+ th.addEventListener("click", () => {
412
+ if (column.sortable === false) return;
413
+ this.sortState = this.sortState?.columnId !== column.id ? { columnId: column.id, direction: "asc" } : this.sortState.direction === "asc" ? { columnId: column.id, direction: "desc" } : null;
414
+ this.render();
415
+ });
416
+ header.appendChild(th);
417
+ });
418
+ thead.appendChild(header);
419
+ table.appendChild(thead);
420
+ const tbody = document.createElement("tbody");
421
+ rows.forEach((row, rowIndex) => this.appendRow(tbody, row, rowIndex, columns, leading));
422
+ table.appendChild(tbody);
423
+ return table;
424
+ }
425
+ renderMergedHeaders(columns, leading) {
426
+ const row = document.createElement("tr");
427
+ if (leading) {
428
+ const spacer = cell("", "th");
429
+ spacer.colSpan = leading;
430
+ row.appendChild(spacer);
431
+ }
432
+ (this.mergedHeaders ?? []).forEach((header) => {
433
+ const count = header.columnIds.filter((columnId) => columns.some((column) => column.id === columnId)).length;
434
+ if (!count) return;
435
+ const th = cell(header.headerName, "th");
436
+ th.colSpan = count;
437
+ th.style.cssText = "padding:8px;border:1px solid #bfdbfe;background:#e8f1ff;text-align:center";
438
+ row.appendChild(th);
439
+ });
440
+ return row;
441
+ }
442
+ appendRow(tbody, row, rowIndex, columns, leading) {
443
+ const tr = document.createElement("tr");
444
+ if (this.checkboxSelection) {
445
+ const td = document.createElement("td");
446
+ const checkbox = document.createElement("input");
447
+ checkbox.type = "checkbox";
448
+ checkbox.checked = this.selected.has(this.rowId(row, rowIndex));
449
+ checkbox.addEventListener("change", () => {
450
+ checkbox.checked ? this.selected.add(this.rowId(row, rowIndex)) : this.selected.delete(this.rowId(row, rowIndex));
451
+ this.rowSelectionChange.emit(this.rows.filter((entry, index) => this.selected.has(this.rowId(entry, index))));
452
+ this.render();
453
+ });
454
+ td.appendChild(checkbox);
455
+ tr.appendChild(td);
456
+ }
457
+ if (this.rowNumbers) {
458
+ const rowNumber = cell(String(rowIndex + 1));
459
+ const up = this.button("\u2191", () => this.moveRow(rowIndex, -1));
460
+ up.disabled = rowIndex <= 0;
461
+ const down = this.button("\u2193", () => this.moveRow(rowIndex, 1));
462
+ down.disabled = rowIndex >= this.rows.length - 1;
463
+ rowNumber.append(" ", up, down);
464
+ tr.appendChild(rowNumber);
465
+ }
466
+ columns.forEach((column, columnIndex) => {
467
+ const td = cell(format(row, column, this.columns));
468
+ td.style.cssText = "padding:10px;border:1px solid #dbe3ef";
469
+ if (this.activeCell?.rowIndex === rowIndex && this.activeCell.columnId === column.id) {
470
+ td.style.outline = "2px solid #2563eb";
471
+ td.style.outlineOffset = "-2px";
472
+ }
473
+ if (this.findText && format(row, column, this.columns).toLowerCase().includes(this.findText.toLowerCase())) {
474
+ td.style.background = "rgba(37,99,235,.1)";
475
+ }
476
+ if (this.getTreeDataPath && columnIndex === 0) td.style.paddingLeft = `${12 + Math.max(0, this.getTreeDataPath(row).length - 1) * 24}px`;
477
+ td.addEventListener("click", () => {
478
+ this.activeCell = { rowIndex, columnId: column.id };
479
+ this.cellClick.emit({ row, rowIndex, column });
480
+ this.render();
481
+ });
482
+ if (column.editable) td.addEventListener("dblclick", () => this.editCell(td, row, rowIndex, column));
483
+ tr.appendChild(td);
484
+ });
485
+ tbody.appendChild(tr);
486
+ if (this.masterDetailRenderer) {
487
+ const detailRow = document.createElement("tr");
488
+ const detail = document.createElement("td");
489
+ detail.colSpan = columns.length + leading;
490
+ const content = this.masterDetailRenderer(row);
491
+ content instanceof Node ? detail.appendChild(content) : detail.textContent = String(content ?? "");
492
+ detailRow.appendChild(detail);
493
+ tbody.appendChild(detailRow);
494
+ }
495
+ }
496
+ editCell(td, row, rowIndex, column) {
497
+ const oldValue = rawValue(row, column);
498
+ const input = document.createElement("input");
499
+ input.value = String(oldValue ?? "");
500
+ td.replaceChildren(input);
501
+ input.focus();
502
+ input.addEventListener("blur", () => {
503
+ this.setCellValue(row, rowIndex, column, input.value);
504
+ this.render();
505
+ }, { once: true });
506
+ }
507
+ renderToolsPanel() {
508
+ const panel = document.createElement("div");
509
+ panel.style.cssText = "display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;padding:12px;border:1px solid #dbe3ef;border-top:0;background:#f8fbff";
510
+ const columnsSection = document.createElement("section");
511
+ columnsSection.appendChild(document.createElement("strong")).textContent = "Columns";
512
+ this.columns.forEach((column) => {
513
+ const label = document.createElement("label");
514
+ label.style.cssText = "display:flex;gap:8px;align-items:center;margin-top:6px";
515
+ const checkbox = document.createElement("input");
516
+ checkbox.type = "checkbox";
517
+ checkbox.checked = !this.hiddenColumnIds.has(column.id);
518
+ checkbox.addEventListener("change", () => {
519
+ checkbox.checked ? this.hiddenColumnIds.delete(column.id) : this.hiddenColumnIds.add(column.id);
520
+ this.render();
521
+ });
522
+ label.append(checkbox, column.headerName);
523
+ columnsSection.appendChild(label);
524
+ });
525
+ const pivotSection = document.createElement("section");
526
+ pivotSection.appendChild(document.createElement("strong")).textContent = "Pivot";
527
+ pivotSection.append(
528
+ this.select("Row group", this.groupBy, "No group", (value2) => this.updatePivot({ groupBy: value2 })),
529
+ this.select("Pivot by", this.pivotBy, "Pivot off", (value2) => this.updatePivot({ pivotBy: value2 })),
530
+ this.select("Value", this.pivotValueColumns?.[0], "No value", (value2) => this.updatePivot({ pivotValueColumns: value2 ? [value2] : [] })),
531
+ this.select("Aggregation", this.pivotAggregation, "", (value2) => this.updatePivot({ pivotAggregation: value2 }), ["sum", "avg", "count", "min", "max"])
532
+ );
533
+ const filterSection = document.createElement("section");
534
+ filterSection.appendChild(document.createElement("strong")).textContent = "Advanced filter";
535
+ const columnSelect = this.select("", this.columns[0]?.id, "", () => void 0, this.columns.map((column) => column.id));
536
+ const operatorSelect = this.select("", "contains", "", () => void 0, ["contains", "equals", "gt", "gte", "lt", "lte", "blank", "notBlank"]);
537
+ const valueInput = document.createElement("input");
538
+ valueInput.placeholder = "Value";
539
+ valueInput.style.cssText = "min-height:32px;padding:0 10px;border:1px solid #bfdbfe;border-radius:8px";
540
+ const apply = this.button("Apply", () => {
541
+ this.updateAdvancedFilter(
542
+ columnSelect.querySelector("select").value,
543
+ operatorSelect.querySelector("select").value,
544
+ valueInput.value
545
+ );
546
+ });
547
+ const clear = this.button("Clear", () => {
548
+ this.advancedFilterModel = null;
549
+ this.advancedFilterModelChange.emit(null);
550
+ this.render();
551
+ });
552
+ filterSection.append(columnSelect, operatorSelect, valueInput, apply, clear);
553
+ panel.append(columnsSection, pivotSection, filterSection);
554
+ return panel;
555
+ }
556
+ select(labelText, value2, emptyLabel, onChange, fixedOptions) {
557
+ const label = document.createElement("label");
558
+ label.style.cssText = "display:flex;gap:8px;align-items:center;margin-top:6px";
559
+ if (labelText) label.append(`${labelText} `);
560
+ const select = document.createElement("select");
561
+ select.style.cssText = "min-height:32px;padding:0 8px;border:1px solid #bfdbfe;border-radius:8px";
562
+ if (emptyLabel) {
563
+ const option = document.createElement("option");
564
+ option.value = "";
565
+ option.textContent = emptyLabel;
566
+ select.appendChild(option);
567
+ }
568
+ (fixedOptions ?? this.columns.map((column) => String(column.field))).forEach((entry) => {
569
+ const option = document.createElement("option");
570
+ option.value = entry;
571
+ option.textContent = this.columns.find((column) => column.id === entry || column.field === entry)?.headerName ?? entry;
572
+ select.appendChild(option);
573
+ });
574
+ select.value = value2 ?? "";
575
+ select.addEventListener("change", () => onChange(select.value || void 0));
576
+ label.appendChild(select);
577
+ return label;
578
+ }
579
+ renderStatus(totalRows) {
580
+ const status = document.createElement("div");
581
+ status.style.cssText = "display:flex;gap:16px;padding:10px;border:1px solid #dbe3ef;border-top:0;font-weight:700";
582
+ status.append(`${totalRows} rows`, `${this.selected.size} selected`, this.sortState ? `Sorted ${this.sortState.direction}` : "Unsorted");
583
+ return status;
584
+ }
585
+ button(text, onClick) {
586
+ const button = document.createElement("button");
587
+ button.type = "button";
588
+ button.textContent = text;
589
+ button.style.cssText = "min-height:32px;padding:0 10px;border:1px solid #bfdbfe;border-radius:8px;background:#fff;color:#1d4ed8;font-weight:800";
590
+ button.addEventListener("click", onClick);
591
+ return button;
592
+ }
593
+ };
594
+ __decorateClass([
595
+ Input({ required: true })
596
+ ], GridNexaAngularComponent.prototype, "columns", 2);
597
+ __decorateClass([
598
+ Input({ required: true })
599
+ ], GridNexaAngularComponent.prototype, "rows", 2);
600
+ __decorateClass([
601
+ Input()
602
+ ], GridNexaAngularComponent.prototype, "mergedHeaders", 2);
603
+ __decorateClass([
604
+ Input()
605
+ ], GridNexaAngularComponent.prototype, "rowNumbers", 2);
606
+ __decorateClass([
607
+ Input()
608
+ ], GridNexaAngularComponent.prototype, "checkboxSelection", 2);
609
+ __decorateClass([
610
+ Input()
611
+ ], GridNexaAngularComponent.prototype, "enableRangeSelection", 2);
612
+ __decorateClass([
613
+ Input()
614
+ ], GridNexaAngularComponent.prototype, "enableFillHandle", 2);
615
+ __decorateClass([
616
+ Input()
617
+ ], GridNexaAngularComponent.prototype, "enableUndoRedo", 2);
618
+ __decorateClass([
619
+ Input()
620
+ ], GridNexaAngularComponent.prototype, "quickFilterText", 2);
621
+ __decorateClass([
622
+ Input()
623
+ ], GridNexaAngularComponent.prototype, "columnFilters", 2);
624
+ __decorateClass([
625
+ Input()
626
+ ], GridNexaAngularComponent.prototype, "externalFilter", 2);
627
+ __decorateClass([
628
+ Input()
629
+ ], GridNexaAngularComponent.prototype, "advancedFilter", 2);
630
+ __decorateClass([
631
+ Input()
632
+ ], GridNexaAngularComponent.prototype, "advancedFilterModel", 2);
633
+ __decorateClass([
634
+ Input()
635
+ ], GridNexaAngularComponent.prototype, "pageSize", 2);
636
+ __decorateClass([
637
+ Input()
638
+ ], GridNexaAngularComponent.prototype, "groupBy", 2);
639
+ __decorateClass([
640
+ Input()
641
+ ], GridNexaAngularComponent.prototype, "pivotBy", 2);
642
+ __decorateClass([
643
+ Input()
644
+ ], GridNexaAngularComponent.prototype, "pivotValueColumns", 2);
645
+ __decorateClass([
646
+ Input()
647
+ ], GridNexaAngularComponent.prototype, "pivotAggregation", 2);
648
+ __decorateClass([
649
+ Input()
650
+ ], GridNexaAngularComponent.prototype, "getRowId", 2);
651
+ __decorateClass([
652
+ Input()
653
+ ], GridNexaAngularComponent.prototype, "getTreeDataPath", 2);
654
+ __decorateClass([
655
+ Input()
656
+ ], GridNexaAngularComponent.prototype, "masterDetailRenderer", 2);
657
+ __decorateClass([
658
+ Input()
659
+ ], GridNexaAngularComponent.prototype, "transaction", 2);
660
+ __decorateClass([
661
+ Input()
662
+ ], GridNexaAngularComponent.prototype, "localeText", 2);
663
+ __decorateClass([
664
+ Output()
665
+ ], GridNexaAngularComponent.prototype, "rowSelectionChange", 2);
666
+ __decorateClass([
667
+ Output()
668
+ ], GridNexaAngularComponent.prototype, "cellClick", 2);
669
+ __decorateClass([
670
+ Output()
671
+ ], GridNexaAngularComponent.prototype, "cellValueChange", 2);
672
+ __decorateClass([
673
+ Output()
674
+ ], GridNexaAngularComponent.prototype, "advancedFilterModelChange", 2);
675
+ __decorateClass([
676
+ Output()
677
+ ], GridNexaAngularComponent.prototype, "pivotModelChange", 2);
678
+ __decorateClass([
679
+ Output()
680
+ ], GridNexaAngularComponent.prototype, "serverSideOperation", 2);
681
+ __decorateClass([
682
+ ViewChild("host", { static: true })
683
+ ], GridNexaAngularComponent.prototype, "host", 2);
684
+ GridNexaAngularComponent = __decorateClass([
685
+ Component({
686
+ selector: "grid-nexa",
687
+ standalone: true,
688
+ template: '<div #host class="gridnexa-angular-host"></div>'
689
+ })
690
+ ], GridNexaAngularComponent);
691
+ export {
692
+ GridNexaAngularComponent
693
+ };