@angular-bootstrap/ngbootstrap 0.0.11 → 0.0.13

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.
@@ -1,10 +1,10 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Input, Directive, EventEmitter, Output, ChangeDetectionStrategy, Component, inject, Injectable, signal, TemplateRef, QueryList, ContentChildren, ContentChild, InjectionToken, Optional, Inject, ElementRef, HostListener, HostBinding, ViewChild, forwardRef, ViewChildren } from '@angular/core';
2
+ import { Input, Directive, EventEmitter, Output, ChangeDetectionStrategy, Component, inject, Injectable, signal, TemplateRef, Optional, HostListener, ChangeDetectorRef, QueryList, ContentChildren, ContentChild, ViewChild, InjectionToken, Inject, ElementRef, HostBinding, forwardRef, ViewChildren } from '@angular/core';
3
3
  import * as i1 from '@angular/common';
4
4
  import { CommonModule } from '@angular/common';
5
5
  import * as i2 from '@angular/forms';
6
6
  import { FormBuilder, FormControl, Validators, FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
7
- import { firstValueFrom, isObservable } from 'rxjs';
7
+ import { isObservable, firstValueFrom } from 'rxjs';
8
8
 
9
9
  class NgbCellTemplate {
10
10
  template;
@@ -12,10 +12,10 @@ class NgbCellTemplate {
12
12
  constructor(template) {
13
13
  this.template = template;
14
14
  }
15
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbCellTemplate, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
16
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: NgbCellTemplate, isStandalone: true, selector: "ng-template[ngbCell]", inputs: { field: ["ngbCell", "field"] }, ngImport: i0 });
15
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbCellTemplate, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
16
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: NgbCellTemplate, isStandalone: true, selector: "ng-template[ngbCell]", inputs: { field: ["ngbCell", "field"] }, ngImport: i0 });
17
17
  }
18
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbCellTemplate, decorators: [{
18
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbCellTemplate, decorators: [{
19
19
  type: Directive,
20
20
  args: [{ selector: 'ng-template[ngbCell]' }]
21
21
  }], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { field: [{
@@ -28,10 +28,10 @@ class NgbEditorTemplate {
28
28
  constructor(template) {
29
29
  this.template = template;
30
30
  }
31
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbEditorTemplate, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
32
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: NgbEditorTemplate, isStandalone: true, selector: "ng-template[ngbEditor]", inputs: { field: ["ngbEditor", "field"] }, ngImport: i0 });
31
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbEditorTemplate, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
32
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: NgbEditorTemplate, isStandalone: true, selector: "ng-template[ngbEditor]", inputs: { field: ["ngbEditor", "field"] }, ngImport: i0 });
33
33
  }
34
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbEditorTemplate, decorators: [{
34
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbEditorTemplate, decorators: [{
35
35
  type: Directive,
36
36
  args: [{ selector: 'ng-template[ngbEditor]' }]
37
37
  }], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { field: [{
@@ -44,10 +44,10 @@ class NgbFilterTemplate {
44
44
  constructor(template) {
45
45
  this.template = template;
46
46
  }
47
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbFilterTemplate, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
48
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: NgbFilterTemplate, isStandalone: true, selector: "ng-template[ngbFilter]", inputs: { field: ["ngbFilter", "field"] }, ngImport: i0 });
47
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbFilterTemplate, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
48
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: NgbFilterTemplate, isStandalone: true, selector: "ng-template[ngbFilter]", inputs: { field: ["ngbFilter", "field"] }, ngImport: i0 });
49
49
  }
50
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbFilterTemplate, decorators: [{
50
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbFilterTemplate, decorators: [{
51
51
  type: Directive,
52
52
  args: [{ selector: 'ng-template[ngbFilter]' }]
53
53
  }], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { field: [{
@@ -59,10 +59,10 @@ class NgbGlobalFilterTemplate {
59
59
  constructor(template) {
60
60
  this.template = template;
61
61
  }
62
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbGlobalFilterTemplate, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
63
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: NgbGlobalFilterTemplate, isStandalone: true, selector: "ng-template[ngbGlobalFilter]", ngImport: i0 });
62
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbGlobalFilterTemplate, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
63
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: NgbGlobalFilterTemplate, isStandalone: true, selector: "ng-template[ngbGlobalFilter]", ngImport: i0 });
64
64
  }
65
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbGlobalFilterTemplate, decorators: [{
65
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbGlobalFilterTemplate, decorators: [{
66
66
  type: Directive,
67
67
  args: [{ selector: 'ng-template[ngbGlobalFilter]' }]
68
68
  }], ctorParameters: () => [{ type: i0.TemplateRef }] });
@@ -71,10 +71,10 @@ class NgbRowDetailTemplate {
71
71
  constructor(template) {
72
72
  this.template = template;
73
73
  }
74
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbRowDetailTemplate, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
75
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: NgbRowDetailTemplate, isStandalone: true, selector: "ng-template[ngbRowDetail]", ngImport: i0 });
74
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbRowDetailTemplate, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
75
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: NgbRowDetailTemplate, isStandalone: true, selector: "ng-template[ngbRowDetail]", ngImport: i0 });
76
76
  }
77
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbRowDetailTemplate, decorators: [{
77
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbRowDetailTemplate, decorators: [{
78
78
  type: Directive,
79
79
  args: [{ selector: 'ng-template[ngbRowDetail]' }]
80
80
  }], ctorParameters: () => [{ type: i0.TemplateRef }] });
@@ -114,8 +114,8 @@ class NgbPaginationComponent {
114
114
  return;
115
115
  this.pageChange.emit(p);
116
116
  }
117
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbPaginationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
118
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: NgbPaginationComponent, isStandalone: true, selector: "ngb-pagination", inputs: { page: "page", pageSize: "pageSize", collectionSize: "collectionSize", maxSize: "maxSize" }, outputs: { pageChange: "pageChange" }, ngImport: i0, template: `
117
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbPaginationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
118
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.9", type: NgbPaginationComponent, isStandalone: true, selector: "ngb-pagination", inputs: { page: "page", pageSize: "pageSize", collectionSize: "collectionSize", maxSize: "maxSize" }, outputs: { pageChange: "pageChange" }, ngImport: i0, template: `
119
119
  <nav *ngIf="totalPages >= 1" aria-label="Pagination">
120
120
  <ul class="pagination pagination-sm justify-content-center mb-0">
121
121
  <li class="page-item" [class.disabled]="page === 1">
@@ -134,7 +134,7 @@ class NgbPaginationComponent {
134
134
  </nav>
135
135
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
136
136
  }
137
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbPaginationComponent, decorators: [{
137
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbPaginationComponent, decorators: [{
138
138
  type: Component,
139
139
  args: [{
140
140
  selector: 'ngb-pagination',
@@ -190,10 +190,10 @@ class NgbExportService {
190
190
  throw new Error('No ExcelExportAdapter provided');
191
191
  return this.excel.export(p);
192
192
  }
193
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbExportService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
194
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbExportService, providedIn: 'root' });
193
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbExportService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
194
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbExportService, providedIn: 'root' });
195
195
  }
196
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbExportService, decorators: [{
196
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbExportService, decorators: [{
197
197
  type: Injectable,
198
198
  args: [{ providedIn: 'root' }]
199
199
  }] });
@@ -323,14 +323,313 @@ class XlsxAdapter {
323
323
  class ExportButtonDirective {
324
324
  // Expose the TemplateRef so the grid can render it with a context
325
325
  templateRef = inject(TemplateRef);
326
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ExportButtonDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
327
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: ExportButtonDirective, isStandalone: true, selector: "[ngbExportButton]", ngImport: i0 });
326
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: ExportButtonDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
327
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: ExportButtonDirective, isStandalone: true, selector: "[ngbExportButton]", ngImport: i0 });
328
328
  }
329
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ExportButtonDirective, decorators: [{
329
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: ExportButtonDirective, decorators: [{
330
330
  type: Directive,
331
331
  args: [{ selector: '[ngbExportButton]' }]
332
332
  }] });
333
333
 
334
+ class NgbGridHighlightDirective {
335
+ grid;
336
+ rowKey;
337
+ highlightColumnIndex;
338
+ highlightedIndex = [];
339
+ constructor(grid) {
340
+ this.grid = grid;
341
+ }
342
+ ngOnInit() {
343
+ this.apply();
344
+ }
345
+ ngOnChanges(_ch) {
346
+ this.apply();
347
+ }
348
+ apply() {
349
+ if (!this.grid)
350
+ return;
351
+ this.grid.highlightRowKey = this.rowKey;
352
+ this.grid.highlightColKey = this.highlightColumnIndex;
353
+ this.grid.highlightedIndex = this.highlightedIndex ?? [];
354
+ this.grid.updateHighlightCache();
355
+ }
356
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbGridHighlightDirective, deps: [{ token: Datagrid, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
357
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: NgbGridHighlightDirective, isStandalone: true, selector: "[ngbGridHighlight]", inputs: { rowKey: ["ngbGridHighlight", "rowKey"], highlightColumnIndex: "highlightColumnIndex", highlightedIndex: "highlightedIndex" }, usesOnChanges: true, ngImport: i0 });
358
+ }
359
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbGridHighlightDirective, decorators: [{
360
+ type: Directive,
361
+ args: [{
362
+ selector: '[ngbGridHighlight]',
363
+ standalone: true
364
+ }]
365
+ }], ctorParameters: () => [{ type: Datagrid, decorators: [{
366
+ type: Optional
367
+ }] }], propDecorators: { rowKey: [{
368
+ type: Input,
369
+ args: ['ngbGridHighlight']
370
+ }], highlightColumnIndex: [{
371
+ type: Input
372
+ }], highlightedIndex: [{
373
+ type: Input
374
+ }] } });
375
+
376
+ const MIN_WIDTH = 40;
377
+ const MAX_WIDTH = 320;
378
+ const MAX_SAMPLE_ROWS = 50;
379
+ const CHAR_WIDTH_FACTOR = 0.6;
380
+ const CHROME_TOLERANCE = 12; // small buffer to ignore reapplication of padding/borders
381
+ class NgbSyncColgroupDirective {
382
+ el;
383
+ syncId;
384
+ syncRole = 'header';
385
+ static registry = new Map();
386
+ mutationObserver;
387
+ resizeObserver;
388
+ rafId = null;
389
+ constructor(el) {
390
+ this.el = el;
391
+ }
392
+ ngAfterViewInit() {
393
+ if (!this.syncId)
394
+ return;
395
+ const entry = this.ensureEntry();
396
+ if (this.syncRole === 'header') {
397
+ entry.header = this;
398
+ this.measureAndDistribute();
399
+ }
400
+ else {
401
+ entry.bodies.add(this);
402
+ if (entry.header)
403
+ entry.header.measureAndDistribute();
404
+ if (entry.widths?.length)
405
+ this.apply(entry.widths);
406
+ this.startObservers();
407
+ }
408
+ }
409
+ ngOnDestroy() {
410
+ if (!this.syncId)
411
+ return;
412
+ if (this.rafId != null)
413
+ cancelAnimationFrame(this.rafId);
414
+ this.mutationObserver?.disconnect();
415
+ this.resizeObserver?.disconnect();
416
+ const entry = NgbSyncColgroupDirective.registry.get(this.syncId);
417
+ if (!entry)
418
+ return;
419
+ if (entry.header === this)
420
+ entry.header = undefined;
421
+ entry.bodies.delete(this);
422
+ if (!entry.header && entry.bodies.size === 0) {
423
+ NgbSyncColgroupDirective.registry.delete(this.syncId);
424
+ }
425
+ }
426
+ onResize() {
427
+ if (this.syncRole === 'header')
428
+ this.measureAndDistribute();
429
+ }
430
+ measureAndDistribute() {
431
+ const widths = this.measure();
432
+ if (!widths.length)
433
+ return;
434
+ const entry = this.ensureEntry();
435
+ if (entry.widths && this.areWidthsEqual(entry.widths, widths))
436
+ return;
437
+ entry.widths = widths;
438
+ this.apply(widths);
439
+ entry.bodies.forEach(b => b.apply(widths));
440
+ }
441
+ ensureEntry() {
442
+ let entry = NgbSyncColgroupDirective.registry.get(this.syncId);
443
+ if (!entry) {
444
+ entry = { bodies: new Set() };
445
+ NgbSyncColgroupDirective.registry.set(this.syncId, entry);
446
+ }
447
+ return entry;
448
+ }
449
+ startObservers() {
450
+ const bodyTable = this.tableEl();
451
+ const tbody = bodyTable?.querySelector('tbody');
452
+ if (tbody && typeof MutationObserver !== 'undefined') {
453
+ this.mutationObserver = new MutationObserver(() => this.scheduleMeasure());
454
+ this.mutationObserver.observe(tbody, { childList: true, subtree: true, characterData: true });
455
+ }
456
+ if (bodyTable && typeof ResizeObserver !== 'undefined') {
457
+ this.resizeObserver = new ResizeObserver(() => this.scheduleMeasure());
458
+ this.resizeObserver.observe(bodyTable);
459
+ }
460
+ }
461
+ scheduleMeasure() {
462
+ if (this.rafId != null)
463
+ return;
464
+ this.rafId = requestAnimationFrame(() => {
465
+ this.rafId = null;
466
+ const entry = NgbSyncColgroupDirective.registry.get(this.syncId);
467
+ entry?.header?.measureAndDistribute();
468
+ });
469
+ }
470
+ colElements() {
471
+ return Array.from(this.el.nativeElement.querySelectorAll('col'));
472
+ }
473
+ tableEl() {
474
+ return this.el.nativeElement.closest('table');
475
+ }
476
+ firstBodyForSync() {
477
+ const entry = NgbSyncColgroupDirective.registry.get(this.syncId);
478
+ const bodyDir = this.syncRole === 'body'
479
+ ? this
480
+ : entry ? Array.from(entry.bodies.values())[0] : undefined;
481
+ return bodyDir?.tableEl() ?? null;
482
+ }
483
+ measure() {
484
+ const bodyTable = this.firstBodyForSync();
485
+ const cols = this.colElements();
486
+ const entry = NgbSyncColgroupDirective.registry.get(this.syncId);
487
+ if (!cols.length)
488
+ return [];
489
+ if (!bodyTable) {
490
+ return entry?.widths ?? [];
491
+ }
492
+ const bodyRows = Array.from(bodyTable.querySelectorAll('tbody tr')).slice(0, MAX_SAMPLE_ROWS);
493
+ const headerTable = this.syncRole === 'header' ? this.tableEl() : null;
494
+ const headerRow = headerTable?.querySelector('thead tr');
495
+ return cols.map((col, colIndex) => {
496
+ const fixedWidth = (col.getAttribute('data-fixed') ?? '').toLowerCase() === 'true';
497
+ if (fixedWidth) {
498
+ const explicit = parseFloat(col.style.width || '') || MIN_WIDTH;
499
+ return Math.min(Math.max(explicit, MIN_WIDTH), MAX_WIDTH);
500
+ }
501
+ // Measure header content width if available
502
+ let headerContentWidth = 0;
503
+ if (headerRow) {
504
+ const th = headerRow.querySelectorAll('th')[colIndex];
505
+ if (th) {
506
+ headerContentWidth = this.measureCellContent(th);
507
+ }
508
+ }
509
+ // Measure body content width
510
+ let maxContent = 0;
511
+ for (const tr of bodyRows) {
512
+ const td = tr.querySelectorAll('td')[colIndex];
513
+ if (!td)
514
+ continue;
515
+ maxContent = Math.max(maxContent, this.measureCellContent(td));
516
+ if (maxContent >= MAX_WIDTH)
517
+ break;
518
+ }
519
+ // Use the maximum of header and body content widths
520
+ let finalContent = Math.max(headerContentWidth, maxContent);
521
+ if (finalContent === 0)
522
+ finalContent = MIN_WIDTH;
523
+ let width = Math.min(Math.max(finalContent, MIN_WIDTH), MAX_WIDTH);
524
+ const prev = entry?.widths?.[colIndex];
525
+ if (prev && width > prev) {
526
+ // If the only change is re-adding cell chrome (common when interactive elements span the cell),
527
+ // keep the previous stable width to avoid incremental growth.
528
+ const delta = width - prev;
529
+ if (delta <= CHROME_TOLERANCE)
530
+ width = prev;
531
+ }
532
+ return width;
533
+ });
534
+ }
535
+ apply(widths) {
536
+ const cols = this.colElements();
537
+ widths.forEach((w, i) => {
538
+ const col = cols[i];
539
+ if (!col)
540
+ return;
541
+ col.style.width = w ? `${w}px` : '';
542
+ });
543
+ }
544
+ measureCellContent(cell) {
545
+ const style = getComputedStyle(cell);
546
+ const cellClientWidth = cell.clientWidth;
547
+ const cellRect = cell.getBoundingClientRect();
548
+ const chrome = (parseFloat(style.paddingLeft) || 0) +
549
+ (parseFloat(style.paddingRight) || 0) +
550
+ (parseFloat(style.borderLeftWidth) || 0) +
551
+ (parseFloat(style.borderRightWidth) || 0);
552
+ const interactiveEls = Array.from(cell.querySelectorAll('input, select, textarea, button'));
553
+ let interactiveWidth = null;
554
+ if (interactiveEls.length) {
555
+ interactiveWidth = interactiveEls.reduce((total, el) => {
556
+ const rect = el.getBoundingClientRect();
557
+ const elStyle = getComputedStyle(el);
558
+ const widthStr = (el.style.width || elStyle.width || '').toString().trim();
559
+ const usesPercentWidth = widthStr.endsWith('%');
560
+ const matchesCellWidth = (!!cellClientWidth && rect.width && Math.abs(rect.width - cellClientWidth) <= 1) ||
561
+ (cellRect.width > 0 && rect.width > 0 && Math.abs(rect.width - cellRect.width) <= 1);
562
+ const spansCell = usesPercentWidth || matchesCellWidth;
563
+ const margin = (parseFloat(elStyle.marginLeft) || 0) + (parseFloat(elStyle.marginRight) || 0);
564
+ if (spansCell) {
565
+ const textWidth = this.measureTextWidth((el.textContent ?? '').trim(), elStyle);
566
+ const paddingBorder = (parseFloat(elStyle.paddingLeft) || 0) +
567
+ (parseFloat(elStyle.paddingRight) || 0) +
568
+ (parseFloat(elStyle.borderLeftWidth) || 0) +
569
+ (parseFloat(elStyle.borderRightWidth) || 0);
570
+ return total + Math.ceil(textWidth + paddingBorder + margin);
571
+ }
572
+ const rectWidth = rect.width || parseFloat(elStyle.width) || ((el.textContent ?? '').length * ((parseFloat(elStyle.fontSize) || 14) * CHAR_WIDTH_FACTOR));
573
+ return total + rectWidth + margin;
574
+ }, 0);
575
+ interactiveWidth = Math.ceil((interactiveWidth || 0) + chrome);
576
+ }
577
+ const text = (cell.textContent ?? '').trim();
578
+ const textWidth = Math.ceil(this.measureTextWidth(text, style) + chrome);
579
+ if (interactiveWidth != null) {
580
+ return Math.max(textWidth, interactiveWidth);
581
+ }
582
+ return textWidth;
583
+ }
584
+ areWidthsEqual(prev, next) {
585
+ if (prev.length !== next.length)
586
+ return false;
587
+ return prev.every((v, i) => v === next[i]);
588
+ }
589
+ measureTextWidth(text, style) {
590
+ const fontSize = parseFloat(style.fontSize) || 14;
591
+ const font = style.font || `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${style.fontSize} / ${style.lineHeight} ${style.fontFamily}`;
592
+ let contentWidth = 0;
593
+ const isJsdom = typeof navigator !== 'undefined' && /jsdom/i.test(navigator.userAgent);
594
+ const canvas = !isJsdom && typeof document !== 'undefined' ? document.createElement('canvas') : null;
595
+ let ctx = null;
596
+ if (canvas?.getContext) {
597
+ try {
598
+ ctx = canvas.getContext('2d');
599
+ }
600
+ catch {
601
+ ctx = null;
602
+ }
603
+ }
604
+ if (ctx && font) {
605
+ ctx.font = font;
606
+ const metrics = ctx.measureText(text);
607
+ contentWidth = metrics?.width ?? 0;
608
+ }
609
+ if (!contentWidth) {
610
+ contentWidth = text.length * (fontSize * CHAR_WIDTH_FACTOR);
611
+ }
612
+ return contentWidth;
613
+ }
614
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbSyncColgroupDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
615
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: NgbSyncColgroupDirective, isStandalone: true, selector: "[ngbSyncColgroup]", inputs: { syncId: ["ngbSyncColgroup", "syncId"], syncRole: "syncRole" }, host: { listeners: { "window:resize": "onResize()" } }, ngImport: i0 });
616
+ }
617
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbSyncColgroupDirective, decorators: [{
618
+ type: Directive,
619
+ args: [{
620
+ selector: '[ngbSyncColgroup]',
621
+ standalone: true
622
+ }]
623
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { syncId: [{
624
+ type: Input,
625
+ args: ['ngbSyncColgroup']
626
+ }], syncRole: [{
627
+ type: Input
628
+ }], onResize: [{
629
+ type: HostListener,
630
+ args: ['window:resize']
631
+ }] } });
632
+
334
633
  const MAX_EMAIL_LENGTH = 254;
