@ornery/ui-grid-vanilla 0.1.7

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.mjs ADDED
@@ -0,0 +1,1495 @@
1
+ // src/index.ts
2
+ import { registerRustWasmGridEngine } from "@ornery/ui-grid-core";
3
+
4
+ // src/ui-grid-standalone.element.ts
5
+ import {
6
+ GRID_CORE_CSS,
7
+ SORT_DIRECTIONS as SORT_DIRECTIONS2,
8
+ canGridMoveColumns as canGridMoveColumns2,
9
+ downloadGridCsvFile,
10
+ exportCsvRows,
11
+ getCellValue as getCellValue2,
12
+ sanitizeDownloadFilename
13
+ } from "@ornery/ui-grid-core";
14
+
15
+ // src/grid-controller.ts
16
+ import {
17
+ SORT_DIRECTIONS,
18
+ beginGridCellEditCommand,
19
+ buildGridRows,
20
+ buildGridCellContext,
21
+ cancelGridCellEditCommand,
22
+ canGridExpandRows,
23
+ canGridMoveColumns,
24
+ clearGridFiltersCommand,
25
+ clearGridGroupingCommand,
26
+ commitGridCellEditCommand,
27
+ computePinnedOffset,
28
+ createGridApi,
29
+ defaultGridEngine,
30
+ expandAllGridRowsCommand,
31
+ expandAllGridTreeRowsCommand,
32
+ findGridRowById as coreFindGridRowById,
33
+ formatGridCellDisplayValue,
34
+ getCellValue,
35
+ getCurrentPageValue,
36
+ getEffectivePageSize,
37
+ getFirstRowIndexValue,
38
+ getLastRowIndexValue,
39
+ getTotalPagesValue,
40
+ gridCellIndent,
41
+ gridColumnWidth,
42
+ gridEditorInputType,
43
+ gridExpandToggleLabelForRow,
44
+ gridFilterPlaceholder,
45
+ gridGroupingButtonLabel,
46
+ gridSortButtonLabel,
47
+ gridTreeToggleLabelForRow,
48
+ headerLabel,
49
+ isGridColumnFilterable,
50
+ isGridColumnGrouped,
51
+ isGridColumnSortable,
52
+ isGridFilteringEnabled,
53
+ isGridGroupingEnabled,
54
+ isGridPaginationEnabled,
55
+ isGridSortingEnabled,
56
+ isGridTreeEnabled,
57
+ isGridTreeRowExpanded,
58
+ isPinningEnabled,
59
+ isColumnPinnable,
60
+ gridGroupDisclosureLabel,
61
+ moveGridVisibleColumnCommand,
62
+ parseGridEditedValue,
63
+ pinGridColumnCommand,
64
+ resolveGridLabels,
65
+ resolveGridRowId as coreResolveGridRowId,
66
+ seekGridPaginationCommand,
67
+ setGridPaginationPageSizeCommand,
68
+ setPathValue,
69
+ shouldShowGridExpandToggle,
70
+ shouldShowGridPaginationControls,
71
+ shouldShowGridTreeToggle,
72
+ sortGridColumnCommand,
73
+ toggleGridGroupingCommand,
74
+ toggleGridRowExpansionCommand,
75
+ toggleGridTreeRowCommand,
76
+ updateGridFilterCommand
77
+ } from "@ornery/ui-grid-core";
78
+ function orderVisibleColumns(columns, order) {
79
+ return [...columns].filter((column) => column.visible !== false).sort((left, right) => order.indexOf(left.name) - order.indexOf(right.name));
80
+ }
81
+ function buildGridTemplateColumns(columns) {
82
+ return columns.map((column) => gridColumnWidth(column)).join(" ");
83
+ }
84
+ var VanillaGridController = class {
85
+ options;
86
+ activeFilters = {};
87
+ sortState = {
88
+ columnName: null,
89
+ direction: SORT_DIRECTIONS.none
90
+ };
91
+ groupByColumns = [];
92
+ collapsedGroups = {};
93
+ hiddenRowReasons = {};
94
+ expandedRows = {};
95
+ expandedTreeRows = {};
96
+ pinnedColumns = {};
97
+ columnOrder = [];
98
+ currentPage = 1;
99
+ pageSize = 0;
100
+ editingCell = null;
101
+ editingValue = "";
102
+ pipeline;
103
+ labels;
104
+ visibleColumns = [];
105
+ apiRegistered = false;
106
+ subscribers = /* @__PURE__ */ new Set();
107
+ gridApi;
108
+ constructor(options) {
109
+ this.options = options;
110
+ this.labels = resolveGridLabels(options.labels);
111
+ this.columnOrder = options.columnDefs.map((column) => column.name);
112
+ this.pipeline = {
113
+ visibleRows: [],
114
+ displayItems: [],
115
+ virtualizationEnabled: false,
116
+ pipelineMs: 0,
117
+ totalItems: 0
118
+ };
119
+ this.gridApi = createGridApi({
120
+ refresh: () => this.refresh(),
121
+ getVisibleRows: () => this.pipeline.visibleRows,
122
+ setRowInvisible: (row, reason) => this.setRowInvisible(row, reason),
123
+ clearRowInvisible: (row, reason) => this.clearRowInvisible(row, reason),
124
+ setFilter: (columnName, value) => this.setFilter(columnName, value),
125
+ clearAllFilters: () => this.clearAllFilters(),
126
+ sortColumn: (columnName, direction) => this.sortColumn(columnName, direction),
127
+ moveColumn: (fromIndex, toIndex) => {
128
+ const visibleNames = this.visibleColumns.map((column) => column.name);
129
+ const source = visibleNames[fromIndex];
130
+ const target = visibleNames[toIndex];
131
+ if (source && target) {
132
+ this.moveVisibleColumn(source, target);
133
+ }
134
+ },
135
+ pinColumn: (columnName, direction) => this.pinColumn(columnName, direction),
136
+ toggleGrouping: (columnName) => this.toggleGrouping(columnName),
137
+ clearGrouping: () => this.clearGrouping(),
138
+ benchmark: (iterations) => this.benchmark(iterations),
139
+ exportCsv: () => {
140
+ },
141
+ paginationGetPage: () => this.getCurrentPage(),
142
+ paginationGetTotalPages: () => this.getTotalPages(),
143
+ paginationGetFirstRowIndex: () => this.getFirstRowIndex(),
144
+ paginationGetLastRowIndex: () => this.getLastRowIndex(),
145
+ paginationNextPage: () => this.seekPage(this.currentPage + 1),
146
+ paginationPreviousPage: () => this.seekPage(this.currentPage - 1),
147
+ paginationSeek: (page) => this.seekPage(page),
148
+ paginationSetPageSize: (pageSize) => this.setPageSize(pageSize),
149
+ toggleRowExpansion: (row) => this.toggleRowExpansion(row),
150
+ expandAllRows: () => this.expandAllRows(),
151
+ collapseAllRows: () => this.collapseAllRows(),
152
+ toggleAllRows: () => {
153
+ if (Object.keys(this.expandedRows).length > 0) {
154
+ this.collapseAllRows();
155
+ return;
156
+ }
157
+ this.expandAllRows();
158
+ },
159
+ treeToggleRow: (row) => this.toggleTreeRow(row),
160
+ treeExpandAllRows: () => this.expandAllTreeRows(),
161
+ treeCollapseAllRows: () => this.collapseAllTreeRows(),
162
+ treeExpandRow: (row) => this.setTreeRowExpanded(row, true),
163
+ treeCollapseRow: (row) => this.setTreeRowExpanded(row, false),
164
+ treeGetRowChildren: (row) => {
165
+ const rowId = this.resolveRowId(row);
166
+ return this.pipeline.visibleRows.filter((candidate) => candidate.parentId === rowId);
167
+ },
168
+ treeGetState: () => ({ ...this.expandedTreeRows }),
169
+ treeSetState: (state) => {
170
+ this.expandedTreeRows = { ...state };
171
+ this.refresh();
172
+ },
173
+ beginCellEdit: (row, columnName, triggerEvent) => this.beginCellEdit(row, columnName, triggerEvent),
174
+ endCellEdit: () => {
175
+ this.commitCellEdit();
176
+ },
177
+ cancelCellEdit: () => {
178
+ this.cancelCellEdit();
179
+ },
180
+ getEditingCell: () => this.editingCell
181
+ });
182
+ this.refresh();
183
+ }
184
+ setOptions(options) {
185
+ this.options = options;
186
+ this.labels = resolveGridLabels(options.labels);
187
+ if (this.columnOrder.length === 0) {
188
+ this.columnOrder = options.columnDefs.map((column) => column.name);
189
+ } else {
190
+ const names = new Set(options.columnDefs.map((column) => column.name));
191
+ this.columnOrder = this.columnOrder.filter((name) => names.has(name));
192
+ for (const column of options.columnDefs) {
193
+ if (!this.columnOrder.includes(column.name)) {
194
+ this.columnOrder.push(column.name);
195
+ }
196
+ }
197
+ }
198
+ this.apiRegistered = false;
199
+ this.refresh();
200
+ }
201
+ subscribe(subscriber) {
202
+ this.subscribers.add(subscriber);
203
+ subscriber(this.getSnapshot());
204
+ return () => {
205
+ this.subscribers.delete(subscriber);
206
+ };
207
+ }
208
+ getSnapshot() {
209
+ const totalPages = this.getTotalPages();
210
+ return {
211
+ options: this.options,
212
+ labels: this.labels,
213
+ rowSize: this.getRowSize(),
214
+ visibleColumns: this.visibleColumns,
215
+ gridTemplateColumns: buildGridTemplateColumns(this.visibleColumns),
216
+ pipeline: this.pipeline,
217
+ activeFilters: this.activeFilters,
218
+ sortState: this.sortState,
219
+ groupByColumns: this.groupByColumns,
220
+ pinnedColumns: this.pinnedColumns,
221
+ currentPage: this.getCurrentPage(),
222
+ totalPages,
223
+ firstRowIndex: this.getFirstRowIndex(),
224
+ lastRowIndex: this.getLastRowIndex(),
225
+ pageSize: this.getEffectivePageSize(),
226
+ editingCell: this.editingCell,
227
+ editingValue: this.editingValue
228
+ };
229
+ }
230
+ getDisplayItems() {
231
+ return this.pipeline.displayItems;
232
+ }
233
+ setCollapsedGroup(groupId, collapsed) {
234
+ this.collapsedGroups = {
235
+ ...this.collapsedGroups,
236
+ [groupId]: collapsed
237
+ };
238
+ this.refresh();
239
+ }
240
+ toggleSort(columnName) {
241
+ const current = this.sortState.columnName === columnName ? this.sortState.direction : SORT_DIRECTIONS.none;
242
+ if (current === SORT_DIRECTIONS.none) {
243
+ this.sortColumn(columnName, SORT_DIRECTIONS.asc);
244
+ return;
245
+ }
246
+ if (current === SORT_DIRECTIONS.asc) {
247
+ this.sortColumn(columnName, SORT_DIRECTIONS.desc);
248
+ return;
249
+ }
250
+ this.sortColumn(columnName, SORT_DIRECTIONS.none);
251
+ }
252
+ sortColumn(columnName, direction = SORT_DIRECTIONS.none) {
253
+ sortGridColumnCommand(
254
+ this.gridApi,
255
+ (next) => {
256
+ this.sortState = next;
257
+ },
258
+ columnName,
259
+ direction
260
+ );
261
+ this.refresh();
262
+ }
263
+ setFilter(columnName, value) {
264
+ updateGridFilterCommand(
265
+ this.gridApi,
266
+ (updater) => {
267
+ this.activeFilters = updater(this.activeFilters);
268
+ },
269
+ () => this.activeFilters,
270
+ columnName,
271
+ value
272
+ );
273
+ this.currentPage = 1;
274
+ this.refresh();
275
+ }
276
+ clearAllFilters() {
277
+ clearGridFiltersCommand(this.gridApi, (next) => {
278
+ this.activeFilters = next;
279
+ });
280
+ this.currentPage = 1;
281
+ this.refresh();
282
+ }
283
+ toggleGrouping(columnName) {
284
+ toggleGridGroupingCommand(
285
+ this.gridApi,
286
+ isGridGroupingEnabled(this.options),
287
+ (updater) => {
288
+ this.groupByColumns = updater(this.groupByColumns);
289
+ },
290
+ () => this.groupByColumns,
291
+ columnName
292
+ );
293
+ this.refresh();
294
+ }
295
+ clearGrouping() {
296
+ clearGridGroupingCommand(this.gridApi, (next) => {
297
+ this.groupByColumns = next;
298
+ });
299
+ this.refresh();
300
+ }
301
+ seekPage(page) {
302
+ seekGridPaginationCommand(
303
+ this.gridApi,
304
+ (next) => {
305
+ this.currentPage = next;
306
+ },
307
+ () => this.getTotalPages(),
308
+ () => this.getEffectivePageSize(),
309
+ page
310
+ );
311
+ this.refresh();
312
+ }
313
+ setPageSize(pageSize) {
314
+ setGridPaginationPageSizeCommand(
315
+ this.gridApi,
316
+ (next) => {
317
+ this.pageSize = next;
318
+ },
319
+ (next) => {
320
+ this.currentPage = next;
321
+ },
322
+ pageSize
323
+ );
324
+ this.refresh();
325
+ }
326
+ pinColumn(columnName, direction) {
327
+ pinGridColumnCommand(
328
+ this.gridApi,
329
+ isPinningEnabled(this.options),
330
+ (next) => {
331
+ this.pinnedColumns = next;
332
+ },
333
+ () => this.pinnedColumns,
334
+ columnName,
335
+ direction
336
+ );
337
+ this.refresh();
338
+ }
339
+ moveVisibleColumn(columnName, targetColumnName) {
340
+ moveGridVisibleColumnCommand(
341
+ this.gridApi,
342
+ canGridMoveColumns(this.options),
343
+ this.columnOrder,
344
+ this.visibleColumns.map((column) => column.name),
345
+ columnName,
346
+ targetColumnName,
347
+ (next) => {
348
+ this.columnOrder = next;
349
+ }
350
+ );
351
+ this.refresh();
352
+ }
353
+ toggleRowExpansion(row) {
354
+ const rowId = this.resolveRowId(row);
355
+ toggleGridRowExpansionCommand(
356
+ this.gridApi,
357
+ canGridExpandRows(this.options),
358
+ this.expandedRows,
359
+ rowId,
360
+ (next) => {
361
+ this.expandedRows = next;
362
+ },
363
+ (candidateRowId) => this.findRowById(candidateRowId)
364
+ );
365
+ this.refresh();
366
+ }
367
+ expandAllRows() {
368
+ expandAllGridRowsCommand(
369
+ (data) => this.buildRowsFromData(data),
370
+ this.options.data,
371
+ (next) => {
372
+ this.expandedRows = next;
373
+ }
374
+ );
375
+ this.refresh();
376
+ }
377
+ collapseAllRows() {
378
+ this.expandedRows = {};
379
+ this.refresh();
380
+ }
381
+ toggleTreeRow(row) {
382
+ const rowId = this.resolveRowId(row);
383
+ toggleGridTreeRowCommand(
384
+ this.gridApi,
385
+ this.expandedTreeRows,
386
+ rowId,
387
+ (next) => {
388
+ this.expandedTreeRows = next;
389
+ },
390
+ (candidateRowId) => this.findRowById(candidateRowId)
391
+ );
392
+ this.refresh();
393
+ }
394
+ expandAllTreeRows() {
395
+ expandAllGridTreeRowsCommand(
396
+ (data) => this.buildRowsFromData(data),
397
+ this.options.data,
398
+ (next) => {
399
+ this.expandedTreeRows = next;
400
+ }
401
+ );
402
+ this.refresh();
403
+ }
404
+ collapseAllTreeRows() {
405
+ this.expandedTreeRows = {};
406
+ this.refresh();
407
+ }
408
+ setTreeRowExpanded(row, expanded) {
409
+ const rowId = this.resolveRowId(row);
410
+ if (expanded) {
411
+ this.expandedTreeRows = {
412
+ ...this.expandedTreeRows,
413
+ [rowId]: true
414
+ };
415
+ } else {
416
+ const nextState = { ...this.expandedTreeRows };
417
+ delete nextState[rowId];
418
+ this.expandedTreeRows = nextState;
419
+ }
420
+ this.refresh();
421
+ }
422
+ beginCellEdit(row, columnName, triggerEvent) {
423
+ const rowId = this.resolveRowId(row);
424
+ const gridRow = this.findRowById(rowId);
425
+ const column = this.findColumnByName(columnName);
426
+ if (!gridRow || !column) {
427
+ return;
428
+ }
429
+ beginGridCellEditCommand(
430
+ this.gridApi,
431
+ {
432
+ setFocusedCell: () => {
433
+ },
434
+ setEditingCell: (next) => {
435
+ this.editingCell = next;
436
+ },
437
+ setEditingValue: (next) => {
438
+ this.editingValue = next;
439
+ }
440
+ },
441
+ gridRow,
442
+ column,
443
+ getCellValue(gridRow.entity, column),
444
+ triggerEvent
445
+ );
446
+ this.refresh();
447
+ }
448
+ updateEditingValue(value) {
449
+ this.editingValue = value;
450
+ this.emit();
451
+ }
452
+ commitCellEdit() {
453
+ commitGridCellEditCommand(this.gridApi, {
454
+ getEditingCell: () => this.editingCell,
455
+ getEditingValue: () => this.editingValue,
456
+ setEditingCell: (next) => {
457
+ this.editingCell = next;
458
+ },
459
+ setEditingValue: (next) => {
460
+ this.editingValue = next;
461
+ },
462
+ findRowById: (rowId) => this.findRowById(rowId),
463
+ findColumnByName: (columnName) => this.findColumnByName(columnName),
464
+ parseEditedValue: (column, value, oldValue) => parseGridEditedValue(column, value, oldValue),
465
+ setCellValue: (rowEntity, column, value) => setPathValue(rowEntity, column.field ?? column.name, value)
466
+ });
467
+ this.refresh();
468
+ }
469
+ cancelCellEdit() {
470
+ cancelGridCellEditCommand(this.gridApi, {
471
+ getEditingCell: () => this.editingCell,
472
+ setEditingCell: (next) => {
473
+ this.editingCell = next;
474
+ },
475
+ setEditingValue: (next) => {
476
+ this.editingValue = next;
477
+ },
478
+ findRowById: (rowId) => this.findRowById(rowId),
479
+ findColumnByName: (columnName) => this.findColumnByName(columnName)
480
+ });
481
+ this.refresh();
482
+ }
483
+ isGroupingEnabled() {
484
+ return isGridGroupingEnabled(this.options);
485
+ }
486
+ isSortingEnabled() {
487
+ return isGridSortingEnabled(this.options);
488
+ }
489
+ isFilteringEnabled() {
490
+ return isGridFilteringEnabled(this.options);
491
+ }
492
+ isPaginationEnabled() {
493
+ return isGridPaginationEnabled(this.options);
494
+ }
495
+ shouldShowPaginationControls() {
496
+ return shouldShowGridPaginationControls(this.options);
497
+ }
498
+ isTreeEnabled() {
499
+ return isGridTreeEnabled(this.options);
500
+ }
501
+ isExpandableEnabled() {
502
+ return canGridExpandRows(this.options);
503
+ }
504
+ isColumnSortable(column) {
505
+ return isGridColumnSortable(this.options, column);
506
+ }
507
+ isColumnFilterable(column) {
508
+ return isGridColumnFilterable(this.options, column);
509
+ }
510
+ isColumnGrouped(column) {
511
+ return isGridColumnGrouped(this.groupByColumns, column);
512
+ }
513
+ sortButtonLabel(column) {
514
+ return gridSortButtonLabel(this.getSortDirection(column), this.labels);
515
+ }
516
+ groupingButtonLabel(column) {
517
+ return gridGroupingButtonLabel(this.isColumnGrouped(column), this.labels);
518
+ }
519
+ filterPlaceholder(column) {
520
+ return gridFilterPlaceholder(this.isColumnFilterable(column), this.labels);
521
+ }
522
+ getSortDirection(column) {
523
+ return this.sortState.columnName === column.name ? this.sortState.direction : SORT_DIRECTIONS.none;
524
+ }
525
+ isPinningEnabled() {
526
+ return isPinningEnabled(this.options);
527
+ }
528
+ isColumnPinnable(column) {
529
+ return isColumnPinnable(this.options, column);
530
+ }
531
+ isPinned(column) {
532
+ return this.pinnedColumns[column.name] !== void 0;
533
+ }
534
+ isPinnedLeftLast(column) {
535
+ const leftPinned = this.visibleColumns.filter((c) => this.pinnedColumns[c.name] === "left");
536
+ return leftPinned.length > 0 && leftPinned[leftPinned.length - 1].name === column.name;
537
+ }
538
+ isPinnedRightFirst(column) {
539
+ const rightPinned = this.visibleColumns.filter((c) => this.pinnedColumns[c.name] === "right");
540
+ return rightPinned.length > 0 && rightPinned[0].name === column.name;
541
+ }
542
+ groupDisclosureLabel(group) {
543
+ return gridGroupDisclosureLabel(group.collapsed, this.labels);
544
+ }
545
+ pinnedOffset(column) {
546
+ return computePinnedOffset(this.visibleColumns, this.pinnedColumns, column);
547
+ }
548
+ displayValue(row, column) {
549
+ return formatGridCellDisplayValue(buildGridCellContext(row, column));
550
+ }
551
+ headerLabel(column) {
552
+ return headerLabel(column);
553
+ }
554
+ cellIndent(row, column) {
555
+ return gridCellIndent(this.options, this.visibleColumns, row, column);
556
+ }
557
+ showTreeToggle(row, column) {
558
+ return shouldShowGridTreeToggle(this.options, this.visibleColumns, row, column);
559
+ }
560
+ showExpandToggle(_row, column) {
561
+ return shouldShowGridExpandToggle(this.options, this.visibleColumns, column);
562
+ }
563
+ isTreeRowExpanded(row) {
564
+ return isGridTreeRowExpanded(this.expandedTreeRows, row);
565
+ }
566
+ treeToggleLabel(row) {
567
+ return gridTreeToggleLabelForRow(this.expandedTreeRows, row, this.labels);
568
+ }
569
+ expandToggleLabel(row) {
570
+ return gridExpandToggleLabelForRow(row, this.labels);
571
+ }
572
+ editorInputType(column) {
573
+ return gridEditorInputType(column);
574
+ }
575
+ isEditingCell(rowId, columnName) {
576
+ return this.editingCell?.rowId === rowId && this.editingCell?.columnName === columnName;
577
+ }
578
+ getState() {
579
+ return {
580
+ sortState: { ...this.sortState },
581
+ activeFilters: { ...this.activeFilters },
582
+ groupByColumns: [...this.groupByColumns],
583
+ pinnedColumns: { ...this.pinnedColumns },
584
+ columnOrder: [...this.columnOrder],
585
+ currentPage: this.currentPage,
586
+ pageSize: this.pageSize
587
+ };
588
+ }
589
+ setState(state) {
590
+ if (state.sortState !== void 0) this.sortState = { ...state.sortState };
591
+ if (state.activeFilters !== void 0) this.activeFilters = { ...state.activeFilters };
592
+ if (state.groupByColumns !== void 0) this.groupByColumns = [...state.groupByColumns];
593
+ if (state.pinnedColumns !== void 0) this.pinnedColumns = { ...state.pinnedColumns };
594
+ if (state.columnOrder !== void 0) this.columnOrder = [...state.columnOrder];
595
+ if (state.currentPage !== void 0) this.currentPage = state.currentPage;
596
+ if (state.pageSize !== void 0) this.pageSize = state.pageSize;
597
+ this.refresh();
598
+ }
599
+ refresh() {
600
+ const orderedColumns = orderVisibleColumns(this.options.columnDefs, this.columnOrder);
601
+ const pinnedEntries = Object.entries(this.pinnedColumns);
602
+ if (pinnedEntries.length === 0) {
603
+ this.visibleColumns = orderedColumns;
604
+ } else {
605
+ const byName = new Map(orderedColumns.map((column) => [column.name, column]));
606
+ const pinnedLeft = pinnedEntries.filter((entry) => entry[1] === "left").map((entry) => byName.get(entry[0])).filter((column) => column !== void 0);
607
+ const pinnedRight = pinnedEntries.filter((entry) => entry[1] === "right").map((entry) => byName.get(entry[0])).filter((column) => column !== void 0);
608
+ const middleColumns = orderedColumns.filter(
609
+ (column) => this.pinnedColumns[column.name] === void 0
610
+ );
611
+ this.visibleColumns = [...pinnedLeft, ...middleColumns, ...pinnedRight];
612
+ }
613
+ this.pipeline = defaultGridEngine.buildPipeline({
614
+ options: this.options,
615
+ columns: this.visibleColumns,
616
+ activeFilters: this.activeFilters,
617
+ sortState: this.sortState,
618
+ groupByColumns: this.groupByColumns,
619
+ collapsedGroups: this.collapsedGroups,
620
+ hiddenRowReasons: this.hiddenRowReasons,
621
+ expandedRows: this.expandedRows,
622
+ expandedTreeRows: this.expandedTreeRows,
623
+ currentPage: this.currentPage,
624
+ pageSize: this.pageSize,
625
+ rowSize: this.getRowSize()
626
+ });
627
+ if (!this.apiRegistered) {
628
+ this.options.onRegisterApi?.(this.gridApi);
629
+ this.apiRegistered = true;
630
+ }
631
+ this.emit();
632
+ }
633
+ emit() {
634
+ const snapshot = this.getSnapshot();
635
+ for (const subscriber of this.subscribers) {
636
+ subscriber(snapshot);
637
+ }
638
+ }
639
+ findRowById(rowId) {
640
+ return coreFindGridRowById(this.buildRowsFromData(this.options.data), rowId);
641
+ }
642
+ buildRowsFromData(data) {
643
+ return buildGridRows(
644
+ { ...this.options, data },
645
+ this.getRowSize(),
646
+ this.hiddenRowReasons,
647
+ this.expandedRows
648
+ );
649
+ }
650
+ findColumnByName(columnName) {
651
+ return this.visibleColumns.find((column) => column.name === columnName);
652
+ }
653
+ resolveRowId(row) {
654
+ return coreResolveGridRowId(this.options, row);
655
+ }
656
+ getRowSize() {
657
+ return this.options.rowHeight ?? 44;
658
+ }
659
+ getTotalPages() {
660
+ return getTotalPagesValue(this.options, this.pipeline.totalItems, this.pageSize);
661
+ }
662
+ getEffectivePageSize() {
663
+ return getEffectivePageSize(this.options, this.pageSize, this.pipeline.totalItems);
664
+ }
665
+ getCurrentPage() {
666
+ return getCurrentPageValue(
667
+ this.options,
668
+ this.currentPage,
669
+ this.pipeline.totalItems,
670
+ this.pageSize
671
+ );
672
+ }
673
+ getFirstRowIndex() {
674
+ return getFirstRowIndexValue(
675
+ this.options,
676
+ this.currentPage,
677
+ this.pipeline.totalItems,
678
+ this.pageSize
679
+ );
680
+ }
681
+ getLastRowIndex() {
682
+ return getLastRowIndexValue(
683
+ this.options,
684
+ this.currentPage,
685
+ this.pipeline.totalItems,
686
+ this.pageSize
687
+ );
688
+ }
689
+ setRowInvisible(row, reason) {
690
+ const rowId = this.resolveRowId(row);
691
+ const next = { ...this.hiddenRowReasons };
692
+ const reasonValue = reason ?? "api";
693
+ next[rowId] = [...next[rowId] ?? [], reasonValue];
694
+ this.hiddenRowReasons = next;
695
+ this.refresh();
696
+ }
697
+ clearRowInvisible(row, reason) {
698
+ const rowId = this.resolveRowId(row);
699
+ const existing = this.hiddenRowReasons[rowId] ?? [];
700
+ if (existing.length === 0) {
701
+ return;
702
+ }
703
+ if (!reason) {
704
+ const next2 = { ...this.hiddenRowReasons };
705
+ delete next2[rowId];
706
+ this.hiddenRowReasons = next2;
707
+ this.refresh();
708
+ return;
709
+ }
710
+ const filtered = existing.filter((entry) => entry !== reason);
711
+ const next = { ...this.hiddenRowReasons };
712
+ if (filtered.length > 0) {
713
+ next[rowId] = filtered;
714
+ } else {
715
+ delete next[rowId];
716
+ }
717
+ this.hiddenRowReasons = next;
718
+ this.refresh();
719
+ }
720
+ benchmark(iterations) {
721
+ const now = () => typeof performance === "undefined" ? Date.now() : performance.now();
722
+ const loops = Math.max(1, iterations ?? this.options.benchmark?.iterations ?? 25);
723
+ const started = now();
724
+ let lastResult = this.pipeline;
725
+ for (let index = 0; index < loops; index += 1) {
726
+ lastResult = defaultGridEngine.buildPipeline({
727
+ options: this.options,
728
+ columns: this.visibleColumns,
729
+ activeFilters: this.activeFilters,
730
+ sortState: this.sortState,
731
+ groupByColumns: this.groupByColumns,
732
+ collapsedGroups: this.collapsedGroups,
733
+ hiddenRowReasons: this.hiddenRowReasons,
734
+ expandedRows: this.expandedRows,
735
+ expandedTreeRows: this.expandedTreeRows,
736
+ currentPage: this.currentPage,
737
+ pageSize: this.pageSize,
738
+ rowSize: this.getRowSize()
739
+ });
740
+ }
741
+ const elapsedMs = now() - started;
742
+ const result = {
743
+ iterations: loops,
744
+ totalMs: elapsedMs,
745
+ averageMs: elapsedMs / loops,
746
+ visibleRows: lastResult.visibleRows.length,
747
+ renderedItems: lastResult.displayItems.length
748
+ };
749
+ this.gridApi.core.raise.benchmarkComplete(result);
750
+ return result;
751
+ }
752
+ };
753
+ function createVanillaGridController(options) {
754
+ return new VanillaGridController(options);
755
+ }
756
+
757
+ // src/ui-grid-standalone.element.ts
758
+ function escapeHtml(value) {
759
+ const text = String(value ?? "");
760
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
761
+ }
762
+ function asGroupItem(item) {
763
+ return item;
764
+ }
765
+ function isRowItem(item) {
766
+ return item.kind === "row";
767
+ }
768
+ var DEFAULT_ICONS = {
769
+ sortNone: { path: "M7 6h10v2H7V6Zm0 5h7v2H7v-2Zm0 5h4v2H7v-2Z" },
770
+ sortAsc: { path: "M12 5l-6 6h4v8h4v-8h4z" },
771
+ sortDesc: { path: "M12 19l6-6h-4V5h-4v8H6z" },
772
+ group: { path: "M4 6h8v4H4V6Zm0 8h8v4H4v-4Zm10-8h6v4h-6V6Zm0 8h6v4h-6v-4Z" },
773
+ groupExpanded: { path: "M7 10l5 5 5-5z" },
774
+ groupCollapsed: { path: "M10 7l5 5-5 5z" },
775
+ treeExpanded: { path: "M7 10l5 5 5-5z" },
776
+ treeCollapsed: { path: "M10 7l5 5-5 5z" },
777
+ expandExpanded: { path: "M7 10l5 5 5-5z" },
778
+ expandCollapsed: { path: "M10 7l5 5-5 5z" },
779
+ pin: { path: "M16 12V4h1V2H7v2h1v8l-2 2v2h5v6l1 1 1-1v-6h5v-2l-2-2z" },
780
+ pinLeft: { path: "M10 6 4 12l6 6v-4h10v-4H10V6z" },
781
+ pinRight: { path: "M14 6v4H4v4h10v4l6-6-6-6z" },
782
+ paginationPrev: { path: "M15.41 7.41 14 6l-6 6 6 6 1.41-1.41L10.83 12z" },
783
+ paginationNext: { path: "M8.59 16.59 10 18l6-6-6-6-1.41 1.41L13.17 12z" }
784
+ };
785
+ var UiGridStandaloneElement = class extends HTMLElement {
786
+ controller = null;
787
+ snapshot = null;
788
+ unsubscribe = null;
789
+ activeOptions = null;
790
+ iconOverrides = {};
791
+ templateObserver = null;
792
+ openPinMenuColumn = null;
793
+ focusedCell = null;
794
+ draggedColumnName = null;
795
+ dropTargetColumnName = null;
796
+ scrollPosition = 0;
797
+ scrollFrame = null;
798
+ suppressScrollEvent = false;
799
+ lastVirtualStartIndex = -1;
800
+ measuredHeaderStickyHeight = 0;
801
+ measuredFilterStickyHeight = 0;
802
+ stickyHeightRelayoutQueued = false;
803
+ benchmarkAverage = "\u2014";
804
+ get options() {
805
+ return this.activeOptions ?? {
806
+ id: "__ui-grid-pending__",
807
+ data: [],
808
+ columnDefs: []
809
+ };
810
+ }
811
+ set options(value) {
812
+ this.activeOptions = value;
813
+ this.iconOverrides = this.activeOptions.iconOverrides ?? {};
814
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
815
+ }
816
+ get controlIcons() {
817
+ return this.iconOverrides;
818
+ }
819
+ set controlIcons(value) {
820
+ this.iconOverrides = { ...value };
821
+ this.render();
822
+ }
823
+ getState() {
824
+ return this.controller?.getState() ?? null;
825
+ }
826
+ setState(state) {
827
+ this.controller?.setState(state);
828
+ }
829
+ connectedCallback() {
830
+ this.ensureShadowRoot();
831
+ this.bindEvents();
832
+ this.observeTemplateSlots();
833
+ if (this.activeOptions) {
834
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
835
+ } else {
836
+ this.render();
837
+ }
838
+ }
839
+ disconnectedCallback() {
840
+ this.unsubscribe?.();
841
+ this.unsubscribe = null;
842
+ this.templateObserver?.disconnect();
843
+ this.templateObserver = null;
844
+ if (this.scrollFrame !== null) {
845
+ cancelAnimationFrame(this.scrollFrame);
846
+ this.scrollFrame = null;
847
+ }
848
+ }
849
+ ensureShadowRoot() {
850
+ if (!this.shadowRoot) {
851
+ this.attachShadow({ mode: "open" });
852
+ }
853
+ return this.shadowRoot;
854
+ }
855
+ ensureController(options) {
856
+ if (this.controller) {
857
+ this.controller.setOptions(options);
858
+ return;
859
+ }
860
+ this.controller = createVanillaGridController(options);
861
+ this.unsubscribe = this.controller.subscribe((snapshot) => {
862
+ this.snapshot = snapshot;
863
+ this.render();
864
+ });
865
+ }
866
+ observeTemplateSlots() {
867
+ if (this.templateObserver) {
868
+ return;
869
+ }
870
+ this.templateObserver = new MutationObserver(() => {
871
+ if (this.activeOptions) {
872
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
873
+ } else {
874
+ this.render();
875
+ }
876
+ });
877
+ this.templateObserver.observe(this, {
878
+ childList: true,
879
+ subtree: true,
880
+ attributes: true,
881
+ attributeFilter: ["slot"]
882
+ });
883
+ }
884
+ buildEffectiveOptions(options) {
885
+ const hasExpandableSlot = this.getTemplateMarkup("expandable-row") !== null;
886
+ if (options.enableExpandable && hasExpandableSlot && !options.expandableRowTemplate) {
887
+ return {
888
+ ...options,
889
+ expandableRowTemplate: {
890
+ createEmbeddedView: () => void 0
891
+ }
892
+ };
893
+ }
894
+ return options;
895
+ }
896
+ bindEvents() {
897
+ const root = this.ensureShadowRoot();
898
+ if (root.__uiGridBound) {
899
+ return;
900
+ }
901
+ root.addEventListener("click", (event) => {
902
+ const target = event.target;
903
+ if (!target || !this.controller || !this.snapshot) {
904
+ return;
905
+ }
906
+ const actionNode = target.closest("[data-action]");
907
+ if (!actionNode) {
908
+ return;
909
+ }
910
+ const action = actionNode.dataset["action"];
911
+ if (!action) {
912
+ return;
913
+ }
914
+ if (this.openPinMenuColumn && !target.closest(".pin-control")) {
915
+ this.openPinMenuColumn = null;
916
+ this.render();
917
+ return;
918
+ }
919
+ if (action === "sort") {
920
+ const columnName = actionNode.dataset["column"];
921
+ if (columnName) {
922
+ this.controller.toggleSort(columnName);
923
+ }
924
+ return;
925
+ }
926
+ if (action === "group") {
927
+ const columnName = actionNode.dataset["column"];
928
+ if (columnName) {
929
+ this.controller.toggleGrouping(columnName);
930
+ }
931
+ return;
932
+ }
933
+ if (action === "toggle-group") {
934
+ const groupId = actionNode.dataset["group"];
935
+ if (groupId) {
936
+ const collapsed = actionNode.dataset["collapsed"] !== "true";
937
+ this.controller.setCollapsedGroup(groupId, collapsed);
938
+ }
939
+ return;
940
+ }
941
+ if (action === "pin-trigger") {
942
+ const columnName = actionNode.dataset["column"];
943
+ const column = columnName ? this.snapshot.visibleColumns.find((candidate) => candidate.name === columnName) : void 0;
944
+ if (columnName && column) {
945
+ if (this.controller.isPinned(column)) {
946
+ this.openPinMenuColumn = null;
947
+ this.controller.pinColumn(columnName, "none");
948
+ return;
949
+ }
950
+ this.openPinMenuColumn = this.openPinMenuColumn === columnName ? null : columnName;
951
+ this.render();
952
+ }
953
+ return;
954
+ }
955
+ if (action === "pin-left" || action === "pin-right") {
956
+ const columnName = actionNode.dataset["column"];
957
+ if (columnName) {
958
+ this.openPinMenuColumn = null;
959
+ this.controller.pinColumn(columnName, action === "pin-left" ? "left" : "right");
960
+ }
961
+ return;
962
+ }
963
+ if (action === "toggle-tree") {
964
+ const rowId = actionNode.dataset["row"];
965
+ if (rowId) {
966
+ this.controller.toggleTreeRow(rowId);
967
+ }
968
+ return;
969
+ }
970
+ if (action === "toggle-expand") {
971
+ const rowId = actionNode.dataset["row"];
972
+ if (rowId) {
973
+ this.controller.toggleRowExpansion(rowId);
974
+ }
975
+ return;
976
+ }
977
+ if (action === "page-prev") {
978
+ this.controller.seekPage(this.snapshot.currentPage - 1);
979
+ return;
980
+ }
981
+ if (action === "page-next") {
982
+ this.controller.seekPage(this.snapshot.currentPage + 1);
983
+ return;
984
+ }
985
+ if (action === "benchmark") {
986
+ const result = this.controller.gridApi.core.benchmark();
987
+ this.benchmarkAverage = result.averageMs.toFixed(2);
988
+ this.render();
989
+ return;
990
+ }
991
+ if (action === "export-csv") {
992
+ this.downloadCsv();
993
+ }
994
+ });
995
+ root.addEventListener("focusin", (event) => {
996
+ const target = event.target;
997
+ if (!target) {
998
+ return;
999
+ }
1000
+ const cell = target.closest(".body-cell[data-row][data-column]");
1001
+ const rowId = cell?.dataset["row"];
1002
+ const columnName = cell?.dataset["column"];
1003
+ if (rowId && columnName) {
1004
+ this.focusedCell = { rowId, columnName };
1005
+ }
1006
+ });
1007
+ root.addEventListener("focusout", (event) => {
1008
+ const relatedTarget = event.relatedTarget;
1009
+ if (!relatedTarget?.closest(".body-cell[data-row][data-column]")) {
1010
+ this.focusedCell = null;
1011
+ }
1012
+ });
1013
+ root.addEventListener("input", (event) => {
1014
+ const target = event.target;
1015
+ if (!target || !this.controller) {
1016
+ return;
1017
+ }
1018
+ if (target instanceof HTMLInputElement && target.dataset["role"] === "filter") {
1019
+ const columnName = target.dataset["column"];
1020
+ if (columnName) {
1021
+ this.controller.setFilter(columnName, target.value);
1022
+ }
1023
+ }
1024
+ if (target instanceof HTMLInputElement && target.dataset["role"] === "editor") {
1025
+ this.controller.updateEditingValue(target.value);
1026
+ }
1027
+ });
1028
+ root.addEventListener("change", (event) => {
1029
+ const target = event.target;
1030
+ if (!target || !this.controller) {
1031
+ return;
1032
+ }
1033
+ if (target instanceof HTMLSelectElement && target.dataset["role"] === "page-size") {
1034
+ const value = Number.parseInt(target.value, 10);
1035
+ this.controller.setPageSize(value);
1036
+ }
1037
+ });
1038
+ root.addEventListener("dblclick", (event) => {
1039
+ const target = event.target;
1040
+ if (!target || !this.controller) {
1041
+ return;
1042
+ }
1043
+ const cell = target.closest(".body-cell");
1044
+ if (!cell) {
1045
+ return;
1046
+ }
1047
+ const rowId = cell.dataset["row"];
1048
+ const columnName = cell.dataset["column"];
1049
+ if (rowId && columnName) {
1050
+ this.controller.beginCellEdit(rowId, columnName, event);
1051
+ }
1052
+ });
1053
+ root.addEventListener("keydown", (event) => {
1054
+ const keyboardEvent = event;
1055
+ const target = event.target;
1056
+ if (!target || !this.controller) {
1057
+ return;
1058
+ }
1059
+ if (target instanceof HTMLInputElement && target.dataset["role"] === "editor") {
1060
+ if (keyboardEvent.key === "Enter") {
1061
+ event.preventDefault();
1062
+ this.controller.commitCellEdit();
1063
+ return;
1064
+ }
1065
+ if (keyboardEvent.key === "Escape") {
1066
+ event.preventDefault();
1067
+ this.controller.cancelCellEdit();
1068
+ }
1069
+ return;
1070
+ }
1071
+ const cell = target.closest(".body-cell");
1072
+ if (!cell) {
1073
+ return;
1074
+ }
1075
+ const rowId = cell.dataset["row"];
1076
+ const columnName = cell.dataset["column"];
1077
+ if (!rowId || !columnName) {
1078
+ return;
1079
+ }
1080
+ if (keyboardEvent.key === "Enter" || keyboardEvent.key === "F2") {
1081
+ event.preventDefault();
1082
+ this.controller.beginCellEdit(rowId, columnName, event);
1083
+ return;
1084
+ }
1085
+ if (keyboardEvent.key.length === 1 && !keyboardEvent.ctrlKey && !keyboardEvent.metaKey && !keyboardEvent.altKey) {
1086
+ event.preventDefault();
1087
+ this.controller.beginCellEdit(rowId, columnName, event);
1088
+ this.controller.updateEditingValue(keyboardEvent.key);
1089
+ }
1090
+ });
1091
+ root.addEventListener(
1092
+ "blur",
1093
+ (event) => {
1094
+ const target = event.target;
1095
+ if (!target || !this.controller) {
1096
+ return;
1097
+ }
1098
+ if (target instanceof HTMLInputElement && target.dataset["role"] === "editor") {
1099
+ this.controller.commitCellEdit();
1100
+ }
1101
+ },
1102
+ true
1103
+ );
1104
+ root.addEventListener("dragstart", (event) => {
1105
+ const dragEvent = event;
1106
+ const target = event.target;
1107
+ const headerCell = target?.closest(".header-cell[data-column]");
1108
+ if (!headerCell || !this.snapshot || !canGridMoveColumns2(this.snapshot.options)) {
1109
+ event.preventDefault();
1110
+ return;
1111
+ }
1112
+ const columnName = headerCell.dataset["column"];
1113
+ if (!columnName || !dragEvent.dataTransfer) {
1114
+ event.preventDefault();
1115
+ return;
1116
+ }
1117
+ this.draggedColumnName = columnName;
1118
+ this.dropTargetColumnName = null;
1119
+ dragEvent.dataTransfer.effectAllowed = "move";
1120
+ dragEvent.dataTransfer.setData("text/plain", columnName);
1121
+ headerCell.classList.add("is-dragging");
1122
+ });
1123
+ root.addEventListener("dragover", (event) => {
1124
+ const dragEvent = event;
1125
+ const target = event.target;
1126
+ const headerCell = target?.closest(".header-cell[data-column]");
1127
+ if (!headerCell || !this.draggedColumnName || !dragEvent.dataTransfer) {
1128
+ return;
1129
+ }
1130
+ const columnName = headerCell.dataset["column"];
1131
+ if (!columnName || columnName === this.draggedColumnName) {
1132
+ return;
1133
+ }
1134
+ event.preventDefault();
1135
+ dragEvent.dataTransfer.dropEffect = "move";
1136
+ if (this.dropTargetColumnName !== columnName) {
1137
+ root.querySelectorAll(".header-cell.is-drag-target").forEach((element) => element.classList.remove("is-drag-target"));
1138
+ this.dropTargetColumnName = columnName;
1139
+ headerCell.classList.add("is-drag-target");
1140
+ }
1141
+ });
1142
+ root.addEventListener("dragleave", (event) => {
1143
+ const target = event.target;
1144
+ const headerCell = target?.closest(".header-cell[data-column]");
1145
+ if (headerCell && headerCell.dataset["column"] === this.dropTargetColumnName) {
1146
+ headerCell.classList.remove("is-drag-target");
1147
+ }
1148
+ });
1149
+ root.addEventListener("drop", (event) => {
1150
+ event.preventDefault();
1151
+ const dragEvent = event;
1152
+ const target = event.target;
1153
+ const headerCell = target?.closest(".header-cell[data-column]");
1154
+ const targetColumn = headerCell?.dataset["column"];
1155
+ const sourceColumn = this.draggedColumnName ?? dragEvent.dataTransfer?.getData("text/plain") ?? null;
1156
+ this.draggedColumnName = null;
1157
+ this.dropTargetColumnName = null;
1158
+ root.querySelectorAll(".header-cell.is-dragging, .header-cell.is-drag-target").forEach((element) => element.classList.remove("is-dragging", "is-drag-target"));
1159
+ if (!sourceColumn || !targetColumn || sourceColumn === targetColumn || !this.controller) {
1160
+ return;
1161
+ }
1162
+ this.controller.moveVisibleColumn(sourceColumn, targetColumn);
1163
+ });
1164
+ root.addEventListener("dragend", () => {
1165
+ this.draggedColumnName = null;
1166
+ this.dropTargetColumnName = null;
1167
+ root.querySelectorAll(".header-cell.is-dragging, .header-cell.is-drag-target").forEach((element) => element.classList.remove("is-dragging", "is-drag-target"));
1168
+ });
1169
+ root.addEventListener(
1170
+ "scroll",
1171
+ (event) => {
1172
+ const target = event.target;
1173
+ if (!target?.classList.contains("grid-table") || this.suppressScrollEvent) {
1174
+ return;
1175
+ }
1176
+ this.scrollPosition = target.scrollTop;
1177
+ if (!this.snapshot?.pipeline.virtualizationEnabled) {
1178
+ return;
1179
+ }
1180
+ const stickyChromeHeight = this.measuredHeaderStickyHeight + this.measuredFilterStickyHeight;
1181
+ const bodyScrollTop = Math.max(0, this.scrollPosition - stickyChromeHeight);
1182
+ const overscan = 4;
1183
+ const nextStartIndex = Math.max(
1184
+ 0,
1185
+ Math.floor(bodyScrollTop / this.snapshot.rowSize) - overscan
1186
+ );
1187
+ if (nextStartIndex === this.lastVirtualStartIndex) {
1188
+ return;
1189
+ }
1190
+ if (this.scrollFrame !== null) {
1191
+ cancelAnimationFrame(this.scrollFrame);
1192
+ }
1193
+ this.scrollFrame = requestAnimationFrame(() => {
1194
+ this.scrollFrame = null;
1195
+ this.render();
1196
+ });
1197
+ },
1198
+ true
1199
+ );
1200
+ root.__uiGridBound = true;
1201
+ }
1202
+ render() {
1203
+ const root = this.ensureShadowRoot();
1204
+ const snapshot = this.snapshot;
1205
+ const previousGridTable = root.querySelector(".grid-table");
1206
+ if (previousGridTable && !this.suppressScrollEvent) {
1207
+ this.scrollPosition = previousGridTable.scrollTop;
1208
+ }
1209
+ if (!snapshot) {
1210
+ root.innerHTML = `<style>${GRID_CORE_CSS}</style><section class="grid-shell ui-grid-shell"><div class="empty-state ui-grid-no-row-overlay"><strong>No grid options provided.</strong></div></section>`;
1211
+ return;
1212
+ }
1213
+ const controller = this.controller;
1214
+ const options = snapshot.options;
1215
+ const labels = snapshot.labels;
1216
+ const templateColumns = snapshot.gridTemplateColumns;
1217
+ const sortEnabled = controller.isSortingEnabled();
1218
+ const filterEnabled = controller.isFilteringEnabled();
1219
+ const groupingEnabled = controller.isGroupingEnabled();
1220
+ const pinningEnabled = controller.isPinningEnabled();
1221
+ const paginationEnabled = controller.isPaginationEnabled();
1222
+ const showPagination = controller.shouldShowPaginationControls();
1223
+ const virtualizationEnabled = snapshot.pipeline.virtualizationEnabled;
1224
+ const viewportHeight = options.viewportHeight ?? 560;
1225
+ const headerStickyTop = this.measuredHeaderStickyHeight || options.headerRowHeight || 50;
1226
+ const stickyChromeHeight = this.measuredHeaderStickyHeight + this.measuredFilterStickyHeight;
1227
+ const bodyViewportHeight = Math.max(snapshot.rowSize, viewportHeight - stickyChromeHeight);
1228
+ const visibleRowCount = snapshot.pipeline.visibleRows.length;
1229
+ const totalRows = options.data.length;
1230
+ const pinnedCount = Object.keys(snapshot.pinnedColumns).length;
1231
+ let startIndex = 0;
1232
+ let itemsToRender = snapshot.pipeline.displayItems;
1233
+ let virtualOffset = 0;
1234
+ const totalVirtualHeight = snapshot.pipeline.displayItems.length * snapshot.rowSize;
1235
+ if (virtualizationEnabled) {
1236
+ const bodyScrollTop = Math.max(0, this.scrollPosition - stickyChromeHeight);
1237
+ const overscan = 4;
1238
+ startIndex = Math.max(0, Math.floor(bodyScrollTop / snapshot.rowSize) - overscan);
1239
+ this.lastVirtualStartIndex = startIndex;
1240
+ const visibleCount = Math.ceil(bodyViewportHeight / snapshot.rowSize) + overscan * 2;
1241
+ itemsToRender = snapshot.pipeline.displayItems.slice(
1242
+ startIndex,
1243
+ Math.min(snapshot.pipeline.displayItems.length, startIndex + visibleCount)
1244
+ );
1245
+ virtualOffset = startIndex * snapshot.rowSize;
1246
+ } else {
1247
+ this.lastVirtualStartIndex = -1;
1248
+ }
1249
+ const slotRegistry = this.renderSlotRegistry(snapshot.visibleColumns);
1250
+ const header = snapshot.visibleColumns.map(
1251
+ (column) => this.renderHeaderCell(column, sortEnabled, groupingEnabled, pinningEnabled, options)
1252
+ ).join("");
1253
+ const filterRow = filterEnabled ? `<div class="filter-grid ui-grid-header" style="grid-template-columns:${templateColumns}">${snapshot.visibleColumns.map((column) => this.renderFilterCell(column)).join("")}</div>` : "";
1254
+ const bodyContent = itemsToRender.map((item, index) => this.renderDisplayItem(item, startIndex + index)).join("");
1255
+ const body = snapshot.pipeline.displayItems.length > 0 ? virtualizationEnabled ? `<div class="grid-virtual-spacer" style="height:${totalVirtualHeight}px"><div class="body-grid ui-grid-canvas grid-virtual-body" style="grid-template-columns:${templateColumns};top:${virtualOffset}px">${bodyContent}</div></div>` : `<div class="body-grid ui-grid-canvas" style="grid-template-columns:${templateColumns}">${bodyContent}</div>` : `<div class="empty-state ui-grid-no-row-overlay"><strong>${escapeHtml(options.emptyMessage ?? labels.emptyHeading)}</strong><p>${escapeHtml(labels.emptyDescription)}</p></div>`;
1256
+ const pagination = paginationEnabled && showPagination ? this.renderPagination(snapshot) : "";
1257
+ const hero = `<header class="grid-hero"><div><p class="eyebrow">Vanilla web component</p><h1>${escapeHtml(options.title ?? "UI Grid")}</h1><p class="deck">Framework-neutral custom element with Shadow DOM styling, slot templates, pinning, virtualization, editing, tree view, grouping, and state restore.</p></div><div class="hero-actions"><button type="button" class="action action-secondary" data-action="benchmark">Benchmark</button><button type="button" class="action action-secondary" data-action="export-csv">Export CSV</button><div class="stats-card"><span>${visibleRowCount}</span><small>${escapeHtml(labels.statsVisibleRows)}</small></div></div></header>`;
1258
+ const metrics = `<section class="metrics-strip" aria-label="Grid metrics"><article><strong>${snapshot.pipeline.pipelineMs.toFixed(2)} ms</strong><span>pipeline</span></article><article><strong>${virtualizationEnabled ? "On" : "Off"}</strong><span>virtualization</span></article><article><strong>${pinnedCount}</strong><span>pinned columns</span></article><article><strong>${escapeHtml(this.benchmarkAverage)}</strong><span>benchmark avg</span></article></section>`;
1259
+ const toolbar = `<div class="grid-toolbar"><div><strong>${visibleRowCount}</strong><span>${escapeHtml(labels.toolbarOf)} ${totalRows} ${escapeHtml(labels.toolbarRows)}</span></div><p>${escapeHtml(options.id)}</p></div>`;
1260
+ const gridTableStyle = `${virtualizationEnabled ? `height:${viewportHeight}px;overflow-y:auto;` : ""}--ui-grid-header-sticky-top:${headerStickyTop}px;`;
1261
+ root.innerHTML = `<style>${GRID_CORE_CSS}</style><section class="grid-shell ui-grid-shell">${slotRegistry}${hero}${metrics}<section class="grid-frame ui-grid" role="grid" aria-label="${escapeHtml(options.title ?? "Data grid")}">${toolbar}<div class="grid-table ui-grid-contents-wrapper" style="${gridTableStyle}"><div class="header-grid ui-grid-header ui-grid-header-canvas" style="grid-template-columns:${templateColumns}">${header}</div>${filterRow}${body}</div>${pagination}</section></section>`;
1262
+ const gridTable = root.querySelector(".grid-table");
1263
+ if (gridTable && this.scrollPosition > 0 && virtualizationEnabled) {
1264
+ this.suppressScrollEvent = true;
1265
+ gridTable.scrollTop = this.scrollPosition;
1266
+ requestAnimationFrame(() => {
1267
+ this.suppressScrollEvent = false;
1268
+ });
1269
+ }
1270
+ const headerGrid = root.querySelector(".header-grid");
1271
+ const filterGrid = root.querySelector(".filter-grid");
1272
+ const nextHeaderStickyHeight = headerGrid?.offsetHeight ?? 0;
1273
+ const nextFilterStickyHeight = filterGrid?.offsetHeight ?? 0;
1274
+ const stickyHeightsChanged = nextHeaderStickyHeight !== this.measuredHeaderStickyHeight || nextFilterStickyHeight !== this.measuredFilterStickyHeight;
1275
+ this.measuredHeaderStickyHeight = nextHeaderStickyHeight;
1276
+ this.measuredFilterStickyHeight = nextFilterStickyHeight;
1277
+ if (stickyHeightsChanged && !this.stickyHeightRelayoutQueued) {
1278
+ this.stickyHeightRelayoutQueued = true;
1279
+ requestAnimationFrame(() => {
1280
+ this.stickyHeightRelayoutQueued = false;
1281
+ if (!this.snapshot) {
1282
+ return;
1283
+ }
1284
+ this.render();
1285
+ });
1286
+ }
1287
+ }
1288
+ renderHeaderCell(column, sortEnabled, groupingEnabled, pinningEnabled, options) {
1289
+ const controller = this.controller;
1290
+ const sortDirection = controller.getSortDirection(column);
1291
+ const sortLabel = controller.sortButtonLabel(column);
1292
+ const groupingLabel = controller.groupingButtonLabel(column);
1293
+ const canSort = sortEnabled && controller.isColumnSortable(column);
1294
+ const canGroup = groupingEnabled && column.enableGrouping !== false;
1295
+ const canPin = pinningEnabled && controller.isColumnPinnable(column);
1296
+ const isPinned = controller.isPinned(column);
1297
+ const pinOffset = isPinned ? controller.pinnedOffset(column) : null;
1298
+ const isPinnedLeftLast = controller.isPinnedLeftLast(column);
1299
+ const isPinnedRightFirst = controller.isPinnedRightFirst(column);
1300
+ const isDragTarget = this.dropTargetColumnName === column.name;
1301
+ const isDragging = this.draggedColumnName === column.name;
1302
+ const classes = [
1303
+ "header-cell",
1304
+ sortDirection !== SORT_DIRECTIONS2.none ? "is-active" : "",
1305
+ isPinned ? "is-pinned" : "",
1306
+ isPinnedLeftLast ? "is-pinned-left-last" : "",
1307
+ isPinnedRightFirst ? "is-pinned-right-first" : "",
1308
+ this.openPinMenuColumn === column.name ? "is-pin-menu-open" : "",
1309
+ isDragTarget ? "is-drag-target" : "",
1310
+ isDragging ? "is-dragging" : ""
1311
+ ].filter(Boolean).join(" ");
1312
+ const stickyStyle = pinOffset ? `${pinOffset.side}:${pinOffset.offset};` : "";
1313
+ const sortIconKey = sortDirection === SORT_DIRECTIONS2.asc ? "sortAsc" : sortDirection === SORT_DIRECTIONS2.desc ? "sortDesc" : "sortNone";
1314
+ const draggable = canGridMoveColumns2(options) ? 'draggable="true"' : "";
1315
+ const pinLabel = isPinned ? this.snapshot?.labels.unpin ?? "Unpin" : this.snapshot?.labels.pinColumn ?? "Pin";
1316
+ return `<div class="${classes}" data-column="${escapeHtml(column.name)}" ${draggable} style="${stickyStyle}"><span class="header-label">${escapeHtml(controller.headerLabel(column))}</span><span class="header-actions">${sortEnabled ? `<button type="button" class="header-action" data-action="sort" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(sortLabel)}" ${canSort ? "" : "disabled"}>${this.renderControlIcon(sortIconKey)}<span class="sr-only">${escapeHtml(sortLabel)}</span></button>` : ""}${groupingEnabled ? `<button type="button" class="chip-action${controller.isColumnGrouped(column) ? " chip-action-active" : ""}" data-action="group" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(groupingLabel)}" ${canGroup ? "" : "disabled"}>${this.renderControlIcon("group")}<span class="sr-only">${escapeHtml(groupingLabel)}</span></button>` : ""}${canPin ? `<div class="pin-control${this.openPinMenuColumn === column.name ? " pin-control-open" : ""}"><button type="button" class="chip-action pin-trigger${isPinned ? " chip-action-active" : ""}" data-action="pin-trigger" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(pinLabel)}">${this.renderControlIcon("pin")}<span class="sr-only">${escapeHtml(pinLabel)}</span></button><div class="pin-menu" role="menu" aria-label="${escapeHtml(pinLabel)}"><button type="button" class="pin-menu-action" data-action="pin-left" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(this.snapshot?.labels.pinLeft ?? "Pin left")}">${this.renderIconWithClass("control-icon", "pinLeft")}<span class="sr-only">${escapeHtml(this.snapshot?.labels.pinLeft ?? "Pin left")}</span></button><button type="button" class="pin-menu-action" data-action="pin-right" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(this.snapshot?.labels.pinRight ?? "Pin right")}">${this.renderIconWithClass("control-icon", "pinRight")}<span class="sr-only">${escapeHtml(this.snapshot?.labels.pinRight ?? "Pin right")}</span></button></div></div>` : ""}</span></div>`;
1317
+ }
1318
+ renderFilterCell(column) {
1319
+ const value = this.snapshot?.activeFilters[column.name] ?? "";
1320
+ const controller = this.controller;
1321
+ const canFilter = controller.isColumnFilterable(column);
1322
+ const pinOffset = controller.isPinned(column) ? controller.pinnedOffset(column) : null;
1323
+ const classes = [
1324
+ "filter-cell",
1325
+ controller.isPinned(column) ? "is-pinned" : "",
1326
+ controller.isPinnedLeftLast(column) ? "is-pinned-left-last" : "",
1327
+ controller.isPinnedRightFirst(column) ? "is-pinned-right-first" : ""
1328
+ ].filter(Boolean).join(" ");
1329
+ const stickyStyle = pinOffset ? `${pinOffset.side}:${pinOffset.offset};` : "";
1330
+ return `<label class="${classes}" style="${stickyStyle}"><input class="ui-grid-filter-input" data-role="filter" data-column="${escapeHtml(column.name)}" placeholder="${escapeHtml(controller.filterPlaceholder(column))}" value="${escapeHtml(value)}" ${canFilter ? "" : "disabled"}></label>`;
1331
+ }
1332
+ renderDisplayItem(item, displayIndex) {
1333
+ if (!this.snapshot || !this.controller) {
1334
+ return "";
1335
+ }
1336
+ if (item.kind === "group") {
1337
+ const group = asGroupItem(item);
1338
+ const iconKey = group.collapsed ? "groupCollapsed" : "groupExpanded";
1339
+ const disclosureLabel = this.controller.groupDisclosureLabel(group);
1340
+ return `<button type="button" class="group-row ui-grid-row ui-grid-group-row" data-action="toggle-group" data-group="${escapeHtml(group.id)}" data-collapsed="${group.collapsed ? "true" : "false"}" style="grid-column: 1 / -1; padding-inline-start:${group.depth * 20 + 10}px">${this.renderIconWithClass("toggle-icon group-disclosure-icon", iconKey)}<span class="sr-only">${escapeHtml(disclosureLabel)}</span><strong>${escapeHtml(group.field)}: ${escapeHtml(group.label)}</strong><span>${group.count} ${escapeHtml(this.snapshot.labels.groupRowsSuffix)}</span></button>`;
1341
+ }
1342
+ if (item.kind === "expandable") {
1343
+ const row = item.row;
1344
+ return `<div class="expandable-row ui-grid-row ui-grid-expandable-row" style="grid-column:1 / -1">${this.renderExpandableTemplate(row)}</div>`;
1345
+ }
1346
+ if (!isRowItem(item)) {
1347
+ return "";
1348
+ }
1349
+ return this.snapshot.visibleColumns.map((column) => this.renderBodyCell(item.row, column, displayIndex)).join("");
1350
+ }
1351
+ renderBodyCell(row, column, displayIndex) {
1352
+ if (!this.snapshot || !this.controller) {
1353
+ return "";
1354
+ }
1355
+ const controller = this.controller;
1356
+ const rowId = row.id;
1357
+ const columnName = column.name;
1358
+ const editing = controller.isEditingCell(rowId, columnName);
1359
+ const pinOffset = controller.isPinned(column) ? controller.pinnedOffset(column) : null;
1360
+ const treeToggle = controller.showTreeToggle(row, column) ? `<button type="button" class="row-toggle" data-action="toggle-tree" data-row="${escapeHtml(rowId)}" aria-label="${escapeHtml(controller.treeToggleLabel(row))}">${this.renderIconWithClass("toggle-icon", controller.isTreeRowExpanded(row) ? "treeExpanded" : "treeCollapsed")}<span class="sr-only">${escapeHtml(controller.treeToggleLabel(row))}</span></button>` : "";
1361
+ const expandToggle = controller.showExpandToggle(row, column) ? `<button type="button" class="row-toggle row-toggle-expand" data-action="toggle-expand" data-row="${escapeHtml(rowId)}" aria-label="${escapeHtml(controller.expandToggleLabel(row))}">${this.renderIconWithClass("toggle-icon", row.expanded ? "expandExpanded" : "expandCollapsed")}<span class="sr-only">${escapeHtml(controller.expandToggleLabel(row))}</span></button>` : "";
1362
+ const content = editing ? `<input class="cell-editor" data-role="editor" data-row="${escapeHtml(rowId)}" data-column="${escapeHtml(columnName)}" type="${escapeHtml(controller.editorInputType(column))}" value="${escapeHtml(this.snapshot.editingValue)}">` : this.renderCellTemplate(row, column, displayIndex);
1363
+ const classes = [
1364
+ "body-cell",
1365
+ "ui-grid-cell",
1366
+ displayIndex % 2 !== 0 ? "body-cell-odd" : "",
1367
+ column.align === "center" ? "align-center" : "",
1368
+ column.align === "end" ? "align-end" : "",
1369
+ controller.isPinned(column) ? "is-pinned" : "",
1370
+ controller.isPinnedLeftLast(column) ? "is-pinned-left-last" : "",
1371
+ controller.isPinnedRightFirst(column) ? "is-pinned-right-first" : "",
1372
+ this.focusedCell?.rowId === rowId && this.focusedCell.columnName === columnName ? "cell-focused" : "",
1373
+ editing ? "cell-editing" : ""
1374
+ ].filter(Boolean).join(" ");
1375
+ const stickyStyle = pinOffset ? `${pinOffset.side}:${pinOffset.offset};` : "";
1376
+ return `<div class="${classes}" tabindex="0" data-row="${escapeHtml(rowId)}" data-column="${escapeHtml(columnName)}" style="${stickyStyle}"><div class="cell-shell" style="padding-inline-start:${escapeHtml(controller.cellIndent(row, column))}">${treeToggle}${expandToggle}${content}</div></div>`;
1377
+ }
1378
+ renderPagination(snapshot) {
1379
+ const pageSizes = snapshot.options.paginationPageSizes ?? [10, 25, 50, 100];
1380
+ return `<footer class="pagination-bar ui-grid-pagination"><p>${snapshot.firstRowIndex + 1}-${snapshot.lastRowIndex + 1} of ${snapshot.pipeline.totalItems}</p><div class="pagination-controls"><button type="button" class="action action-secondary pagination-button" data-action="page-prev" aria-label="${escapeHtml(snapshot.labels.paginationPrevious)}" ${snapshot.currentPage <= 1 ? "disabled" : ""}>${this.renderIconWithClass("pagination-icon", "paginationPrev")}<span class="sr-only">${escapeHtml(snapshot.labels.paginationPrevious)}</span></button><span>${escapeHtml(snapshot.labels.paginationPage)} ${snapshot.currentPage} ${escapeHtml(snapshot.labels.paginationOf)} ${snapshot.totalPages}</span><button type="button" class="action action-secondary pagination-button" data-action="page-next" aria-label="${escapeHtml(snapshot.labels.paginationNext)}" ${snapshot.currentPage >= snapshot.totalPages ? "disabled" : ""}>${this.renderIconWithClass("pagination-icon", "paginationNext")}<span class="sr-only">${escapeHtml(snapshot.labels.paginationNext)}</span></button><label class="pagination-size"><span class="sr-only">${escapeHtml(snapshot.labels.paginationRows)}</span><select class="page-size" data-role="page-size">${pageSizes.map(
1381
+ (size) => `<option value="${size}" ${size === snapshot.pageSize ? "selected" : ""}>${size}</option>`
1382
+ ).join("")}</select></label></div></footer>`;
1383
+ }
1384
+ renderSlotRegistry(columns) {
1385
+ const cellSlots = columns.map((column) => `<slot name="${escapeHtml(this.cellSlotName(column))}"></slot>`).join("");
1386
+ return `<div hidden class="slot-registry">${cellSlots}<slot name="expandable-row"></slot></div>`;
1387
+ }
1388
+ renderCellTemplate(row, column, displayIndex) {
1389
+ const templateMarkup = this.getTemplateMarkup(this.cellSlotName(column));
1390
+ if (!templateMarkup) {
1391
+ return `<span class="cell-value">${escapeHtml(this.controller?.displayValue(row, column) ?? "")}</span>`;
1392
+ }
1393
+ const rawRow = row.entity;
1394
+ const rawValue = getCellValue2(rawRow, column);
1395
+ const valueText = rawValue == null ? "" : String(rawValue);
1396
+ return this.interpolateTemplate(templateMarkup, {
1397
+ $implicit: rawValue,
1398
+ value: rawValue,
1399
+ valueText,
1400
+ valueLower: valueText.toLowerCase(),
1401
+ row: rawRow,
1402
+ column,
1403
+ rowIndex: this.rowIndexFor(row, displayIndex)
1404
+ });
1405
+ }
1406
+ renderExpandableTemplate(row) {
1407
+ const templateMarkup = this.getTemplateMarkup("expandable-row");
1408
+ if (!templateMarkup) {
1409
+ return `<p>${escapeHtml(String(row.entity["name"] ?? row.id))}</p>`;
1410
+ }
1411
+ return this.interpolateTemplate(templateMarkup, {
1412
+ $implicit: row.entity,
1413
+ row: row.entity,
1414
+ expanded: row.expanded,
1415
+ rowIndex: this.rowIndexFor(row)
1416
+ });
1417
+ }
1418
+ getTemplateMarkup(slotName) {
1419
+ const template = this.querySelector(`template[slot="${slotName}"]`);
1420
+ return template?.innerHTML ?? null;
1421
+ }
1422
+ interpolateTemplate(templateMarkup, context) {
1423
+ return templateMarkup.replace(/{{\s*([^}]+?)\s*}}/g, (_match, expression) => {
1424
+ const value = this.resolveTemplateValue(context, String(expression).trim());
1425
+ return escapeHtml(value);
1426
+ });
1427
+ }
1428
+ resolveTemplateValue(context, expression) {
1429
+ const segments = expression.split(".").filter(Boolean);
1430
+ let current = context;
1431
+ for (const segment of segments) {
1432
+ if (current == null || typeof current !== "object") {
1433
+ return "";
1434
+ }
1435
+ current = current[segment];
1436
+ }
1437
+ return current ?? "";
1438
+ }
1439
+ rowIndexFor(row, fallback = 0) {
1440
+ return this.snapshot?.pipeline.visibleRows.findIndex((candidate) => candidate.id === row.id) ?? fallback;
1441
+ }
1442
+ cellSlotName(column) {
1443
+ return `cell-${column.name}`;
1444
+ }
1445
+ downloadCsv() {
1446
+ if (!this.snapshot) {
1447
+ return;
1448
+ }
1449
+ const csv = exportCsvRows(this.snapshot.visibleColumns, this.snapshot.pipeline.visibleRows);
1450
+ const filename = sanitizeDownloadFilename(`${this.snapshot.options.id}.csv`);
1451
+ downloadGridCsvFile(csv, filename);
1452
+ }
1453
+ renderControlIcon(key) {
1454
+ return this.renderIconWithClass("control-icon", key);
1455
+ }
1456
+ renderIconWithClass(svgClass, key) {
1457
+ const icon = this.iconOverrides[key] ?? DEFAULT_ICONS[key];
1458
+ const viewBox = escapeHtml(icon.viewBox ?? "0 0 24 24");
1459
+ const path = escapeHtml(icon.path);
1460
+ return `<svg class="${svgClass}" viewBox="${viewBox}" aria-hidden="true" focusable="false"><path d="${path}"></path></svg>`;
1461
+ }
1462
+ };
1463
+ async function defineStandaloneUiGridElement(tagName = "ui-grid-element") {
1464
+ if (!customElements.get(tagName)) {
1465
+ customElements.define(tagName, UiGridStandaloneElement);
1466
+ }
1467
+ }
1468
+
1469
+ // src/index.ts
1470
+ async function registerVanillaUiGridRustModule(module, input) {
1471
+ await module.default(input);
1472
+ registerRustWasmGridEngine({
1473
+ buildPipeline(context) {
1474
+ return module.build_pipeline_js(context);
1475
+ }
1476
+ });
1477
+ }
1478
+ async function mountVanillaUiGrid(target, options, rustModule, tagName = "ui-grid-element") {
1479
+ if (rustModule) {
1480
+ await registerVanillaUiGridRustModule(rustModule);
1481
+ }
1482
+ await defineStandaloneUiGridElement(tagName);
1483
+ const grid = document.createElement(tagName);
1484
+ grid.options = options;
1485
+ target.replaceChildren(grid);
1486
+ return grid;
1487
+ }
1488
+ export {
1489
+ UiGridStandaloneElement,
1490
+ createVanillaGridController,
1491
+ defineStandaloneUiGridElement,
1492
+ defineStandaloneUiGridElement as defineUiGridElement,
1493
+ mountVanillaUiGrid,
1494
+ registerVanillaUiGridRustModule
1495
+ };