@framesquared/grid 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1868 @@
1
+ // src/grid/Grid.ts
2
+ import { Panel } from "@framesquared/ui";
3
+
4
+ // src/grid/column/Column.ts
5
+ var Column = class {
6
+ text;
7
+ dataIndex;
8
+ width;
9
+ flex;
10
+ minWidth;
11
+ maxWidth;
12
+ sortable;
13
+ hidden;
14
+ align;
15
+ config;
16
+ _renderer;
17
+ constructor(config = {}) {
18
+ this.config = config;
19
+ this.text = config.text ?? "";
20
+ this.dataIndex = config.dataIndex ?? "";
21
+ this.width = config.width ?? 0;
22
+ this.flex = config.flex ?? 0;
23
+ this.minWidth = config.minWidth ?? 40;
24
+ this.maxWidth = config.maxWidth ?? 0;
25
+ this.sortable = config.sortable ?? true;
26
+ this.hidden = config.hidden ?? false;
27
+ this.align = config.align ?? "left";
28
+ this._renderer = config.renderer ?? null;
29
+ }
30
+ getCellValue(record) {
31
+ return record.get ? record.get(this.dataIndex) : record.data?.[this.dataIndex];
32
+ }
33
+ renderCell(value, metaData, record, rowIndex, colIndex) {
34
+ if (this._renderer) {
35
+ return this._renderer(value, metaData, record, rowIndex, colIndex);
36
+ }
37
+ return this.formatValue(value);
38
+ }
39
+ formatValue(value) {
40
+ if (value === null || value === void 0) return "";
41
+ return String(value);
42
+ }
43
+ /** Create the TD content — override in special columns. */
44
+ renderCellHtml(value, _metaData, record, rowIndex, colIndex) {
45
+ return this.renderCell(value, _metaData, record, rowIndex, colIndex);
46
+ }
47
+ };
48
+ var NumberColumn = class extends Column {
49
+ _format;
50
+ constructor(config = {}) {
51
+ super({ ...config, xtype: "numbercolumn" });
52
+ this._format = config.format ?? "0";
53
+ }
54
+ formatValue(value) {
55
+ if (value === null || value === void 0) return "";
56
+ const n = Number(value);
57
+ if (isNaN(n)) return String(value);
58
+ const match = this._format.match(/0\.(0+)/);
59
+ if (match) {
60
+ return n.toFixed(match[1].length);
61
+ }
62
+ return String(n);
63
+ }
64
+ };
65
+ var DateColumn = class extends Column {
66
+ _format;
67
+ constructor(config = {}) {
68
+ super({ ...config, xtype: "datecolumn" });
69
+ this._format = config.format ?? "Y-m-d";
70
+ }
71
+ formatValue(value) {
72
+ if (!value) return "";
73
+ const d = value instanceof Date ? value : new Date(String(value));
74
+ if (isNaN(d.getTime())) return String(value);
75
+ const Y = d.getFullYear();
76
+ const m = String(d.getMonth() + 1).padStart(2, "0");
77
+ const day = String(d.getDate()).padStart(2, "0");
78
+ return this._format.replace("Y", String(Y)).replace("m", m).replace("d", day);
79
+ }
80
+ };
81
+ var BooleanColumn = class extends Column {
82
+ _trueText;
83
+ _falseText;
84
+ constructor(config = {}) {
85
+ super({ ...config, xtype: "booleancolumn" });
86
+ this._trueText = config.trueText ?? "true";
87
+ this._falseText = config.falseText ?? "false";
88
+ }
89
+ formatValue(value) {
90
+ return value ? this._trueText : this._falseText;
91
+ }
92
+ };
93
+ var CheckColumn = class extends Column {
94
+ constructor(config = {}) {
95
+ super({ ...config, xtype: "checkcolumn" });
96
+ }
97
+ renderCellHtml(value, _metaData, _record, _rowIndex, _colIndex) {
98
+ const checked = value ? " checked" : "";
99
+ return `<input type="checkbox"${checked} class="x-check-col-input" />`;
100
+ }
101
+ };
102
+ var ActionColumn = class extends Column {
103
+ _actions;
104
+ constructor(config = {}) {
105
+ super({ ...config, xtype: "actioncolumn", sortable: false });
106
+ this._actions = config.actions ?? [];
107
+ }
108
+ get actions() {
109
+ return this._actions;
110
+ }
111
+ renderCellHtml(_value, _metaData, _record, rowIndex, _colIndex) {
112
+ return this._actions.map(
113
+ (a, ai) => `<span class="x-action-col-icon ${a.iconCls ?? ""}" title="${a.tooltip ?? ""}" data-action="${ai}" data-row="${rowIndex}"></span>`
114
+ ).join("");
115
+ }
116
+ };
117
+ var RowNumbererColumn = class extends Column {
118
+ constructor(config = {}) {
119
+ super({ text: "#", width: 40, sortable: false, ...config, xtype: "rownumberer", dataIndex: "" });
120
+ }
121
+ renderCellHtml(_value, _metaData, _record, rowIndex, _colIndex) {
122
+ return String(rowIndex + 1);
123
+ }
124
+ };
125
+ function createColumn(config) {
126
+ switch (config.xtype) {
127
+ case "numbercolumn":
128
+ return new NumberColumn(config);
129
+ case "datecolumn":
130
+ return new DateColumn(config);
131
+ case "booleancolumn":
132
+ return new BooleanColumn(config);
133
+ case "checkcolumn":
134
+ return new CheckColumn(config);
135
+ case "actioncolumn":
136
+ return new ActionColumn(config);
137
+ case "rownumberer":
138
+ return new RowNumbererColumn(config);
139
+ default:
140
+ return new Column(config);
141
+ }
142
+ }
143
+
144
+ // src/grid/HeaderContainer.ts
145
+ var HeaderContainer = class {
146
+ columns;
147
+ headerRow = null;
148
+ thElements = [];
149
+ constructor(config) {
150
+ this.columns = config.columns;
151
+ }
152
+ render() {
153
+ this.headerRow = document.createElement("tr");
154
+ this.headerRow.classList.add("x-grid-header-row");
155
+ this.thElements = [];
156
+ for (const col of this.columns) {
157
+ const th = document.createElement("th");
158
+ th.classList.add("x-column-header");
159
+ th.textContent = col.text;
160
+ if (col.width > 0) th.style.width = `${col.width}px`;
161
+ if (col.hidden) th.style.display = "none";
162
+ if (col.align) th.style.textAlign = col.align;
163
+ th.setAttribute("data-dataindex", col.dataIndex);
164
+ this.thElements.push(th);
165
+ this.headerRow.appendChild(th);
166
+ }
167
+ return this.headerRow;
168
+ }
169
+ getColumns() {
170
+ return this.columns;
171
+ }
172
+ getThElements() {
173
+ return this.thElements;
174
+ }
175
+ setColumnHidden(index, hidden) {
176
+ const th = this.thElements[index];
177
+ if (th) th.style.display = hidden ? "none" : "";
178
+ }
179
+ setSortIndicator(index, direction) {
180
+ for (const th of this.thElements) {
181
+ th.classList.remove("x-column-header-sort-ASC", "x-column-header-sort-DESC");
182
+ }
183
+ if (direction !== null && this.thElements[index]) {
184
+ this.thElements[index].classList.add(`x-column-header-sort-${direction}`);
185
+ }
186
+ }
187
+ };
188
+
189
+ // src/grid/GridView.ts
190
+ var GridView = class {
191
+ store;
192
+ columns;
193
+ emptyText;
194
+ tableEl = null;
195
+ tbodyEl = null;
196
+ theadEl = null;
197
+ headerContainer;
198
+ /** Event listeners set by Grid */
199
+ onItemClick;
200
+ onItemDblClick;
201
+ /** Set by RowSelectionModel so selection classes survive refresh() */
202
+ isSelected;
203
+ constructor(config) {
204
+ this.store = config.store;
205
+ this.columns = config.columns;
206
+ this.emptyText = config.emptyText ?? "";
207
+ this.headerContainer = new HeaderContainer({ columns: this.columns });
208
+ }
209
+ render() {
210
+ this.tableEl = document.createElement("table");
211
+ this.tableEl.classList.add("x-grid-table");
212
+ this.tableEl.style.width = "100%";
213
+ this.tableEl.style.borderCollapse = "collapse";
214
+ this.theadEl = document.createElement("thead");
215
+ const headerRow = this.headerContainer.render();
216
+ this.theadEl.appendChild(headerRow);
217
+ this.tableEl.appendChild(this.theadEl);
218
+ this.tbodyEl = document.createElement("tbody");
219
+ this.tableEl.appendChild(this.tbodyEl);
220
+ this.renderRows();
221
+ return this.tableEl;
222
+ }
223
+ refresh() {
224
+ this.renderRows();
225
+ }
226
+ renderRows() {
227
+ if (!this.tbodyEl) return;
228
+ this.tbodyEl.innerHTML = "";
229
+ const records = this.store.getRange();
230
+ if (records.length === 0 && this.emptyText) {
231
+ const emptyRow = document.createElement("tr");
232
+ const emptyTd = document.createElement("td");
233
+ emptyTd.colSpan = this.columns.length;
234
+ emptyTd.classList.add("x-grid-empty");
235
+ emptyTd.textContent = this.emptyText;
236
+ emptyRow.appendChild(emptyTd);
237
+ this.tbodyEl.appendChild(emptyRow);
238
+ return;
239
+ }
240
+ for (let rowIdx = 0; rowIdx < records.length; rowIdx++) {
241
+ const record = records[rowIdx];
242
+ const tr = this.createRow(record, rowIdx);
243
+ this.tbodyEl.appendChild(tr);
244
+ }
245
+ }
246
+ createRow(record, rowIndex) {
247
+ const tr = document.createElement("tr");
248
+ tr.classList.add("x-grid-row");
249
+ if (rowIndex % 2 === 1) tr.classList.add("x-grid-row-alt");
250
+ if (this.isSelected?.(record)) tr.classList.add("x-grid-row-selected");
251
+ tr.setAttribute("data-rowindex", String(rowIndex));
252
+ for (let colIdx = 0; colIdx < this.columns.length; colIdx++) {
253
+ const col = this.columns[colIdx];
254
+ const td = document.createElement("td");
255
+ td.classList.add("x-grid-cell");
256
+ if (col.hidden) td.style.display = "none";
257
+ if (col.align) td.style.textAlign = col.align;
258
+ if (col.config.tdCls) td.classList.add(col.config.tdCls);
259
+ const value = col.getCellValue(record);
260
+ const html = col.renderCellHtml(value, {}, record, rowIndex, colIdx);
261
+ td.innerHTML = html;
262
+ if (col instanceof ActionColumn) {
263
+ td.addEventListener("click", (e) => {
264
+ const target = e.target.closest(".x-action-col-icon");
265
+ if (!target) return;
266
+ const actionIdx = parseInt(target.getAttribute("data-action") ?? "0", 10);
267
+ const action = col.actions[actionIdx];
268
+ if (action?.handler) action.handler(record, rowIndex);
269
+ });
270
+ }
271
+ if (col.config.xtype === "checkcolumn") {
272
+ const input = td.querySelector('input[type="checkbox"]');
273
+ if (input) {
274
+ input.addEventListener("change", () => {
275
+ record.set?.(col.dataIndex, input.checked);
276
+ });
277
+ }
278
+ }
279
+ tr.appendChild(td);
280
+ }
281
+ tr.addEventListener("click", (e) => {
282
+ this.onItemClick?.(record, rowIndex, e);
283
+ });
284
+ tr.addEventListener("dblclick", (e) => {
285
+ this.onItemDblClick?.(record, rowIndex, e);
286
+ });
287
+ return tr;
288
+ }
289
+ setColumnHidden(index, hidden) {
290
+ this.headerContainer.setColumnHidden(index, hidden);
291
+ if (this.tbodyEl) {
292
+ for (const row of this.tbodyEl.rows) {
293
+ if (row.cells[index]) {
294
+ row.cells[index].style.display = hidden ? "none" : "";
295
+ }
296
+ }
297
+ }
298
+ }
299
+ getTable() {
300
+ return this.tableEl;
301
+ }
302
+ };
303
+
304
+ // src/grid/Grid.ts
305
+ var Grid = class extends Panel {
306
+ static $className = "Ext.grid.Grid";
307
+ constructor(config = {}) {
308
+ super({ xtype: "grid", ...config });
309
+ }
310
+ initialize() {
311
+ super.initialize();
312
+ const cfg = this._config;
313
+ this._store = cfg.store ?? null;
314
+ this._columns = (cfg.columns ?? []).map((c) => c instanceof Column ? c : createColumn(c));
315
+ this._gridView = null;
316
+ this._sortableColumns = cfg.sortableColumns ?? true;
317
+ this._sortDirection = "ASC";
318
+ this._sortColumnIndex = -1;
319
+ }
320
+ afterRender() {
321
+ super.afterRender();
322
+ const cfg = this._config;
323
+ this.el.classList.add("x-grid");
324
+ if (!this._store) return;
325
+ this._gridView = new GridView({
326
+ store: this._store,
327
+ columns: this._columns,
328
+ emptyText: cfg.emptyText
329
+ });
330
+ this._gridView.onItemClick = (record, rowIndex, event) => {
331
+ this.fire("itemclick", this, record, rowIndex, event);
332
+ };
333
+ this._gridView.onItemDblClick = (record, rowIndex, event) => {
334
+ this.fire("itemdblclick", this, record, rowIndex, event);
335
+ };
336
+ const body = this.getBodyEl();
337
+ const table = this._gridView.render();
338
+ body.appendChild(table);
339
+ if (this._sortableColumns) {
340
+ this.setupSortableHeaders();
341
+ }
342
+ if (this._store?.on) {
343
+ this._store.on("datachanged", () => this.onStoreDataChanged());
344
+ this._store.on("sort", () => this.onStoreDataChanged());
345
+ this._store.on("filter", () => this.onStoreDataChanged());
346
+ }
347
+ }
348
+ // -----------------------------------------------------------------------
349
+ // Sorting
350
+ // -----------------------------------------------------------------------
351
+ setupSortableHeaders() {
352
+ const ths = this._gridView.headerContainer.getThElements();
353
+ for (let i = 0; i < ths.length; i++) {
354
+ const col = this._columns[i];
355
+ if (!col.sortable) continue;
356
+ ths[i].style.cursor = "pointer";
357
+ ths[i].addEventListener("click", () => {
358
+ this.onHeaderClick(i);
359
+ });
360
+ }
361
+ }
362
+ onHeaderClick(colIndex) {
363
+ const col = this._columns[colIndex];
364
+ if (!col.sortable || !col.dataIndex) return;
365
+ if (this._sortColumnIndex === colIndex) {
366
+ this._sortDirection = this._sortDirection === "ASC" ? "DESC" : "ASC";
367
+ } else {
368
+ this._sortDirection = "ASC";
369
+ this._sortColumnIndex = colIndex;
370
+ }
371
+ this._gridView.headerContainer.setSortIndicator(colIndex, this._sortDirection);
372
+ if (this._store?.sort) {
373
+ this._store.sort(col.dataIndex, this._sortDirection);
374
+ }
375
+ }
376
+ // -----------------------------------------------------------------------
377
+ // Column visibility
378
+ // -----------------------------------------------------------------------
379
+ hideColumn(index) {
380
+ if (this._gridView) {
381
+ this._gridView.setColumnHidden(index, true);
382
+ }
383
+ }
384
+ showColumn(index) {
385
+ if (this._gridView) {
386
+ this._gridView.setColumnHidden(index, false);
387
+ }
388
+ }
389
+ // -----------------------------------------------------------------------
390
+ // Store data changed
391
+ // -----------------------------------------------------------------------
392
+ onStoreDataChanged() {
393
+ if (this._gridView) {
394
+ this._gridView.refresh();
395
+ }
396
+ }
397
+ // -----------------------------------------------------------------------
398
+ // Accessors
399
+ // -----------------------------------------------------------------------
400
+ getStore() {
401
+ return this._store;
402
+ }
403
+ getColumns() {
404
+ return [...this._columns];
405
+ }
406
+ getView() {
407
+ return this._gridView;
408
+ }
409
+ };
410
+
411
+ // src/grid/feature/Grouping.ts
412
+ var Grouping = class {
413
+ grid = null;
414
+ groupField;
415
+ startCollapsed;
416
+ collapsedGroups = /* @__PURE__ */ new Set();
417
+ groupHeaderTpl;
418
+ constructor(config) {
419
+ this.groupField = config.groupField;
420
+ this.startCollapsed = config.startCollapsed ?? false;
421
+ this.groupHeaderTpl = config.groupHeaderTpl ?? ((gv) => gv);
422
+ }
423
+ init(grid) {
424
+ this.grid = grid;
425
+ this.rebuildGroupedView();
426
+ }
427
+ collapse(groupValue) {
428
+ this.collapsedGroups.add(groupValue);
429
+ this.setGroupRowsVisible(groupValue, false);
430
+ this.grid?.fire?.("groupcollapse", this.grid, groupValue);
431
+ }
432
+ expand(groupValue) {
433
+ this.collapsedGroups.delete(groupValue);
434
+ this.setGroupRowsVisible(groupValue, true);
435
+ this.grid?.fire?.("groupexpand", this.grid, groupValue);
436
+ }
437
+ isCollapsed(groupValue) {
438
+ return this.collapsedGroups.has(groupValue);
439
+ }
440
+ rebuildGroupedView() {
441
+ const view = this.grid?.getView?.();
442
+ if (!view) return;
443
+ const table = view.getTable?.();
444
+ if (!table) return;
445
+ const tbody = table.querySelector("tbody");
446
+ if (!tbody) return;
447
+ const store = this.grid.getStore();
448
+ const records = store.getRange();
449
+ const columns = this.grid.getColumns();
450
+ const groups = this.groupRecords(records);
451
+ tbody.innerHTML = "";
452
+ for (const [groupValue, groupRecords] of groups) {
453
+ if (this.startCollapsed) this.collapsedGroups.add(groupValue);
454
+ const groupEl = document.createElement("tbody");
455
+ groupEl.classList.add("x-grid-group");
456
+ groupEl.setAttribute("data-group", groupValue);
457
+ const headerRow = document.createElement("tr");
458
+ headerRow.classList.add("x-grid-group-hd");
459
+ const headerTd = document.createElement("td");
460
+ headerTd.colSpan = columns.length;
461
+ headerTd.classList.add("x-grid-group-title");
462
+ headerTd.textContent = this.groupHeaderTpl(groupValue, groupRecords);
463
+ headerRow.appendChild(headerTd);
464
+ headerRow.style.cursor = "pointer";
465
+ headerRow.addEventListener("click", () => {
466
+ if (this.isCollapsed(groupValue)) this.expand(groupValue);
467
+ else this.collapse(groupValue);
468
+ });
469
+ groupEl.appendChild(headerRow);
470
+ for (let i = 0; i < groupRecords.length; i++) {
471
+ const rec = groupRecords[i];
472
+ const tr = document.createElement("tr");
473
+ tr.classList.add("x-grid-row");
474
+ if (this.collapsedGroups.has(groupValue)) tr.style.display = "none";
475
+ for (let ci = 0; ci < columns.length; ci++) {
476
+ const col = columns[ci];
477
+ const td = document.createElement("td");
478
+ td.classList.add("x-grid-cell");
479
+ const val = col.getCellValue(rec);
480
+ td.innerHTML = col.renderCellHtml(val, {}, rec, i, ci);
481
+ tr.appendChild(td);
482
+ }
483
+ groupEl.appendChild(tr);
484
+ }
485
+ table.appendChild(groupEl);
486
+ }
487
+ tbody.remove();
488
+ }
489
+ groupRecords(records) {
490
+ const groups = /* @__PURE__ */ new Map();
491
+ for (const rec of records) {
492
+ const val = String(rec.get(this.groupField));
493
+ if (!groups.has(val)) groups.set(val, []);
494
+ groups.get(val).push(rec);
495
+ }
496
+ return groups;
497
+ }
498
+ setGroupRowsVisible(groupValue, visible) {
499
+ const groupEl = this.grid?.el?.querySelector(`.x-grid-group[data-group="${groupValue}"]`);
500
+ if (!groupEl) return;
501
+ const rows = groupEl.querySelectorAll(".x-grid-row");
502
+ for (const row of rows) {
503
+ row.style.display = visible ? "" : "none";
504
+ }
505
+ }
506
+ };
507
+
508
+ // src/grid/feature/Summary.ts
509
+ var Summary = class {
510
+ grid = null;
511
+ summaryTypes;
512
+ constructor(config) {
513
+ this.summaryTypes = config.summaryTypes;
514
+ }
515
+ init(grid) {
516
+ this.grid = grid;
517
+ this.renderSummary();
518
+ }
519
+ renderSummary() {
520
+ const view = this.grid?.getView?.();
521
+ if (!view) return;
522
+ const table = view.getTable?.();
523
+ if (!table) return;
524
+ const store = this.grid.getStore();
525
+ const records = store.getRange();
526
+ const columns = this.grid.getColumns();
527
+ const tr = this.buildSummaryRow(records, columns);
528
+ const tbody = table.querySelector("tbody");
529
+ if (tbody) tbody.appendChild(tr);
530
+ }
531
+ buildSummaryRow(records, columns) {
532
+ const tr = document.createElement("tr");
533
+ tr.classList.add("x-grid-summary-row");
534
+ for (const col of columns) {
535
+ const td = document.createElement("td");
536
+ td.classList.add("x-grid-cell", "x-grid-summary-cell");
537
+ const type = this.summaryTypes[col.dataIndex];
538
+ if (type) {
539
+ td.textContent = String(this.calculate(records, col.dataIndex, type));
540
+ }
541
+ tr.appendChild(td);
542
+ }
543
+ return tr;
544
+ }
545
+ calculate(records, field, type) {
546
+ if (typeof type === "function") return type(records);
547
+ const values = records.map((r) => Number(r.get(field))).filter((n) => !isNaN(n));
548
+ switch (type) {
549
+ case "sum":
550
+ return values.reduce((a, b) => a + b, 0);
551
+ case "count":
552
+ return records.length;
553
+ case "average":
554
+ return values.length ? Math.round(values.reduce((a, b) => a + b, 0) / values.length) : 0;
555
+ case "min":
556
+ return values.length ? Math.min(...values) : 0;
557
+ case "max":
558
+ return values.length ? Math.max(...values) : 0;
559
+ default:
560
+ return "";
561
+ }
562
+ }
563
+ };
564
+
565
+ // src/grid/feature/GroupingSummary.ts
566
+ var GroupingSummary = class {
567
+ grid = null;
568
+ groupField;
569
+ summaryTypes;
570
+ constructor(config) {
571
+ this.groupField = config.groupField;
572
+ this.summaryTypes = config.summaryTypes;
573
+ }
574
+ init(grid) {
575
+ this.grid = grid;
576
+ this.rebuild();
577
+ }
578
+ rebuild() {
579
+ const view = this.grid?.getView?.();
580
+ if (!view) return;
581
+ const table = view.getTable?.();
582
+ if (!table) return;
583
+ const tbody = table.querySelector("tbody");
584
+ if (!tbody) return;
585
+ const store = this.grid.getStore();
586
+ const records = store.getRange();
587
+ const columns = this.grid.getColumns();
588
+ const groups = this.groupRecords(records);
589
+ tbody.innerHTML = "";
590
+ for (const [groupValue, groupRecords] of groups) {
591
+ const groupEl = document.createElement("tbody");
592
+ groupEl.classList.add("x-grid-group");
593
+ groupEl.setAttribute("data-group", groupValue);
594
+ const headerRow = document.createElement("tr");
595
+ headerRow.classList.add("x-grid-group-hd");
596
+ const headerTd = document.createElement("td");
597
+ headerTd.colSpan = columns.length;
598
+ headerTd.textContent = groupValue;
599
+ headerRow.appendChild(headerTd);
600
+ groupEl.appendChild(headerRow);
601
+ for (let i = 0; i < groupRecords.length; i++) {
602
+ const rec = groupRecords[i];
603
+ const tr = document.createElement("tr");
604
+ tr.classList.add("x-grid-row");
605
+ for (let ci = 0; ci < columns.length; ci++) {
606
+ const col = columns[ci];
607
+ const td = document.createElement("td");
608
+ td.classList.add("x-grid-cell");
609
+ td.innerHTML = col.renderCellHtml(col.getCellValue(rec), {}, rec, i, ci);
610
+ tr.appendChild(td);
611
+ }
612
+ groupEl.appendChild(tr);
613
+ }
614
+ const sumRow = document.createElement("tr");
615
+ sumRow.classList.add("x-grid-group-summary");
616
+ for (const col of columns) {
617
+ const td = document.createElement("td");
618
+ td.classList.add("x-grid-cell");
619
+ const type = this.summaryTypes[col.dataIndex];
620
+ if (type) td.textContent = String(this.calculate(groupRecords, col.dataIndex, type));
621
+ sumRow.appendChild(td);
622
+ }
623
+ groupEl.appendChild(sumRow);
624
+ table.appendChild(groupEl);
625
+ }
626
+ tbody.remove();
627
+ }
628
+ groupRecords(records) {
629
+ const groups = /* @__PURE__ */ new Map();
630
+ for (const rec of records) {
631
+ const val = String(rec.get(this.groupField));
632
+ if (!groups.has(val)) groups.set(val, []);
633
+ groups.get(val).push(rec);
634
+ }
635
+ return groups;
636
+ }
637
+ calculate(records, field, type) {
638
+ if (typeof type === "function") return type(records);
639
+ const values = records.map((r) => Number(r.get(field))).filter((n) => !isNaN(n));
640
+ switch (type) {
641
+ case "sum":
642
+ return values.reduce((a, b) => a + b, 0);
643
+ case "count":
644
+ return records.length;
645
+ case "average":
646
+ return values.length ? Math.round(values.reduce((a, b) => a + b, 0) / values.length) : 0;
647
+ case "min":
648
+ return values.length ? Math.min(...values) : 0;
649
+ case "max":
650
+ return values.length ? Math.max(...values) : 0;
651
+ default:
652
+ return "";
653
+ }
654
+ }
655
+ };
656
+
657
+ // src/grid/feature/RowBody.ts
658
+ var RowBody = class {
659
+ grid = null;
660
+ getAdditionalData;
661
+ constructor(config) {
662
+ this.getAdditionalData = config.getAdditionalData;
663
+ }
664
+ init(grid) {
665
+ this.grid = grid;
666
+ this.addRowBodies();
667
+ }
668
+ addRowBodies() {
669
+ const view = this.grid?.getView?.();
670
+ if (!view) return;
671
+ const table = view.getTable?.();
672
+ if (!table) return;
673
+ const tbody = table.querySelector("tbody");
674
+ if (!tbody) return;
675
+ const store = this.grid.getStore();
676
+ const records = store.getRange();
677
+ const columns = this.grid.getColumns();
678
+ const rows = Array.from(tbody.querySelectorAll("tr.x-grid-row"));
679
+ for (let i = 0; i < rows.length && i < records.length; i++) {
680
+ const rec = records[i];
681
+ const result = this.getAdditionalData(rec.data, i, rec);
682
+ const bodyRow = document.createElement("tr");
683
+ bodyRow.classList.add("x-grid-rowbody");
684
+ if (result.rowBodyCls) bodyRow.classList.add(result.rowBodyCls);
685
+ const td = document.createElement("td");
686
+ td.colSpan = columns.length;
687
+ td.innerHTML = result.rowBody;
688
+ bodyRow.appendChild(td);
689
+ rows[i].after(bodyRow);
690
+ }
691
+ }
692
+ };
693
+
694
+ // src/grid/plugin/CellEditing.ts
695
+ var CellEditing = class {
696
+ grid = null;
697
+ activeEditor = null;
698
+ activeInput = null;
699
+ activeRecord = null;
700
+ activeColumn = null;
701
+ activeCell = null;
702
+ originalValue = "";
703
+ init(grid) {
704
+ this.grid = grid;
705
+ this.attachCellListeners();
706
+ }
707
+ attachCellListeners() {
708
+ const view = this.grid?.getView?.();
709
+ if (!view) return;
710
+ const table = view.getTable?.();
711
+ if (!table) return;
712
+ table.addEventListener("dblclick", (e) => {
713
+ const td = e.target.closest("td.x-grid-cell");
714
+ if (!td) return;
715
+ const tr = td.closest("tr.x-grid-row");
716
+ if (!tr) return;
717
+ const rowIdx = parseInt(tr.getAttribute("data-rowindex") ?? "0", 10);
718
+ const colIdx = Array.from(tr.children).indexOf(td);
719
+ const columns = this.grid.getColumns();
720
+ const col = columns[colIdx];
721
+ if (!col?.config?.editor) return;
722
+ const store = this.grid.getStore();
723
+ const record = store.getAt(rowIdx);
724
+ if (!record) return;
725
+ this.startEdit(record, col, td);
726
+ });
727
+ }
728
+ startEdit(record, column, cell) {
729
+ if (this.activeEditor) this.cancelEdit();
730
+ this.grid?.fire?.("beforeedit", this.grid, { record, column });
731
+ this.activeRecord = record;
732
+ this.activeColumn = column;
733
+ this.activeCell = cell;
734
+ this.originalValue = String(record.get(column.dataIndex) ?? "");
735
+ const editor = document.createElement("div");
736
+ editor.classList.add("x-grid-cell-editor");
737
+ const input = document.createElement("input");
738
+ input.type = "text";
739
+ input.value = this.originalValue;
740
+ input.classList.add("x-grid-editor-input");
741
+ input.addEventListener("keydown", (e) => {
742
+ if (e.key === "Enter") {
743
+ e.preventDefault();
744
+ this.completeEdit();
745
+ } else if (e.key === "Escape") {
746
+ e.preventDefault();
747
+ this.cancelEdit();
748
+ }
749
+ });
750
+ editor.appendChild(input);
751
+ cell.innerHTML = "";
752
+ cell.appendChild(editor);
753
+ this.activeEditor = editor;
754
+ this.activeInput = input;
755
+ input.focus();
756
+ input.select();
757
+ }
758
+ completeEdit() {
759
+ if (!this.activeInput || !this.activeRecord || !this.activeColumn || !this.activeCell) return;
760
+ const newValue = this.activeInput.value;
761
+ this.activeRecord.set(this.activeColumn.dataIndex, newValue);
762
+ this.activeCell.innerHTML = newValue;
763
+ this.grid?.fire?.("edit", this.grid, {
764
+ record: this.activeRecord,
765
+ column: this.activeColumn,
766
+ value: newValue,
767
+ originalValue: this.originalValue
768
+ });
769
+ this.cleanup();
770
+ }
771
+ cancelEdit() {
772
+ if (!this.activeCell) return;
773
+ this.activeCell.innerHTML = this.originalValue;
774
+ this.grid?.fire?.("canceledit", this.grid);
775
+ this.cleanup();
776
+ }
777
+ cleanup() {
778
+ this.activeEditor = null;
779
+ this.activeInput = null;
780
+ this.activeRecord = null;
781
+ this.activeColumn = null;
782
+ this.activeCell = null;
783
+ this.originalValue = "";
784
+ }
785
+ };
786
+
787
+ // src/grid/plugin/RowEditing.ts
788
+ var RowEditing = class {
789
+ grid = null;
790
+ activeRowIndex = -1;
791
+ editorRow = null;
792
+ inputs = /* @__PURE__ */ new Map();
793
+ originalValues = /* @__PURE__ */ new Map();
794
+ init(grid) {
795
+ this.grid = grid;
796
+ }
797
+ startEdit(rowIndex) {
798
+ if (this.editorRow) this.cancelEdit();
799
+ const store = this.grid.getStore();
800
+ const record = store.getAt(rowIndex);
801
+ if (!record) return;
802
+ this.activeRowIndex = rowIndex;
803
+ const columns = this.grid.getColumns();
804
+ const table = this.grid.getView()?.getTable?.();
805
+ if (!table) return;
806
+ const rows = table.querySelectorAll("tbody tr.x-grid-row");
807
+ const targetRow = rows[rowIndex];
808
+ if (!targetRow) return;
809
+ this.editorRow = document.createElement("tr");
810
+ this.editorRow.classList.add("x-grid-row-editor");
811
+ this.inputs.clear();
812
+ this.originalValues.clear();
813
+ for (const col of columns) {
814
+ const td = document.createElement("td");
815
+ td.classList.add("x-grid-cell");
816
+ if (col.config?.editor) {
817
+ const input = document.createElement("input");
818
+ input.type = "text";
819
+ const val = String(record.get(col.dataIndex) ?? "");
820
+ input.value = val;
821
+ this.inputs.set(col.dataIndex, input);
822
+ this.originalValues.set(col.dataIndex, val);
823
+ td.appendChild(input);
824
+ } else {
825
+ td.textContent = String(col.getCellValue(record) ?? "");
826
+ }
827
+ this.editorRow.appendChild(td);
828
+ }
829
+ targetRow.style.display = "none";
830
+ targetRow.after(this.editorRow);
831
+ this.grid?.fire?.("beforeedit", this.grid, { record, rowIndex });
832
+ }
833
+ completeEdit() {
834
+ if (this.activeRowIndex < 0 || !this.grid) return;
835
+ const store = this.grid.getStore();
836
+ const record = store.getAt(this.activeRowIndex);
837
+ if (!record) return;
838
+ for (const [field, input] of this.inputs) {
839
+ record.set(field, input.value);
840
+ }
841
+ this.grid.fire?.("edit", this.grid, { record, rowIndex: this.activeRowIndex });
842
+ this.teardown();
843
+ this.grid.getView()?.refresh?.();
844
+ }
845
+ cancelEdit() {
846
+ this.grid?.fire?.("canceledit", this.grid);
847
+ this.teardown();
848
+ }
849
+ teardown() {
850
+ if (this.editorRow?.parentNode) {
851
+ const prev = this.editorRow.previousElementSibling;
852
+ if (prev) prev.style.display = "";
853
+ this.editorRow.remove();
854
+ }
855
+ this.editorRow = null;
856
+ this.inputs.clear();
857
+ this.originalValues.clear();
858
+ this.activeRowIndex = -1;
859
+ }
860
+ };
861
+
862
+ // src/grid/plugin/RowExpander.ts
863
+ var RowExpander = class {
864
+ grid = null;
865
+ rowBodyTpl;
866
+ singleExpand;
867
+ expandedRows = /* @__PURE__ */ new Set();
868
+ constructor(config) {
869
+ this.rowBodyTpl = config.rowBodyTpl;
870
+ this.singleExpand = config.singleExpand ?? false;
871
+ }
872
+ init(grid) {
873
+ this.grid = grid;
874
+ this.addExpanders();
875
+ }
876
+ addExpanders() {
877
+ const view = this.grid?.getView?.();
878
+ if (!view) return;
879
+ const table = view.getTable?.();
880
+ if (!table) return;
881
+ const store = this.grid.getStore();
882
+ const records = store.getRange();
883
+ const columns = this.grid.getColumns();
884
+ const rows = table.querySelectorAll("tbody tr.x-grid-row");
885
+ for (let i = 0; i < rows.length && i < records.length; i++) {
886
+ const row = rows[i];
887
+ const record = records[i];
888
+ const expanderTd = document.createElement("td");
889
+ expanderTd.classList.add("x-grid-cell");
890
+ const btn = document.createElement("span");
891
+ btn.classList.add("x-grid-row-expander");
892
+ btn.textContent = "+";
893
+ btn.style.cursor = "pointer";
894
+ expanderTd.appendChild(btn);
895
+ row.insertBefore(expanderTd, row.firstChild);
896
+ btn.addEventListener("click", () => {
897
+ this.toggle(i, record, row, columns.length + 1);
898
+ });
899
+ }
900
+ }
901
+ toggle(rowIndex, record, row, colSpan) {
902
+ if (this.expandedRows.has(rowIndex)) {
903
+ this.collapseRow(rowIndex, row);
904
+ } else {
905
+ if (this.singleExpand) this.collapseAll();
906
+ this.expandRow(rowIndex, record, row, colSpan);
907
+ }
908
+ }
909
+ expandRow(rowIndex, record, row, colSpan) {
910
+ this.expandedRows.add(rowIndex);
911
+ const bodyRow = document.createElement("tr");
912
+ bodyRow.classList.add("x-grid-rowexpand-body");
913
+ bodyRow.setAttribute("data-expand-row", String(rowIndex));
914
+ const td = document.createElement("td");
915
+ td.colSpan = colSpan;
916
+ td.innerHTML = this.rowBodyTpl(record);
917
+ bodyRow.appendChild(td);
918
+ row.after(bodyRow);
919
+ }
920
+ collapseRow(rowIndex, _row) {
921
+ this.expandedRows.delete(rowIndex);
922
+ const table = this.grid?.getView?.()?.getTable?.();
923
+ if (!table) return;
924
+ const bodyRow = table.querySelector(`tr[data-expand-row="${rowIndex}"]`);
925
+ bodyRow?.remove();
926
+ }
927
+ collapseAll() {
928
+ const table = this.grid?.getView?.()?.getTable?.();
929
+ if (!table) return;
930
+ for (const idx of this.expandedRows) {
931
+ const bodyRow = table.querySelector(`tr[data-expand-row="${idx}"]`);
932
+ bodyRow?.remove();
933
+ }
934
+ this.expandedRows.clear();
935
+ }
936
+ };
937
+
938
+ // src/grid/plugin/Clipboard.ts
939
+ var GridClipboard = class {
940
+ grid = null;
941
+ init(grid) {
942
+ this.grid = grid;
943
+ }
944
+ getGridText() {
945
+ if (!this.grid) return "";
946
+ const store = this.grid.getStore();
947
+ const records = store.getRange();
948
+ const columns = this.grid.getColumns();
949
+ const lines = [];
950
+ lines.push(columns.map((c) => c.text).join(" "));
951
+ for (const rec of records) {
952
+ const cells = columns.map((c) => {
953
+ const val = c.getCellValue(rec);
954
+ return val === null || val === void 0 ? "" : String(val);
955
+ });
956
+ lines.push(cells.join(" "));
957
+ }
958
+ return lines.join("\n");
959
+ }
960
+ async doCopy() {
961
+ const text = this.getGridText();
962
+ this.grid?.fire?.("beforecopy", this.grid, text);
963
+ try {
964
+ if (navigator?.clipboard?.writeText) {
965
+ await navigator.clipboard.writeText(text);
966
+ }
967
+ } catch {
968
+ }
969
+ this.grid?.fire?.("copy", this.grid, text);
970
+ }
971
+ };
972
+
973
+ // src/grid/plugin/DragDrop.ts
974
+ var GridDragDrop = class {
975
+ grid = null;
976
+ init(grid) {
977
+ this.grid = grid;
978
+ this.setupDrag();
979
+ }
980
+ setupDrag() {
981
+ const view = this.grid?.getView?.();
982
+ if (!view) return;
983
+ const table = view.getTable?.();
984
+ if (!table) return;
985
+ const rows = table.querySelectorAll("tbody tr.x-grid-row");
986
+ for (const row of rows) {
987
+ row.setAttribute("draggable", "true");
988
+ row.addEventListener("dragstart", (e) => {
989
+ const idx = row.getAttribute("data-rowindex");
990
+ e.dataTransfer?.setData("text/plain", idx ?? "");
991
+ this.grid?.fire?.("beforedrop", this.grid, parseInt(idx ?? "0", 10));
992
+ });
993
+ row.addEventListener("dragover", (e) => {
994
+ e.preventDefault();
995
+ });
996
+ row.addEventListener("drop", (e) => {
997
+ e.preventDefault();
998
+ const fromIdx = parseInt(e.dataTransfer?.getData("text/plain") ?? "0", 10);
999
+ const toIdx = parseInt(row.getAttribute("data-rowindex") ?? "0", 10);
1000
+ if (fromIdx !== toIdx) this.moveRow(fromIdx, toIdx);
1001
+ });
1002
+ }
1003
+ }
1004
+ moveRow(fromIndex, toIndex) {
1005
+ const store = this.grid?.getStore?.();
1006
+ if (!store) return;
1007
+ const records = store.data?.items;
1008
+ if (!records || fromIndex >= records.length) return;
1009
+ const [moved] = records.splice(fromIndex, 1);
1010
+ records.splice(toIndex, 0, moved);
1011
+ this.grid?.fire?.("drop", this.grid, moved, fromIndex, toIndex);
1012
+ this.grid?.getView?.()?.refresh?.();
1013
+ }
1014
+ };
1015
+
1016
+ // src/grid/selection/RowSelectionModel.ts
1017
+ var RowSelectionModel = class {
1018
+ grid = null;
1019
+ mode;
1020
+ checkboxSelect;
1021
+ selected = /* @__PURE__ */ new Set();
1022
+ listeners = {};
1023
+ constructor(config = {}) {
1024
+ this.mode = config.mode ?? "SINGLE";
1025
+ this.checkboxSelect = config.checkboxSelect ?? false;
1026
+ }
1027
+ on(event, fn) {
1028
+ (this.listeners[event] ??= []).push(fn);
1029
+ }
1030
+ fire(event, ...args) {
1031
+ (this.listeners[event] ?? []).forEach((fn) => fn(...args));
1032
+ }
1033
+ init(grid) {
1034
+ this.grid = grid;
1035
+ this.attachRowListeners();
1036
+ const view = grid.getView();
1037
+ if (view) view.isSelected = (record) => this.selected.has(record);
1038
+ if (this.checkboxSelect) this.addCheckboxColumn();
1039
+ }
1040
+ // -----------------------------------------------------------------------
1041
+ // Selection API
1042
+ // -----------------------------------------------------------------------
1043
+ select(record, keepExisting = false) {
1044
+ if (!record) return;
1045
+ this.fire("beforeselect", this, record);
1046
+ if (this.mode === "SINGLE") {
1047
+ for (const r of this.selected) {
1048
+ if (r !== record) this.doDeselect(r);
1049
+ }
1050
+ if (this.selected.has(record)) return;
1051
+ this.doSelect(record);
1052
+ } else if (this.mode === "SIMPLE") {
1053
+ if (this.selected.has(record)) {
1054
+ this.doDeselect(record);
1055
+ } else {
1056
+ this.doSelect(record);
1057
+ }
1058
+ } else {
1059
+ if (keepExisting) {
1060
+ if (this.selected.has(record)) {
1061
+ this.doDeselect(record);
1062
+ return;
1063
+ }
1064
+ this.doSelect(record);
1065
+ } else {
1066
+ for (const r of this.selected) {
1067
+ if (r !== record) this.doDeselect(r);
1068
+ }
1069
+ this.doSelect(record);
1070
+ }
1071
+ }
1072
+ }
1073
+ deselect(record) {
1074
+ if (this.selected.has(record)) {
1075
+ this.doDeselect(record);
1076
+ }
1077
+ }
1078
+ selectRange(start, end) {
1079
+ const store = this.grid?.getStore();
1080
+ if (!store) return;
1081
+ const records = store.getRange();
1082
+ const startIdx = records.indexOf(start);
1083
+ const endIdx = records.indexOf(end);
1084
+ if (startIdx < 0 || endIdx < 0) return;
1085
+ const low = Math.min(startIdx, endIdx);
1086
+ const high = Math.max(startIdx, endIdx);
1087
+ for (let i = low; i <= high; i++) {
1088
+ this.doSelect(records[i]);
1089
+ }
1090
+ }
1091
+ selectAll() {
1092
+ const store = this.grid?.getStore();
1093
+ if (!store) return;
1094
+ for (const rec of store.getRange()) {
1095
+ this.doSelect(rec);
1096
+ }
1097
+ }
1098
+ deselectAll() {
1099
+ for (const rec of [...this.selected]) {
1100
+ this.doDeselect(rec);
1101
+ }
1102
+ }
1103
+ getSelection() {
1104
+ return [...this.selected];
1105
+ }
1106
+ isSelected(record) {
1107
+ return this.selected.has(record);
1108
+ }
1109
+ getCount() {
1110
+ return this.selected.size;
1111
+ }
1112
+ // -----------------------------------------------------------------------
1113
+ // Internal
1114
+ // -----------------------------------------------------------------------
1115
+ doSelect(record) {
1116
+ if (this.selected.has(record)) return;
1117
+ this.selected.add(record);
1118
+ this.updateRowClass(record, true);
1119
+ this.fire("select", this, record);
1120
+ this.fire("selectionchange", this, this.getSelection());
1121
+ }
1122
+ doDeselect(record) {
1123
+ this.selected.delete(record);
1124
+ this.updateRowClass(record, false);
1125
+ this.fire("deselect", this, record);
1126
+ this.fire("selectionchange", this, this.getSelection());
1127
+ }
1128
+ updateRowClass(record, selected) {
1129
+ const store = this.grid?.getStore();
1130
+ if (!store) return;
1131
+ const records = store.getRange();
1132
+ const idx = records.indexOf(record);
1133
+ if (idx < 0) return;
1134
+ const view = this.grid.getView();
1135
+ const table = view?.getTable();
1136
+ if (!table) return;
1137
+ const row = table.querySelector(`tbody tr[data-rowindex="${idx}"]`);
1138
+ if (row) {
1139
+ row.classList.toggle("x-grid-row-selected", selected);
1140
+ }
1141
+ }
1142
+ // -----------------------------------------------------------------------
1143
+ // Row click listener
1144
+ // -----------------------------------------------------------------------
1145
+ attachRowListeners() {
1146
+ const view = this.grid?.getView();
1147
+ const table = view?.getTable();
1148
+ if (!table) return;
1149
+ table.addEventListener("click", (e) => {
1150
+ const tr = e.target.closest("tr.x-grid-row");
1151
+ if (!tr) return;
1152
+ if (e.target.closest(".x-grid-row-checker")) return;
1153
+ const rowIdx = parseInt(tr.getAttribute("data-rowindex") ?? "-1", 10);
1154
+ const store = this.grid.getStore();
1155
+ if (!store) return;
1156
+ const record = store.getAt(rowIdx);
1157
+ if (!record) return;
1158
+ if (this.mode === "MULTI" && e.ctrlKey) {
1159
+ this.select(record, true);
1160
+ } else if (this.mode === "MULTI" && e.shiftKey) {
1161
+ const sel = this.getSelection();
1162
+ const anchor = sel.length > 0 ? sel[sel.length - 1] : record;
1163
+ this.deselectAll();
1164
+ this.selectRange(anchor, record);
1165
+ } else if (this.mode === "SIMPLE") {
1166
+ this.select(record);
1167
+ } else {
1168
+ this.select(record);
1169
+ }
1170
+ });
1171
+ }
1172
+ // -----------------------------------------------------------------------
1173
+ // Checkbox column
1174
+ // -----------------------------------------------------------------------
1175
+ addCheckboxColumn() {
1176
+ const view = this.grid?.getView();
1177
+ const table = view?.getTable();
1178
+ if (!table) return;
1179
+ const store = this.grid.getStore();
1180
+ if (!store) return;
1181
+ const records = store.getRange();
1182
+ const thead = table.querySelector("thead tr");
1183
+ if (thead) {
1184
+ const th = document.createElement("th");
1185
+ th.classList.add("x-column-header");
1186
+ const headerCb = document.createElement("input");
1187
+ headerCb.type = "checkbox";
1188
+ headerCb.classList.add("x-grid-header-checker");
1189
+ headerCb.addEventListener("click", () => {
1190
+ if (headerCb.checked) this.selectAll();
1191
+ else this.deselectAll();
1192
+ });
1193
+ th.appendChild(headerCb);
1194
+ thead.insertBefore(th, thead.firstChild);
1195
+ }
1196
+ const rows = table.querySelectorAll("tbody tr.x-grid-row");
1197
+ for (let i = 0; i < rows.length && i < records.length; i++) {
1198
+ const row = rows[i];
1199
+ const record = records[i];
1200
+ const td = document.createElement("td");
1201
+ td.classList.add("x-grid-cell");
1202
+ const cb = document.createElement("input");
1203
+ cb.type = "checkbox";
1204
+ cb.classList.add("x-grid-row-checker");
1205
+ cb.addEventListener("click", (e) => {
1206
+ e.stopPropagation();
1207
+ if (cb.checked) {
1208
+ this.doSelect(record);
1209
+ } else {
1210
+ this.doDeselect(record);
1211
+ }
1212
+ });
1213
+ td.appendChild(cb);
1214
+ row.insertBefore(td, row.firstChild);
1215
+ }
1216
+ }
1217
+ };
1218
+
1219
+ // src/grid/selection/CellSelectionModel.ts
1220
+ var CellSelectionModel = class {
1221
+ grid = null;
1222
+ position = null;
1223
+ listeners = {};
1224
+ maxRow = 0;
1225
+ maxCol = 0;
1226
+ on(event, fn) {
1227
+ (this.listeners[event] ??= []).push(fn);
1228
+ }
1229
+ fire(event, ...args) {
1230
+ (this.listeners[event] ?? []).forEach((fn) => fn(...args));
1231
+ }
1232
+ init(grid) {
1233
+ this.grid = grid;
1234
+ const store = grid.getStore();
1235
+ this.maxRow = (store?.getCount() ?? 1) - 1;
1236
+ this.maxCol = grid.getColumns().length - 1;
1237
+ this.attachListeners();
1238
+ }
1239
+ getCurrentPosition() {
1240
+ return this.position ? { ...this.position } : null;
1241
+ }
1242
+ // -----------------------------------------------------------------------
1243
+ // Internal
1244
+ // -----------------------------------------------------------------------
1245
+ selectCell(row, col) {
1246
+ this.clearSelection();
1247
+ row = Math.max(0, Math.min(row, this.maxRow));
1248
+ col = Math.max(0, Math.min(col, this.maxCol));
1249
+ this.position = { row, column: col };
1250
+ const cell = this.getCellElement(row, col);
1251
+ if (cell) cell.classList.add("x-grid-cell-selected");
1252
+ this.fire("selectionchange", this, this.position);
1253
+ }
1254
+ clearSelection() {
1255
+ if (!this.grid) return;
1256
+ const table = this.grid.getView()?.getTable();
1257
+ if (!table) return;
1258
+ const selected = table.querySelectorAll(".x-grid-cell-selected");
1259
+ for (const el of selected) el.classList.remove("x-grid-cell-selected");
1260
+ }
1261
+ getCellElement(row, col) {
1262
+ const table = this.grid?.getView()?.getTable();
1263
+ if (!table) return null;
1264
+ const tr = table.querySelector(`tbody tr[data-rowindex="${row}"]`);
1265
+ if (!tr) return null;
1266
+ return tr.children[col] ?? null;
1267
+ }
1268
+ attachListeners() {
1269
+ const view = this.grid?.getView();
1270
+ const table = view?.getTable();
1271
+ if (!table) return;
1272
+ table.addEventListener("click", (e) => {
1273
+ const td = e.target.closest("td.x-grid-cell");
1274
+ if (!td) return;
1275
+ const tr = td.closest("tr.x-grid-row");
1276
+ if (!tr) return;
1277
+ const rowIdx = parseInt(tr.getAttribute("data-rowindex") ?? "0", 10);
1278
+ const colIdx = Array.from(tr.children).indexOf(td);
1279
+ this.selectCell(rowIdx, colIdx);
1280
+ });
1281
+ if (this.grid.el) {
1282
+ this.grid.el.setAttribute("tabindex", "0");
1283
+ this.grid.el.addEventListener("keydown", (e) => {
1284
+ if (!this.position) return;
1285
+ let { row, column } = this.position;
1286
+ switch (e.key) {
1287
+ case "ArrowRight":
1288
+ column++;
1289
+ break;
1290
+ case "ArrowLeft":
1291
+ column--;
1292
+ break;
1293
+ case "ArrowDown":
1294
+ row++;
1295
+ break;
1296
+ case "ArrowUp":
1297
+ row--;
1298
+ break;
1299
+ case "Tab":
1300
+ e.preventDefault();
1301
+ if (e.shiftKey) column--;
1302
+ else column++;
1303
+ break;
1304
+ default:
1305
+ return;
1306
+ }
1307
+ this.selectCell(row, column);
1308
+ });
1309
+ }
1310
+ }
1311
+ };
1312
+
1313
+ // src/grid/selection/SpreadsheetSelectionModel.ts
1314
+ var SpreadsheetSelectionModel = class {
1315
+ grid = null;
1316
+ range = null;
1317
+ listeners = {};
1318
+ on(event, fn) {
1319
+ (this.listeners[event] ??= []).push(fn);
1320
+ }
1321
+ fire(event, ...args) {
1322
+ (this.listeners[event] ?? []).forEach((fn) => fn(...args));
1323
+ }
1324
+ init(grid) {
1325
+ this.grid = grid;
1326
+ this.attachListeners();
1327
+ }
1328
+ getSelectedRange() {
1329
+ return this.range ? { ...this.range } : null;
1330
+ }
1331
+ // -----------------------------------------------------------------------
1332
+ // Internal
1333
+ // -----------------------------------------------------------------------
1334
+ attachListeners() {
1335
+ const view = this.grid?.getView?.();
1336
+ const table = view?.getTable?.();
1337
+ if (!table) return;
1338
+ table.addEventListener("click", (e) => {
1339
+ const td = e.target.closest("td.x-grid-cell");
1340
+ if (!td) return;
1341
+ const tr = td.closest("tr.x-grid-row");
1342
+ if (!tr) return;
1343
+ const rowIdx = parseInt(tr.getAttribute("data-rowindex") ?? "0", 10);
1344
+ const colIdx = Array.from(tr.children).indexOf(td);
1345
+ if (e.shiftKey && this.range) {
1346
+ this.range = {
1347
+ startRow: this.range.startRow,
1348
+ startCol: this.range.startCol,
1349
+ endRow: rowIdx,
1350
+ endCol: colIdx
1351
+ };
1352
+ } else {
1353
+ this.range = {
1354
+ startRow: rowIdx,
1355
+ startCol: colIdx,
1356
+ endRow: rowIdx,
1357
+ endCol: colIdx
1358
+ };
1359
+ }
1360
+ this.highlightRange();
1361
+ this.fire("selectionchange", this, this.range);
1362
+ });
1363
+ }
1364
+ highlightRange() {
1365
+ const table = this.grid?.getView()?.getTable?.();
1366
+ if (!table || !this.range) return;
1367
+ const prev = table.querySelectorAll(".x-grid-cell-selected");
1368
+ for (const el of prev) el.classList.remove("x-grid-cell-selected");
1369
+ const { startRow, startCol, endRow, endCol } = this.range;
1370
+ const minRow = Math.min(startRow, endRow);
1371
+ const maxRow = Math.max(startRow, endRow);
1372
+ const minCol = Math.min(startCol, endCol);
1373
+ const maxCol = Math.max(startCol, endCol);
1374
+ const rows = table.querySelectorAll("tbody tr.x-grid-row");
1375
+ for (let r = minRow; r <= maxRow && r < rows.length; r++) {
1376
+ const row = rows[r];
1377
+ for (let c = minCol; c <= maxCol && c < row.children.length; c++) {
1378
+ row.children[c].classList.add("x-grid-cell-selected");
1379
+ }
1380
+ }
1381
+ }
1382
+ };
1383
+
1384
+ // src/grid/Lockable.ts
1385
+ var Lockable = class {
1386
+ grid = null;
1387
+ init(grid) {
1388
+ this.grid = grid;
1389
+ this.rebuild();
1390
+ }
1391
+ rebuild() {
1392
+ const grid = this.grid;
1393
+ const view = grid?.getView?.();
1394
+ const table = view?.getTable?.();
1395
+ if (!table || !grid.el) return;
1396
+ const store = grid.getStore();
1397
+ const records = store.getRange();
1398
+ const columns = grid.getColumns();
1399
+ const lockedCols = columns.filter((c) => c.config?.locked);
1400
+ const normalCols = columns.filter((c) => !c.config?.locked);
1401
+ if (lockedCols.length === 0) return;
1402
+ const body = grid.getBodyEl();
1403
+ table.remove();
1404
+ const container = document.createElement("div");
1405
+ container.classList.add("x-grid-lock-container");
1406
+ container.style.display = "flex";
1407
+ container.style.width = "100%";
1408
+ container.style.overflow = "hidden";
1409
+ const lockedPanel = document.createElement("div");
1410
+ lockedPanel.classList.add("x-grid-locked");
1411
+ lockedPanel.style.overflowX = "hidden";
1412
+ lockedPanel.style.overflowY = "auto";
1413
+ lockedPanel.style.flexShrink = "0";
1414
+ lockedPanel.appendChild(this.buildTable(lockedCols, records));
1415
+ const normalPanel = document.createElement("div");
1416
+ normalPanel.classList.add("x-grid-normal");
1417
+ normalPanel.style.overflowX = "auto";
1418
+ normalPanel.style.overflowY = "auto";
1419
+ normalPanel.style.flexGrow = "1";
1420
+ normalPanel.appendChild(this.buildTable(normalCols, records));
1421
+ lockedPanel.addEventListener("scroll", () => {
1422
+ normalPanel.scrollTop = lockedPanel.scrollTop;
1423
+ });
1424
+ normalPanel.addEventListener("scroll", () => {
1425
+ lockedPanel.scrollTop = normalPanel.scrollTop;
1426
+ });
1427
+ container.appendChild(lockedPanel);
1428
+ container.appendChild(normalPanel);
1429
+ body.appendChild(container);
1430
+ }
1431
+ buildTable(columns, records) {
1432
+ const table = document.createElement("table");
1433
+ table.classList.add("x-grid-table");
1434
+ table.style.width = "100%";
1435
+ table.style.borderCollapse = "collapse";
1436
+ const thead = document.createElement("thead");
1437
+ const headerRow = document.createElement("tr");
1438
+ for (const col of columns) {
1439
+ const th = document.createElement("th");
1440
+ th.classList.add("x-column-header");
1441
+ th.textContent = col.text;
1442
+ if (col.width > 0) th.style.width = `${col.width}px`;
1443
+ headerRow.appendChild(th);
1444
+ }
1445
+ thead.appendChild(headerRow);
1446
+ table.appendChild(thead);
1447
+ const tbody = document.createElement("tbody");
1448
+ for (let ri = 0; ri < records.length; ri++) {
1449
+ const rec = records[ri];
1450
+ const tr = document.createElement("tr");
1451
+ tr.classList.add("x-grid-row");
1452
+ tr.setAttribute("data-rowindex", String(ri));
1453
+ if (ri % 2 === 1) tr.classList.add("x-grid-row-alt");
1454
+ for (let ci = 0; ci < columns.length; ci++) {
1455
+ const col = columns[ci];
1456
+ const td = document.createElement("td");
1457
+ td.classList.add("x-grid-cell");
1458
+ const val = col.getCellValue(rec);
1459
+ td.innerHTML = col.renderCellHtml(val, {}, rec, ri, ci);
1460
+ tr.appendChild(td);
1461
+ }
1462
+ tbody.appendChild(tr);
1463
+ }
1464
+ table.appendChild(tbody);
1465
+ return table;
1466
+ }
1467
+ };
1468
+
1469
+ // src/grid/state/GridState.ts
1470
+ var GridState = class {
1471
+ grid = null;
1472
+ stateId;
1473
+ constructor(config) {
1474
+ this.stateId = config.stateId;
1475
+ }
1476
+ init(grid) {
1477
+ this.grid = grid;
1478
+ }
1479
+ get storageKey() {
1480
+ return `ext-grid-state-${this.stateId}`;
1481
+ }
1482
+ save() {
1483
+ if (!this.grid) return;
1484
+ const columns = this.grid.getColumns();
1485
+ const view = this.grid.getView?.();
1486
+ const ths = view?.headerContainer?.getThElements?.() ?? [];
1487
+ const colState = columns.map((col, i) => ({
1488
+ dataIndex: col.dataIndex,
1489
+ width: col.width,
1490
+ hidden: ths[i] ? ths[i].style.display === "none" : col.hidden
1491
+ }));
1492
+ const state = { columns: colState };
1493
+ const sortIdx = this.grid._sortColumnIndex;
1494
+ const sortDir = this.grid._sortDirection;
1495
+ if (sortIdx >= 0 && columns[sortIdx]) {
1496
+ state.sort = {
1497
+ field: columns[sortIdx].dataIndex,
1498
+ direction: sortDir
1499
+ };
1500
+ }
1501
+ localStorage.setItem(this.storageKey, JSON.stringify(state));
1502
+ }
1503
+ restore() {
1504
+ if (!this.grid) return;
1505
+ const raw = localStorage.getItem(this.storageKey);
1506
+ if (!raw) return;
1507
+ let state;
1508
+ try {
1509
+ state = JSON.parse(raw);
1510
+ } catch {
1511
+ return;
1512
+ }
1513
+ const columns = this.grid.getColumns();
1514
+ if (state.columns) {
1515
+ for (const saved of state.columns) {
1516
+ const idx = columns.findIndex((c) => c.dataIndex === saved.dataIndex);
1517
+ if (idx < 0) continue;
1518
+ if (saved.hidden) {
1519
+ this.grid.hideColumn(idx);
1520
+ } else {
1521
+ this.grid.showColumn(idx);
1522
+ }
1523
+ }
1524
+ }
1525
+ }
1526
+ clear() {
1527
+ localStorage.removeItem(this.storageKey);
1528
+ }
1529
+ };
1530
+
1531
+ // src/tree/TreePanel.ts
1532
+ import { Panel as Panel2 } from "@framesquared/ui";
1533
+ var TreePanel = class extends Panel2 {
1534
+ static $className = "Ext.tree.Panel";
1535
+ constructor(config = {}) {
1536
+ super({ xtype: "treepanel", ...config });
1537
+ }
1538
+ initialize() {
1539
+ super.initialize();
1540
+ const cfg = this._config;
1541
+ this._treeStore = cfg.store;
1542
+ this._rootVisible = cfg.rootVisible ?? true;
1543
+ this._singleExpand = cfg.singleExpand ?? false;
1544
+ this._checkable = cfg.checkable ?? false;
1545
+ this._cascadeCheck = cfg.cascadeCheck ?? false;
1546
+ this._treeBody = null;
1547
+ this._checkedSet = /* @__PURE__ */ new Set();
1548
+ }
1549
+ afterRender() {
1550
+ super.afterRender();
1551
+ const cfg = this._config;
1552
+ this.el.classList.add("x-treepanel");
1553
+ if (cfg.useArrows) this.el.classList.add("x-tree-arrows");
1554
+ if (cfg.lines) this.el.classList.add("x-tree-lines");
1555
+ this._treeBody = document.createElement("div");
1556
+ this._treeBody.classList.add("x-tree-body");
1557
+ this.getBodyEl().appendChild(this._treeBody);
1558
+ this.renderTree();
1559
+ }
1560
+ // -----------------------------------------------------------------------
1561
+ // Rendering
1562
+ // -----------------------------------------------------------------------
1563
+ refresh() {
1564
+ this.renderTree();
1565
+ }
1566
+ renderTree() {
1567
+ if (!this._treeBody || !this._treeStore) return;
1568
+ this._treeBody.innerHTML = "";
1569
+ const nodes = this._treeStore.getVisibleNodes(this._rootVisible);
1570
+ for (const node of nodes) {
1571
+ this._treeBody.appendChild(this.createNodeElement(node));
1572
+ }
1573
+ }
1574
+ createNodeElement(node) {
1575
+ const row = document.createElement("div");
1576
+ row.classList.add("x-tree-node");
1577
+ row.setAttribute("data-node-id", node.id);
1578
+ const indent = document.createElement("span");
1579
+ indent.classList.add("x-tree-indent");
1580
+ const indentLevel = this._rootVisible ? node.depth : Math.max(0, node.depth - 1);
1581
+ indent.style.paddingLeft = `${indentLevel * 20}px`;
1582
+ row.appendChild(indent);
1583
+ if (!node.leaf) {
1584
+ const expander = document.createElement("span");
1585
+ expander.classList.add("x-tree-expander");
1586
+ expander.textContent = node.expanded ? "\u25BE" : "\u25B8";
1587
+ expander.style.cursor = "pointer";
1588
+ expander.addEventListener("click", (e) => {
1589
+ e.stopPropagation();
1590
+ this.toggleNode(node);
1591
+ });
1592
+ row.appendChild(expander);
1593
+ } else {
1594
+ const spacer = document.createElement("span");
1595
+ spacer.classList.add("x-tree-expander-spacer");
1596
+ spacer.textContent = " ";
1597
+ row.appendChild(spacer);
1598
+ }
1599
+ if (this._checkable) {
1600
+ const cb = document.createElement("input");
1601
+ cb.type = "checkbox";
1602
+ cb.classList.add("x-tree-checkbox");
1603
+ cb.checked = node.checked;
1604
+ cb.addEventListener("click", (e) => {
1605
+ e.stopPropagation();
1606
+ const checked = cb.checked;
1607
+ node.checked = checked;
1608
+ if (this._cascadeCheck) {
1609
+ this._treeStore.cascadeCheck(node, checked);
1610
+ }
1611
+ this.fire("checkchange", this, node, checked);
1612
+ this.renderTree();
1613
+ });
1614
+ row.appendChild(cb);
1615
+ }
1616
+ const icon = document.createElement("span");
1617
+ icon.classList.add("x-tree-icon", node.leaf ? "x-tree-icon-leaf" : "x-tree-icon-folder");
1618
+ row.appendChild(icon);
1619
+ const text = document.createElement("span");
1620
+ text.classList.add("x-tree-node-text");
1621
+ text.textContent = node.text;
1622
+ row.appendChild(text);
1623
+ return row;
1624
+ }
1625
+ // -----------------------------------------------------------------------
1626
+ // Expand / Collapse
1627
+ // -----------------------------------------------------------------------
1628
+ toggleNode(node) {
1629
+ if (node.expanded) {
1630
+ this.collapseNode(node);
1631
+ } else {
1632
+ this.expandNode(node);
1633
+ }
1634
+ }
1635
+ expandNode(node) {
1636
+ this.fire("beforeitemexpand", this, node);
1637
+ if (this._singleExpand && node.parent) {
1638
+ for (const sibling of node.parent.children) {
1639
+ if (sibling !== node && sibling.expanded) {
1640
+ this._treeStore.collapseAll(sibling);
1641
+ }
1642
+ }
1643
+ }
1644
+ node.expanded = true;
1645
+ this.fire("itemexpand", this, node);
1646
+ this.renderTree();
1647
+ }
1648
+ collapseNode(node) {
1649
+ this.fire("beforeitemcollapse", this, node);
1650
+ node.expanded = false;
1651
+ this.fire("itemcollapse", this, node);
1652
+ this.renderTree();
1653
+ }
1654
+ expandAll() {
1655
+ this._treeStore.expandAll();
1656
+ this.renderTree();
1657
+ }
1658
+ collapseAll() {
1659
+ this._treeStore.collapseAll();
1660
+ this.renderTree();
1661
+ }
1662
+ async expandPath(path) {
1663
+ const node = this._treeStore.findByPath(path);
1664
+ if (!node) return;
1665
+ this._treeStore.expandToNode(node);
1666
+ this.renderTree();
1667
+ }
1668
+ // -----------------------------------------------------------------------
1669
+ // Checkbox
1670
+ // -----------------------------------------------------------------------
1671
+ getChecked() {
1672
+ return this._treeStore.getChecked();
1673
+ }
1674
+ // -----------------------------------------------------------------------
1675
+ // Accessors
1676
+ // -----------------------------------------------------------------------
1677
+ getStore() {
1678
+ return this._treeStore;
1679
+ }
1680
+ };
1681
+
1682
+ // src/tree/TreeStore.ts
1683
+ var nodeCounter = 0;
1684
+ function buildNode(cfg, parent, depth) {
1685
+ const node = {
1686
+ id: cfg.id ?? `tree-node-${nodeCounter++}`,
1687
+ text: cfg.text,
1688
+ leaf: cfg.leaf ?? (!cfg.children || cfg.children.length === 0),
1689
+ expanded: cfg.expanded ?? false,
1690
+ checked: cfg.checked ?? false,
1691
+ children: [],
1692
+ parent,
1693
+ depth,
1694
+ data: { ...cfg }
1695
+ };
1696
+ if (cfg.children) {
1697
+ node.children = cfg.children.map((c) => buildNode(c, node, depth + 1));
1698
+ }
1699
+ return node;
1700
+ }
1701
+ var TreeStore = class {
1702
+ root;
1703
+ nodeMap = /* @__PURE__ */ new Map();
1704
+ constructor(config) {
1705
+ nodeCounter = 0;
1706
+ this.root = buildNode(config.root, null, 0);
1707
+ this.buildMap(this.root);
1708
+ }
1709
+ buildMap(node) {
1710
+ this.nodeMap.set(node.id, node);
1711
+ for (const child of node.children) this.buildMap(child);
1712
+ }
1713
+ getRoot() {
1714
+ return this.root;
1715
+ }
1716
+ getNodeById(id) {
1717
+ return this.nodeMap.get(id) ?? null;
1718
+ }
1719
+ /**
1720
+ * Returns visible nodes in display order (respecting expanded state).
1721
+ * If rootVisible is true, the root is included.
1722
+ */
1723
+ getVisibleNodes(rootVisible = true) {
1724
+ const result = [];
1725
+ const walk = (node, include) => {
1726
+ if (include) result.push(node);
1727
+ if (node.expanded || node === this.root && !include) {
1728
+ }
1729
+ if (node.expanded) {
1730
+ for (const child of node.children) walk(child, true);
1731
+ }
1732
+ };
1733
+ if (rootVisible) {
1734
+ walk(this.root, true);
1735
+ } else {
1736
+ for (const child of this.root.children) walk(child, true);
1737
+ }
1738
+ return result;
1739
+ }
1740
+ appendChild(parent, childCfg) {
1741
+ const child = buildNode(childCfg, parent, parent.depth + 1);
1742
+ parent.children.push(child);
1743
+ parent.leaf = false;
1744
+ this.buildMap(child);
1745
+ return child;
1746
+ }
1747
+ removeNode(id) {
1748
+ const node = this.nodeMap.get(id);
1749
+ if (!node || !node.parent) return false;
1750
+ const parent = node.parent;
1751
+ const idx = parent.children.indexOf(node);
1752
+ if (idx >= 0) parent.children.splice(idx, 1);
1753
+ this.removeFromMap(node);
1754
+ return true;
1755
+ }
1756
+ removeFromMap(node) {
1757
+ this.nodeMap.delete(node.id);
1758
+ for (const child of node.children) this.removeFromMap(child);
1759
+ }
1760
+ getPath(node) {
1761
+ const parts = [];
1762
+ let current = node;
1763
+ while (current) {
1764
+ parts.unshift(current.text);
1765
+ current = current.parent;
1766
+ }
1767
+ return "/" + parts.join("/");
1768
+ }
1769
+ findByPath(path) {
1770
+ const parts = path.split("/").filter(Boolean);
1771
+ let current = this.root;
1772
+ if (parts[0] !== current.text) return null;
1773
+ for (let i = 1; i < parts.length; i++) {
1774
+ const child = current.children.find((c) => c.text === parts[i]);
1775
+ if (!child) return null;
1776
+ current = child;
1777
+ }
1778
+ return current;
1779
+ }
1780
+ /** Expand all ancestors of a node so it becomes visible. */
1781
+ expandToNode(node) {
1782
+ let current = node.parent;
1783
+ while (current) {
1784
+ current.expanded = true;
1785
+ current = current.parent;
1786
+ }
1787
+ }
1788
+ /** Set expanded=true on all non-leaf nodes. */
1789
+ expandAll(node) {
1790
+ const target = node ?? this.root;
1791
+ if (!target.leaf) target.expanded = true;
1792
+ for (const child of target.children) this.expandAll(child);
1793
+ }
1794
+ /** Set expanded=false on all nodes. */
1795
+ collapseAll(node) {
1796
+ const target = node ?? this.root;
1797
+ target.expanded = false;
1798
+ for (const child of target.children) this.collapseAll(child);
1799
+ }
1800
+ /** Cascade checked state to all descendants. */
1801
+ cascadeCheck(node, checked) {
1802
+ node.checked = checked;
1803
+ for (const child of node.children) this.cascadeCheck(child, checked);
1804
+ }
1805
+ /** Get all checked nodes. */
1806
+ getChecked(node) {
1807
+ const result = [];
1808
+ const walk = (n) => {
1809
+ if (n.checked) result.push(n);
1810
+ for (const child of n.children) walk(child);
1811
+ };
1812
+ walk(node ?? this.root);
1813
+ return result;
1814
+ }
1815
+ // Store-like interface for Grid compatibility
1816
+ getRange() {
1817
+ return this.getVisibleNodes(true);
1818
+ }
1819
+ getCount() {
1820
+ return this.getRange().length;
1821
+ }
1822
+ getAt(i) {
1823
+ return this.getRange()[i];
1824
+ }
1825
+ get(field) {
1826
+ return this.root[field];
1827
+ }
1828
+ on(_evt, _fn) {
1829
+ }
1830
+ sort() {
1831
+ }
1832
+ isLoading() {
1833
+ return false;
1834
+ }
1835
+ getTotalCount() {
1836
+ return this.getCount();
1837
+ }
1838
+ };
1839
+ export {
1840
+ ActionColumn,
1841
+ BooleanColumn,
1842
+ CellEditing,
1843
+ CellSelectionModel,
1844
+ CheckColumn,
1845
+ Column,
1846
+ DateColumn,
1847
+ Grid,
1848
+ GridClipboard,
1849
+ GridDragDrop,
1850
+ GridState,
1851
+ GridView,
1852
+ Grouping,
1853
+ GroupingSummary,
1854
+ HeaderContainer,
1855
+ Lockable,
1856
+ NumberColumn,
1857
+ RowBody,
1858
+ RowEditing,
1859
+ RowExpander,
1860
+ RowNumbererColumn,
1861
+ RowSelectionModel,
1862
+ SpreadsheetSelectionModel,
1863
+ Summary,
1864
+ TreePanel,
1865
+ TreeStore,
1866
+ createColumn
1867
+ };
1868
+ //# sourceMappingURL=index.js.map