@zengrid/angular 1.2.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,971 @@
1
+ import {
2
+ Component,
3
+ ElementRef,
4
+ ChangeDetectionStrategy,
5
+ afterNextRender,
6
+ viewChild,
7
+ contentChildren,
8
+ input,
9
+ output,
10
+ model,
11
+ effect,
12
+ inject,
13
+ PLATFORM_ID,
14
+ ChangeDetectorRef,
15
+ OutputEmitterRef,
16
+ NgZone,
17
+ DestroyRef,
18
+ } from '@angular/core';
19
+ import { isPlatformBrowser } from '@angular/common';
20
+ import { Grid } from '@zengrid/core';
21
+ import type {
22
+ GridOptions,
23
+ GridExportOptions,
24
+ GridEvents,
25
+ SortState,
26
+ FilterModel,
27
+ CellRange,
28
+ CellRef,
29
+ ColumnDef,
30
+ ColumnStateSnapshot,
31
+ GridStateSnapshot,
32
+ GridPlugin,
33
+ ColumnConstraints,
34
+ HeaderRenderer,
35
+ VisibleRange,
36
+ PaginationConfig,
37
+ LoadingConfig,
38
+ CellOverflowConfig,
39
+ RowHeightConfig,
40
+ SortIcons,
41
+ RendererCacheConfig,
42
+ } from '@zengrid/core';
43
+ import { GridApiImpl, PluginHost } from '@zengrid/core';
44
+ import { resolveOperationMode, type OperationMode } from '@zengrid/shared';
45
+ import { EVENT_MAP } from '../utils/event-map';
46
+ import { ZenColumnComponent } from './zen-column.component';
47
+ import { TemplateBridgeService } from '../services/template-bridge.service';
48
+ import { ZEN_GRID_CONFIG } from '../services/zen-grid-config.token';
49
+
50
+ @Component({
51
+ selector: 'zen-grid',
52
+ standalone: true,
53
+ template: `
54
+ @if (_isBrowser) {
55
+ <div #gridContainer class="zen-grid-container"></div>
56
+ } @else {
57
+ <div class="zen-grid-placeholder" [style.height.px]="placeholderHeight"></div>
58
+ }
59
+ <div style="display:none">
60
+ <ng-content></ng-content>
61
+ </div>
62
+ `,
63
+ styles: [`
64
+ :host {
65
+ display: block;
66
+ position: relative;
67
+ height: 100%;
68
+ }
69
+ .zen-grid-container {
70
+ width: 100%;
71
+ height: 100%;
72
+ }
73
+ .zen-grid-placeholder {
74
+ width: 100%;
75
+ background: #f5f5f5;
76
+ }
77
+ `],
78
+ changeDetection: ChangeDetectionStrategy.OnPush,
79
+ })
80
+ export class ZenGridComponent {
81
+ private readonly platformId = inject(PLATFORM_ID);
82
+ private readonly cdr = inject(ChangeDetectorRef);
83
+ private readonly ngZone = inject(NgZone);
84
+ private readonly destroyRef = inject(DestroyRef);
85
+ private readonly templateBridge = inject(TemplateBridgeService);
86
+ private readonly globalConfig = inject(ZEN_GRID_CONFIG, { optional: true });
87
+
88
+ readonly _isBrowser = isPlatformBrowser(this.platformId);
89
+
90
+ get placeholderHeight(): number {
91
+ const rowHeight = this.getResolvedRowHeight();
92
+ const height = typeof rowHeight === 'number' ? rowHeight : 30;
93
+ return this.rowCount() * height;
94
+ }
95
+
96
+ private readonly gridContainerRef = viewChild<ElementRef<HTMLElement>>('gridContainer');
97
+ private readonly columnChildren = contentChildren(ZenColumnComponent);
98
+
99
+ private grid: Grid | null = null;
100
+ private gridReady = false;
101
+ private readonly explicitInputs = new Set<'rowHeight' | 'colWidth' | 'columns'>();
102
+ private pendingModelDataSync: any[][] | null = null;
103
+
104
+ // --- Core Inputs ---
105
+ readonly rowCount = input.required<number>();
106
+ readonly colCount = input.required<number>();
107
+ readonly rowHeight = input<number | number[], number | number[]>(30, {
108
+ transform: (value: number | number[]) => this.markExplicitInput('rowHeight', value),
109
+ });
110
+ readonly colWidth = input<number | number[], number | number[]>(100, {
111
+ transform: (value: number | number[]) => this.markExplicitInput('colWidth', value),
112
+ });
113
+ readonly columns = input<ColumnDef[], ColumnDef[]>([], {
114
+ transform: (value: ColumnDef[]) => this.markExplicitInput('columns', value),
115
+ });
116
+
117
+ // --- Feature Inputs ---
118
+ readonly enableSelection = input<boolean | undefined>(undefined);
119
+ readonly enableMultiSelection = input<boolean | undefined>(undefined);
120
+ readonly selectionType = input<'cell' | 'row' | 'column' | 'range' | undefined>(undefined);
121
+ readonly enableColumnResize = input<boolean | undefined>(undefined);
122
+ readonly enableColumnDrag = input<boolean | undefined>(undefined);
123
+ readonly enableKeyboardNavigation = input<boolean | undefined>(undefined);
124
+ readonly enableA11y = input<boolean | undefined>(undefined);
125
+ readonly enableCellPooling = input<boolean | undefined>(undefined);
126
+ readonly hideLastColumnBorder = input<boolean | undefined>(undefined);
127
+
128
+ // --- Mode Inputs ---
129
+ readonly dataMode = input<OperationMode | undefined>(undefined);
130
+
131
+ // --- Backend Callback Inputs ---
132
+ readonly onDataRequest = input<GridOptions['onDataRequest']>(undefined);
133
+ readonly onLoadMoreRows = input<GridOptions['onLoadMoreRows']>(undefined);
134
+
135
+ // --- Config Object Inputs ---
136
+ readonly pagination = input<PaginationConfig | undefined>(undefined);
137
+ readonly loading = input<LoadingConfig | undefined>(undefined);
138
+ readonly infiniteScrolling = input<GridOptions['infiniteScrolling']>(undefined);
139
+ readonly rowHeightMode = input<'fixed' | 'auto' | 'content-aware' | undefined>(undefined);
140
+ readonly rowHeightConfig = input<RowHeightConfig | undefined>(undefined);
141
+ readonly headerHeight = input<GridOptions['headerHeight']>(undefined);
142
+ readonly columnResize = input<GridOptions['columnResize']>(undefined);
143
+ readonly columnDrag = input<GridOptions['columnDrag']>(undefined);
144
+ readonly cellOverflow = input<CellOverflowConfig | undefined>(undefined);
145
+ readonly editing = input<GridOptions['editing']>(undefined);
146
+ readonly autoResize = input<boolean | undefined>(undefined);
147
+ readonly overscanRows = input<number | undefined>(undefined);
148
+ readonly overscanCols = input<number | undefined>(undefined);
149
+ readonly sortIcons = input<SortIcons | undefined>(undefined);
150
+ readonly rendererCache = input<RendererCacheConfig | undefined>(undefined);
151
+
152
+ // --- Callback Inputs (GridOptions event callbacks) ---
153
+ readonly onScrollCallback = input<GridOptions['onScroll']>(undefined);
154
+ readonly onCellClickCallback = input<GridOptions['onCellClick']>(undefined);
155
+ readonly onCellDoubleClickCallback = input<GridOptions['onCellDoubleClick']>(undefined);
156
+ readonly onCellContextMenuCallback = input<GridOptions['onCellContextMenu']>(undefined);
157
+ readonly onSelectionChangeCallback = input<GridOptions['onSelectionChange']>(undefined);
158
+ readonly onPageChangeCallback = input<GridOptions['onPageChange']>(undefined);
159
+ readonly onColumnWidthsChangeCallback = input<GridOptions['onColumnWidthsChange']>(undefined);
160
+
161
+ // --- Plugin Input ---
162
+ readonly plugins = input<GridPlugin[]>([]);
163
+
164
+ // --- Two-way Model Signals ---
165
+ readonly data = model<any[][]>([]);
166
+ readonly selection = model<CellRange[]>([]);
167
+ readonly sortState = model<SortState[]>([]);
168
+ readonly filterState = model<FilterModel[]>([]);
169
+ readonly currentPage = model<number>(1);
170
+
171
+ // --- Cell Events ---
172
+ readonly cellClick = output<GridEvents['cell:click']>();
173
+ readonly cellDoubleClick = output<GridEvents['cell:doubleClick']>();
174
+ readonly cellContextMenu = output<GridEvents['cell:contextMenu']>();
175
+ readonly cellChange = output<GridEvents['cell:change']>();
176
+ readonly cellBeforeChange = output<GridEvents['cell:beforeChange']>();
177
+ readonly cellAfterChange = output<GridEvents['cell:afterChange']>();
178
+
179
+ // --- Selection Events ---
180
+ readonly selectionChangeEvent = output<GridEvents['selection:change']>({ alias: 'selectionChanged' });
181
+ readonly selectionStart = output<GridEvents['selection:start']>();
182
+ readonly selectionEnd = output<GridEvents['selection:end']>();
183
+
184
+ // --- Editing Events ---
185
+ readonly editStart = output<GridEvents['edit:start']>();
186
+ readonly editEnd = output<GridEvents['edit:end']>();
187
+ readonly editCommit = output<GridEvents['edit:commit']>();
188
+ readonly editCancel = output<GridEvents['edit:cancel']>();
189
+
190
+ // --- Scroll Events ---
191
+ readonly scroll = output<GridEvents['scroll']>();
192
+ readonly scrollStart = output<GridEvents['scroll:start']>();
193
+ readonly scrollEnd = output<GridEvents['scroll:end']>();
194
+
195
+ // --- Sort Events ---
196
+ readonly sortChange = output<GridEvents['sort:change']>();
197
+ readonly sortBeforeSort = output<GridEvents['sort:beforeSort']>();
198
+ readonly sortAfterSort = output<GridEvents['sort:afterSort']>();
199
+
200
+ // --- Filter Events ---
201
+ readonly filterChange = output<GridEvents['filter:change']>();
202
+ readonly filterBeforeFilter = output<GridEvents['filter:beforeFilter']>();
203
+ readonly filterAfterFilter = output<GridEvents['filter:afterFilter']>();
204
+ readonly filterExport = output<GridEvents['filter:export']>();
205
+ readonly filterStart = output<GridEvents['filter:start']>();
206
+ readonly filterEnd = output<GridEvents['filter:end']>();
207
+ readonly filterError = output<GridEvents['filter:error']>();
208
+ readonly filterClear = output<GridEvents['filter:clear']>();
209
+
210
+ // --- Focus Events ---
211
+ readonly focusChange = output<GridEvents['focus:change']>();
212
+ readonly focusIn = output<GridEvents['focus:in']>();
213
+ readonly focusOut = output<GridEvents['focus:out']>();
214
+
215
+ // --- Keyboard Events ---
216
+ readonly keyDown = output<GridEvents['key:down']>();
217
+ readonly keyUp = output<GridEvents['key:up']>();
218
+ readonly keyPress = output<GridEvents['key:press']>();
219
+
220
+ // --- Column Events ---
221
+ readonly columnResizeEvent = output<GridEvents['column:resize']>({ alias: 'columnResize' });
222
+ readonly columnMove = output<GridEvents['column:move']>();
223
+ readonly columnHide = output<GridEvents['column:hide']>();
224
+ readonly columnShow = output<GridEvents['column:show']>();
225
+ readonly columnDragStart = output<GridEvents['column:dragStart']>();
226
+ readonly columnDragEvent = output<GridEvents['column:drag']>({ alias: 'columnDrag' });
227
+ readonly columnDragEnd = output<GridEvents['column:dragEnd']>();
228
+ readonly columnDragCancel = output<GridEvents['column:dragCancel']>();
229
+
230
+ // --- Header Events ---
231
+ readonly headerClick = output<GridEvents['header:click']>();
232
+ readonly headerDoubleClick = output<GridEvents['header:doubleClick']>();
233
+ readonly headerContextMenu = output<GridEvents['header:contextMenu']>();
234
+ readonly headerHover = output<GridEvents['header:hover']>();
235
+ readonly headerSortClick = output<GridEvents['header:sort:click']>();
236
+ readonly headerFilterClick = output<GridEvents['header:filter:click']>();
237
+ readonly headerCheckboxChange = output<GridEvents['header:checkbox:change']>();
238
+
239
+ // --- Lifecycle Events ---
240
+ readonly renderStart = output<GridEvents['render:start']>();
241
+ readonly renderEnd = output<GridEvents['render:end']>();
242
+ readonly dataLoad = output<GridEvents['data:load']>();
243
+ readonly dataChange = output<GridEvents['data:change']>();
244
+ readonly loadingStart = output<GridEvents['loading:start']>();
245
+ readonly loadingEnd = output<GridEvents['loading:end']>();
246
+ readonly loadingProgress = output<GridEvents['loading:progress']>();
247
+ readonly undoRedoChange = output<GridEvents['undo-redo:change']>();
248
+ readonly gridDestroy = output<GridEvents['destroy']>();
249
+ readonly gridError = output<GridEvents['error']>();
250
+ readonly gridWarning = output<GridEvents['warning']>();
251
+
252
+ // --- Row Events ---
253
+ readonly rowInsert = output<GridEvents['row:insert']>();
254
+ readonly rowDelete = output<GridEvents['row:delete']>();
255
+ readonly rowMove = output<GridEvents['row:move']>();
256
+
257
+ // --- Clipboard Events ---
258
+ readonly copy = output<GridEvents['copy']>();
259
+ readonly cut = output<GridEvents['cut']>();
260
+ readonly paste = output<GridEvents['paste']>();
261
+
262
+ private outputMap: Record<string, OutputEmitterRef<any>> = {};
263
+ private eventHandlers: Array<{ event: string; handler: (payload: any) => void }> = [];
264
+
265
+ constructor() {
266
+ this.buildOutputMap();
267
+
268
+ this.destroyRef.onDestroy(() => {
269
+ if (this.grid) {
270
+ for (const { event, handler } of this.eventHandlers) {
271
+ this.grid.off(event as any, handler);
272
+ }
273
+ this.grid.destroy();
274
+ this.grid = null;
275
+ }
276
+ this.eventHandlers = [];
277
+ this.gridReady = false;
278
+ this.templateBridge.destroyAll();
279
+ });
280
+
281
+ afterNextRender(() => {
282
+ this.ngZone.runOutsideAngular(() => {
283
+ this.initGrid();
284
+ });
285
+ });
286
+
287
+ // Sync data input → grid (skip first run, initGrid handles initial data)
288
+ effect(() => {
289
+ const data = this.data();
290
+ if (!this.gridReady || this.getResolvedDataMode() !== 'frontend') return;
291
+ if (!Array.isArray(data)) return;
292
+
293
+ if (this.pendingModelDataSync === data) {
294
+ this.pendingModelDataSync = null;
295
+ return;
296
+ }
297
+
298
+ this.grid!.setData(data);
299
+ });
300
+
301
+ // Sync columns from content children or input (skip first run)
302
+ effect(() => {
303
+ this.columnChildren();
304
+ this.columns();
305
+ if (!this.gridReady) return;
306
+
307
+ const resolvedColumns = this.getComponentColumns();
308
+ if (resolvedColumns !== undefined) {
309
+ this.grid!.updateOptions({ columns: resolvedColumns });
310
+ }
311
+ });
312
+
313
+ // Sync option changes with previous-value diffing (only sends changed values)
314
+ const prevOptions: Record<string, any> = {};
315
+ effect(() => {
316
+ const current: Array<[string, any]> = [
317
+ ['rowHeight', this.getResolvedRowHeight()],
318
+ ['colWidth', this.getResolvedColWidth()],
319
+ ['selectionType', this.selectionType()],
320
+ ['enableSelection', this.enableSelection()],
321
+ ['enableMultiSelection', this.enableMultiSelection()],
322
+ ['enableKeyboardNavigation', this.enableKeyboardNavigation()],
323
+ ['enableA11y', this.enableA11y()],
324
+ ['enableColumnResize', this.enableColumnResize()],
325
+ ['enableColumnDrag', this.enableColumnDrag()],
326
+ ['dataMode', this.getConfiguredDataMode()],
327
+ ['pagination', this.pagination()],
328
+ ['loading', this.loading()],
329
+ ['infiniteScrolling', this.infiniteScrolling()],
330
+ ['rowHeightMode', this.rowHeightMode()],
331
+ ['rowHeightConfig', this.rowHeightConfig()],
332
+ ['columnResize', this.columnResize()],
333
+ ['columnDrag', this.columnDrag()],
334
+ ['cellOverflow', this.cellOverflow()],
335
+ ['autoResize', this.autoResize()],
336
+ ['overscanRows', this.overscanRows()],
337
+ ['overscanCols', this.overscanCols()],
338
+ ];
339
+
340
+ if (!this.gridReady) {
341
+ // Store initial values for diffing on next run
342
+ for (const [key, val] of current) {
343
+ prevOptions[key] = val;
344
+ }
345
+ return;
346
+ }
347
+
348
+ const changed: Partial<GridOptions> = {};
349
+ let hasChanges = false;
350
+
351
+ for (const [key, val] of current) {
352
+ if (val !== undefined && val !== prevOptions[key]) {
353
+ (changed as any)[key] = val;
354
+ hasChanges = true;
355
+ }
356
+ prevOptions[key] = val;
357
+ }
358
+
359
+ if (hasChanges) {
360
+ this.grid!.updateOptions(changed);
361
+ }
362
+ });
363
+ }
364
+
365
+ private markExplicitInput<T>(name: 'rowHeight' | 'colWidth' | 'columns', value: T): T {
366
+ this.explicitInputs.add(name);
367
+ return value;
368
+ }
369
+
370
+ private hasExplicitInput(name: 'rowHeight' | 'colWidth' | 'columns'): boolean {
371
+ return this.explicitInputs.has(name);
372
+ }
373
+
374
+ private getResolvedDataMode(): 'frontend' | 'backend' {
375
+ return resolveOperationMode(
376
+ {
377
+ mode: this.getConfiguredDataMode(),
378
+ callback:
379
+ this.onDataRequest() ??
380
+ (this.globalConfig?.['onDataRequest'] as GridOptions['onDataRequest'] | undefined),
381
+ },
382
+ { rowCount: this.rowCount() }
383
+ );
384
+ }
385
+
386
+ private getConfiguredDataMode(): OperationMode | undefined {
387
+ return this.dataMode() ?? (this.globalConfig?.['dataMode'] as OperationMode | undefined);
388
+ }
389
+
390
+ private getResolvedRowHeight(): number | number[] {
391
+ const configured = this.globalConfig?.['rowHeight'] as GridOptions['rowHeight'] | undefined;
392
+ return this.hasExplicitInput('rowHeight') || configured === undefined ? this.rowHeight() : configured;
393
+ }
394
+
395
+ private getResolvedColWidth(): number | number[] {
396
+ const configured = this.globalConfig?.['colWidth'] as GridOptions['colWidth'] | undefined;
397
+ return this.hasExplicitInput('colWidth') || configured === undefined ? this.colWidth() : configured;
398
+ }
399
+
400
+ private getComponentColumns(): ColumnDef[] | undefined {
401
+ const columnChildren = this.columnChildren();
402
+ if (columnChildren.length > 0) {
403
+ return columnChildren.map(c => c.toColumnDef(this.templateBridge));
404
+ }
405
+
406
+ const inputColumns = this.columns();
407
+ if (this.hasExplicitInput('columns') || inputColumns.length > 0) {
408
+ return inputColumns;
409
+ }
410
+
411
+ return undefined;
412
+ }
413
+
414
+ private buildOutputMap(): void {
415
+ this.outputMap = {
416
+ cellClick: this.cellClick,
417
+ cellDoubleClick: this.cellDoubleClick,
418
+ cellContextMenu: this.cellContextMenu,
419
+ cellChange: this.cellChange,
420
+ cellBeforeChange: this.cellBeforeChange,
421
+ cellAfterChange: this.cellAfterChange,
422
+ selectionChange: this.selectionChangeEvent,
423
+ selectionStart: this.selectionStart,
424
+ selectionEnd: this.selectionEnd,
425
+ editStart: this.editStart,
426
+ editEnd: this.editEnd,
427
+ editCommit: this.editCommit,
428
+ editCancel: this.editCancel,
429
+ scroll: this.scroll,
430
+ scrollStart: this.scrollStart,
431
+ scrollEnd: this.scrollEnd,
432
+ sortChange: this.sortChange,
433
+ sortBeforeSort: this.sortBeforeSort,
434
+ sortAfterSort: this.sortAfterSort,
435
+ filterChange: this.filterChange,
436
+ filterBeforeFilter: this.filterBeforeFilter,
437
+ filterAfterFilter: this.filterAfterFilter,
438
+ filterExport: this.filterExport,
439
+ filterStart: this.filterStart,
440
+ filterEnd: this.filterEnd,
441
+ filterError: this.filterError,
442
+ filterClear: this.filterClear,
443
+ focusChange: this.focusChange,
444
+ focusIn: this.focusIn,
445
+ focusOut: this.focusOut,
446
+ keyDown: this.keyDown,
447
+ keyUp: this.keyUp,
448
+ keyPress: this.keyPress,
449
+ columnResize: this.columnResizeEvent,
450
+ columnMove: this.columnMove,
451
+ columnHide: this.columnHide,
452
+ columnShow: this.columnShow,
453
+ columnDragStart: this.columnDragStart,
454
+ columnDrag: this.columnDragEvent,
455
+ columnDragEnd: this.columnDragEnd,
456
+ columnDragCancel: this.columnDragCancel,
457
+ headerClick: this.headerClick,
458
+ headerDoubleClick: this.headerDoubleClick,
459
+ headerContextMenu: this.headerContextMenu,
460
+ headerHover: this.headerHover,
461
+ headerSortClick: this.headerSortClick,
462
+ headerFilterClick: this.headerFilterClick,
463
+ headerCheckboxChange: this.headerCheckboxChange,
464
+ renderStart: this.renderStart,
465
+ renderEnd: this.renderEnd,
466
+ dataLoad: this.dataLoad,
467
+ dataChange: this.dataChange,
468
+ loadingStart: this.loadingStart,
469
+ loadingEnd: this.loadingEnd,
470
+ loadingProgress: this.loadingProgress,
471
+ undoRedoChange: this.undoRedoChange,
472
+ gridDestroy: this.gridDestroy,
473
+ gridError: this.gridError,
474
+ gridWarning: this.gridWarning,
475
+ rowInsert: this.rowInsert,
476
+ rowDelete: this.rowDelete,
477
+ rowMove: this.rowMove,
478
+ copy: this.copy,
479
+ cut: this.cut,
480
+ paste: this.paste,
481
+ };
482
+ }
483
+
484
+ private initGrid(): void {
485
+ const containerRef = this.gridContainerRef();
486
+ if (!containerRef) return;
487
+
488
+ const container = containerRef.nativeElement;
489
+ const dataValue = this.data();
490
+ const componentColumns = this.getComponentColumns();
491
+ const resolvedDataMode = this.getResolvedDataMode();
492
+
493
+ const options: GridOptions = {
494
+ ...(this.globalConfig ?? {}),
495
+ rowCount: this.rowCount(),
496
+ colCount: this.colCount(),
497
+ ...this.getOptionalOptions(),
498
+ rowHeight: this.getResolvedRowHeight(),
499
+ colWidth: this.getResolvedColWidth(),
500
+ ...(componentColumns !== undefined ? { columns: componentColumns } : {}),
501
+ };
502
+
503
+ this.grid = new Grid(container, options);
504
+
505
+ // Wire up events
506
+ this.wireEvents();
507
+
508
+ // Frontend mode owns the in-memory dataset, including an explicit empty array.
509
+ if (resolvedDataMode === 'frontend' && Array.isArray(dataValue)) {
510
+ this.grid.setData(dataValue);
511
+ }
512
+
513
+ // Install plugins
514
+ const pluginList = this.plugins();
515
+ for (const plugin of pluginList) {
516
+ this.grid.usePlugin(plugin);
517
+ }
518
+
519
+ this.grid.render();
520
+ this.gridReady = true;
521
+ }
522
+
523
+ private getOptionalOptions(): Partial<GridOptions> {
524
+ const opts: Partial<GridOptions> = {};
525
+
526
+ const enSel = this.enableSelection();
527
+ const enMulti = this.enableMultiSelection();
528
+ const selType = this.selectionType();
529
+ const enKb = this.enableKeyboardNavigation();
530
+ const enA11y = this.enableA11y();
531
+ const enPool = this.enableCellPooling();
532
+ const hideLastColBorder = this.hideLastColumnBorder();
533
+ const enResize = this.enableColumnResize();
534
+ const enDrag = this.enableColumnDrag();
535
+ const dMode = this.getConfiguredDataMode();
536
+ const dReq = this.onDataRequest();
537
+ const loadMore = this.onLoadMoreRows();
538
+ const pag = this.pagination();
539
+ const load = this.loading();
540
+ const infScroll = this.infiniteScrolling();
541
+ const rhMode = this.rowHeightMode();
542
+ const rhConfig = this.rowHeightConfig();
543
+ const hdrHeight = this.headerHeight();
544
+ const colResizeConf = this.columnResize();
545
+ const colDragConf = this.columnDrag();
546
+ const overflow = this.cellOverflow();
547
+ const editingConf = this.editing();
548
+ const autoRes = this.autoResize();
549
+ const oRows = this.overscanRows();
550
+ const oCols = this.overscanCols();
551
+ const sIcons = this.sortIcons();
552
+ const rCache = this.rendererCache();
553
+ const onScrollCb = this.onScrollCallback();
554
+ const onCellClickCb = this.onCellClickCallback();
555
+ const onCellDblClickCb = this.onCellDoubleClickCallback();
556
+ const onCellCtxCb = this.onCellContextMenuCallback();
557
+ const onSelChangeCb = this.onSelectionChangeCallback();
558
+ const onPageChangeCb = this.onPageChangeCallback();
559
+ const onColWidthsCb = this.onColumnWidthsChangeCallback();
560
+
561
+ if (enSel !== undefined) opts.enableSelection = enSel;
562
+ if (enMulti !== undefined) opts.enableMultiSelection = enMulti;
563
+ if (selType !== undefined) opts.selectionType = selType;
564
+ if (enKb !== undefined) opts.enableKeyboardNavigation = enKb;
565
+ if (enA11y !== undefined) opts.enableA11y = enA11y;
566
+ if (enPool !== undefined) opts.enableCellPooling = enPool;
567
+ if (hideLastColBorder !== undefined) opts.hideLastColumnBorder = hideLastColBorder;
568
+ if (enResize !== undefined) opts.enableColumnResize = enResize;
569
+ if (enDrag !== undefined) opts.enableColumnDrag = enDrag;
570
+ if (dMode !== undefined) opts.dataMode = dMode;
571
+ if (dReq !== undefined) opts.onDataRequest = dReq;
572
+ if (loadMore !== undefined) opts.onLoadMoreRows = loadMore;
573
+ if (pag !== undefined) opts.pagination = pag;
574
+ if (load !== undefined) opts.loading = load;
575
+ if (infScroll !== undefined) opts.infiniteScrolling = infScroll;
576
+ if (rhMode !== undefined) opts.rowHeightMode = rhMode;
577
+ if (rhConfig !== undefined) opts.rowHeightConfig = rhConfig;
578
+ if (hdrHeight !== undefined) opts.headerHeight = hdrHeight;
579
+ if (colResizeConf !== undefined) opts.columnResize = colResizeConf;
580
+ if (colDragConf !== undefined) opts.columnDrag = colDragConf;
581
+ if (overflow !== undefined) opts.cellOverflow = overflow;
582
+ if (editingConf !== undefined) opts.editing = editingConf;
583
+ if (autoRes !== undefined) opts.autoResize = autoRes;
584
+ if (oRows !== undefined) opts.overscanRows = oRows;
585
+ if (oCols !== undefined) opts.overscanCols = oCols;
586
+ if (sIcons !== undefined) opts.sortIcons = sIcons;
587
+ if (rCache !== undefined) opts.rendererCache = rCache;
588
+ if (onScrollCb !== undefined) opts.onScroll = onScrollCb;
589
+ if (onCellClickCb !== undefined) opts.onCellClick = onCellClickCb;
590
+ if (onCellDblClickCb !== undefined) opts.onCellDoubleClick = onCellDblClickCb;
591
+ if (onCellCtxCb !== undefined) opts.onCellContextMenu = onCellCtxCb;
592
+ if (onSelChangeCb !== undefined) opts.onSelectionChange = onSelChangeCb;
593
+ if (onPageChangeCb !== undefined) opts.onPageChange = onPageChangeCb;
594
+ if (onColWidthsCb !== undefined) opts.onColumnWidthsChange = onColWidthsCb;
595
+
596
+ return opts;
597
+ }
598
+
599
+ private wireEvents(): void {
600
+ if (!this.grid) return;
601
+
602
+ for (const [gridEvent, outputName] of Object.entries(EVENT_MAP)) {
603
+ const outputRef = this.outputMap[outputName];
604
+ if (!outputRef) continue;
605
+
606
+ const handler = (payload: any) => {
607
+ this.ngZone.run(() => {
608
+ outputRef.emit(payload);
609
+
610
+ // Update two-way model signals for specific events
611
+ if (gridEvent === 'selection:change' && payload?.ranges) {
612
+ this.selection.set(payload.ranges);
613
+ } else if (gridEvent === 'sort:change' && payload?.sortState) {
614
+ this.sortState.set(payload.sortState);
615
+ } else if (gridEvent === 'filter:change' && payload?.filterState) {
616
+ this.filterState.set(payload.filterState);
617
+ } else if (gridEvent === 'data:change') {
618
+ // data:change doesn't carry full data, skip model update
619
+ }
620
+
621
+ this.cdr.markForCheck();
622
+ });
623
+ };
624
+
625
+ this.grid.on(gridEvent as any, handler);
626
+ this.eventHandlers.push({ event: gridEvent, handler });
627
+ }
628
+ }
629
+
630
+ // --- Public API Methods ---
631
+
632
+ get gridInstance(): Grid | null {
633
+ return this.grid;
634
+ }
635
+
636
+ render(): void {
637
+ this.grid?.render();
638
+ }
639
+
640
+ refresh(): void {
641
+ this.grid?.refresh();
642
+ }
643
+
644
+ clearCache(): void {
645
+ this.grid?.clearCache();
646
+ }
647
+
648
+ updateCells(cells: CellRef[]): void {
649
+ this.grid?.updateCells(cells);
650
+ }
651
+
652
+ setData(data: any[][]): void {
653
+ if (this.getResolvedDataMode() === 'frontend') {
654
+ if (this.grid) {
655
+ this.pendingModelDataSync = data;
656
+ this.grid.setData(data);
657
+ }
658
+ }
659
+
660
+ this.data.set(data);
661
+ }
662
+
663
+ getData(row: number, col: number): any {
664
+ return this.grid?.getData(row, col);
665
+ }
666
+
667
+ // --- Sort API ---
668
+ sortColumn(column: number, direction: 'asc' | 'desc' | null = 'asc'): void {
669
+ if (direction === null) this.grid?.sort.clear();
670
+ else this.grid?.sort.apply([{ column, direction }]);
671
+ }
672
+
673
+ toggleSort(column: number): void {
674
+ this.grid?.sort.toggle(column);
675
+ }
676
+
677
+ getSortState(): SortState[] {
678
+ return this.grid?.sort.getState() ?? [];
679
+ }
680
+
681
+ clearSort(): void {
682
+ this.grid?.sort.clear();
683
+ }
684
+
685
+ setSortState(sortState: SortState[]): void {
686
+ this.grid?.sort.setState(sortState);
687
+ }
688
+
689
+ // --- Filter API ---
690
+ setFilter(column: number, operator: string, value: any): void {
691
+ this.grid?.filter.set(column, operator, value);
692
+ }
693
+
694
+ setColumnFilter(
695
+ column: number,
696
+ conditions: Array<{ operator: string; value: any }>,
697
+ logic: 'AND' | 'OR' = 'AND'
698
+ ): void {
699
+ this.grid?.filter.setColumn(column, conditions, logic);
700
+ }
701
+
702
+ getFilterState(): FilterModel[] {
703
+ return this.grid?.filter.getState() ?? [];
704
+ }
705
+
706
+ setFilterState(models: FilterModel[]): void {
707
+ this.grid?.filter.setState(models);
708
+ }
709
+
710
+ clearFilters(): void {
711
+ this.grid?.filter.clear();
712
+ }
713
+
714
+ clearColumnFilter(column: number): void {
715
+ this.grid?.filter.clearColumn(column);
716
+ }
717
+
718
+ setQuickFilter(query: string, columns?: number[]): void {
719
+ this.grid?.filter.setQuick(query, columns);
720
+ }
721
+
722
+ clearQuickFilter(): void {
723
+ this.grid?.filter.clearQuick();
724
+ }
725
+
726
+ // --- Export API ---
727
+ exportCSV(options: GridExportOptions = {}): string {
728
+ return this.grid?.export.csv(options) ?? '';
729
+ }
730
+
731
+ exportTSV(options: GridExportOptions = {}): string {
732
+ return this.grid?.export.tsv(options) ?? '';
733
+ }
734
+
735
+ // --- Pagination API ---
736
+ goToPage(page: number): void {
737
+ this.grid?.pagination.goTo(page);
738
+ this.currentPage.set(page);
739
+ }
740
+
741
+ nextPage(): void {
742
+ this.grid?.pagination.next();
743
+ this.currentPage.set(this.grid?.pagination.getCurrentPage() ?? 1);
744
+ }
745
+
746
+ previousPage(): void {
747
+ this.grid?.pagination.previous();
748
+ this.currentPage.set(this.grid?.pagination.getCurrentPage() ?? 1);
749
+ }
750
+
751
+ setPageSize(pageSize: number): void {
752
+ this.grid?.pagination.setPageSize(pageSize);
753
+ }
754
+
755
+ getCurrentPage(): number {
756
+ return this.grid?.pagination.getCurrentPage() ?? 1;
757
+ }
758
+
759
+ getPageSize(): number {
760
+ return this.grid?.pagination.getPageSize() ?? 0;
761
+ }
762
+
763
+ getTotalPages(): number {
764
+ return this.grid?.pagination.getTotalPages() ?? 0;
765
+ }
766
+
767
+ // --- Column API ---
768
+ resizeColumn(col: number, width: number): void {
769
+ this.grid?.resizeColumn(col, width);
770
+ }
771
+
772
+ autoFitColumn(col: number): void {
773
+ this.grid?.autoFitColumn(col);
774
+ }
775
+
776
+ autoFitAllColumns(): void {
777
+ this.grid?.autoFitAllColumns();
778
+ }
779
+
780
+ setColumnConstraints(col: number, constraints: ColumnConstraints): void {
781
+ this.grid?.setColumnConstraints(col, constraints);
782
+ }
783
+
784
+ // --- State Persistence API ---
785
+ getColumnState(): ColumnStateSnapshot[] {
786
+ return this.grid?.getColumnState() ?? [];
787
+ }
788
+
789
+ applyColumnState(
790
+ state: ColumnStateSnapshot[],
791
+ options?: { applyWidth?: boolean; applyVisibility?: boolean; applyOrder?: boolean }
792
+ ): void {
793
+ this.grid?.applyColumnState(state, options);
794
+ }
795
+
796
+ getStateSnapshot(): GridStateSnapshot | null {
797
+ return this.grid?.getStateSnapshot() ?? null;
798
+ }
799
+
800
+ applyStateSnapshot(snapshot: GridStateSnapshot): void {
801
+ this.grid?.applyStateSnapshot(snapshot);
802
+ }
803
+
804
+ // --- Scroll API ---
805
+ scrollToCell(row: number, col: number): void {
806
+ this.grid?.scrollToCell(row, col);
807
+ }
808
+
809
+ getScrollPosition(): { top: number; left: number } {
810
+ return this.grid?.getScrollPosition() ?? { top: 0, left: 0 };
811
+ }
812
+
813
+ getVisibleRange(): VisibleRange | null {
814
+ return this.grid?.getVisibleRange() ?? null;
815
+ }
816
+
817
+ // --- Header API ---
818
+ registerHeaderRenderer(name: string, renderer: HeaderRenderer): void {
819
+ this.grid?.registerHeaderRenderer(name, renderer);
820
+ }
821
+
822
+ updateHeader(columnIndex: number): void {
823
+ this.grid?.updateHeader(columnIndex);
824
+ }
825
+
826
+ updateAllHeaders(): void {
827
+ this.grid?.updateAllHeaders();
828
+ }
829
+
830
+ // --- Plugin API ---
831
+ usePlugin(plugin: GridPlugin): void {
832
+ this.grid?.usePlugin(plugin);
833
+ }
834
+
835
+ // --- Stats ---
836
+ getStats(): any {
837
+ return this.grid?.getStats();
838
+ }
839
+
840
+ getDimensions(): { width: number; height: number } {
841
+ return this.grid?.getDimensions() ?? { width: 0, height: 0 };
842
+ }
843
+
844
+ getColumnPosition(col: number): { x: number; width: number } {
845
+ return this.grid?.getColumnPosition(col) ?? { x: 0, width: 0 };
846
+ }
847
+
848
+ // --- Data Mode ---
849
+ getDataMode(): 'frontend' | 'backend' {
850
+ return this.grid?.getDataMode() ?? 'frontend';
851
+ }
852
+
853
+ // --- Sort (missing methods) ---
854
+ getColumnSort(column: number): any {
855
+ return this.grid?.getColumnSort(column);
856
+ }
857
+
858
+ getSortIcons(): { asc: string; desc: string } {
859
+ return this.grid?.getSortIcons() ?? { asc: '▲', desc: '▼' };
860
+ }
861
+
862
+ getSortMode(): 'frontend' | 'backend' {
863
+ return this.grid?.getSortMode() ?? 'frontend';
864
+ }
865
+
866
+ // --- Filter (missing methods) ---
867
+ getFieldFilterState(): any {
868
+ return this.grid?.getFieldFilterState();
869
+ }
870
+
871
+ getFilterExports(): any {
872
+ return this.grid?.getFilterExports();
873
+ }
874
+
875
+ getFilterMode(): 'frontend' | 'backend' {
876
+ return this.grid?.getFilterMode() ?? 'frontend';
877
+ }
878
+
879
+ getQuickFilter(): { query: string; columns: number[] | null } {
880
+ return this.grid?.getQuickFilter() ?? { query: '', columns: null };
881
+ }
882
+
883
+ // --- Pagination (missing methods) ---
884
+ firstPage(): void {
885
+ this.grid?.firstPage();
886
+ this.currentPage.set(this.grid?.getCurrentPage() ?? 1);
887
+ }
888
+
889
+ lastPage(): void {
890
+ this.grid?.lastPage();
891
+ this.currentPage.set(this.grid?.getCurrentPage() ?? 1);
892
+ }
893
+
894
+ // --- Scroll (missing methods) ---
895
+ scrollThroughCells(
896
+ cells: Array<{ row: number; col: number }>,
897
+ options?: {
898
+ delayMs?: number;
899
+ smooth?: boolean;
900
+ onCellReached?: (cell: { row: number; col: number }, index: number) => void;
901
+ }
902
+ ): { promise: Promise<void>; abort: () => void } {
903
+ return this.grid?.scrollThroughCells(cells, options) ?? {
904
+ promise: Promise.resolve(),
905
+ abort: () => {},
906
+ };
907
+ }
908
+
909
+ // --- Column Resize/Drag (missing methods) ---
910
+ attachColumnResize(headerElement: HTMLElement): void {
911
+ this.grid?.attachColumnResize(headerElement);
912
+ }
913
+
914
+ detachColumnResize(): void {
915
+ this.grid?.detachColumnResize();
916
+ }
917
+
918
+ updateColumnResizeHandles(): void {
919
+ this.grid?.updateColumnResizeHandles();
920
+ }
921
+
922
+ attachColumnDrag(headerElement: HTMLElement): void {
923
+ this.grid?.attachColumnDrag(headerElement);
924
+ }
925
+
926
+ detachColumnDrag(): void {
927
+ this.grid?.detachColumnDrag();
928
+ }
929
+
930
+ isDragging(): boolean {
931
+ return this.grid?.isDragging() ?? false;
932
+ }
933
+
934
+ // --- Renderer Registration ---
935
+ registerRenderer(name: string, renderer: any): void {
936
+ this.grid?.registerRenderer(name, renderer);
937
+ }
938
+
939
+ // --- Options Update ---
940
+ updateOptions(options: Partial<GridOptions>): void {
941
+ this.grid?.updateOptions(options);
942
+ }
943
+
944
+ // --- Header (missing methods) ---
945
+ refreshHeaders(): void {
946
+ this.grid?.refreshHeaders();
947
+ }
948
+
949
+ // --- Infinite Scroll ---
950
+ resetInfiniteScrolling(): void {
951
+ this.grid?.resetInfiniteScrolling();
952
+ }
953
+
954
+ getSlidingWindowStats(): any {
955
+ return this.grid?.getSlidingWindowStats();
956
+ }
957
+
958
+ // --- Plugin System (advanced) ---
959
+ getStore(): any {
960
+ return this.grid?.getStore();
961
+ }
962
+
963
+ getPluginHost(): PluginHost | null {
964
+ return this.grid?.getPluginHost() ?? null;
965
+ }
966
+
967
+ getGridApi(): GridApiImpl | null {
968
+ return this.grid?.getGridApi() ?? null;
969
+ }
970
+
971
+ }