335
634
  const isReasonableEmail = (value) => {
336
635
  if (value == null)
@@ -372,6 +671,40 @@ class Datagrid {
372
671
  addButtonAriaLabel = 'Add row';
373
672
  /** Visible text rendered inside the add-row button. */
374
673
  addButtonText = '+ Add';
674
+ /** Shows sticky toggle column and keeps pinned rows at the top of the list. */
675
+ stickyRows = false;
676
+ /** Enables sticky column header when scrolling. */
677
+ stickyHeader = false;
678
+ /** Enables sticky footer when scrolling. */
679
+ stickyFooter = false;
680
+ /** Enables scroll table body container */
681
+ scrollable = true;
682
+ /** Row height used to stack multiple sticky rows without overlap (px). */
683
+ stickyRowHeight = 40;
684
+ /** Header height (px) used to offset sticky rows below the header. */
685
+ stickyHeaderHeight = 40;
686
+ /** Footer height (px) used to offset scrollable area. */
687
+ stickyFooterHeight = 56;
688
+ /** Bootstrap-like table styling options. */
689
+ tableOptions = {};
690
+ /** Selection mode for rows. */
691
+ selectionMode = 'none';
692
+ /** Selection activation: row click, checkbox only, or both. */
693
+ selectionBehavior = 'row';
694
+ /** Keyboard modifier rules for multi-select. */
695
+ selectionKeyMode = 'desktop';
696
+ /** Enable header select-all checkbox for multiple mode. */
697
+ selectAllEnabled = true;
698
+ /** A11y labels for selection controls. */
699
+ selectionA11yLabels = {};
700
+ /** Disable selection for specific rows. */
701
+ selectionDisabledFn;
702
+ /** Highlight items (row/cell). */
703
+ highlightedIndex = [];
704
+ /** Row key for highlighting. */
705
+ highlightRowKey = null;
706
+ /** Column key for highlighting. */
707
+ highlightColKey = null;
375
708
  /** Accessible label for the global filter input. */
376
709
  globalFilterAriaLabel = 'Search all columns';
377
710
  /** Accessible label announced when expanding a row. */
@@ -402,6 +735,9 @@ class Datagrid {
402
735
  dataProviderSelection; // used when pages='selection'
403
736
  // Grab the directive and its TemplateRef
404
737
  exportButtonDir;
738
+ bodyScroller;
739
+ headerScroller;
740
+ colgroupSyncId = `dg-col-${Math.random().toString(36).slice(2, 8)}`;
405
741
  exporting = false;
406
742
  rowAdd = new EventEmitter();
407
743
  // events
@@ -412,6 +748,7 @@ class Datagrid {
412
748
  sortChange = new EventEmitter();
413
749
  filtersChange = new EventEmitter();
414
750
  pageChange = new EventEmitter();
751
+ selectionChange = new EventEmitter();
415
752
  rowDetailTpl;
416
753
  exporter = inject(NgbExportService); // instead of constructor(private exporter: NgbExportService) {}
417
754
  expanded = new Set();
@@ -423,6 +760,10 @@ class Datagrid {
423
760
  errorsNew = {};
424
761
  // --- sorting (from previous step)
425
762
  sort = { active: null, direction: '' };
763
+ stickyRowIds = new Set();
764
+ selectedRowIds = new Set();
765
+ selectionAnchor = null;
766
+ highlightRowMap = new Map();
426
767
  // --- filtering
427
768
  globalFilter = '';
428
769
  filters = {}; // per-column text
@@ -436,6 +777,7 @@ class Datagrid {
436
777
  saveAttemptedNew = false;
437
778
  addDraftRowId = null;
438
779
  defaultEditService = new NgbDatagridDefaultEditService();
780
+ cdr = inject(ChangeDetectorRef);
439
781
  norm(v) {
440
782
  return (v ?? '').toString().toLowerCase().trim();
441
783
  }
@@ -538,19 +880,22 @@ class Datagrid {
538
880
  });
539
881
  }
540
882
  get sorted() {
541
- if (!this.enableSorting || !this.sort.active || !this.sort.direction)
542
- return this.filtered;
543
- const copy = [...this.filtered];
544
- const { active, direction } = this.sort;
545
- copy.sort((a, b) => {
546
- const av = a?.[active];
547
- const bv = b?.[active];
548
- const cmp = typeof av === 'string' && typeof bv === 'string'
549
- ? av.localeCompare(bv, undefined, { numeric: true, sensitivity: 'base' })
550
- : av < bv ? -1 : av > bv ? 1 : 0;
551
- return direction === 'asc' ? cmp : -cmp;
552
- });
553
- return copy;
883
+ const base = (!this.enableSorting || !this.sort.active || !this.sort.direction)
884
+ ? [...this.filtered]
885
+ : (() => {
886
+ const copy = [...this.filtered];
887
+ const { active, direction } = this.sort;
888
+ copy.sort((a, b) => {
889
+ const av = a?.[active];
890
+ const bv = b?.[active];
891
+ const cmp = typeof av === 'string' && typeof bv === 'string'
892
+ ? av.localeCompare(bv, undefined, { numeric: true, sensitivity: 'base' })
893
+ : av < bv ? -1 : av > bv ? 1 : 0;
894
+ return direction === 'asc' ? cmp : -cmp;
895
+ });
896
+ return copy;
897
+ })();
898
+ return this.withStickyRowsFirst(base);
554
899
  }
555
900
  // pagination (filled in next step)
556
901
  get paged() {
@@ -565,14 +910,78 @@ class Datagrid {
565
910
  }
566
911
  get startIndex() { const total = this.sorted.length; return total ? (this.page - 1) * this.pageSize + 1 : 0; }
567
912
  get endIndex() { return Math.min(this.page * this.pageSize, this.sorted.length); }
913
+ get shouldEnableScroll() {
914
+ return this.scrollable;
915
+ }
916
+ get isHeaderSticky() {
917
+ return this.stickyHeaderEnabled && this.shouldEnableScroll;
918
+ }
919
+ get isFooterSticky() {
920
+ return this.stickyFooterEnabled && this.shouldEnableScroll;
921
+ }
568
922
  get detailColspan() {
569
923
  const actionCols = (this.enableEdit || this.enableDelete) ? 1 : 0;
570
924
  const caretCol = this.rowDetailTpl ? 1 : 0;
571
- return this.columns.length + actionCols + caretCol;
925
+ const stickyCol = this.stickyRowsEnabled ? 1 : 0;
926
+ const selectionCol = this.isSelectionEnabled() ? 1 : 0;
927
+ return this.columns.length + actionCols + caretCol + stickyCol + selectionCol;
928
+ }
929
+ get tableClassList() {
930
+ const opts = this.tableOptions || {};
931
+ const cls = ['table'];
932
+ if (opts.stripedRows)
933
+ cls.push('table-striped');
934
+ if (opts.stripedColumns)
935
+ cls.push('table-striped-columns');
936
+ if (opts.hoverRows)
937
+ cls.push('table-hover');
938
+ if (opts.bordered)
939
+ cls.push('table-bordered');
940
+ if (opts.borderless)
941
+ cls.push('table-borderless');
942
+ if (opts.small)
943
+ cls.push('table-sm');
944
+ if (opts.groupDividers)
945
+ cls.push('table-group-divider');
946
+ if (opts.align === 'middle')
947
+ cls.push('align-middle');
948
+ if (opts.align === 'bottom')
949
+ cls.push('align-bottom');
950
+ return cls;
951
+ }
952
+ get responsiveWrapperClasses() {
953
+ const r = this.tableOptions?.responsive;
954
+ if (r === false)
955
+ return [];
956
+ if (r === true || r === undefined)
957
+ return ['table-responsive'];
958
+ return [`table-responsive-${r}`];
959
+ }
960
+ updateHighlightCache() {
961
+ this.highlightRowMap.clear();
962
+ (this.highlightedIndex || []).forEach(item => {
963
+ const rowKey = item.row;
964
+ if (!this.highlightRowMap.has(rowKey))
965
+ this.highlightRowMap.set(rowKey, []);
966
+ this.highlightRowMap.get(rowKey)?.push(item);
967
+ });
572
968
  }
573
969
  headerText(col) {
574
970
  return (col.header ?? col.field ?? '').toString();
575
971
  }
972
+ headerTitle(col) {
973
+ const t = col?.title;
974
+ return (t ?? this.headerText(col)) ?? '';
975
+ }
976
+ cellTitle(row, col) {
977
+ const def = col?.cellTitle;
978
+ if (typeof def === 'function')
979
+ return def(row) ?? '';
980
+ if (typeof def === 'string')
981
+ return def;
982
+ const val = row?.[col.field];
983
+ return val === undefined || val === null ? '' : String(val);
984
+ }
576
985
  columnFilterAriaLabel(col) {
577
986
  return `${this.headerText(col)} filter`;
578
987
  }
@@ -599,6 +1008,18 @@ class Datagrid {
599
1008
  exportAriaLabel(kind) {
600
1009
  return kind === 'pdf' ? this.exportPdfAriaLabel : this.exportExcelAriaLabel;
601
1010
  }
1011
+ withStickyRowsFirst(rows) {
1012
+ if (!this.stickyRows || this.stickyRowIds.size === 0)
1013
+ return rows;
1014
+ const sticky = [];
1015
+ const rest = [];
1016
+ rows.forEach(r => {
1017
+ const di = this.data.indexOf(r);
1018
+ const id = this.getRowId(di >= 0 ? di : 0, r);
1019
+ (this.stickyRowIds.has(id) ? sticky : rest).push(r);
1020
+ });
1021
+ return [...sticky, ...rest];
1022
+ }
602
1023
  // Helpers to map from paged index → original data index
603
1024
  // helper used above
604
1025
  dataIndexFromPaged(i) {
@@ -697,6 +1118,8 @@ class Datagrid {
697
1118
  ngOnChanges(ch) {
698
1119
  if (ch['columns'])
699
1120
  this.rebuildFilterForm();
1121
+ if (ch['highlightedIndex'])
1122
+ this.updateHighlightCache();
700
1123
  }
701
1124
  getRowId(rowIndex, row) {
702
1125
  return this.trackBy ? this.trackBy(rowIndex, row) : rowIndex;
@@ -841,6 +1264,32 @@ class Datagrid {
841
1264
  const rowIndex = di >= 0 ? di : index;
842
1265
  return this.trackBy ? this.trackBy(rowIndex, row) : rowIndex;
843
1266
  };
1267
+ rowKeyValue(row, rowIndex) {
1268
+ const key = this.highlightRowKey;
1269
+ if (typeof key === 'function')
1270
+ return key(row, rowIndex);
1271
+ if (typeof key === 'string')
1272
+ return row?.[key];
1273
+ return rowIndex;
1274
+ }
1275
+ colKeyValue(column, colIndex) {
1276
+ const key = this.highlightColKey;
1277
+ if (typeof key === 'function')
1278
+ return key(column, colIndex);
1279
+ if (typeof key === 'string')
1280
+ return column?.[key];
1281
+ return colIndex;
1282
+ }
1283
+ isRowHighlighted(row, rowIndex) {
1284
+ const key = this.rowKeyValue(row, rowIndex);
1285
+ return this.highlightRowMap.has(key) && this.highlightRowMap.get(key)?.some(h => h.columnKey === undefined) === true;
1286
+ }
1287
+ isCellHighlighted(row, rowIndex, column, colIndex) {
1288
+ const key = this.rowKeyValue(row, rowIndex);
1289
+ const colKey = this.colKeyValue(column, colIndex);
1290
+ const items = this.highlightRowMap.get(key);
1291
+ return !!items?.some(h => h.columnKey === colKey);
1292
+ }
844
1293
  toggleSort(field) {
845
1294
  if (!this.enableSorting)
846
1295
  return;
@@ -920,18 +1369,181 @@ class Datagrid {
920
1369
  const r = this.responsive;
921
1370
  return r === true || (!!r && r.enabled === true);
922
1371
  }
923
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: Datagrid, deps: [], target: i0.ɵɵFactoryTarget.Component });
924
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: Datagrid, isStandalone: true, selector: "ngb-datagrid", inputs: { columns: "columns", data: "data", enableSorting: "enableSorting", enableFiltering: "enableFiltering", enableGlobalFilter: "enableGlobalFilter", enablePagination: "enablePagination", enableEdit: "enableEdit", enableDelete: "enableDelete", pageSizeOptions: "pageSizeOptions", enableAdd: "enableAdd", addButtonAriaLabel: "addButtonAriaLabel", addButtonText: "addButtonText", globalFilterAriaLabel: "globalFilterAriaLabel", expandRowAriaLabel: "expandRowAriaLabel", collapseRowAriaLabel: "collapseRowAriaLabel", exportPdfAriaLabel: "exportPdfAriaLabel", exportExcelAriaLabel: "exportExcelAriaLabel", newRowDefaults: "newRowDefaults", strictEmail: "strictEmail", editOnRowClick: "editOnRowClick", singleExpand: "singleExpand", exportOptions: "exportOptions", theme: "theme", responsive: "responsive", trackBy: "trackBy", editService: "editService", dataProviderAll: "dataProviderAll", dataProviderSelection: "dataProviderSelection", pageSize: "pageSize" }, outputs: { rowAdd: "rowAdd", rowEdit: "rowEdit", rowSave: "rowSave", rowCancel: "rowCancel", rowDelete: "rowDelete", sortChange: "sortChange", filtersChange: "filtersChange", pageChange: "pageChange" }, providers: [
1372
+ isRowSticky(row, pagedIndex) {
1373
+ if (!this.stickyRows)
1374
+ return false;
1375
+ const di = this.data.indexOf(row);
1376
+ const id = this.getRowId(di >= 0 ? di : pagedIndex, row);
1377
+ return this.stickyRowIds.has(id);
1378
+ }
1379
+ toggleStickyRow(pagedIndex) {
1380
+ if (!this.stickyRows)
1381
+ return;
1382
+ const row = this.paged[pagedIndex];
1383
+ if (!row)
1384
+ return;
1385
+ const di = this.data.indexOf(row);
1386
+ const id = this.getRowId(di >= 0 ? di : pagedIndex, row);
1387
+ if (this.stickyRowIds.has(id))
1388
+ this.stickyRowIds.delete(id);
1389
+ else
1390
+ this.stickyRowIds.add(id);
1391
+ }
1392
+ stickyIcon(row, pagedIndex) {
1393
+ return this.isRowSticky(row, pagedIndex) ? 'pin-angle-fill' : 'pin-fill';
1394
+ }
1395
+ stickyTop(row, pagedIndex) {
1396
+ if (!this.isRowSticky(row, pagedIndex))
1397
+ return null;
1398
+ const stickyBefore = this.paged
1399
+ .slice(0, pagedIndex)
1400
+ .filter((r, idx) => this.isRowSticky(r, idx)).length;
1401
+ return stickyBefore * this.stickyRowHeight;
1402
+ }
1403
+ // Selection helpers
1404
+ get stickyRowsEnabled() {
1405
+ return this.tableOptions?.stickyRows ?? this.stickyRows;
1406
+ }
1407
+ get stickyHeaderEnabled() {
1408
+ return this.tableOptions?.stickyHeader ?? this.stickyHeader;
1409
+ }
1410
+ get stickyFooterEnabled() {
1411
+ return this.tableOptions?.stickyFooter ?? this.stickyFooter;
1412
+ }
1413
+ isSelectionEnabled() {
1414
+ return this.selectionMode !== 'none';
1415
+ }
1416
+ isCheckboxOnly() {
1417
+ return this.selectionBehavior === 'checkbox';
1418
+ }
1419
+ isSelectionDisabled(row, pagedIndex) {
1420
+ if (!this.selectionDisabledFn)
1421
+ return false;
1422
+ const di = this.data.indexOf(this.paged[pagedIndex]);
1423
+ return this.selectionDisabledFn(row, di >= 0 ? di : pagedIndex);
1424
+ }
1425
+ isRowSelected(row, pagedIndex) {
1426
+ const di = this.data.indexOf(this.paged[pagedIndex]);
1427
+ const id = this.getRowId(di >= 0 ? di : pagedIndex, row);
1428
+ return this.selectedRowIds.has(id);
1429
+ }
1430
+ toggleSelection(pagedIndex, event) {
1431
+ if (!this.isSelectionEnabled())
1432
+ return;
1433
+ const row = this.paged[pagedIndex];
1434
+ if (!row || this.isSelectionDisabled(row, pagedIndex))
1435
+ return;
1436
+ const di = this.data.indexOf(row);
1437
+ const id = this.getRowId(di >= 0 ? di : pagedIndex, row);
1438
+ const currentlySelected = this.selectedRowIds.has(id);
1439
+ const isMulti = this.selectionMode === 'multiple';
1440
+ const desktop = this.selectionKeyMode === 'desktop';
1441
+ const shift = desktop && !!(event && 'shiftKey' in event && event.shiftKey);
1442
+ const meta = desktop && !!(event && (('metaKey' in event && event.metaKey) || ('ctrlKey' in event && event.ctrlKey)));
1443
+ if (!isMulti) {
1444
+ this.selectedRowIds.clear();
1445
+ this.selectedRowIds.add(id);
1446
+ this.selectionAnchor = pagedIndex;
1447
+ this.emitSelection(row, di, true);
1448
+ return;
1449
+ }
1450
+ if (shift && this.selectionAnchor != null) {
1451
+ const start = Math.min(this.selectionAnchor, pagedIndex);
1452
+ const end = Math.max(this.selectionAnchor, pagedIndex);
1453
+ this.selectedRowIds.clear();
1454
+ for (let i = start; i <= end; i++) {
1455
+ const r = this.paged[i];
1456
+ if (!r || this.isSelectionDisabled(r, i))
1457
+ continue;
1458
+ const rid = this.getRowId(this.data.indexOf(r), r);
1459
+ this.selectedRowIds.add(rid);
1460
+ }
1461
+ this.emitSelection(row, di, true);
1462
+ return;
1463
+ }
1464
+ if (meta || !desktop) {
1465
+ if (currentlySelected)
1466
+ this.selectedRowIds.delete(id);
1467
+ else
1468
+ this.selectedRowIds.add(id);
1469
+ }
1470
+ else {
1471
+ this.selectedRowIds.clear();
1472
+ this.selectedRowIds.add(id);
1473
+ }
1474
+ this.selectionAnchor = pagedIndex;
1475
+ this.emitSelection(row, di, !currentlySelected || (!meta && desktop));
1476
+ }
1477
+ emitSelection(row, dataIndex, selected) {
1478
+ this.selectionChange.emit({
1479
+ selected: this.data.filter((r, idx) => this.selectedRowIds.has(this.getRowId(idx, r))),
1480
+ lastAction: { row, index: dataIndex, selected }
1481
+ });
1482
+ }
1483
+ toggleSelectAllCurrentPage() {
1484
+ if (this.selectionMode !== 'multiple' || !this.selectAllEnabled)
1485
+ return;
1486
+ const allSelectable = this.paged.filter((r, i) => !this.isSelectionDisabled(r, i));
1487
+ const allSelected = allSelectable.every((r, i) => this.isRowSelected(r, i));
1488
+ if (allSelected) {
1489
+ allSelectable.forEach((r, i) => {
1490
+ const rid = this.getRowId(this.data.indexOf(r), r);
1491
+ this.selectedRowIds.delete(rid);
1492
+ });
1493
+ }
1494
+ else {
1495
+ allSelectable.forEach((r, i) => {
1496
+ const rid = this.getRowId(this.data.indexOf(r), r);
1497
+ this.selectedRowIds.add(rid);
1498
+ });
1499
+ }
1500
+ this.selectionChange.emit({
1501
+ selected: this.data.filter((r, idx) => this.selectedRowIds.has(this.getRowId(idx, r))),
1502
+ lastAction: null
1503
+ });
1504
+ }
1505
+ isPageAllSelected() {
1506
+ const selectable = this.paged.filter((r, i) => !this.isSelectionDisabled(r, i));
1507
+ return selectable.length > 0 && selectable.every((r, i) => this.isRowSelected(r, i));
1508
+ }
1509
+ isPageIndeterminate() {
1510
+ const selectable = this.paged.filter((r, i) => !this.isSelectionDisabled(r, i));
1511
+ const selectedCount = selectable.filter((r, i) => this.isRowSelected(r, i)).length;
1512
+ return selectedCount > 0 && selectedCount < selectable.length;
1513
+ }
1514
+ selectAllLabel() {
1515
+ const labels = this.selectionA11yLabels || {};
1516
+ return this.isPageAllSelected()
1517
+ ? (labels.unselectAll || 'Unselect all rows')
1518
+ : (labels.selectAll || 'Select all rows');
1519
+ }
1520
+ rowSelectionLabel(index) {
1521
+ const labels = this.selectionA11yLabels || {};
1522
+ const selected = this.isRowSelected(this.paged[index], index);
1523
+ return selected
1524
+ ? (labels.unselectRow || `Unselect row ${index + 1}`)
1525
+ : (labels.selectRow || `Select row ${index + 1}`);
1526
+ }
1527
+ onRowSelect(ev, pagedIndex) {
1528
+ if (!this.isSelectionEnabled() || this.isCheckboxOnly())
1529
+ return;
1530
+ const target = ev.target;
1531
+ if (target.closest('input, button, a, select, textarea,label'))
1532
+ return;
1533
+ this.toggleSelection(pagedIndex, ev);
1534
+ }
1535
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: Datagrid, deps: [], target: i0.ɵɵFactoryTarget.Component });
1536
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.9", type: Datagrid, isStandalone: true, selector: "ngb-datagrid", inputs: { columns: "columns", data: "data", enableSorting: "enableSorting", enableFiltering: "enableFiltering", enableGlobalFilter: "enableGlobalFilter", enablePagination: "enablePagination", enableEdit: "enableEdit", enableDelete: "enableDelete", pageSizeOptions: "pageSizeOptions", enableAdd: "enableAdd", addButtonAriaLabel: "addButtonAriaLabel", addButtonText: "addButtonText", stickyRows: "stickyRows", stickyHeader: "stickyHeader", stickyFooter: "stickyFooter", scrollable: "scrollable", stickyRowHeight: "stickyRowHeight", stickyHeaderHeight: "stickyHeaderHeight", stickyFooterHeight: "stickyFooterHeight", tableOptions: "tableOptions", selectionMode: "selectionMode", selectionBehavior: "selectionBehavior", selectionKeyMode: "selectionKeyMode", selectAllEnabled: "selectAllEnabled", selectionA11yLabels: "selectionA11yLabels", selectionDisabledFn: "selectionDisabledFn", highlightedIndex: "highlightedIndex", highlightRowKey: "highlightRowKey", highlightColKey: "highlightColKey", globalFilterAriaLabel: "globalFilterAriaLabel", expandRowAriaLabel: "expandRowAriaLabel", collapseRowAriaLabel: "collapseRowAriaLabel", exportPdfAriaLabel: "exportPdfAriaLabel", exportExcelAriaLabel: "exportExcelAriaLabel", newRowDefaults: "newRowDefaults", strictEmail: "strictEmail", editOnRowClick: "editOnRowClick", singleExpand: "singleExpand", exportOptions: "exportOptions", theme: "theme", responsive: "responsive", trackBy: "trackBy", editService: "editService", dataProviderAll: "dataProviderAll", dataProviderSelection: "dataProviderSelection", pageSize: "pageSize" }, outputs: { rowAdd: "rowAdd", rowEdit: "rowEdit", rowSave: "rowSave", rowCancel: "rowCancel", rowDelete: "rowDelete", sortChange: "sortChange", filtersChange: "filtersChange", pageChange: "pageChange", selectionChange: "selectionChange" }, providers: [
925
1537
  { provide: PdfExportAdapter, useClass: JsPdfAdapter },
926
1538
  { provide: ExcelExportAdapter, useClass: XlsxAdapter }
927
- ], queries: [{ propertyName: "exportButtonDir", first: true, predicate: ExportButtonDirective, descendants: true }, { propertyName: "rowDetailTpl", first: true, predicate: NgbRowDetailTemplate, descendants: true }, { propertyName: "cellTplQ", predicate: NgbCellTemplate }, { propertyName: "editTplQ", predicate: NgbEditorTemplate }, { propertyName: "filterTplQ", predicate: NgbFilterTemplate }, { propertyName: "globalTplQ", predicate: NgbGlobalFilterTemplate }], usesOnChanges: true, ngImport: i0, template: "<!-- eslint-disable @angular-eslint/template/interactive-supports-focus -->\n<!-- eslint-disable @angular-eslint/template/click-events-have-key-events -->\n<div class=\"ngb-grid\" [attr.data-theme]=\"theme\" [class.ngb-responsive]=\"isResponsiveEnabled()\">\n <!-- Export toolbar -->\n <div class=\"d-flex justify-content-end mb-2\" *ngIf=\"exportOptions?.enabled\">\n <ng-container *ngIf=\"exportButtonTpl; else defaultExportBtns\"\n [ngTemplateOutlet]=\"exportButtonTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: triggerExport }\">\n </ng-container>\n\n <ng-template #defaultExportBtns>\n <button type=\"button\"\n class=\"btn btn-sm btn-outline-secondary me-2\"\n *ngIf=\"exportOptions.type === 'pdf' || exportOptions.type === 'both'\"\n [disabled]=\"exporting\"\n (click)=\"export('pdf')\"\n [attr.aria-label]=\"exportAriaLabel('pdf')\">\n Export to PDF\n </button>\n <button type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n *ngIf=\"exportOptions.type === 'excel' || exportOptions.type === 'both'\"\n [disabled]=\"exporting\"\n (click)=\"export('excel')\"\n [attr.aria-label]=\"exportAriaLabel('excel')\">\n Export to Excel\n </button>\n </ng-template>\n </div>\n <div class=\"d-flex justify-content-end mb-2\" *ngIf=\"enableAdd\">\n <button\n type=\"button\"\n class=\"btn btn-sm btn-primary\"\n (click)=\"startAdd()\"\n [disabled]=\"addingNew\"\n [attr.aria-label]=\"addButtonAriaLabel || addButtonText\"\n >\n {{ addButtonText }}\n </button>\n </div>\n <!-- Global filter -->\n <div class=\"mb-2\" *ngIf=\"enableFiltering && enableGlobalFilter\">\n <ng-container\n *ngIf=\"globalTpl; else defaultGlobalFilter\"\n [ngTemplateOutlet]=\"globalTpl?.template\"\n [ngTemplateOutletContext]=\"{ $implicit: globalFilterCtrl }\"\n >\n </ng-container>\n\n <ng-template #defaultGlobalFilter>\n <input\n type=\"search\"\n class=\"form-control form-control-sm\"\n placeholder=\"Search all columns...\"\n [formControl]=\"globalFilterCtrl\"\n [attr.aria-label]=\"globalFilterAriaLabel\"\n />\n </ng-template>\n </div>\n\n <table class=\"table table-bordered\"\n role=\"grid\"\n [attr.aria-readonly]=\"(enableEdit || enableAdd) ? 'false' : 'true'\">\n <thead class=\"thead-light\">\n <tr>\n <th *ngIf=\"rowDetailTpl\" style=\"width:1%\" scope=\"col\" aria-hidden=\"true\"></th>\n <th\n *ngFor=\"let col of columns\"\n [attr.data-title]=\"col.header\"\n [class.sortable]=\"enableSorting && col.sortable\"\n scope=\"col\"\n [attr.aria-sort]=\"enableSorting && col.sortable ? ariaSortFor(col.field) : null\"\n >\n <ng-container *ngIf=\"enableSorting && col.sortable; else plainHeader\">\n <button\n type=\"button\"\n class=\"btn btn-link p-0 text-start w-100\"\n (click)=\"toggleSort(col.field)\"\n [attr.aria-label]=\"sortButtonAriaLabel(col)\"\n >\n <span>{{ col.header }}</span>\n <span\n class=\"sort-indicator\"\n *ngIf=\"enableSorting && sort.active === col.field\"\n aria-hidden=\"true\"\n >\n {{\n sort.direction === 'asc'\n ? '\u25B2'\n : sort.direction === 'desc'\n ? '\u25BC'\n : ''\n }}\n </span>\n </button>\n </ng-container>\n <ng-template #plainHeader>\n <span>{{ col.header }}</span>\n </ng-template>\n </th>\n <th\n *ngIf=\"enableEdit || enableDelete\"\n class=\"text-center\"\n scope=\"col\"\n style=\"width: 1%\"\n >\n Actions\n </th>\n </tr>\n\n <!-- Per-column filters -->\n <tr *ngIf=\"anyFilterable\" [formGroup]=\"filterForm\">\n <th *ngFor=\"let col of columns\" [attr.data-title]=\"col.header\">\n <ng-container *ngIf=\"col.filterable\">\n <ng-container *ngIf=\"filterTpls[col.field] as ft; else defaultFilter\">\n <ng-container\n [ngTemplateOutlet]=\"ft.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: filterForm.get(col.field),\n control: filterForm.get(col.field),\n col: col\n }\"\n >\n </ng-container>\n </ng-container>\n <ng-template #defaultFilter>\n <input\n type=\"text\"\n class=\"form-control form-control-sm\"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"columnFilterAriaLabel(col)\"\n />\n </ng-template>\n </ng-container>\n </th>\n <th *ngIf=\"enableEdit || enableDelete\"></th>\n </tr>\n </thead>\n\n <tbody>\n <!-- ADD NEW ROW -->\n <tr *ngIf=\"addingNew\" [formGroup]=\"addForm\">\n <!-- blank caret cell to keep alignment when detail column is present -->\n <td *ngIf=\"rowDetailTpl\"></td>\n\n <td *ngFor=\"let col of columns\" [attr.data-title]=\"col.header\">\n <ng-container [ngSwitch]=\"col.type\">\n <!-- boolean -->\n <div\n *ngSwitchCase=\"'boolean'\"\n class=\"form-check m-0\"\n [class.is-invalid]=\"(addForm.get(col.field)?.touched || saveAttemptedNew) && addForm.get(col.field)?.invalid\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"inputAriaLabel(col)\"\n [attr.aria-invalid]=\"(addForm.get(col.field)?.touched || saveAttemptedNew) && addForm.get(col.field)?.invalid ? 'true' : null\"\n [attr.aria-describedby]=\"(addForm.get(col.field)?.touched || saveAttemptedNew) && addForm.get(col.field)?.invalid ? 'add-error-' + col.field : null\"\n />\n </div>\n\n <!-- number/email/date/text -->\n <input\n *ngSwitchDefault\n class=\"form-control form-control-sm\"\n [class.is-invalid]=\"(addForm.get(col.field)?.touched || saveAttemptedNew) && addForm.get(col.field)?.invalid\"\n [attr.type]=\"col.type === 'number' ? 'number' :\n col.type === 'email' ? 'email' :\n col.type === 'date' ? 'date' : 'text'\"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"inputAriaLabel(col)\"\n [attr.aria-invalid]=\"(addForm.get(col.field)?.touched || saveAttemptedNew) && addForm.get(col.field)?.invalid ? 'true' : null\"\n [attr.aria-describedby]=\"(addForm.get(col.field)?.touched || saveAttemptedNew) && addForm.get(col.field)?.invalid ? 'add-error-' + col.field : null\"\n />\n </ng-container>\n\n <div class=\"invalid-feedback d-block\"\n *ngIf=\"(addForm.get(col.field)?.touched || saveAttemptedNew) && addForm.get(col.field)?.errors as e\"\n [attr.id]=\"'add-error-' + col.field\"\n role=\"alert\">\n <ng-container *ngIf=\"e['required']\">Required</ng-container>\n <ng-container *ngIf=\"e['email']\">Invalid email</ng-container>\n <ng-container *ngIf=\"e['number']\">Invalid number</ng-container>\n <ng-container *ngIf=\"e['date']\">Invalid date</ng-container>\n </div>\n </td>\n\n <td *ngIf=\"enableEdit || enableDelete\" class=\"text-nowrap text-center\">\n <button type=\"button\" class=\"btn btn-sm btn-success me-1\" (click)=\"saveAdd()\" [disabled]=\"addForm.invalid\">Save</button>\n <button type=\"button\" class=\"btn btn-sm btn-secondary\" (click)=\"cancelAdd()\">Cancel</button>\n </td>\n </tr>\n\n <!-- DATA ROWS + DETAIL ROWS -->\n <ng-container *ngFor=\"let row of paged; let i = index; trackBy: trackRow\">\n\n <!-- DATA ROW (click-to-edit supported) -->\n <tr (click)=\"onRowClick($event, i)\" [formGroup]=\"editForm\">\n\n <!-- caret/expander cell (left-most) -->\n <td *ngIf=\"rowDetailTpl\" class=\"text-center align-middle\">\n <button\n type=\"button\"\n class=\"expand btn btn-link p-0\"\n (click)=\"$event.stopPropagation(); toggleExpand(i)\"\n [attr.aria-expanded]=\"isExpanded(i)\"\n [attr.aria-controls]=\"'dg-row-detail-' + i\"\n [attr.aria-label]=\"isExpanded(i) ? collapseRowAriaLabel : expandRowAriaLabel\">\n {{ isExpanded(i) ? '-' : '+' }}\n </button>\n </td>\n\n <!-- DATA CELLS -->\n <td *ngFor=\"let col of columns\" [attr.data-title]=\"col.header\">\n <!-- EDIT MODE -->\n <ng-container *ngIf=\"editingIndex === i && (col.editable ?? true); else readCell\">\n <!-- editor template override -->\n <ng-container *ngIf=\"editTpls[col.field] as et; else defaultEditor\"\n [ngTemplateOutlet]=\"et.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: editForm.get(col.field),\n control: editForm.get(col.field),\n row: row, col: col, form: editForm, index: i, isNew: false\n }\">\n </ng-container>\n\n <!-- default editors -->\n <ng-template #defaultEditor>\n <ng-container [ngSwitch]=\"col.type\">\n <div *ngSwitchCase=\"'boolean'\" class=\"form-check m-0\">\n <input type=\"checkbox\"\n class=\"form-check-input\"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"inputAriaLabel(col)\"\n [attr.aria-invalid]=\"(editForm?.touched || saveAttemptedEdit) && editForm?.get(col.field)?.invalid ? 'true' : null\"\n [attr.aria-describedby]=\"(editForm?.touched || saveAttemptedEdit) && editForm?.get(col.field)?.invalid ? 'edit-error-' + col.field : null\" />\n </div>\n <select *ngSwitchCase=\"'select'\"\n class=\"form-select form-select-sm\"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"inputAriaLabel(col)\">\n <option *ngFor=\"let o of col.options ?? []\" [ngValue]=\"o.value\">{{ o.label }}</option>\n </select>\n <input *ngSwitchDefault\n class=\"form-control form-control-sm\"\n [attr.type]=\"col.type === 'number' ? 'number' :\n col.type === 'email' ? 'email' :\n col.type === 'date' ? 'date' : 'text'\"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"inputAriaLabel(col)\"\n [attr.aria-invalid]=\"(editForm?.touched || saveAttemptedEdit) && editForm?.get(col.field)?.invalid ? 'true' : null\"\n [attr.aria-describedby]=\"(editForm?.touched || saveAttemptedEdit) && editForm?.get(col.field)?.invalid ? 'edit-error-' + col.field : null\" />\n </ng-container>\n\n <div class=\"invalid-feedback d-block\"\n *ngIf=\"(editForm?.touched || saveAttemptedEdit) && editForm?.get(col.field)?.errors as e\"\n [attr.id]=\"'edit-error-' + col.field\"\n role=\"alert\">\n <span *ngIf=\"e['required']\">Required</span>\n <span *ngIf=\"e['email']\">Invalid email</span>\n <span *ngIf=\"e['number']\">Invalid number</span>\n <span *ngIf=\"e['date']\">Invalid date</span>\n </div>\n </ng-template>\n </ng-container>\n\n <!-- READ MODE (with optional cell template) -->\n <ng-template #readCell>\n <ng-container *ngIf=\"cellTpls[col.field] as ct; else defaultCell\"\n [ngTemplateOutlet]=\"ct.template\"\n [ngTemplateOutletContext]=\"{ $implicit: row[col.field], row: row, col: col, index: i }\">\n </ng-container>\n <ng-template #defaultCell>\n <ng-container [ngSwitch]=\"col.type\">\n <span *ngSwitchCase=\"'boolean'\">{{ row[col.field] ? 'Yes' : 'No' }}</span>\n <span *ngSwitchDefault>{{ row[col.field] }}</span>\n </ng-container>\n </ng-template>\n </ng-template>\n </td>\n\n <!-- ACTIONS -->\n <td *ngIf=\"enableEdit || enableDelete\" class=\"text-nowrap text-center\">\n <ng-container *ngIf=\"editingIndex !== i; else editBtns\">\n <button type=\"button\" *ngIf=\"enableEdit\" class=\"btn btn-sm btn-outline-primary me-1 no-edit-trigger\" (click)=\"startEdit(i)\">Edit</button>\n <button type=\"button\" *ngIf=\"enableDelete\" class=\"btn btn-sm btn-outline-danger no-edit-trigger\" (click)=\"deleteRow(i)\">Delete</button>\n </ng-container>\n <ng-template #editBtns>\n <button type=\"button\" class=\"btn btn-sm btn-success me-1\" (click)=\"saveEdit(i)\" [disabled]=\"editForm.invalid\">Save</button>\n <button type=\"button\" class=\"btn btn-sm btn-secondary\" (click)=\"cancelEdit(i)\">Cancel</button>\n </ng-template>\n </td>\n </tr>\n\n <!-- DETAIL ROW (spans all columns) -->\n <tr *ngIf=\"rowDetailTpl && isExpanded(i)\" [attr.id]=\"'dg-row-detail-' + i\">\n <td [attr.colspan]=\"detailColspan\" role=\"region\">\n <ng-container [ngTemplateOutlet]=\"rowDetailTpl.template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i }\">\n </ng-container>\n </td>\n </tr>\n\n </ng-container>\n </tbody>\n\n </table>\n\n <!-- Footer (left = count, center = pagination, right = page size) -->\n <div *ngIf=\"enablePagination\" class=\"d-flex align-items-center justify-content-between mt-2\">\n <!-- left: count -->\n <div class=\"small text-muted\" aria-live=\"polite\">\n {{ startIndex }} - {{ endIndex }} of {{ sorted.length }}\n\n </div>\n\n <!-- center: pager -->\n <div class=\"flex-grow-1 d-flex justify-content-center\">\n <ngb-pagination\n [page]=\"page\"\n [pageSize]=\"pageSize\"\n [collectionSize]=\"sorted.length\"\n [maxSize]=\"5\"\n (pageChange)=\"onPage($event)\">\n </ngb-pagination>\n </div>\n\n <!-- right: rows per page -->\n <div class=\"d-flex align-items-center gap-2\">\n <label class=\"form-label form-label-sm mb-0\" for=\"pageSize\">Rows:</label>\n <select\n id=\"pageSize\"\n class=\"form-select form-select-sm\"\n [(ngModel)]=\"pageSize\"\n (ngModelChange)=\"onPageSize($event)\"\n aria-label=\"Rows per page\">\n <option *ngFor=\"let s of pageSizeOptions\" [value]=\"s\">{{ s }}</option>\n </select>\n </div>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.table{margin-bottom:.5rem}th.sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.sort-indicator{margin-left:.35rem;font-size:.75rem}.expand{cursor:pointer;font-size:24px;line-height:18px;display:block}.ngb-grid[data-theme=material]{--ngb-primary: #3f51b5}.ngb-grid[data-theme=material] .btn{border-color:var(--ngb-primary)}.ngb-grid[data-theme=material] table.table th,.ngb-grid[data-theme=material] table.table td{border-bottom:1px solid #e5e7eb}.ngb-grid[data-theme=tailwind]{--ngb-primary: rgb(29 78 216)}.ngb-grid[data-theme=tailwind] .btn{border-color:var(--ngb-primary);border-radius:.75rem}.ngb-grid[data-theme=tailwind] table.table th,.ngb-grid[data-theme=tailwind] table.table td{border-bottom:1px solid #e5e7eb;padding:.5rem}.ngb-responsive .table{width:100%}@media(max-width:768px){.ngb-responsive thead{display:none}.ngb-responsive tbody tr{display:grid;grid-template-columns:1fr;gap:.5rem;border:1px solid #e5e7eb;border-radius:.75rem;padding:.75rem;margin-bottom:.75rem}.ngb-responsive tbody td{display:grid;grid-template-columns:8rem 1fr}.ngb-responsive tbody td:before{content:attr(data-title);font-weight:600;opacity:.75;padding-right:.5rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: NgbPaginationComponent, selector: "ngb-pagination", inputs: ["page", "pageSize", "collectionSize", "maxSize"], outputs: ["pageChange"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1539
+ ], queries: [{ propertyName: "exportButtonDir", first: true, predicate: ExportButtonDirective, descendants: true }, { propertyName: "rowDetailTpl", first: true, predicate: NgbRowDetailTemplate, descendants: true }, { propertyName: "cellTplQ", predicate: NgbCellTemplate }, { propertyName: "editTplQ", predicate: NgbEditorTemplate }, { propertyName: "filterTplQ", predicate: NgbFilterTemplate }, { propertyName: "globalTplQ", predicate: NgbGlobalFilterTemplate }], viewQueries: [{ propertyName: "bodyScroller", first: true, predicate: ["bodyScroller"], descendants: true }, { propertyName: "headerScroller", first: true, predicate: ["headerScroller"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<!-- eslint-disable @angular-eslint/template/interactive-supports-focus -->\n<!-- eslint-disable @angular-eslint/template/click-events-have-key-events -->\n<div\n class=\"ngb-grid\"\n [attr.data-theme]=\"theme\"\n [class.ngb-responsive]=\"isResponsiveEnabled()\"\n>\n <!-- Export toolbar -->\n <div class=\"d-flex justify-content-end mb-2\" *ngIf=\"exportOptions?.enabled\">\n <ng-container\n *ngIf=\"exportButtonTpl; else defaultExportBtns\"\n [ngTemplateOutlet]=\"exportButtonTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: triggerExport }\"\n >\n </ng-container>\n\n <ng-template #defaultExportBtns>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary me-2\"\n *ngIf=\"exportOptions.type === 'pdf' || exportOptions.type === 'both'\"\n [disabled]=\"exporting\"\n (click)=\"export('pdf')\"\n [attr.aria-label]=\"exportAriaLabel('pdf')\"\n >\n Export to PDF\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n *ngIf=\"exportOptions.type === 'excel' || exportOptions.type === 'both'\"\n [disabled]=\"exporting\"\n (click)=\"export('excel')\"\n [attr.aria-label]=\"exportAriaLabel('excel')\"\n >\n Export to Excel\n </button>\n </ng-template>\n </div>\n <div class=\"d-flex justify-content-end mb-2\" *ngIf=\"enableAdd\">\n <button\n type=\"button\"\n class=\"btn btn-sm btn-primary\"\n (click)=\"startAdd()\"\n [disabled]=\"addingNew\"\n [attr.aria-label]=\"addButtonAriaLabel || addButtonText\"\n >\n {{ addButtonText }}\n </button>\n </div>\n <!-- Global filter -->\n <div class=\"mb-2\" *ngIf=\"enableFiltering && enableGlobalFilter\">\n <ng-container\n *ngIf=\"globalTpl; else defaultGlobalFilter\"\n [ngTemplateOutlet]=\"globalTpl?.template\"\n [ngTemplateOutletContext]=\"{ $implicit: globalFilterCtrl }\"\n >\n </ng-container>\n\n <ng-template #defaultGlobalFilter>\n <input\n type=\"search\"\n class=\"form-control form-control-sm\"\n placeholder=\"Search all columns...\"\n [formControl]=\"globalFilterCtrl\"\n [attr.aria-label]=\"globalFilterAriaLabel\"\n />\n </ng-template>\n </div>\n\n <div\n class=\"table-wrapper\"\n [ngClass]=\"responsiveWrapperClasses\"\n >\n <!-- <div class=\"table-header\"> -->\n <table class=\"grid-table grid-header mb-0\" [ngClass]=\"tableClassList\">\n <caption\n *ngIf=\"tableOptions?.caption\"\n [class.caption-top]=\"(tableOptions?.captionSide ?? 'top') === 'top'\"\n >\n {{\n tableOptions?.caption\n }}\n </caption>\n <colgroup [ngbSyncColgroup]=\"colgroupSyncId\" syncRole=\"header\">\n <col *ngIf=\"isSelectionEnabled()\" style=\"width: 1%\" />\n <col *ngIf=\"rowDetailTpl\" style=\"width: 1%\" data-fixed=\"true\" />\n <col *ngIf=\"stickyRowsEnabled\" style=\"width: 1%\" />\n <col *ngFor=\"let col of columns\" />\n <col *ngIf=\"enableEdit || enableDelete\" style=\"width: 1%\" />\n </colgroup>\n <thead class=\"table-light\">\n <tr>\n <th\n *ngIf=\"isSelectionEnabled()\"\n class=\"text-center\"\n scope=\"col\"\n style=\"width: 1%\"\n >\n <ng-container\n *ngIf=\"selectionMode === 'multiple' && selectAllEnabled\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"isPageAllSelected()\"\n [indeterminate]=\"isPageIndeterminate()\"\n (change)=\"toggleSelectAllCurrentPage()\"\n [attr.aria-label]=\"selectAllLabel()\"\n />\n </ng-container>\n </th>\n <th\n *ngIf=\"rowDetailTpl\"\n style=\"width: 1%\"\n scope=\"col\"\n aria-hidden=\"true\"\n ></th>\n <th\n *ngIf=\"stickyRowsEnabled\"\n style=\"width: 1%\"\n scope=\"col\"\n class=\"text-center\"\n data-title=\"Sticky\"\n ></th>\n <th\n *ngFor=\"let col of columns\"\n [attr.data-title]=\"col.header\"\n [class.sortable]=\"enableSorting && col.sortable\"\n scope=\"col\"\n [attr.title]=\"headerTitle(col)\"\n [attr.aria-sort]=\"\n enableSorting && col.sortable ? ariaSortFor(col.field) : null\n \"\n >\n <ng-container\n *ngIf=\"enableSorting && col.sortable; else plainHeader\"\n >\n <button\n type=\"button\"\n class=\"btn btn-link p-0 text-start w-100\"\n (click)=\"toggleSort(col.field)\"\n [attr.aria-label]=\"sortButtonAriaLabel(col)\"\n >\n <span>{{ col.header }}</span>\n <span\n class=\"sort-indicator\"\n *ngIf=\"enableSorting && sort.active === col.field\"\n aria-hidden=\"true\"\n >\n {{\n sort.direction === \"asc\"\n ? \"\u25B2\"\n : sort.direction === \"desc\"\n ? \"\u25BC\"\n : \"\"\n }}\n </span>\n </button>\n </ng-container>\n <ng-template #plainHeader>\n <span>{{ col.header }}</span>\n </ng-template>\n </th>\n <th\n *ngIf=\"enableEdit || enableDelete\"\n class=\"text-center\"\n scope=\"col\"\n style=\"width: 1%\"\n >\n Actions\n </th>\n </tr>\n\n <!-- Per-column filters -->\n <tr *ngIf=\"anyFilterable\" class=\"filter-row\" [formGroup]=\"filterForm\">\n <th *ngIf=\"isSelectionEnabled()\"></th>\n <th *ngIf=\"stickyRowsEnabled\"></th>\n <th *ngFor=\"let col of columns\" [attr.data-title]=\"col.header\">\n <ng-container *ngIf=\"col.filterable\">\n <ng-container\n *ngIf=\"filterTpls[col.field] as ft; else defaultFilter\"\n >\n <ng-container\n [ngTemplateOutlet]=\"ft.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: filterForm.get(col.field),\n control: filterForm.get(col.field),\n col: col,\n }\"\n >\n </ng-container>\n </ng-container>\n <ng-template #defaultFilter>\n <input\n type=\"text\"\n class=\"form-control form-control-sm\"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"columnFilterAriaLabel(col)\"\n />\n </ng-template>\n </ng-container>\n </th>\n <th *ngIf=\"enableEdit || enableDelete\"></th>\n </tr>\n </thead>\n </table>\n <!-- </div> -->\n\n <div [class.table-body-scroll]=\"shouldEnableScroll\" #bodyScroller>\n <table\n class=\"grid-table grid-body mb-0\"\n [ngClass]=\"tableClassList\"\n role=\"grid\"\n [attr.aria-readonly]=\"enableEdit || enableAdd ? 'false' : 'true'\"\n >\n <colgroup [ngbSyncColgroup]=\"colgroupSyncId\" syncRole=\"body\">\n <col *ngIf=\"isSelectionEnabled()\" style=\"width: 1%\" />\n <col *ngIf=\"rowDetailTpl\" style=\"width: 1%\" data-fixed=\"true\" />\n <col *ngIf=\"stickyRowsEnabled\" style=\"width: 1%\" />\n <col *ngFor=\"let col of columns\" />\n <col *ngIf=\"enableEdit || enableDelete\" style=\"width: 1%\" />\n </colgroup>\n <tbody>\n <!-- ADD NEW ROW -->\n <tr *ngIf=\"addingNew\" [formGroup]=\"addForm\">\n <!-- blank caret cell to keep alignment when detail column is present -->\n <td *ngIf=\"isSelectionEnabled()\"></td>\n <td *ngIf=\"rowDetailTpl\"></td>\n <td *ngIf=\"stickyRowsEnabled\" class=\"text-center\"></td>\n\n <td *ngFor=\"let col of columns\" [attr.data-title]=\"col.header\">\n <ng-container [ngSwitch]=\"col.type\">\n <!-- boolean -->\n <div\n *ngSwitchCase=\"'boolean'\"\n class=\"form-check m-0\"\n [class.is-invalid]=\"\n (addForm.get(col.field)?.touched || saveAttemptedNew) &&\n addForm.get(col.field)?.invalid\n \"\n >\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"inputAriaLabel(col)\"\n [attr.aria-invalid]=\"\n (addForm.get(col.field)?.touched || saveAttemptedNew) &&\n addForm.get(col.field)?.invalid\n ? 'true'\n : null\n \"\n [attr.aria-describedby]=\"\n (addForm.get(col.field)?.touched || saveAttemptedNew) &&\n addForm.get(col.field)?.invalid\n ? 'add-error-' + col.field\n : null\n \"\n />\n </div>\n\n <!-- number/email/date/text -->\n <input\n *ngSwitchDefault\n class=\"form-control form-control-sm\"\n [class.is-invalid]=\"\n (addForm.get(col.field)?.touched || saveAttemptedNew) &&\n addForm.get(col.field)?.invalid\n \"\n [attr.type]=\"\n col.type === 'number'\n ? 'number'\n : col.type === 'email'\n ? 'email'\n : col.type === 'date'\n ? 'date'\n : 'text'\n \"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"inputAriaLabel(col)\"\n [attr.aria-invalid]=\"\n (addForm.get(col.field)?.touched || saveAttemptedNew) &&\n addForm.get(col.field)?.invalid\n ? 'true'\n : null\n \"\n [attr.aria-describedby]=\"\n (addForm.get(col.field)?.touched || saveAttemptedNew) &&\n addForm.get(col.field)?.invalid\n ? 'add-error-' + col.field\n : null\n \"\n />\n </ng-container>\n\n <div\n class=\"invalid-feedback d-block\"\n *ngIf=\"\n (addForm.get(col.field)?.touched || saveAttemptedNew) &&\n addForm.get(col.field)?.errors as e\n \"\n [attr.id]=\"'add-error-' + col.field\"\n role=\"alert\"\n >\n <ng-container *ngIf=\"e['required']\">Required</ng-container>\n <ng-container *ngIf=\"e['email']\">Invalid email</ng-container>\n <ng-container *ngIf=\"e['number']\">Invalid number</ng-container>\n <ng-container *ngIf=\"e['date']\">Invalid date</ng-container>\n </div>\n </td>\n\n <td\n *ngIf=\"enableEdit || enableDelete\"\n class=\"text-nowrap text-center\"\n >\n <button\n type=\"button\"\n class=\"btn btn-sm btn-success me-1\"\n (click)=\"saveAdd()\"\n [disabled]=\"addForm.invalid\"\n >\n Save\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-secondary\"\n (click)=\"cancelAdd()\"\n >\n Cancel\n </button>\n </td>\n </tr>\n\n <!-- DATA ROWS + DETAIL ROWS -->\n <ng-container\n *ngFor=\"let row of paged; let i = index; trackBy: trackRow\"\n >\n <!-- DATA ROW (click-to-edit supported) -->\n <tr\n (click)=\"onRowSelect($event, i); onRowClick($event, i)\"\n [formGroup]=\"editForm\"\n [class.sticky-row]=\"isRowSticky(row, i)\"\n [class.row-selected]=\"isRowSelected(row, i)\"\n [class.row-highlight]=\"isRowHighlighted(row, i)\"\n [attr.aria-selected]=\"isRowSelected(row, i) ? 'true' : null\"\n [style.top.px]=\"stickyTop(row, i)\"\n [style.z-index]=\"isRowSticky(row, i) ? 3 : null\"\n >\n <td *ngIf=\"isSelectionEnabled()\" class=\"text-center align-middle\">\n <input\n type=\"checkbox\"\n [checked]=\"isRowSelected(row, i)\"\n [disabled]=\"\n selectionMode === 'none' ||\n selectionBehavior === 'row' ||\n isSelectionDisabled(row, i)\n \"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleSelection(i, $event)\"\n [attr.aria-label]=\"rowSelectionLabel(i)\"\n />\n </td>\n\n <!-- caret/expander cell (left-most) -->\n <td *ngIf=\"rowDetailTpl\" class=\"text-center align-middle\">\n <button\n type=\"button\"\n class=\"expand btn btn-link p-0\"\n (click)=\"$event.stopPropagation(); toggleExpand(i)\"\n [attr.aria-expanded]=\"isExpanded(i)\"\n [attr.aria-controls]=\"'dg-row-detail-' + i\"\n [attr.aria-label]=\"\n isExpanded(i) ? collapseRowAriaLabel : expandRowAriaLabel\n \"\n >\n {{ isExpanded(i) ? \"-\" : \"+\" }}\n </button>\n </td>\n\n <td\n *ngIf=\"stickyRowsEnabled\"\n class=\"text-center align-middle\"\n data-title=\"Sticky\"\n >\n <button\n type=\"button\"\n class=\"btn btn-link p-0 no-edit-trigger sticky-toggle\"\n (click)=\"$event.stopPropagation(); toggleStickyRow(i)\"\n [attr.aria-pressed]=\"isRowSticky(row, i)\"\n aria-label=\"Toggle sticky row\"\n >\n <ng-container *ngIf=\"stickyIcon(row, i) as icon\">\n <span\n class=\"bi\"\n [ngClass]=\"'bi-' + icon\"\n aria-hidden=\"true\"\n ></span>\n </ng-container>\n </button>\n </td>\n\n <!-- DATA CELLS -->\n <td\n *ngFor=\"let col of columns; let ci = index\"\n [attr.data-title]=\"col.header\"\n [attr.title]=\"cellTitle(row, col)\"\n [class.cell-highlight]=\"isCellHighlighted(row, i, col, ci)\"\n >\n <!-- EDIT MODE -->\n <ng-container\n *ngIf=\"\n editingIndex === i && (col.editable ?? true);\n else readCell\n \"\n >\n <!-- editor template override -->\n <ng-container\n *ngIf=\"editTpls[col.field] as et; else defaultEditor\"\n [ngTemplateOutlet]=\"et.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: editForm.get(col.field),\n control: editForm.get(col.field),\n row: row,\n col: col,\n form: editForm,\n index: i,\n isNew: false,\n }\"\n >\n </ng-container>\n\n <!-- default editors -->\n <ng-template #defaultEditor>\n <ng-container [ngSwitch]=\"col.type\">\n <div *ngSwitchCase=\"'boolean'\" class=\"form-check m-0\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"inputAriaLabel(col)\"\n [attr.aria-invalid]=\"\n (editForm?.touched || saveAttemptedEdit) &&\n editForm?.get(col.field)?.invalid\n ? 'true'\n : null\n \"\n [attr.aria-describedby]=\"\n (editForm?.touched || saveAttemptedEdit) &&\n editForm?.get(col.field)?.invalid\n ? 'edit-error-' + col.field\n : null\n \"\n />\n </div>\n <select\n *ngSwitchCase=\"'select'\"\n class=\"form-select form-select-sm\"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"inputAriaLabel(col)\"\n >\n <option\n *ngFor=\"let o of col.options ?? []\"\n [ngValue]=\"o.value\"\n >\n {{ o.label }}\n </option>\n </select>\n <input\n *ngSwitchDefault\n class=\"form-control form-control-sm\"\n [attr.type]=\"\n col.type === 'number'\n ? 'number'\n : col.type === 'email'\n ? 'email'\n : col.type === 'date'\n ? 'date'\n : 'text'\n \"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"inputAriaLabel(col)\"\n [attr.aria-invalid]=\"\n (editForm?.touched || saveAttemptedEdit) &&\n editForm?.get(col.field)?.invalid\n ? 'true'\n : null\n \"\n [attr.aria-describedby]=\"\n (editForm?.touched || saveAttemptedEdit) &&\n editForm?.get(col.field)?.invalid\n ? 'edit-error-' + col.field\n : null\n \"\n />\n </ng-container>\n\n <div\n class=\"invalid-feedback d-block\"\n *ngIf=\"\n (editForm?.touched || saveAttemptedEdit) &&\n editForm?.get(col.field)?.errors as e\n \"\n [attr.id]=\"'edit-error-' + col.field\"\n role=\"alert\"\n >\n <span *ngIf=\"e['required']\">Required</span>\n <span *ngIf=\"e['email']\">Invalid email</span>\n <span *ngIf=\"e['number']\">Invalid number</span>\n <span *ngIf=\"e['date']\">Invalid date</span>\n </div>\n </ng-template>\n </ng-container>\n\n <!-- READ MODE (with optional cell template) -->\n <ng-template #readCell>\n <ng-container\n *ngIf=\"cellTpls[col.field] as ct; else defaultCell\"\n [ngTemplateOutlet]=\"ct.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: row[col.field],\n row: row,\n col: col,\n index: i,\n }\"\n >\n </ng-container>\n <ng-template #defaultCell>\n <ng-container [ngSwitch]=\"col.type\">\n <span *ngSwitchCase=\"'boolean'\">{{\n row[col.field] ? \"Yes\" : \"No\"\n }}</span>\n <span *ngSwitchDefault>{{ row[col.field] }}</span>\n </ng-container>\n </ng-template>\n </ng-template>\n </td>\n\n <!-- ACTIONS -->\n <td\n *ngIf=\"enableEdit || enableDelete\"\n class=\"text-nowrap text-center\"\n >\n <ng-container *ngIf=\"editingIndex !== i; else editBtns\">\n <button\n type=\"button\"\n *ngIf=\"enableEdit\"\n class=\"btn btn-sm btn-outline-primary me-1 no-edit-trigger\"\n (click)=\"startEdit(i)\"\n >\n Edit\n </button>\n <button\n type=\"button\"\n *ngIf=\"enableDelete\"\n class=\"btn btn-sm btn-outline-danger no-edit-trigger\"\n (click)=\"deleteRow(i)\"\n >\n Delete\n </button>\n </ng-container>\n <ng-template #editBtns>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-success me-1\"\n (click)=\"saveEdit(i)\"\n [disabled]=\"editForm.invalid\"\n >\n Save\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-secondary\"\n (click)=\"cancelEdit(i)\"\n >\n Cancel\n </button>\n </ng-template>\n </td>\n </tr>\n\n <!-- DETAIL ROW (spans all columns) -->\n <tr\n *ngIf=\"rowDetailTpl && isExpanded(i)\"\n [attr.id]=\"'dg-row-detail-' + i\"\n >\n <td [attr.colspan]=\"detailColspan\" role=\"region\">\n <ng-container\n [ngTemplateOutlet]=\"rowDetailTpl.template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i }\"\n >\n </ng-container>\n </td>\n </tr>\n </ng-container>\n </tbody>\n </table>\n </div>\n <table class=\"table grid-table grid-foot mb-0\">\n <colgroup>\n <ng-container *ngFor=\"let col of columns\">\n <col [style.width.px]=\"col?.width\" />\n </ng-container>\n </colgroup>\n <tbody>\n <tr>\n <td [attr.colspan]=\"columns.length\" width=\"100%\">\n <div\n *ngIf=\"enablePagination\"\n class=\"d-flex align-items-center justify-content-between mt-2 grid-footer\"\n [class.sticky-footer]=\"isFooterSticky\"\n >\n <!-- left: count -->\n <div class=\"small text-muted\" aria-live=\"polite\">\n {{ startIndex }} - {{ endIndex }} of {{ sorted.length }}\n </div>\n\n <!-- center: pager -->\n <div class=\"flex-grow-1 d-flex justify-content-center\">\n <ngb-pagination\n [page]=\"page\"\n [pageSize]=\"pageSize\"\n [collectionSize]=\"sorted.length\"\n [maxSize]=\"5\"\n (pageChange)=\"onPage($event)\"\n >\n </ngb-pagination>\n </div>\n\n <!-- right: rows per page -->\n <div class=\"d-flex align-items-center gap-2\">\n <label class=\"form-label form-label-sm mb-0\" for=\"pageSize\"\n >Rows:</label\n >\n <select\n id=\"pageSize\"\n class=\"form-select form-select-sm\"\n [(ngModel)]=\"pageSize\"\n (ngModelChange)=\"onPageSize($event)\"\n aria-label=\"Rows per page\"\n >\n <option *ngFor=\"let s of pageSizeOptions\" [value]=\"s\">\n {{ s }}\n </option>\n </select>\n </div>\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n</div>\n\n<!-- Footer (left = count, center = pagination, right = page size) -->\n", styles: ["@charset \"UTF-8\";:host{display:block}.table{margin-bottom:.5rem}th.sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.sort-indicator{margin-left:.35rem;font-size:.75rem}.expand{cursor:pointer;font-size:24px;line-height:18px;display:block}.sticky-toggle{display:inline-flex;align-items:center;justify-content:center;gap:.15rem}.table-wrapper{position:relative}.table-wrapper{overflow-x:auto;border:1px solid #dee2e6;border-radius:12px;background:#fff}.table-body-scroll{max-height:320px;overflow-y:auto;overflow-x:hidden;border-top:1px solid #e9ecef;border-bottom:1px solid #e9ecef;scrollbar-gutter:stable both-edges;display:inline-block;width:fit-content;vertical-align:top}.grid-head{border-bottom:0}.grid-table{width:100%;min-width:1200px;table-layout:fixed;margin:0;border-collapse:separate;border-spacing:0}.grid-head{position:relative;z-index:5}.grid-head thead tr:first-child th:first-child{border-top-left-radius:12px}.grid-head thead th{background:var(--bs-table-bg, #f8f9fa);border-bottom:1px solid #dee2e6;font-weight:600}.grid-table th,.grid-table td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;vertical-align:middle;border-bottom:1px solid #e9ecef;padding:.55rem .65rem}.filter-row td{background:#fff;border-bottom:1px solid #dee2e6;padding-top:.45rem;padding-bottom:.45rem}.sticky-row{position:sticky;background:#ccc;z-index:4}.sticky-row td{background:#ccc}.row-selected{background-color:#e7f1ff}.row-highlight{background-color:var(--bs-warning-bg-subtle, #fff3cd)}.row-highlight td{background-color:inherit}.cell-highlight{background-color:var(--bs-info-bg-subtle, #cff4fc)}.ngb-grid[data-theme=material]{--ngb-primary: #3f51b5}.ngb-grid[data-theme=material] .btn{border-color:var(--ngb-primary)}.ngb-grid[data-theme=material] table.table th,.ngb-grid[data-theme=material] table.table td{border-bottom:1px solid #e5e7eb}.ngb-grid[data-theme=tailwind]{--ngb-primary: rgb(29 78 216)}.ngb-grid[data-theme=tailwind] .btn{border-color:var(--ngb-primary);border-radius:.75rem}.ngb-grid[data-theme=tailwind] table.table th,.ngb-grid[data-theme=tailwind] table.table td{border-bottom:1px solid #e5e7eb;padding:.5rem}.ngb-responsive .table{width:100%}@media(max-width:768px){.ngb-responsive thead{display:none}.ngb-responsive tbody tr{display:grid;grid-template-columns:1fr;gap:.5rem;border:1px solid #e5e7eb;border-radius:.75rem;padding:.75rem;margin-bottom:.75rem}.ngb-responsive tbody td{display:grid;grid-template-columns:8rem 1fr}.ngb-responsive tbody td:before{content:attr(data-title);font-weight:600;opacity:.75;padding-right:.5rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: NgbPaginationComponent, selector: "ngb-pagination", inputs: ["page", "pageSize", "collectionSize", "maxSize"], outputs: ["pageChange"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: NgbSyncColgroupDirective, selector: "[ngbSyncColgroup]", inputs: ["ngbSyncColgroup", "syncRole"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
928
1540
  }
929
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: Datagrid, decorators: [{
1541
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: Datagrid, decorators: [{
930
1542
  type: Component,
931
- args: [{ selector: 'ngb-datagrid', imports: [CommonModule, FormsModule, NgbPaginationComponent, ReactiveFormsModule], providers: [
1543
+ args: [{ selector: 'ngb-datagrid', imports: [CommonModule, FormsModule, NgbPaginationComponent, ReactiveFormsModule, NgbSyncColgroupDirective, NgbGridHighlightDirective], providers: [
932
1544
  { provide: PdfExportAdapter, useClass: JsPdfAdapter },
933
1545
  { provide: ExcelExportAdapter, useClass: XlsxAdapter }
934
- ], standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- eslint-disable @angular-eslint/template/interactive-supports-focus -->\n<!-- eslint-disable @angular-eslint/template/click-events-have-key-events -->\n<div class=\"ngb-grid\" [attr.data-theme]=\"theme\" [class.ngb-responsive]=\"isResponsiveEnabled()\">\n <!-- Export toolbar -->\n <div class=\"d-flex justify-content-end mb-2\" *ngIf=\"exportOptions?.enabled\">\n <ng-container *ngIf=\"exportButtonTpl; else defaultExportBtns\"\n [ngTemplateOutlet]=\"exportButtonTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: triggerExport }\">\n </ng-container>\n\n <ng-template #defaultExportBtns>\n <button type=\"button\"\n class=\"btn btn-sm btn-outline-secondary me-2\"\n *ngIf=\"exportOptions.type === 'pdf' || exportOptions.type === 'both'\"\n [disabled]=\"exporting\"\n (click)=\"export('pdf')\"\n [attr.aria-label]=\"exportAriaLabel('pdf')\">\n Export to PDF\n </button>\n <button type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n *ngIf=\"exportOptions.type === 'excel' || exportOptions.type === 'both'\"\n [disabled]=\"exporting\"\n (click)=\"export('excel')\"\n [attr.aria-label]=\"exportAriaLabel('excel')\">\n Export to Excel\n </button>\n </ng-template>\n </div>\n <div class=\"d-flex justify-content-end mb-2\" *ngIf=\"enableAdd\">\n <button\n type=\"button\"\n class=\"btn btn-sm btn-primary\"\n (click)=\"startAdd()\"\n [disabled]=\"addingNew\"\n [attr.aria-label]=\"addButtonAriaLabel || addButtonText\"\n >\n {{ addButtonText }}\n </button>\n </div>\n <!-- Global filter -->\n <div class=\"mb-2\" *ngIf=\"enableFiltering && enableGlobalFilter\">\n <ng-container\n *ngIf=\"globalTpl; else defaultGlobalFilter\"\n [ngTemplateOutlet]=\"globalTpl?.template\"\n [ngTemplateOutletContext]=\"{ $implicit: globalFilterCtrl }\"\n >\n </ng-container>\n\n <ng-template #defaultGlobalFilter>\n <input\n type=\"search\"\n class=\"form-control form-control-sm\"\n placeholder=\"Search all columns...\"\n [formControl]=\"globalFilterCtrl\"\n [attr.aria-label]=\"globalFilterAriaLabel\"\n />\n </ng-template>\n </div>\n\n <table class=\"table table-bordered\"\n role=\"grid\"\n [attr.aria-readonly]=\"(enableEdit || enableAdd) ? 'false' : 'true'\">\n <thead class=\"thead-light\">\n <tr>\n <th *ngIf=\"rowDetailTpl\" style=\"width:1%\" scope=\"col\" aria-hidden=\"true\"></th>\n <th\n *ngFor=\"let col of columns\"\n [attr.data-title]=\"col.header\"\n [class.sortable]=\"enableSorting && col.sortable\"\n scope=\"col\"\n [attr.aria-sort]=\"enableSorting && col.sortable ? ariaSortFor(col.field) : null\"\n >\n <ng-container *ngIf=\"enableSorting && col.sortable; else plainHeader\">\n <button\n type=\"button\"\n class=\"btn btn-link p-0 text-start w-100\"\n (click)=\"toggleSort(col.field)\"\n [attr.aria-label]=\"sortButtonAriaLabel(col)\"\n >\n <span>{{ col.header }}</span>\n <span\n class=\"sort-indicator\"\n *ngIf=\"enableSorting && sort.active === col.field\"\n aria-hidden=\"true\"\n >\n {{\n sort.direction === 'asc'\n ? '\u25B2'\n : sort.direction === 'desc'\n ? '\u25BC'\n : ''\n }}\n </span>\n </button>\n </ng-container>\n <ng-template #plainHeader>\n <span>{{ col.header }}</span>\n </ng-template>\n </th>\n <th\n *ngIf=\"enableEdit || enableDelete\"\n class=\"text-center\"\n scope=\"col\"\n style=\"width: 1%\"\n >\n Actions\n </th>\n </tr>\n\n <!-- Per-column filters -->\n <tr *ngIf=\"anyFilterable\" [formGroup]=\"filterForm\">\n <th *ngFor=\"let col of columns\" [attr.data-title]=\"col.header\">\n <ng-container *ngIf=\"col.filterable\">\n <ng-container *ngIf=\"filterTpls[col.field] as ft; else defaultFilter\">\n <ng-container\n [ngTemplateOutlet]=\"ft.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: filterForm.get(col.field),\n control: filterForm.get(col.field),\n col: col\n }\"\n >\n </ng-container>\n </ng-container>\n <ng-template #defaultFilter>\n <input\n type=\"text\"\n class=\"form-control form-control-sm\"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"columnFilterAriaLabel(col)\"\n />\n </ng-template>\n </ng-container>\n </th>\n <th *ngIf=\"enableEdit || enableDelete\"></th>\n </tr>\n </thead>\n\n <tbody>\n <!-- ADD NEW ROW -->\n <tr *ngIf=\"addingNew\" [formGroup]=\"addForm\">\n <!-- blank caret cell to keep alignment when detail column is present -->\n <td *ngIf=\"rowDetailTpl\"></td>\n\n <td *ngFor=\"let col of columns\" [attr.data-title]=\"col.header\">\n <ng-container [ngSwitch]=\"col.type\">\n <!-- boolean -->\n <div\n *ngSwitchCase=\"'boolean'\"\n class=\"form-check m-0\"\n [class.is-invalid]=\"(addForm.get(col.field)?.touched || saveAttemptedNew) && addForm.get(col.field)?.invalid\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"inputAriaLabel(col)\"\n [attr.aria-invalid]=\"(addForm.get(col.field)?.touched || saveAttemptedNew) && addForm.get(col.field)?.invalid ? 'true' : null\"\n [attr.aria-describedby]=\"(addForm.get(col.field)?.touched || saveAttemptedNew) && addForm.get(col.field)?.invalid ? 'add-error-' + col.field : null\"\n />\n </div>\n\n <!-- number/email/date/text -->\n <input\n *ngSwitchDefault\n class=\"form-control form-control-sm\"\n [class.is-invalid]=\"(addForm.get(col.field)?.touched || saveAttemptedNew) && addForm.get(col.field)?.invalid\"\n [attr.type]=\"col.type === 'number' ? 'number' :\n col.type === 'email' ? 'email' :\n col.type === 'date' ? 'date' : 'text'\"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"inputAriaLabel(col)\"\n [attr.aria-invalid]=\"(addForm.get(col.field)?.touched || saveAttemptedNew) && addForm.get(col.field)?.invalid ? 'true' : null\"\n [attr.aria-describedby]=\"(addForm.get(col.field)?.touched || saveAttemptedNew) && addForm.get(col.field)?.invalid ? 'add-error-' + col.field : null\"\n />\n </ng-container>\n\n <div class=\"invalid-feedback d-block\"\n *ngIf=\"(addForm.get(col.field)?.touched || saveAttemptedNew) && addForm.get(col.field)?.errors as e\"\n [attr.id]=\"'add-error-' + col.field\"\n role=\"alert\">\n <ng-container *ngIf=\"e['required']\">Required</ng-container>\n <ng-container *ngIf=\"e['email']\">Invalid email</ng-container>\n <ng-container *ngIf=\"e['number']\">Invalid number</ng-container>\n <ng-container *ngIf=\"e['date']\">Invalid date</ng-container>\n </div>\n </td>\n\n <td *ngIf=\"enableEdit || enableDelete\" class=\"text-nowrap text-center\">\n <button type=\"button\" class=\"btn btn-sm btn-success me-1\" (click)=\"saveAdd()\" [disabled]=\"addForm.invalid\">Save</button>\n <button type=\"button\" class=\"btn btn-sm btn-secondary\" (click)=\"cancelAdd()\">Cancel</button>\n </td>\n </tr>\n\n <!-- DATA ROWS + DETAIL ROWS -->\n <ng-container *ngFor=\"let row of paged; let i = index; trackBy: trackRow\">\n\n <!-- DATA ROW (click-to-edit supported) -->\n <tr (click)=\"onRowClick($event, i)\" [formGroup]=\"editForm\">\n\n <!-- caret/expander cell (left-most) -->\n <td *ngIf=\"rowDetailTpl\" class=\"text-center align-middle\">\n <button\n type=\"button\"\n class=\"expand btn btn-link p-0\"\n (click)=\"$event.stopPropagation(); toggleExpand(i)\"\n [attr.aria-expanded]=\"isExpanded(i)\"\n [attr.aria-controls]=\"'dg-row-detail-' + i\"\n [attr.aria-label]=\"isExpanded(i) ? collapseRowAriaLabel : expandRowAriaLabel\">\n {{ isExpanded(i) ? '-' : '+' }}\n </button>\n </td>\n\n <!-- DATA CELLS -->\n <td *ngFor=\"let col of columns\" [attr.data-title]=\"col.header\">\n <!-- EDIT MODE -->\n <ng-container *ngIf=\"editingIndex === i && (col.editable ?? true); else readCell\">\n <!-- editor template override -->\n <ng-container *ngIf=\"editTpls[col.field] as et; else defaultEditor\"\n [ngTemplateOutlet]=\"et.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: editForm.get(col.field),\n control: editForm.get(col.field),\n row: row, col: col, form: editForm, index: i, isNew: false\n }\">\n </ng-container>\n\n <!-- default editors -->\n <ng-template #defaultEditor>\n <ng-container [ngSwitch]=\"col.type\">\n <div *ngSwitchCase=\"'boolean'\" class=\"form-check m-0\">\n <input type=\"checkbox\"\n class=\"form-check-input\"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"inputAriaLabel(col)\"\n [attr.aria-invalid]=\"(editForm?.touched || saveAttemptedEdit) && editForm?.get(col.field)?.invalid ? 'true' : null\"\n [attr.aria-describedby]=\"(editForm?.touched || saveAttemptedEdit) && editForm?.get(col.field)?.invalid ? 'edit-error-' + col.field : null\" />\n </div>\n <select *ngSwitchCase=\"'select'\"\n class=\"form-select form-select-sm\"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"inputAriaLabel(col)\">\n <option *ngFor=\"let o of col.options ?? []\" [ngValue]=\"o.value\">{{ o.label }}</option>\n </select>\n <input *ngSwitchDefault\n class=\"form-control form-control-sm\"\n [attr.type]=\"col.type === 'number' ? 'number' :\n col.type === 'email' ? 'email' :\n col.type === 'date' ? 'date' : 'text'\"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"inputAriaLabel(col)\"\n [attr.aria-invalid]=\"(editForm?.touched || saveAttemptedEdit) && editForm?.get(col.field)?.invalid ? 'true' : null\"\n [attr.aria-describedby]=\"(editForm?.touched || saveAttemptedEdit) && editForm?.get(col.field)?.invalid ? 'edit-error-' + col.field : null\" />\n </ng-container>\n\n <div class=\"invalid-feedback d-block\"\n *ngIf=\"(editForm?.touched || saveAttemptedEdit) && editForm?.get(col.field)?.errors as e\"\n [attr.id]=\"'edit-error-' + col.field\"\n role=\"alert\">\n <span *ngIf=\"e['required']\">Required</span>\n <span *ngIf=\"e['email']\">Invalid email</span>\n <span *ngIf=\"e['number']\">Invalid number</span>\n <span *ngIf=\"e['date']\">Invalid date</span>\n </div>\n </ng-template>\n </ng-container>\n\n <!-- READ MODE (with optional cell template) -->\n <ng-template #readCell>\n <ng-container *ngIf=\"cellTpls[col.field] as ct; else defaultCell\"\n [ngTemplateOutlet]=\"ct.template\"\n [ngTemplateOutletContext]=\"{ $implicit: row[col.field], row: row, col: col, index: i }\">\n </ng-container>\n <ng-template #defaultCell>\n <ng-container [ngSwitch]=\"col.type\">\n <span *ngSwitchCase=\"'boolean'\">{{ row[col.field] ? 'Yes' : 'No' }}</span>\n <span *ngSwitchDefault>{{ row[col.field] }}</span>\n </ng-container>\n </ng-template>\n </ng-template>\n </td>\n\n <!-- ACTIONS -->\n <td *ngIf=\"enableEdit || enableDelete\" class=\"text-nowrap text-center\">\n <ng-container *ngIf=\"editingIndex !== i; else editBtns\">\n <button type=\"button\" *ngIf=\"enableEdit\" class=\"btn btn-sm btn-outline-primary me-1 no-edit-trigger\" (click)=\"startEdit(i)\">Edit</button>\n <button type=\"button\" *ngIf=\"enableDelete\" class=\"btn btn-sm btn-outline-danger no-edit-trigger\" (click)=\"deleteRow(i)\">Delete</button>\n </ng-container>\n <ng-template #editBtns>\n <button type=\"button\" class=\"btn btn-sm btn-success me-1\" (click)=\"saveEdit(i)\" [disabled]=\"editForm.invalid\">Save</button>\n <button type=\"button\" class=\"btn btn-sm btn-secondary\" (click)=\"cancelEdit(i)\">Cancel</button>\n </ng-template>\n </td>\n </tr>\n\n <!-- DETAIL ROW (spans all columns) -->\n <tr *ngIf=\"rowDetailTpl && isExpanded(i)\" [attr.id]=\"'dg-row-detail-' + i\">\n <td [attr.colspan]=\"detailColspan\" role=\"region\">\n <ng-container [ngTemplateOutlet]=\"rowDetailTpl.template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i }\">\n </ng-container>\n </td>\n </tr>\n\n </ng-container>\n </tbody>\n\n </table>\n\n <!-- Footer (left = count, center = pagination, right = page size) -->\n <div *ngIf=\"enablePagination\" class=\"d-flex align-items-center justify-content-between mt-2\">\n <!-- left: count -->\n <div class=\"small text-muted\" aria-live=\"polite\">\n {{ startIndex }} - {{ endIndex }} of {{ sorted.length }}\n\n </div>\n\n <!-- center: pager -->\n <div class=\"flex-grow-1 d-flex justify-content-center\">\n <ngb-pagination\n [page]=\"page\"\n [pageSize]=\"pageSize\"\n [collectionSize]=\"sorted.length\"\n [maxSize]=\"5\"\n (pageChange)=\"onPage($event)\">\n </ngb-pagination>\n </div>\n\n <!-- right: rows per page -->\n <div class=\"d-flex align-items-center gap-2\">\n <label class=\"form-label form-label-sm mb-0\" for=\"pageSize\">Rows:</label>\n <select\n id=\"pageSize\"\n class=\"form-select form-select-sm\"\n [(ngModel)]=\"pageSize\"\n (ngModelChange)=\"onPageSize($event)\"\n aria-label=\"Rows per page\">\n <option *ngFor=\"let s of pageSizeOptions\" [value]=\"s\">{{ s }}</option>\n </select>\n </div>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.table{margin-bottom:.5rem}th.sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.sort-indicator{margin-left:.35rem;font-size:.75rem}.expand{cursor:pointer;font-size:24px;line-height:18px;display:block}.ngb-grid[data-theme=material]{--ngb-primary: #3f51b5}.ngb-grid[data-theme=material] .btn{border-color:var(--ngb-primary)}.ngb-grid[data-theme=material] table.table th,.ngb-grid[data-theme=material] table.table td{border-bottom:1px solid #e5e7eb}.ngb-grid[data-theme=tailwind]{--ngb-primary: rgb(29 78 216)}.ngb-grid[data-theme=tailwind] .btn{border-color:var(--ngb-primary);border-radius:.75rem}.ngb-grid[data-theme=tailwind] table.table th,.ngb-grid[data-theme=tailwind] table.table td{border-bottom:1px solid #e5e7eb;padding:.5rem}.ngb-responsive .table{width:100%}@media(max-width:768px){.ngb-responsive thead{display:none}.ngb-responsive tbody tr{display:grid;grid-template-columns:1fr;gap:.5rem;border:1px solid #e5e7eb;border-radius:.75rem;padding:.75rem;margin-bottom:.75rem}.ngb-responsive tbody td{display:grid;grid-template-columns:8rem 1fr}.ngb-responsive tbody td:before{content:attr(data-title);font-weight:600;opacity:.75;padding-right:.5rem}}\n"] }]
1546
+ ], standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- eslint-disable @angular-eslint/template/interactive-supports-focus -->\n<!-- eslint-disable @angular-eslint/template/click-events-have-key-events -->\n<div\n class=\"ngb-grid\"\n [attr.data-theme]=\"theme\"\n [class.ngb-responsive]=\"isResponsiveEnabled()\"\n>\n <!-- Export toolbar -->\n <div class=\"d-flex justify-content-end mb-2\" *ngIf=\"exportOptions?.enabled\">\n <ng-container\n *ngIf=\"exportButtonTpl; else defaultExportBtns\"\n [ngTemplateOutlet]=\"exportButtonTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: triggerExport }\"\n >\n </ng-container>\n\n <ng-template #defaultExportBtns>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary me-2\"\n *ngIf=\"exportOptions.type === 'pdf' || exportOptions.type === 'both'\"\n [disabled]=\"exporting\"\n (click)=\"export('pdf')\"\n [attr.aria-label]=\"exportAriaLabel('pdf')\"\n >\n Export to PDF\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n *ngIf=\"exportOptions.type === 'excel' || exportOptions.type === 'both'\"\n [disabled]=\"exporting\"\n (click)=\"export('excel')\"\n [attr.aria-label]=\"exportAriaLabel('excel')\"\n >\n Export to Excel\n </button>\n </ng-template>\n </div>\n <div class=\"d-flex justify-content-end mb-2\" *ngIf=\"enableAdd\">\n <button\n type=\"button\"\n class=\"btn btn-sm btn-primary\"\n (click)=\"startAdd()\"\n [disabled]=\"addingNew\"\n [attr.aria-label]=\"addButtonAriaLabel || addButtonText\"\n >\n {{ addButtonText }}\n </button>\n </div>\n <!-- Global filter -->\n <div class=\"mb-2\" *ngIf=\"enableFiltering && enableGlobalFilter\">\n <ng-container\n *ngIf=\"globalTpl; else defaultGlobalFilter\"\n [ngTemplateOutlet]=\"globalTpl?.template\"\n [ngTemplateOutletContext]=\"{ $implicit: globalFilterCtrl }\"\n >\n </ng-container>\n\n <ng-template #defaultGlobalFilter>\n <input\n type=\"search\"\n class=\"form-control form-control-sm\"\n placeholder=\"Search all columns...\"\n [formControl]=\"globalFilterCtrl\"\n [attr.aria-label]=\"globalFilterAriaLabel\"\n />\n </ng-template>\n </div>\n\n <div\n class=\"table-wrapper\"\n [ngClass]=\"responsiveWrapperClasses\"\n >\n <!-- <div class=\"table-header\"> -->\n <table class=\"grid-table grid-header mb-0\" [ngClass]=\"tableClassList\">\n <caption\n *ngIf=\"tableOptions?.caption\"\n [class.caption-top]=\"(tableOptions?.captionSide ?? 'top') === 'top'\"\n >\n {{\n tableOptions?.caption\n }}\n </caption>\n <colgroup [ngbSyncColgroup]=\"colgroupSyncId\" syncRole=\"header\">\n <col *ngIf=\"isSelectionEnabled()\" style=\"width: 1%\" />\n <col *ngIf=\"rowDetailTpl\" style=\"width: 1%\" data-fixed=\"true\" />\n <col *ngIf=\"stickyRowsEnabled\" style=\"width: 1%\" />\n <col *ngFor=\"let col of columns\" />\n <col *ngIf=\"enableEdit || enableDelete\" style=\"width: 1%\" />\n </colgroup>\n <thead class=\"table-light\">\n <tr>\n <th\n *ngIf=\"isSelectionEnabled()\"\n class=\"text-center\"\n scope=\"col\"\n style=\"width: 1%\"\n >\n <ng-container\n *ngIf=\"selectionMode === 'multiple' && selectAllEnabled\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"isPageAllSelected()\"\n [indeterminate]=\"isPageIndeterminate()\"\n (change)=\"toggleSelectAllCurrentPage()\"\n [attr.aria-label]=\"selectAllLabel()\"\n />\n </ng-container>\n </th>\n <th\n *ngIf=\"rowDetailTpl\"\n style=\"width: 1%\"\n scope=\"col\"\n aria-hidden=\"true\"\n ></th>\n <th\n *ngIf=\"stickyRowsEnabled\"\n style=\"width: 1%\"\n scope=\"col\"\n class=\"text-center\"\n data-title=\"Sticky\"\n ></th>\n <th\n *ngFor=\"let col of columns\"\n [attr.data-title]=\"col.header\"\n [class.sortable]=\"enableSorting && col.sortable\"\n scope=\"col\"\n [attr.title]=\"headerTitle(col)\"\n [attr.aria-sort]=\"\n enableSorting && col.sortable ? ariaSortFor(col.field) : null\n \"\n >\n <ng-container\n *ngIf=\"enableSorting && col.sortable; else plainHeader\"\n >\n <button\n type=\"button\"\n class=\"btn btn-link p-0 text-start w-100\"\n (click)=\"toggleSort(col.field)\"\n [attr.aria-label]=\"sortButtonAriaLabel(col)\"\n >\n <span>{{ col.header }}</span>\n <span\n class=\"sort-indicator\"\n *ngIf=\"enableSorting && sort.active === col.field\"\n aria-hidden=\"true\"\n >\n {{\n sort.direction === \"asc\"\n ? \"\u25B2\"\n : sort.direction === \"desc\"\n ? \"\u25BC\"\n : \"\"\n }}\n </span>\n </button>\n </ng-container>\n <ng-template #plainHeader>\n <span>{{ col.header }}</span>\n </ng-template>\n </th>\n <th\n *ngIf=\"enableEdit || enableDelete\"\n class=\"text-center\"\n scope=\"col\"\n style=\"width: 1%\"\n >\n Actions\n </th>\n </tr>\n\n <!-- Per-column filters -->\n <tr *ngIf=\"anyFilterable\" class=\"filter-row\" [formGroup]=\"filterForm\">\n <th *ngIf=\"isSelectionEnabled()\"></th>\n <th *ngIf=\"stickyRowsEnabled\"></th>\n <th *ngFor=\"let col of columns\" [attr.data-title]=\"col.header\">\n <ng-container *ngIf=\"col.filterable\">\n <ng-container\n *ngIf=\"filterTpls[col.field] as ft; else defaultFilter\"\n >\n <ng-container\n [ngTemplateOutlet]=\"ft.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: filterForm.get(col.field),\n control: filterForm.get(col.field),\n col: col,\n }\"\n >\n </ng-container>\n </ng-container>\n <ng-template #defaultFilter>\n <input\n type=\"text\"\n class=\"form-control form-control-sm\"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"columnFilterAriaLabel(col)\"\n />\n </ng-template>\n </ng-container>\n </th>\n <th *ngIf=\"enableEdit || enableDelete\"></th>\n </tr>\n </thead>\n </table>\n <!-- </div> -->\n\n <div [class.table-body-scroll]=\"shouldEnableScroll\" #bodyScroller>\n <table\n class=\"grid-table grid-body mb-0\"\n [ngClass]=\"tableClassList\"\n role=\"grid\"\n [attr.aria-readonly]=\"enableEdit || enableAdd ? 'false' : 'true'\"\n >\n <colgroup [ngbSyncColgroup]=\"colgroupSyncId\" syncRole=\"body\">\n <col *ngIf=\"isSelectionEnabled()\" style=\"width: 1%\" />\n <col *ngIf=\"rowDetailTpl\" style=\"width: 1%\" data-fixed=\"true\" />\n <col *ngIf=\"stickyRowsEnabled\" style=\"width: 1%\" />\n <col *ngFor=\"let col of columns\" />\n <col *ngIf=\"enableEdit || enableDelete\" style=\"width: 1%\" />\n </colgroup>\n <tbody>\n <!-- ADD NEW ROW -->\n <tr *ngIf=\"addingNew\" [formGroup]=\"addForm\">\n <!-- blank caret cell to keep alignment when detail column is present -->\n <td *ngIf=\"isSelectionEnabled()\"></td>\n <td *ngIf=\"rowDetailTpl\"></td>\n <td *ngIf=\"stickyRowsEnabled\" class=\"text-center\"></td>\n\n <td *ngFor=\"let col of columns\" [attr.data-title]=\"col.header\">\n <ng-container [ngSwitch]=\"col.type\">\n <!-- boolean -->\n <div\n *ngSwitchCase=\"'boolean'\"\n class=\"form-check m-0\"\n [class.is-invalid]=\"\n (addForm.get(col.field)?.touched || saveAttemptedNew) &&\n addForm.get(col.field)?.invalid\n \"\n >\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"inputAriaLabel(col)\"\n [attr.aria-invalid]=\"\n (addForm.get(col.field)?.touched || saveAttemptedNew) &&\n addForm.get(col.field)?.invalid\n ? 'true'\n : null\n \"\n [attr.aria-describedby]=\"\n (addForm.get(col.field)?.touched || saveAttemptedNew) &&\n addForm.get(col.field)?.invalid\n ? 'add-error-' + col.field\n : null\n \"\n />\n </div>\n\n <!-- number/email/date/text -->\n <input\n *ngSwitchDefault\n class=\"form-control form-control-sm\"\n [class.is-invalid]=\"\n (addForm.get(col.field)?.touched || saveAttemptedNew) &&\n addForm.get(col.field)?.invalid\n \"\n [attr.type]=\"\n col.type === 'number'\n ? 'number'\n : col.type === 'email'\n ? 'email'\n : col.type === 'date'\n ? 'date'\n : 'text'\n \"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"inputAriaLabel(col)\"\n [attr.aria-invalid]=\"\n (addForm.get(col.field)?.touched || saveAttemptedNew) &&\n addForm.get(col.field)?.invalid\n ? 'true'\n : null\n \"\n [attr.aria-describedby]=\"\n (addForm.get(col.field)?.touched || saveAttemptedNew) &&\n addForm.get(col.field)?.invalid\n ? 'add-error-' + col.field\n : null\n \"\n />\n </ng-container>\n\n <div\n class=\"invalid-feedback d-block\"\n *ngIf=\"\n (addForm.get(col.field)?.touched || saveAttemptedNew) &&\n addForm.get(col.field)?.errors as e\n \"\n [attr.id]=\"'add-error-' + col.field\"\n role=\"alert\"\n >\n <ng-container *ngIf=\"e['required']\">Required</ng-container>\n <ng-container *ngIf=\"e['email']\">Invalid email</ng-container>\n <ng-container *ngIf=\"e['number']\">Invalid number</ng-container>\n <ng-container *ngIf=\"e['date']\">Invalid date</ng-container>\n </div>\n </td>\n\n <td\n *ngIf=\"enableEdit || enableDelete\"\n class=\"text-nowrap text-center\"\n >\n <button\n type=\"button\"\n class=\"btn btn-sm btn-success me-1\"\n (click)=\"saveAdd()\"\n [disabled]=\"addForm.invalid\"\n >\n Save\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-secondary\"\n (click)=\"cancelAdd()\"\n >\n Cancel\n </button>\n </td>\n </tr>\n\n <!-- DATA ROWS + DETAIL ROWS -->\n <ng-container\n *ngFor=\"let row of paged; let i = index; trackBy: trackRow\"\n >\n <!-- DATA ROW (click-to-edit supported) -->\n <tr\n (click)=\"onRowSelect($event, i); onRowClick($event, i)\"\n [formGroup]=\"editForm\"\n [class.sticky-row]=\"isRowSticky(row, i)\"\n [class.row-selected]=\"isRowSelected(row, i)\"\n [class.row-highlight]=\"isRowHighlighted(row, i)\"\n [attr.aria-selected]=\"isRowSelected(row, i) ? 'true' : null\"\n [style.top.px]=\"stickyTop(row, i)\"\n [style.z-index]=\"isRowSticky(row, i) ? 3 : null\"\n >\n <td *ngIf=\"isSelectionEnabled()\" class=\"text-center align-middle\">\n <input\n type=\"checkbox\"\n [checked]=\"isRowSelected(row, i)\"\n [disabled]=\"\n selectionMode === 'none' ||\n selectionBehavior === 'row' ||\n isSelectionDisabled(row, i)\n \"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleSelection(i, $event)\"\n [attr.aria-label]=\"rowSelectionLabel(i)\"\n />\n </td>\n\n <!-- caret/expander cell (left-most) -->\n <td *ngIf=\"rowDetailTpl\" class=\"text-center align-middle\">\n <button\n type=\"button\"\n class=\"expand btn btn-link p-0\"\n (click)=\"$event.stopPropagation(); toggleExpand(i)\"\n [attr.aria-expanded]=\"isExpanded(i)\"\n [attr.aria-controls]=\"'dg-row-detail-' + i\"\n [attr.aria-label]=\"\n isExpanded(i) ? collapseRowAriaLabel : expandRowAriaLabel\n \"\n >\n {{ isExpanded(i) ? \"-\" : \"+\" }}\n </button>\n </td>\n\n <td\n *ngIf=\"stickyRowsEnabled\"\n class=\"text-center align-middle\"\n data-title=\"Sticky\"\n >\n <button\n type=\"button\"\n class=\"btn btn-link p-0 no-edit-trigger sticky-toggle\"\n (click)=\"$event.stopPropagation(); toggleStickyRow(i)\"\n [attr.aria-pressed]=\"isRowSticky(row, i)\"\n aria-label=\"Toggle sticky row\"\n >\n <ng-container *ngIf=\"stickyIcon(row, i) as icon\">\n <span\n class=\"bi\"\n [ngClass]=\"'bi-' + icon\"\n aria-hidden=\"true\"\n ></span>\n </ng-container>\n </button>\n </td>\n\n <!-- DATA CELLS -->\n <td\n *ngFor=\"let col of columns; let ci = index\"\n [attr.data-title]=\"col.header\"\n [attr.title]=\"cellTitle(row, col)\"\n [class.cell-highlight]=\"isCellHighlighted(row, i, col, ci)\"\n >\n <!-- EDIT MODE -->\n <ng-container\n *ngIf=\"\n editingIndex === i && (col.editable ?? true);\n else readCell\n \"\n >\n <!-- editor template override -->\n <ng-container\n *ngIf=\"editTpls[col.field] as et; else defaultEditor\"\n [ngTemplateOutlet]=\"et.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: editForm.get(col.field),\n control: editForm.get(col.field),\n row: row,\n col: col,\n form: editForm,\n index: i,\n isNew: false,\n }\"\n >\n </ng-container>\n\n <!-- default editors -->\n <ng-template #defaultEditor>\n <ng-container [ngSwitch]=\"col.type\">\n <div *ngSwitchCase=\"'boolean'\" class=\"form-check m-0\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"inputAriaLabel(col)\"\n [attr.aria-invalid]=\"\n (editForm?.touched || saveAttemptedEdit) &&\n editForm?.get(col.field)?.invalid\n ? 'true'\n : null\n \"\n [attr.aria-describedby]=\"\n (editForm?.touched || saveAttemptedEdit) &&\n editForm?.get(col.field)?.invalid\n ? 'edit-error-' + col.field\n : null\n \"\n />\n </div>\n <select\n *ngSwitchCase=\"'select'\"\n class=\"form-select form-select-sm\"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"inputAriaLabel(col)\"\n >\n <option\n *ngFor=\"let o of col.options ?? []\"\n [ngValue]=\"o.value\"\n >\n {{ o.label }}\n </option>\n </select>\n <input\n *ngSwitchDefault\n class=\"form-control form-control-sm\"\n [attr.type]=\"\n col.type === 'number'\n ? 'number'\n : col.type === 'email'\n ? 'email'\n : col.type === 'date'\n ? 'date'\n : 'text'\n \"\n [formControlName]=\"col.field\"\n [attr.aria-label]=\"inputAriaLabel(col)\"\n [attr.aria-invalid]=\"\n (editForm?.touched || saveAttemptedEdit) &&\n editForm?.get(col.field)?.invalid\n ? 'true'\n : null\n \"\n [attr.aria-describedby]=\"\n (editForm?.touched || saveAttemptedEdit) &&\n editForm?.get(col.field)?.invalid\n ? 'edit-error-' + col.field\n : null\n \"\n />\n </ng-container>\n\n <div\n class=\"invalid-feedback d-block\"\n *ngIf=\"\n (editForm?.touched || saveAttemptedEdit) &&\n editForm?.get(col.field)?.errors as e\n \"\n [attr.id]=\"'edit-error-' + col.field\"\n role=\"alert\"\n >\n <span *ngIf=\"e['required']\">Required</span>\n <span *ngIf=\"e['email']\">Invalid email</span>\n <span *ngIf=\"e['number']\">Invalid number</span>\n <span *ngIf=\"e['date']\">Invalid date</span>\n </div>\n </ng-template>\n </ng-container>\n\n <!-- READ MODE (with optional cell template) -->\n <ng-template #readCell>\n <ng-container\n *ngIf=\"cellTpls[col.field] as ct; else defaultCell\"\n [ngTemplateOutlet]=\"ct.template\"\n [ngTemplateOutletContext]=\"{\n $implicit: row[col.field],\n row: row,\n col: col,\n index: i,\n }\"\n >\n </ng-container>\n <ng-template #defaultCell>\n <ng-container [ngSwitch]=\"col.type\">\n <span *ngSwitchCase=\"'boolean'\">{{\n row[col.field] ? \"Yes\" : \"No\"\n }}</span>\n <span *ngSwitchDefault>{{ row[col.field] }}</span>\n </ng-container>\n </ng-template>\n </ng-template>\n </td>\n\n <!-- ACTIONS -->\n <td\n *ngIf=\"enableEdit || enableDelete\"\n class=\"text-nowrap text-center\"\n >\n <ng-container *ngIf=\"editingIndex !== i; else editBtns\">\n <button\n type=\"button\"\n *ngIf=\"enableEdit\"\n class=\"btn btn-sm btn-outline-primary me-1 no-edit-trigger\"\n (click)=\"startEdit(i)\"\n >\n Edit\n </button>\n <button\n type=\"button\"\n *ngIf=\"enableDelete\"\n class=\"btn btn-sm btn-outline-danger no-edit-trigger\"\n (click)=\"deleteRow(i)\"\n >\n Delete\n </button>\n </ng-container>\n <ng-template #editBtns>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-success me-1\"\n (click)=\"saveEdit(i)\"\n [disabled]=\"editForm.invalid\"\n >\n Save\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-secondary\"\n (click)=\"cancelEdit(i)\"\n >\n Cancel\n </button>\n </ng-template>\n </td>\n </tr>\n\n <!-- DETAIL ROW (spans all columns) -->\n <tr\n *ngIf=\"rowDetailTpl && isExpanded(i)\"\n [attr.id]=\"'dg-row-detail-' + i\"\n >\n <td [attr.colspan]=\"detailColspan\" role=\"region\">\n <ng-container\n [ngTemplateOutlet]=\"rowDetailTpl.template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i }\"\n >\n </ng-container>\n </td>\n </tr>\n </ng-container>\n </tbody>\n </table>\n </div>\n <table class=\"table grid-table grid-foot mb-0\">\n <colgroup>\n <ng-container *ngFor=\"let col of columns\">\n <col [style.width.px]=\"col?.width\" />\n </ng-container>\n </colgroup>\n <tbody>\n <tr>\n <td [attr.colspan]=\"columns.length\" width=\"100%\">\n <div\n *ngIf=\"enablePagination\"\n class=\"d-flex align-items-center justify-content-between mt-2 grid-footer\"\n [class.sticky-footer]=\"isFooterSticky\"\n >\n <!-- left: count -->\n <div class=\"small text-muted\" aria-live=\"polite\">\n {{ startIndex }} - {{ endIndex }} of {{ sorted.length }}\n </div>\n\n <!-- center: pager -->\n <div class=\"flex-grow-1 d-flex justify-content-center\">\n <ngb-pagination\n [page]=\"page\"\n [pageSize]=\"pageSize\"\n [collectionSize]=\"sorted.length\"\n [maxSize]=\"5\"\n (pageChange)=\"onPage($event)\"\n >\n </ngb-pagination>\n </div>\n\n <!-- right: rows per page -->\n <div class=\"d-flex align-items-center gap-2\">\n <label class=\"form-label form-label-sm mb-0\" for=\"pageSize\"\n >Rows:</label\n >\n <select\n id=\"pageSize\"\n class=\"form-select form-select-sm\"\n [(ngModel)]=\"pageSize\"\n (ngModelChange)=\"onPageSize($event)\"\n aria-label=\"Rows per page\"\n >\n <option *ngFor=\"let s of pageSizeOptions\" [value]=\"s\">\n {{ s }}\n </option>\n </select>\n </div>\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n</div>\n\n<!-- Footer (left = count, center = pagination, right = page size) -->\n", styles: ["@charset \"UTF-8\";:host{display:block}.table{margin-bottom:.5rem}th.sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.sort-indicator{margin-left:.35rem;font-size:.75rem}.expand{cursor:pointer;font-size:24px;line-height:18px;display:block}.sticky-toggle{display:inline-flex;align-items:center;justify-content:center;gap:.15rem}.table-wrapper{position:relative}.table-wrapper{overflow-x:auto;border:1px solid #dee2e6;border-radius:12px;background:#fff}.table-body-scroll{max-height:320px;overflow-y:auto;overflow-x:hidden;border-top:1px solid #e9ecef;border-bottom:1px solid #e9ecef;scrollbar-gutter:stable both-edges;display:inline-block;width:fit-content;vertical-align:top}.grid-head{border-bottom:0}.grid-table{width:100%;min-width:1200px;table-layout:fixed;margin:0;border-collapse:separate;border-spacing:0}.grid-head{position:relative;z-index:5}.grid-head thead tr:first-child th:first-child{border-top-left-radius:12px}.grid-head thead th{background:var(--bs-table-bg, #f8f9fa);border-bottom:1px solid #dee2e6;font-weight:600}.grid-table th,.grid-table td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;vertical-align:middle;border-bottom:1px solid #e9ecef;padding:.55rem .65rem}.filter-row td{background:#fff;border-bottom:1px solid #dee2e6;padding-top:.45rem;padding-bottom:.45rem}.sticky-row{position:sticky;background:#ccc;z-index:4}.sticky-row td{background:#ccc}.row-selected{background-color:#e7f1ff}.row-highlight{background-color:var(--bs-warning-bg-subtle, #fff3cd)}.row-highlight td{background-color:inherit}.cell-highlight{background-color:var(--bs-info-bg-subtle, #cff4fc)}.ngb-grid[data-theme=material]{--ngb-primary: #3f51b5}.ngb-grid[data-theme=material] .btn{border-color:var(--ngb-primary)}.ngb-grid[data-theme=material] table.table th,.ngb-grid[data-theme=material] table.table td{border-bottom:1px solid #e5e7eb}.ngb-grid[data-theme=tailwind]{--ngb-primary: rgb(29 78 216)}.ngb-grid[data-theme=tailwind] .btn{border-color:var(--ngb-primary);border-radius:.75rem}.ngb-grid[data-theme=tailwind] table.table th,.ngb-grid[data-theme=tailwind] table.table td{border-bottom:1px solid #e5e7eb;padding:.5rem}.ngb-responsive .table{width:100%}@media(max-width:768px){.ngb-responsive thead{display:none}.ngb-responsive tbody tr{display:grid;grid-template-columns:1fr;gap:.5rem;border:1px solid #e5e7eb;border-radius:.75rem;padding:.75rem;margin-bottom:.75rem}.ngb-responsive tbody td{display:grid;grid-template-columns:8rem 1fr}.ngb-responsive tbody td:before{content:attr(data-title);font-weight:600;opacity:.75;padding-right:.5rem}}\n"] }]
935
1547
  }], propDecorators: { columns: [{
936
1548
  type: Input
937
1549
  }], data: [{
@@ -956,6 +1568,40 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
956
1568
  type: Input
957
1569
  }], addButtonText: [{
958
1570
  type: Input
1571
+ }], stickyRows: [{
1572
+ type: Input
1573
+ }], stickyHeader: [{
1574
+ type: Input
1575
+ }], stickyFooter: [{
1576
+ type: Input
1577
+ }], scrollable: [{
1578
+ type: Input
1579
+ }], stickyRowHeight: [{
1580
+ type: Input
1581
+ }], stickyHeaderHeight: [{
1582
+ type: Input
1583
+ }], stickyFooterHeight: [{
1584
+ type: Input
1585
+ }], tableOptions: [{
1586
+ type: Input
1587
+ }], selectionMode: [{
1588
+ type: Input
1589
+ }], selectionBehavior: [{
1590
+ type: Input
1591
+ }], selectionKeyMode: [{
1592
+ type: Input
1593
+ }], selectAllEnabled: [{
1594
+ type: Input
1595
+ }], selectionA11yLabels: [{
1596
+ type: Input
1597
+ }], selectionDisabledFn: [{
1598
+ type: Input
1599
+ }], highlightedIndex: [{
1600
+ type: Input
1601
+ }], highlightRowKey: [{
1602
+ type: Input
1603
+ }], highlightColKey: [{
1604
+ type: Input
959
1605
  }], globalFilterAriaLabel: [{
960
1606
  type: Input
961
1607
  }], expandRowAriaLabel: [{
@@ -991,6 +1637,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
991
1637
  }], exportButtonDir: [{
992
1638
  type: ContentChild,
993
1639
  args: [ExportButtonDirective]
1640
+ }], bodyScroller: [{
1641
+ type: ViewChild,
1642
+ args: ['bodyScroller']
1643
+ }], headerScroller: [{
1644
+ type: ViewChild,
1645
+ args: ['headerScroller']
994
1646
  }], rowAdd: [{
995
1647
  type: Output
996
1648
  }], rowEdit: [{
@@ -1007,6 +1659,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
1007
1659
  type: Output
1008
1660
  }], pageChange: [{
1009
1661
  type: Output
1662
+ }], selectionChange: [{
1663
+ type: Output
1010
1664
  }], rowDetailTpl: [{
1011
1665
  type: ContentChild,
1012
1666
  args: [NgbRowDetailTemplate]
@@ -1043,10 +1697,10 @@ class NgbLiveAnnouncer {
1043
1697
  }
1044
1698
  this.node.textContent = message;
1045
1699
  }
1046
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbLiveAnnouncer, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1047
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbLiveAnnouncer, providedIn: 'root' });
1700
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbLiveAnnouncer, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1701
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbLiveAnnouncer, providedIn: 'root' });
1048
1702
  }
1049
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbLiveAnnouncer, decorators: [{
1703
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbLiveAnnouncer, decorators: [{
1050
1704
  type: Injectable,
1051
1705
  args: [{ providedIn: 'root' }]
1052
1706
  }] });
@@ -1099,10 +1753,10 @@ class NgbDndState {
1099
1753
  if (this.store.size === 0)
1100
1754
  this.active.set(false);
1101
1755
  }
1102
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbDndState, deps: [{ token: DND_LIVE_ANNOUNCE, optional: true }, { token: DND_I18N, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
1103
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbDndState, providedIn: 'root' });
1756
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbDndState, deps: [{ token: DND_LIVE_ANNOUNCE, optional: true }, { token: DND_I18N, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
1757
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbDndState, providedIn: 'root' });
1104
1758
  }
1105
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbDndState, decorators: [{
1759
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbDndState, decorators: [{
1106
1760
  type: Injectable,
1107
1761
  args: [{ providedIn: 'root' }]
1108
1762
  }], ctorParameters: () => [{ type: undefined, decorators: [{
@@ -1416,10 +2070,10 @@ class NgbDndListDirective {
1416
2070
  });
1417
2071
  this.state.announce?.(this.state.i18n.dropped());
1418
2072
  }
1419
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbDndListDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1420
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: NgbDndListDirective, isStandalone: true, selector: "[ngbDndList]", inputs: { dndChildrenKey: "dndChildrenKey", list: ["ngbDndList", "list"], dndGroup: "dndGroup", dndDisabled: "dndDisabled", dndCloneOnDrop: "dndCloneOnDrop", dndCloneFn: "dndCloneFn", dndIsPalette: "dndIsPalette", dndCanDrop: "dndCanDrop", dndAriaLabel: "dndAriaLabel" }, outputs: { dndDropped: "dndDropped" }, host: { listeners: { "dragenter": "onDragEnter($event)", "dragover": "onDragOver($event)", "dragleave": "onDragLeave($event)", "document:drop": "onDocumentDrop($event)", "drop": "onDrop($event)" }, properties: { "class.ngb-dnd-denied": "this.denied", "attr.aria-label": "this.ariaLabel", "attr.role": "this.role", "class.ngb-dnd-list": "this.hostClass", "class.ngb-dnd-over": "this.isOver", "attr.data-drop-valid": "this.dataDropValid" } }, ngImport: i0 });
2073
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbDndListDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2074
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: NgbDndListDirective, isStandalone: true, selector: "[ngbDndList]", inputs: { dndChildrenKey: "dndChildrenKey", list: ["ngbDndList", "list"], dndGroup: "dndGroup", dndDisabled: "dndDisabled", dndCloneOnDrop: "dndCloneOnDrop", dndCloneFn: "dndCloneFn", dndIsPalette: "dndIsPalette", dndCanDrop: "dndCanDrop", dndAriaLabel: "dndAriaLabel" }, outputs: { dndDropped: "dndDropped" }, host: { listeners: { "dragenter": "onDragEnter($event)", "dragover": "onDragOver($event)", "dragleave": "onDragLeave($event)", "document:drop": "onDocumentDrop($event)", "drop": "onDrop($event)" }, properties: { "class.ngb-dnd-denied": "this.denied", "attr.aria-label": "this.ariaLabel", "attr.role": "this.role", "class.ngb-dnd-list": "this.hostClass", "class.ngb-dnd-over": "this.isOver", "attr.data-drop-valid": "this.dataDropValid" } }, ngImport: i0 });
1421
2075
  }
1422
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbDndListDirective, decorators: [{
2076
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbDndListDirective, decorators: [{
1423
2077
  type: Directive,
1424
2078
  args: [{
1425
2079
  selector: '[ngbDndList]',
@@ -1568,10 +2222,10 @@ class NgbDndItemDirective {
1568
2222
  this.onDragEnd();
1569
2223
  } // cancel
1570
2224
  }
1571
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbDndItemDirective, deps: [{ token: NgbDndListDirective, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
1572
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: NgbDndItemDirective, isStandalone: true, selector: "[ngbDndItem]", inputs: { item: ["ngbDndItem", "item"], dndGroup: "dndGroup", dndIndex: "dndIndex", dndDisabled: "dndDisabled" }, outputs: { dndDragStart: "dndDragStart", dndDragEnd: "dndDragEnd" }, host: { listeners: { "dragstart": "onDragStart($event)", "dragend": "onDragEnd()", "keydown": "onKeydown($event)" }, properties: { "attr.draggable": "this.draggable", "class.ngb-dnd-dragging": "this.dragging", "attr.aria-grabbed": "this.ariaGrabbed", "attr.role": "this.role", "attr.tabindex": "this.tabIndex" } }, ngImport: i0 });
2225
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbDndItemDirective, deps: [{ token: NgbDndListDirective, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
2226
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: NgbDndItemDirective, isStandalone: true, selector: "[ngbDndItem]", inputs: { item: ["ngbDndItem", "item"], dndGroup: "dndGroup", dndIndex: "dndIndex", dndDisabled: "dndDisabled" }, outputs: { dndDragStart: "dndDragStart", dndDragEnd: "dndDragEnd" }, host: { listeners: { "dragstart": "onDragStart($event)", "dragend": "onDragEnd()", "keydown": "onKeydown($event)" }, properties: { "attr.draggable": "this.draggable", "class.ngb-dnd-dragging": "this.dragging", "attr.aria-grabbed": "this.ariaGrabbed", "attr.role": "this.role", "attr.tabindex": "this.tabIndex" } }, ngImport: i0 });
1573
2227
  }
1574
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbDndItemDirective, decorators: [{
2228
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbDndItemDirective, decorators: [{
1575
2229
  type: Directive,
1576
2230
  args: [{
1577
2231
  selector: '[ngbDndItem]',
@@ -1624,10 +2278,10 @@ class NgbStepLabelDirective {
1624
2278
  constructor(template) {
1625
2279
  this.template = template;
1626
2280
  }
1627
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbStepLabelDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
1628
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: NgbStepLabelDirective, isStandalone: true, selector: "[ngbStepLabel]", inputs: { for: ["ngbStepLabel", "for"] }, ngImport: i0 });
2281
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbStepLabelDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
2282
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: NgbStepLabelDirective, isStandalone: true, selector: "[ngbStepLabel]", inputs: { for: ["ngbStepLabel", "for"] }, ngImport: i0 });
1629
2283
  }
1630
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbStepLabelDirective, decorators: [{
2284
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbStepLabelDirective, decorators: [{
1631
2285
  type: Directive,
1632
2286
  args: [{
1633
2287
  selector: '[ngbStepLabel]',
@@ -1822,10 +2476,10 @@ class NgbStepperComponent {
1822
2476
  step: this.steps[nextIndex],
1823
2477
  });
1824
2478
  }
1825
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbStepperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1826
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: NgbStepperComponent, isStandalone: true, selector: "ngb-stepper", inputs: { steps: "steps", orientation: "orientation", labelPosition: "labelPosition", contentPosition: "contentPosition", allowRevisit: "allowRevisit", lazy: "lazy", theme: "theme", responsive: "responsive", disableAnimation: "disableAnimation", animationDuration: "animationDuration", nextLabel: "nextLabel", previousLabel: "previousLabel", resetLabel: "resetLabel", cancelLabel: "cancelLabel", optionalLabel: "optionalLabel", stateIcons: "stateIcons", selectedIndex: "selectedIndex" }, outputs: { selectedIndexChange: "selectedIndexChange", selectionChange: "selectionChange", nextClicked: "nextClicked", prevClicked: "prevClicked", resetClicked: "resetClicked", cancelClicked: "cancelClicked" }, queries: [{ propertyName: "stepLabelTplDir", first: true, predicate: NgbStepLabelDirective, descendants: true }, { propertyName: "customIconTpl", first: true, predicate: ["ngbStepperIcon"], descendants: true, read: TemplateRef }, { propertyName: "stepContentTpl", first: true, predicate: ["stepContent"], descendants: true, read: TemplateRef }], ngImport: i0, template: "<div\n class=\"ngb-stepper\"\n [class.horizontal]=\"orientation === 'horizontal'\"\n [class.vertical]=\"orientation === 'vertical'\"\n [class.ngb-responsive]=\"responsive\"\n [class.bootstrap]=\"theme === 'bootstrap'\"\n [class.material]=\"theme === 'material'\"\n [class.tailwind]=\"theme === 'tailwind'\"\n role=\"tablist\"\n [attr.aria-orientation]=\"orientation\"\n>\n <!-- HEADER / LABELS -->\n <div class=\"ngb-stepper-header\">\n <ol class=\"ngb-stepper-steps\" role=\"presentation\">\n <li\n *ngFor=\"let step of steps; let i = index\"\n class=\"ngb-stepper-step\"\n [class.active]=\"i === selectedIndex\"\n [class.done]=\"i < selectedIndex && !isStepInError(i)\"\n [class.error]=\"isStepInError(i)\"\n [class.disabled]=\"step.disabled\"\n role=\"tab\"\n [attr.id]=\"stepId(i)\"\n [attr.aria-controls]=\"contentId(i)\"\n [attr.aria-selected]=\"i === selectedIndex\"\n [attr.aria-disabled]=\"step.disabled || (!allowRevisit && i < visitedFrom && i !== selectedIndex)\"\n tabindex=\"{{ i === selectedIndex ? 0 : -1 }}\"\n (click)=\"onHeaderClick(i)\"\n (keydown.enter)=\"onHeaderKeydown($event, i)\"\n (keydown.space)=\"onHeaderKeydown($event, i)\"\n >\n <div\n class=\"ngb-stepper-indicator\"\n [ngClass]=\"indicatorClass(i)\"\n aria-hidden=\"true\"\n >\n <ng-container *ngIf=\"customIconTpl; else defaultIcon\">\n <ng-container\n [ngTemplateOutlet]=\"customIconTpl\"\n [ngTemplateOutletContext]=\"{ index: i, state: stepState(i) }\"\n ></ng-container>\n </ng-container>\n <ng-template #defaultIcon>\n {{ defaultIconForState(stepState(i), i) }}\n </ng-template>\n </div>\n\n <div\n class=\"ngb-stepper-label-container\"\n [ngClass]=\"labelPositionClass\"\n >\n <div class=\"ngb-stepper-label\" *ngIf=\"stepLabelTpl; else textLabel\">\n <ng-container\n [ngTemplateOutlet]=\"stepLabelTpl\"\n [ngTemplateOutletContext]=\"{ index: i, step: step }\"\n ></ng-container>\n </div>\n <ng-template #textLabel>\n <div class=\"ngb-stepper-label-text\">\n {{ step.label || ('Step ' + (i + 1)) }}\n </div>\n </ng-template>\n <div class=\"ngb-stepper-optional\" *ngIf=\"step.optional\">\n {{ optionalLabel }}\n </div>\n </div>\n </li>\n </ol>\n </div>\n\n <!-- CONTENT -->\n <div class=\"ngb-stepper-content\" [class.ngb-stepper-content-top]=\"contentPosition === 'top'\">\n <ng-container *ngIf=\"lazy; else allContent\">\n <ng-container *ngFor=\"let index of [selectedIndex]\">\n <div\n class=\"fade-step\"\n [style.--ngb-stepper-animation-duration]=\"(disableAnimation ? 0 : animationDuration) + 'ms'\"\n role=\"tabpanel\"\n [attr.aria-labelledby]=\"stepId(index)\"\n [attr.id]=\"contentId(index)\"\n >\n <ng-container *ngTemplateOutlet=\"stepContentTpl; context: { index: index }\"></ng-container>\n <div\n *ngIf=\"currentErrorMessage\"\n class=\"ngb-stepper-error\"\n role=\"alert\"\n >\n {{ currentErrorMessage }}\n </div>\n </div>\n </ng-container>\n </ng-container>\n\n <ng-template #allContent>\n <div\n class=\"fade-step\"\n [style.--ngb-stepper-animation-duration]=\"(disableAnimation ? 0 : animationDuration) + 'ms'\"\n role=\"tabpanel\"\n [attr.aria-labelledby]=\"stepId(selectedIndex)\"\n [attr.id]=\"contentId(selectedIndex)\"\n >\n <ng-container *ngTemplateOutlet=\"stepContentTpl; context: { index: selectedIndex }\"></ng-container>\n <div\n *ngIf=\"currentErrorMessage\"\n class=\"ngb-stepper-error\"\n role=\"alert\"\n >\n {{ currentErrorMessage }}\n </div>\n </div>\n </ng-template>\n </div>\n\n <!-- ACTIONS -->\n <div class=\"ngb-stepper-buttons\">\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"onCancel()\"\n >\n {{ cancelLabel }}\n </button>\n\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"onReset()\"\n >\n {{ resetLabel }}\n </button>\n\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"prev()\"\n [disabled]=\"selectedIndex <= 0 || !allowRevisit\"\n >\n {{ previousLabel }}\n </button>\n\n <button\n type=\"button\"\n class=\"btn btn-sm btn-primary\"\n (click)=\"next()\"\n [disabled]=\"selectedIndex >= steps.length - 1 || steps[selectedIndex]?.disabled\"\n >\n {{ nextLabel }}\n </button>\n </div>\n</div>\n", styles: [".ngb-stepper{display:flex;flex-direction:column}.ngb-stepper.horizontal .ngb-stepper-header{display:flex;overflow-x:auto}.ngb-stepper.vertical .ngb-stepper-header{display:block}.ngb-stepper-header{margin-bottom:1rem}.ngb-stepper-steps{list-style:none;padding:0;margin:0;display:flex;flex-wrap:nowrap}.ngb-stepper.vertical .ngb-stepper-steps{flex-direction:column}.ngb-stepper-step{position:relative;display:flex;align-items:center;cursor:pointer}.ngb-stepper.horizontal .ngb-stepper-step{padding:.5rem .75rem}.ngb-stepper.vertical .ngb-stepper-step{padding:.5rem 0}.ngb-stepper-step.disabled{cursor:default;opacity:.6}.ngb-stepper-indicator{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:50%;margin-right:.5rem;flex-shrink:0}.ngb-stepper.bootstrap .ngb-stepper-indicator{border:1px solid var(--bs-border-color, #dee2e6);background-color:var(--bs-body-bg, #fff)}.ngb-stepper.bootstrap .ngb-stepper-step.active .ngb-stepper-indicator{background-color:var(--bs-primary, #0d6efd);color:#fff}.ngb-stepper.bootstrap .ngb-stepper-step.done .ngb-stepper-indicator{background-color:var(--bs-success, #198754);color:#fff}.ngb-stepper.bootstrap .ngb-stepper-step.error .ngb-stepper-indicator{background-color:var(--bs-danger, #dc3545);color:#fff}.ngb-stepper-label-container{display:flex;flex-direction:column}.ngb-stepper-label-top{flex-direction:column-reverse}.ngb-stepper-label-bottom{flex-direction:column}.ngb-stepper-label-left{flex-direction:row-reverse}.ngb-stepper-label-right{flex-direction:row}.ngb-stepper-content{position:relative}.ngb-stepper-content-top{order:-1}.ngb-stepper-buttons{display:flex;justify-content:flex-end;gap:.5rem;margin-top:1rem}.ngb-stepper-error{color:var(--bs-danger, #dc3545);font-size:.875rem;margin-top:.25rem}.ngb-stepper-optional{font-size:.75rem;opacity:.7}.ngb-stepper .fade-step{animation:ngb-stepper-fade var(--ngb-stepper-animation-duration, .15s) ease-in-out;animation-fill-mode:both}@keyframes ngb-stepper-fade{0%{opacity:0;transform:translateY(.35rem)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2479
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbStepperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2480
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.9", type: NgbStepperComponent, isStandalone: true, selector: "ngb-stepper", inputs: { steps: "steps", orientation: "orientation", labelPosition: "labelPosition", contentPosition: "contentPosition", allowRevisit: "allowRevisit", lazy: "lazy", theme: "theme", responsive: "responsive", disableAnimation: "disableAnimation", animationDuration: "animationDuration", nextLabel: "nextLabel", previousLabel: "previousLabel", resetLabel: "resetLabel", cancelLabel: "cancelLabel", optionalLabel: "optionalLabel", stateIcons: "stateIcons", selectedIndex: "selectedIndex" }, outputs: { selectedIndexChange: "selectedIndexChange", selectionChange: "selectionChange", nextClicked: "nextClicked", prevClicked: "prevClicked", resetClicked: "resetClicked", cancelClicked: "cancelClicked" }, queries: [{ propertyName: "stepLabelTplDir", first: true, predicate: NgbStepLabelDirective, descendants: true }, { propertyName: "customIconTpl", first: true, predicate: ["ngbStepperIcon"], descendants: true, read: TemplateRef }, { propertyName: "stepContentTpl", first: true, predicate: ["stepContent"], descendants: true, read: TemplateRef }], ngImport: i0, template: "<div\n class=\"ngb-stepper\"\n [class.horizontal]=\"orientation === 'horizontal'\"\n [class.vertical]=\"orientation === 'vertical'\"\n [class.ngb-responsive]=\"responsive\"\n [class.bootstrap]=\"theme === 'bootstrap'\"\n [class.material]=\"theme === 'material'\"\n [class.tailwind]=\"theme === 'tailwind'\"\n role=\"tablist\"\n [attr.aria-orientation]=\"orientation\"\n>\n <!-- HEADER / LABELS -->\n <div class=\"ngb-stepper-header\">\n <ol class=\"ngb-stepper-steps\" role=\"presentation\">\n <li\n *ngFor=\"let step of steps; let i = index\"\n class=\"ngb-stepper-step\"\n [class.active]=\"i === selectedIndex\"\n [class.done]=\"i < selectedIndex && !isStepInError(i)\"\n [class.error]=\"isStepInError(i)\"\n [class.disabled]=\"step.disabled\"\n role=\"tab\"\n [attr.id]=\"stepId(i)\"\n [attr.aria-controls]=\"contentId(i)\"\n [attr.aria-selected]=\"i === selectedIndex\"\n [attr.aria-disabled]=\"step.disabled || (!allowRevisit && i < visitedFrom && i !== selectedIndex)\"\n tabindex=\"{{ i === selectedIndex ? 0 : -1 }}\"\n (click)=\"onHeaderClick(i)\"\n (keydown.enter)=\"onHeaderKeydown($event, i)\"\n (keydown.space)=\"onHeaderKeydown($event, i)\"\n >\n <div\n class=\"ngb-stepper-indicator\"\n [ngClass]=\"indicatorClass(i)\"\n aria-hidden=\"true\"\n >\n <ng-container *ngIf=\"customIconTpl; else defaultIcon\">\n <ng-container\n [ngTemplateOutlet]=\"customIconTpl\"\n [ngTemplateOutletContext]=\"{ index: i, state: stepState(i) }\"\n ></ng-container>\n </ng-container>\n <ng-template #defaultIcon>\n {{ defaultIconForState(stepState(i), i) }}\n </ng-template>\n </div>\n\n <div\n class=\"ngb-stepper-label-container\"\n [ngClass]=\"labelPositionClass\"\n >\n <div class=\"ngb-stepper-label\" *ngIf=\"stepLabelTpl; else textLabel\">\n <ng-container\n [ngTemplateOutlet]=\"stepLabelTpl\"\n [ngTemplateOutletContext]=\"{ index: i, step: step }\"\n ></ng-container>\n </div>\n <ng-template #textLabel>\n <div class=\"ngb-stepper-label-text\">\n {{ step.label || ('Step ' + (i + 1)) }}\n </div>\n </ng-template>\n <div class=\"ngb-stepper-optional\" *ngIf=\"step.optional\">\n {{ optionalLabel }}\n </div>\n </div>\n </li>\n </ol>\n </div>\n\n <!-- CONTENT -->\n <div class=\"ngb-stepper-content\" [class.ngb-stepper-content-top]=\"contentPosition === 'top'\">\n <ng-container *ngIf=\"lazy; else allContent\">\n <ng-container *ngFor=\"let index of [selectedIndex]\">\n <div\n class=\"fade-step\"\n [style.--ngb-stepper-animation-duration]=\"(disableAnimation ? 0 : animationDuration) + 'ms'\"\n role=\"tabpanel\"\n [attr.aria-labelledby]=\"stepId(index)\"\n [attr.id]=\"contentId(index)\"\n >\n <ng-container *ngTemplateOutlet=\"stepContentTpl; context: { index: index }\"></ng-container>\n <div\n *ngIf=\"currentErrorMessage\"\n class=\"ngb-stepper-error\"\n role=\"alert\"\n >\n {{ currentErrorMessage }}\n </div>\n </div>\n </ng-container>\n </ng-container>\n\n <ng-template #allContent>\n <div\n class=\"fade-step\"\n [style.--ngb-stepper-animation-duration]=\"(disableAnimation ? 0 : animationDuration) + 'ms'\"\n role=\"tabpanel\"\n [attr.aria-labelledby]=\"stepId(selectedIndex)\"\n [attr.id]=\"contentId(selectedIndex)\"\n >\n <ng-container *ngTemplateOutlet=\"stepContentTpl; context: { index: selectedIndex }\"></ng-container>\n <div\n *ngIf=\"currentErrorMessage\"\n class=\"ngb-stepper-error\"\n role=\"alert\"\n >\n {{ currentErrorMessage }}\n </div>\n </div>\n </ng-template>\n </div>\n\n <!-- ACTIONS -->\n <div class=\"ngb-stepper-buttons\">\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"onCancel()\"\n >\n {{ cancelLabel }}\n </button>\n\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"onReset()\"\n >\n {{ resetLabel }}\n </button>\n\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"prev()\"\n [disabled]=\"selectedIndex <= 0 || !allowRevisit\"\n >\n {{ previousLabel }}\n </button>\n\n <button\n type=\"button\"\n class=\"btn btn-sm btn-primary\"\n (click)=\"next()\"\n [disabled]=\"selectedIndex >= steps.length - 1 || steps[selectedIndex]?.disabled\"\n >\n {{ nextLabel }}\n </button>\n </div>\n</div>\n", styles: [".ngb-stepper{display:flex;flex-direction:column}.ngb-stepper.horizontal .ngb-stepper-header{display:flex;overflow-x:auto}.ngb-stepper.vertical .ngb-stepper-header{display:block}.ngb-stepper-header{margin-bottom:1rem}.ngb-stepper-steps{list-style:none;padding:0;margin:0;display:flex;flex-wrap:nowrap}.ngb-stepper.vertical .ngb-stepper-steps{flex-direction:column}.ngb-stepper-step{position:relative;display:flex;align-items:center;cursor:pointer}.ngb-stepper.horizontal .ngb-stepper-step{padding:.5rem .75rem}.ngb-stepper.vertical .ngb-stepper-step{padding:.5rem 0}.ngb-stepper-step.disabled{cursor:default;opacity:.6}.ngb-stepper-indicator{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:50%;margin-right:.5rem;flex-shrink:0}.ngb-stepper.bootstrap .ngb-stepper-indicator{border:1px solid var(--bs-border-color, #dee2e6);background-color:var(--bs-body-bg, #fff)}.ngb-stepper.bootstrap .ngb-stepper-step.active .ngb-stepper-indicator{background-color:var(--bs-primary, #0d6efd);color:#fff}.ngb-stepper.bootstrap .ngb-stepper-step.done .ngb-stepper-indicator{background-color:var(--bs-success, #198754);color:#fff}.ngb-stepper.bootstrap .ngb-stepper-step.error .ngb-stepper-indicator{background-color:var(--bs-danger, #dc3545);color:#fff}.ngb-stepper-label-container{display:flex;flex-direction:column}.ngb-stepper-label-top{flex-direction:column-reverse}.ngb-stepper-label-bottom{flex-direction:column}.ngb-stepper-label-left{flex-direction:row-reverse}.ngb-stepper-label-right{flex-direction:row}.ngb-stepper-content{position:relative}.ngb-stepper-content-top{order:-1}.ngb-stepper-buttons{display:flex;justify-content:flex-end;gap:.5rem;margin-top:1rem}.ngb-stepper-error{color:var(--bs-danger, #dc3545);font-size:.875rem;margin-top:.25rem}.ngb-stepper-optional{font-size:.75rem;opacity:.7}.ngb-stepper .fade-step{animation:ngb-stepper-fade var(--ngb-stepper-animation-duration, .15s) ease-in-out;animation-fill-mode:both}@keyframes ngb-stepper-fade{0%{opacity:0;transform:translateY(.35rem)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1827
2481
  }
1828
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbStepperComponent, decorators: [{
2482
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbStepperComponent, decorators: [{
1829
2483
  type: Component,
1830
2484
  args: [{ selector: 'ngb-stepper', standalone: true, imports: [CommonModule, NgbStepLabelDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n class=\"ngb-stepper\"\n [class.horizontal]=\"orientation === 'horizontal'\"\n [class.vertical]=\"orientation === 'vertical'\"\n [class.ngb-responsive]=\"responsive\"\n [class.bootstrap]=\"theme === 'bootstrap'\"\n [class.material]=\"theme === 'material'\"\n [class.tailwind]=\"theme === 'tailwind'\"\n role=\"tablist\"\n [attr.aria-orientation]=\"orientation\"\n>\n <!-- HEADER / LABELS -->\n <div class=\"ngb-stepper-header\">\n <ol class=\"ngb-stepper-steps\" role=\"presentation\">\n <li\n *ngFor=\"let step of steps; let i = index\"\n class=\"ngb-stepper-step\"\n [class.active]=\"i === selectedIndex\"\n [class.done]=\"i < selectedIndex && !isStepInError(i)\"\n [class.error]=\"isStepInError(i)\"\n [class.disabled]=\"step.disabled\"\n role=\"tab\"\n [attr.id]=\"stepId(i)\"\n [attr.aria-controls]=\"contentId(i)\"\n [attr.aria-selected]=\"i === selectedIndex\"\n [attr.aria-disabled]=\"step.disabled || (!allowRevisit && i < visitedFrom && i !== selectedIndex)\"\n tabindex=\"{{ i === selectedIndex ? 0 : -1 }}\"\n (click)=\"onHeaderClick(i)\"\n (keydown.enter)=\"onHeaderKeydown($event, i)\"\n (keydown.space)=\"onHeaderKeydown($event, i)\"\n >\n <div\n class=\"ngb-stepper-indicator\"\n [ngClass]=\"indicatorClass(i)\"\n aria-hidden=\"true\"\n >\n <ng-container *ngIf=\"customIconTpl; else defaultIcon\">\n <ng-container\n [ngTemplateOutlet]=\"customIconTpl\"\n [ngTemplateOutletContext]=\"{ index: i, state: stepState(i) }\"\n ></ng-container>\n </ng-container>\n <ng-template #defaultIcon>\n {{ defaultIconForState(stepState(i), i) }}\n </ng-template>\n </div>\n\n <div\n class=\"ngb-stepper-label-container\"\n [ngClass]=\"labelPositionClass\"\n >\n <div class=\"ngb-stepper-label\" *ngIf=\"stepLabelTpl; else textLabel\">\n <ng-container\n [ngTemplateOutlet]=\"stepLabelTpl\"\n [ngTemplateOutletContext]=\"{ index: i, step: step }\"\n ></ng-container>\n </div>\n <ng-template #textLabel>\n <div class=\"ngb-stepper-label-text\">\n {{ step.label || ('Step ' + (i + 1)) }}\n </div>\n </ng-template>\n <div class=\"ngb-stepper-optional\" *ngIf=\"step.optional\">\n {{ optionalLabel }}\n </div>\n </div>\n </li>\n </ol>\n </div>\n\n <!-- CONTENT -->\n <div class=\"ngb-stepper-content\" [class.ngb-stepper-content-top]=\"contentPosition === 'top'\">\n <ng-container *ngIf=\"lazy; else allContent\">\n <ng-container *ngFor=\"let index of [selectedIndex]\">\n <div\n class=\"fade-step\"\n [style.--ngb-stepper-animation-duration]=\"(disableAnimation ? 0 : animationDuration) + 'ms'\"\n role=\"tabpanel\"\n [attr.aria-labelledby]=\"stepId(index)\"\n [attr.id]=\"contentId(index)\"\n >\n <ng-container *ngTemplateOutlet=\"stepContentTpl; context: { index: index }\"></ng-container>\n <div\n *ngIf=\"currentErrorMessage\"\n class=\"ngb-stepper-error\"\n role=\"alert\"\n >\n {{ currentErrorMessage }}\n </div>\n </div>\n </ng-container>\n </ng-container>\n\n <ng-template #allContent>\n <div\n class=\"fade-step\"\n [style.--ngb-stepper-animation-duration]=\"(disableAnimation ? 0 : animationDuration) + 'ms'\"\n role=\"tabpanel\"\n [attr.aria-labelledby]=\"stepId(selectedIndex)\"\n [attr.id]=\"contentId(selectedIndex)\"\n >\n <ng-container *ngTemplateOutlet=\"stepContentTpl; context: { index: selectedIndex }\"></ng-container>\n <div\n *ngIf=\"currentErrorMessage\"\n class=\"ngb-stepper-error\"\n role=\"alert\"\n >\n {{ currentErrorMessage }}\n </div>\n </div>\n </ng-template>\n </div>\n\n <!-- ACTIONS -->\n <div class=\"ngb-stepper-buttons\">\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"onCancel()\"\n >\n {{ cancelLabel }}\n </button>\n\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"onReset()\"\n >\n {{ resetLabel }}\n </button>\n\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"prev()\"\n [disabled]=\"selectedIndex <= 0 || !allowRevisit\"\n >\n {{ previousLabel }}\n </button>\n\n <button\n type=\"button\"\n class=\"btn btn-sm btn-primary\"\n (click)=\"next()\"\n [disabled]=\"selectedIndex >= steps.length - 1 || steps[selectedIndex]?.disabled\"\n >\n {{ nextLabel }}\n </button>\n </div>\n</div>\n", styles: [".ngb-stepper{display:flex;flex-direction:column}.ngb-stepper.horizontal .ngb-stepper-header{display:flex;overflow-x:auto}.ngb-stepper.vertical .ngb-stepper-header{display:block}.ngb-stepper-header{margin-bottom:1rem}.ngb-stepper-steps{list-style:none;padding:0;margin:0;display:flex;flex-wrap:nowrap}.ngb-stepper.vertical .ngb-stepper-steps{flex-direction:column}.ngb-stepper-step{position:relative;display:flex;align-items:center;cursor:pointer}.ngb-stepper.horizontal .ngb-stepper-step{padding:.5rem .75rem}.ngb-stepper.vertical .ngb-stepper-step{padding:.5rem 0}.ngb-stepper-step.disabled{cursor:default;opacity:.6}.ngb-stepper-indicator{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:50%;margin-right:.5rem;flex-shrink:0}.ngb-stepper.bootstrap .ngb-stepper-indicator{border:1px solid var(--bs-border-color, #dee2e6);background-color:var(--bs-body-bg, #fff)}.ngb-stepper.bootstrap .ngb-stepper-step.active .ngb-stepper-indicator{background-color:var(--bs-primary, #0d6efd);color:#fff}.ngb-stepper.bootstrap .ngb-stepper-step.done .ngb-stepper-indicator{background-color:var(--bs-success, #198754);color:#fff}.ngb-stepper.bootstrap .ngb-stepper-step.error .ngb-stepper-indicator{background-color:var(--bs-danger, #dc3545);color:#fff}.ngb-stepper-label-container{display:flex;flex-direction:column}.ngb-stepper-label-top{flex-direction:column-reverse}.ngb-stepper-label-bottom{flex-direction:column}.ngb-stepper-label-left{flex-direction:row-reverse}.ngb-stepper-label-right{flex-direction:row}.ngb-stepper-content{position:relative}.ngb-stepper-content-top{order:-1}.ngb-stepper-buttons{display:flex;justify-content:flex-end;gap:.5rem;margin-top:1rem}.ngb-stepper-error{color:var(--bs-danger, #dc3545);font-size:.875rem;margin-top:.25rem}.ngb-stepper-optional{font-size:.75rem;opacity:.7}.ngb-stepper .fade-step{animation:ngb-stepper-fade var(--ngb-stepper-animation-duration, .15s) ease-in-out;animation-fill-mode:both}@keyframes ngb-stepper-fade{0%{opacity:0;transform:translateY(.35rem)}to{opacity:1;transform:translateY(0)}}\n"] }]
1831
2485
  }], propDecorators: { steps: [{
@@ -1894,14 +2548,14 @@ class NgbSplitterPaneComponent {
1894
2548
  collapsed = false;
1895
2549
  collapsedChange = new EventEmitter();
1896
2550
  template;
1897
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbSplitterPaneComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1898
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: NgbSplitterPaneComponent, isStandalone: true, selector: "ngb-splitter-pane", inputs: { size: "size", min: "min", max: "max", scrollable: "scrollable", collapsible: "collapsible", collapsed: "collapsed" }, outputs: { collapsedChange: "collapsedChange" }, viewQueries: [{ propertyName: "template", first: true, predicate: ["content"], descendants: true, static: true }], ngImport: i0, template: `<ng-template #content>
2551
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbSplitterPaneComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2552
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.9", type: NgbSplitterPaneComponent, isStandalone: true, selector: "ngb-splitter-pane", inputs: { size: "size", min: "min", max: "max", scrollable: "scrollable", collapsible: "collapsible", collapsed: "collapsed" }, outputs: { collapsedChange: "collapsedChange" }, viewQueries: [{ propertyName: "template", first: true, predicate: ["content"], descendants: true, static: true }], ngImport: i0, template: `<ng-template #content>
1899
2553
  <div [style.overflow]="scrollable ? 'auto' : 'hidden'" style="height: 100%; width: 100%;">
1900
2554
  <ng-content></ng-content>
1901
2555
  </div>
1902
2556
  </ng-template>`, isInline: true, styles: [":host{display:block;position:relative}\n"] });
1903
2557
  }
1904
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbSplitterPaneComponent, decorators: [{
2558
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbSplitterPaneComponent, decorators: [{
1905
2559
  type: Component,
1906
2560
  args: [{ selector: 'ngb-splitter-pane', standalone: true, template: `<ng-template #content>
1907
2561
  <div [style.overflow]="scrollable ? 'auto' : 'hidden'" style="height: 100%; width: 100%;">
@@ -2133,8 +2787,8 @@ class NgbSplitterComponent {
2133
2787
  nextPercent = Math.max(0, Math.min(100, nextPercent));
2134
2788
  targetPane.size = `${nextPercent}%`;
2135
2789
  }
2136
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbSplitterComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
2137
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: NgbSplitterComponent, isStandalone: true, selector: "ngb-splitter", inputs: { orientation: "orientation", handleThickness: "handleThickness", handleIconColor: "handleIconColor", verticalMinHeight: "verticalMinHeight", resizable: "resizable", barColor: "barColor", lineColor: "lineColor" }, host: { listeners: { "window:mousemove": "onMouseMove($event)", "window:mouseup": "onMouseUp()" } }, queries: [{ propertyName: "panes", predicate: NgbSplitterPaneComponent }], ngImport: i0, template: `
2790
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbSplitterComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
2791
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.9", type: NgbSplitterComponent, isStandalone: true, selector: "ngb-splitter", inputs: { orientation: "orientation", handleThickness: "handleThickness", handleIconColor: "handleIconColor", verticalMinHeight: "verticalMinHeight", resizable: "resizable", barColor: "barColor", lineColor: "lineColor" }, host: { listeners: { "window:mousemove": "onMouseMove($event)", "window:mouseup": "onMouseUp()" } }, queries: [{ propertyName: "panes", predicate: NgbSplitterPaneComponent }], ngImport: i0, template: `
2138
2792
  <div class="splitter-container" [class.vertical]="orientation === 'vertical'"
2139
2793
  [style.min-height]="orientation === 'vertical' ? verticalMinHeight : null"
2140
2794
  [style.height]="orientation === 'vertical' ? verticalMinHeight : null">
@@ -2178,7 +2832,7 @@ class NgbSplitterComponent {
2178
2832
  </div>
2179
2833
  `, isInline: true, styles: [":host{display:block;width:100%}.splitter-container{display:flex;height:100%;width:100%}.splitter-container.vertical{flex-direction:column}.splitbar{background:#ccc;width:5px;position:relative;align-items:center;justify-content:center;flex:0 0 auto;z-index:10}.splitbar.resizable{cursor:col-resize}.splitbar:hover{filter:brightness(90%)}.handle-line{border-radius:2px;opacity:.8;left:45%;position:absolute;top:45%;display:flex;align-items:center;justify-content:center}.splitter-container:not(.vertical) .handle-line{width:2px;height:10px}.pane-wrapper{position:relative;min-width:0;min-height:0;overflow:hidden;flex-shrink:1}.vertical .handle-line{height:2px;width:10px}.vertical .splitbar.resizable{cursor:row-resize}.splitter-container.vertical .splitter-container .splitbar.resizable{cursor:col-resize}.vertical .splitbar{width:100%}.collapse-btn{cursor:pointer;background:#666;width:10px;height:10px;position:absolute;top:50%}.collapse-arrow{font-size:.95rem;-webkit-user-select:none;user-select:none;cursor:pointer;line-height:1;display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;position:absolute;top:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2180
2834
  }
2181
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbSplitterComponent, decorators: [{
2835
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbSplitterComponent, decorators: [{
2182
2836
  type: Component,
2183
2837
  args: [{ selector: 'ngb-splitter', standalone: true, imports: [CommonModule, NgbSplitterPaneComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
2184
2838
  <div class="splitter-container" [class.vertical]="orientation === 'vertical'"
@@ -2255,8 +2909,8 @@ class NgbChipsComponent {
2255
2909
  removeLabel;
2256
2910
  remove = new EventEmitter();
2257
2911
  trackById = (_, item) => item.id;
2258
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbChipsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2259
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: NgbChipsComponent, isStandalone: true, selector: "ngb-chips", inputs: { items: "items", removable: "removable", ariaLabel: "ariaLabel", removeLabel: "removeLabel" }, outputs: { remove: "remove" }, ngImport: i0, template: `
2912
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbChipsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2913
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.9", type: NgbChipsComponent, isStandalone: true, selector: "ngb-chips", inputs: { items: "items", removable: "removable", ariaLabel: "ariaLabel", removeLabel: "removeLabel" }, outputs: { remove: "remove" }, ngImport: i0, template: `
2260
2914
  <div class="ngb-chips" [attr.aria-label]="ariaLabel">
2261
2915
  <span
2262
2916
  *ngFor="let item of items; trackBy: trackById"
@@ -2278,7 +2932,7 @@ class NgbChipsComponent {
2278
2932
  </div>
2279
2933
  `, isInline: true, styles: [":host{display:block}.ngb-chips{display:flex;flex-wrap:wrap;gap:.35rem;align-items:center}.ngb-chip{display:inline-flex;align-items:center;gap:.25rem;max-width:100%}.ngb-chip-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ngb-chip-remove{border:0;background:transparent;padding:0;line-height:1;cursor:pointer;font-size:1rem;opacity:.85}.ngb-chip-remove:disabled{cursor:default;opacity:.4}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2280
2934
  }
2281
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbChipsComponent, decorators: [{
2935
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbChipsComponent, decorators: [{
2282
2936
  type: Component,
2283
2937
  args: [{ selector: 'ngb-chips', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
2284
2938
  <div class="ngb-chips" [attr.aria-label]="ariaLabel">
@@ -3013,8 +3667,8 @@ class NgbTypeaheadComponent {
3013
3667
  return match;
3014
3668
  return { id: `${value}`, label: `${value}`, value };
3015
3669
  }
3016
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbTypeaheadComponent, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
3017
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: NgbTypeaheadComponent, isStandalone: true, selector: "ngb-typeahead", inputs: { data: "data", debounceTime: "debounceTime", characterTyped: "characterTyped", limit: "limit", selectExact: "selectExact", multiSelect: "multiSelect", matchSelection: "matchSelection", showDropdownButton: "showDropdownButton", showClearButton: "showClearButton", updateOnBlur: "updateOnBlur", updateOnTab: "updateOnTab", separator: "separator", chips: "chips", vScroll: "vScroll", vItemSize: "vItemSize", itemTemplate: "itemTemplate", i18n: "i18n" }, outputs: { completeMethod: "completeMethod", onSelect: "onSelect", onUnselect: "onUnselect", onAdd: "onAdd", onFocus: "onFocus", onBlur: "onBlur", onDropdownClick: "onDropdownClick", onClear: "onClear", onInputKeydown: "onInputKeydown", onKeyUp: "onKeyUp", onShow: "onShow", onHide: "onHide", onLazyLoad: "onLazyLoad", selectedItems: "selectedItems", selectionChange: "selectionChange", onChange: "onChange", onScrollEvent: "onScrollEvent" }, host: { listeners: { "document:mousedown": "onDocumentMouseDown($event)", "document:focusin": "onDocumentFocusIn($event)" } }, providers: [
3670
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbTypeaheadComponent, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
3671
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.9", type: NgbTypeaheadComponent, isStandalone: true, selector: "ngb-typeahead", inputs: { data: "data", debounceTime: "debounceTime", characterTyped: "characterTyped", limit: "limit", selectExact: "selectExact", multiSelect: "multiSelect", matchSelection: "matchSelection", showDropdownButton: "showDropdownButton", showClearButton: "showClearButton", updateOnBlur: "updateOnBlur", updateOnTab: "updateOnTab", separator: "separator", chips: "chips", vScroll: "vScroll", vItemSize: "vItemSize", itemTemplate: "itemTemplate", i18n: "i18n" }, outputs: { completeMethod: "completeMethod", onSelect: "onSelect", onUnselect: "onUnselect", onAdd: "onAdd", onFocus: "onFocus", onBlur: "onBlur", onDropdownClick: "onDropdownClick", onClear: "onClear", onInputKeydown: "onInputKeydown", onKeyUp: "onKeyUp", onShow: "onShow", onHide: "onHide", onLazyLoad: "onLazyLoad", selectedItems: "selectedItems", selectionChange: "selectionChange", onChange: "onChange", onScrollEvent: "onScrollEvent" }, host: { listeners: { "document:mousedown": "onDocumentMouseDown($event)", "document:focusin": "onDocumentFocusIn($event)" } }, providers: [
3018
3672
  {
3019
3673
  provide: NG_VALUE_ACCESSOR,
3020
3674
  useExisting: forwardRef(() => NgbTypeaheadComponent),
@@ -3160,7 +3814,7 @@ class NgbTypeaheadComponent {
3160
3814
  </div>
3161
3815
  `, isInline: true, styles: [":host{display:block}.typeahead-root{position:relative}.typeahead-chips-control{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem;min-height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem}.typeahead-chips-control input{border:0;outline:0;flex:1 1 9rem;min-width:6rem;padding:0;margin:0;background:transparent}.typeahead-chips-control input:focus{outline:0;box-shadow:none}.typeahead-panel{position:absolute;top:calc(100% + .25rem);left:0;right:0;z-index:1050;max-height:240px;overflow:auto;opacity:0;transform:translateY(-.25rem);pointer-events:none;transition:opacity .12s ease,transform .12s ease}.typeahead-panel.show{opacity:1;transform:translateY(0);pointer-events:auto}.typeahead-item{display:flex;align-items:center;gap:.5rem}.typeahead-item-checkbox{display:inline-flex;align-items:center;justify-content:center;width:1.15rem;height:1.15rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: NgbChipsComponent, selector: "ngb-chips", inputs: ["items", "removable", "ariaLabel", "removeLabel"], outputs: ["remove"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3162
3816
  }
3163
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbTypeaheadComponent, decorators: [{
3817
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbTypeaheadComponent, decorators: [{
3164
3818
  type: Component,
3165
3819
  args: [{ selector: 'ngb-typeahead', standalone: true, imports: [CommonModule, NgbChipsComponent], providers: [
3166
3820
  {
@@ -3483,8 +4137,8 @@ class NgbTreeComponent {
3483
4137
  }
3484
4138
  return acc;
3485
4139
  }
3486
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3487
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: NgbTreeComponent, isStandalone: true, selector: "ngb-tree", inputs: { nodes: "nodes", type: "type", showCheckbox: "showCheckbox", i18n: "i18n", expandIcon: "expandIcon", collapseIcon: "collapseIcon", plusIcon: "plusIcon", minusIcon: "minusIcon" }, outputs: { expand: "expand", collapse: "collapse", expandAll: "expandAll", collapseAll: "collapseAll", selectionChange: "selectionChange" }, viewQueries: [{ propertyName: "nodeButtons", predicate: ["nodeBtn"], descendants: true, read: ElementRef }], ngImport: i0, template: `
4140
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4141
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.9", type: NgbTreeComponent, isStandalone: true, selector: "ngb-tree", inputs: { nodes: "nodes", type: "type", showCheckbox: "showCheckbox", i18n: "i18n", expandIcon: "expandIcon", collapseIcon: "collapseIcon", plusIcon: "plusIcon", minusIcon: "minusIcon" }, outputs: { expand: "expand", collapse: "collapse", expandAll: "expandAll", collapseAll: "collapseAll", selectionChange: "selectionChange" }, viewQueries: [{ propertyName: "nodeButtons", predicate: ["nodeBtn"], descendants: true, read: ElementRef }], ngImport: i0, template: `
3488
4142
  <div class="d-flex gap-2 mb-2">
3489
4143
  <button type="button" class="btn btn-sm btn-outline-secondary" (click)="expandAllNodes()" [attr.aria-label]="i18n?.expandAll || 'Expand all'">
3490
4144
  {{ i18n?.expandAll || 'Expand all' }}
@@ -3550,7 +4204,7 @@ class NgbTreeComponent {
3550
4204
  </ng-template>
3551
4205
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3552
4206
  }
3553
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbTreeComponent, decorators: [{
4207
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NgbTreeComponent, decorators: [{
3554
4208
  type: Component,
3555
4209
  args: [{
3556
4210
  selector: 'ngb-tree',