@angular-bootstrap/ngbootstrap 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2624 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Input, Directive, EventEmitter, Output, ChangeDetectionStrategy, Component, inject, Injectable, TemplateRef, QueryList, ContentChildren, ContentChild, InjectionToken, signal, Optional, Inject, ElementRef, HostListener, HostBinding, ViewChild, ViewChildren } from '@angular/core';
3
+ import * as i1 from '@angular/common';
4
+ import { CommonModule } from '@angular/common';
5
+ import * as i2 from '@angular/forms';
6
+ import { FormBuilder, FormControl, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
7
+ import { firstValueFrom, isObservable } from 'rxjs';
8
+
9
+ class NgbCellTemplate {
10
+ template;
11
+ field;
12
+ constructor(template) {
13
+ this.template = template;
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 });
17
+ }
18
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbCellTemplate, decorators: [{
19
+ type: Directive,
20
+ args: [{ selector: 'ng-template[ngbCell]' }]
21
+ }], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { field: [{
22
+ type: Input,
23
+ args: ['ngbCell']
24
+ }] } });
25
+ class NgbEditorTemplate {
26
+ template;
27
+ field;
28
+ constructor(template) {
29
+ this.template = template;
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 });
33
+ }
34
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbEditorTemplate, decorators: [{
35
+ type: Directive,
36
+ args: [{ selector: 'ng-template[ngbEditor]' }]
37
+ }], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { field: [{
38
+ type: Input,
39
+ args: ['ngbEditor']
40
+ }] } });
41
+ class NgbFilterTemplate {
42
+ template;
43
+ field;
44
+ constructor(template) {
45
+ this.template = template;
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 });
49
+ }
50
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbFilterTemplate, decorators: [{
51
+ type: Directive,
52
+ args: [{ selector: 'ng-template[ngbFilter]' }]
53
+ }], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { field: [{
54
+ type: Input,
55
+ args: ['ngbFilter']
56
+ }] } });
57
+ class NgbGlobalFilterTemplate {
58
+ template;
59
+ constructor(template) {
60
+ this.template = template;
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 });
64
+ }
65
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbGlobalFilterTemplate, decorators: [{
66
+ type: Directive,
67
+ args: [{ selector: 'ng-template[ngbGlobalFilter]' }]
68
+ }], ctorParameters: () => [{ type: i0.TemplateRef }] });
69
+ class NgbRowDetailTemplate {
70
+ template;
71
+ constructor(template) {
72
+ this.template = template;
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 });
76
+ }
77
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbRowDetailTemplate, decorators: [{
78
+ type: Directive,
79
+ args: [{ selector: 'ng-template[ngbRowDetail]' }]
80
+ }], ctorParameters: () => [{ type: i0.TemplateRef }] });
81
+ /** Handy constant to import in consumer apps */
82
+ const DATAGRID_TEMPLATE_DIRECTIVES = [
83
+ NgbCellTemplate, NgbEditorTemplate, NgbFilterTemplate, NgbGlobalFilterTemplate, NgbRowDetailTemplate
84
+ ];
85
+
86
+ class NgbPaginationComponent {
87
+ page = 1;
88
+ pageSize = 10;
89
+ collectionSize = 0;
90
+ maxSize = 5;
91
+ pageChange = new EventEmitter();
92
+ get totalPages() {
93
+ return Math.max(1, Math.ceil(this.collectionSize / Math.max(1, this.pageSize)));
94
+ }
95
+ get pages() {
96
+ const total = this.totalPages;
97
+ const max = Math.max(3, this.maxSize | 0);
98
+ if (total <= max)
99
+ return Array.from({ length: total }, (_, i) => i + 1);
100
+ const half = Math.floor(max / 2);
101
+ let start = Math.max(1, this.page - half);
102
+ let end = start + max - 1;
103
+ if (end > total) {
104
+ end = total;
105
+ start = end - max + 1;
106
+ }
107
+ const nums = Array.from({ length: end - start + 1 }, (_, i) => start + i);
108
+ const head = start > 1 ? [1, start > 2 ? '…' : 2] : [];
109
+ const tail = end < total ? [end < total - 1 ? '…' : total - 1, total] : [];
110
+ return [...head, ...nums, ...tail];
111
+ }
112
+ go(p) {
113
+ if (p < 1 || p > this.totalPages || p === this.page)
114
+ return;
115
+ this.pageChange.emit(p);
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: `
119
+ <nav *ngIf="totalPages >= 1" aria-label="Pagination">
120
+ <ul class="pagination pagination-sm justify-content-center mb-0">
121
+ <li class="page-item" [class.disabled]="page === 1">
122
+ <button class="page-link" type="button" (click)="go(page - 1)" aria-label="Previous"> <span aria-hidden="true">«</span> </button>
123
+ </li>
124
+
125
+ <li class="page-item" *ngFor="let p of pages" [class.active]="p === page" [class.disabled]="p === '…'">
126
+ <span *ngIf="p === '…'" class="page-link">…</span>
127
+ <button *ngIf="p !== '…'" class="page-link" type="button" (click)="go(p)">{{ p }}</button>
128
+ </li>
129
+
130
+ <li class="page-item" [class.disabled]="page === totalPages">
131
+ <button class="page-link" type="button" (click)="go(page + 1)" aria-label="Next"> <span aria-hidden="true">»</span> </button>
132
+ </li>
133
+ </ul>
134
+ </nav>
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
+ }
137
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbPaginationComponent, decorators: [{
138
+ type: Component,
139
+ args: [{
140
+ selector: 'ngb-pagination',
141
+ standalone: true,
142
+ imports: [CommonModule],
143
+ template: `
144
+ <nav *ngIf="totalPages >= 1" aria-label="Pagination">
145
+ <ul class="pagination pagination-sm justify-content-center mb-0">
146
+ <li class="page-item" [class.disabled]="page === 1">
147
+ <button class="page-link" type="button" (click)="go(page - 1)" aria-label="Previous"> <span aria-hidden="true">«</span> </button>
148
+ </li>
149
+
150
+ <li class="page-item" *ngFor="let p of pages" [class.active]="p === page" [class.disabled]="p === '…'">
151
+ <span *ngIf="p === '…'" class="page-link">…</span>
152
+ <button *ngIf="p !== '…'" class="page-link" type="button" (click)="go(p)">{{ p }}</button>
153
+ </li>
154
+
155
+ <li class="page-item" [class.disabled]="page === totalPages">
156
+ <button class="page-link" type="button" (click)="go(page + 1)" aria-label="Next"> <span aria-hidden="true">»</span> </button>
157
+ </li>
158
+ </ul>
159
+ </nav>
160
+ `,
161
+ changeDetection: ChangeDetectionStrategy.OnPush,
162
+ }]
163
+ }], propDecorators: { page: [{
164
+ type: Input
165
+ }], pageSize: [{
166
+ type: Input
167
+ }], collectionSize: [{
168
+ type: Input
169
+ }], maxSize: [{
170
+ type: Input
171
+ }], pageChange: [{
172
+ type: Output
173
+ }] } });
174
+
175
+ class PdfExportAdapter {
176
+ }
177
+ class ExcelExportAdapter {
178
+ }
179
+ class NgbExportService {
180
+ // Optional injections via inject()
181
+ pdf = inject(PdfExportAdapter, { optional: true });
182
+ excel = inject(ExcelExportAdapter, { optional: true });
183
+ exportPdf(p) {
184
+ if (!this.pdf)
185
+ throw new Error('No PdfExportAdapter provided');
186
+ return this.pdf.export(p);
187
+ }
188
+ exportExcel(p) {
189
+ if (!this.excel)
190
+ throw new Error('No ExcelExportAdapter provided');
191
+ return this.excel.export(p);
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' });
195
+ }
196
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbExportService, decorators: [{
197
+ type: Injectable,
198
+ args: [{ providedIn: 'root' }]
199
+ }] });
200
+
201
+ class JsPdfAdapter {
202
+ async export({ fileName, columns, rows, options }) {
203
+ const { jsPDF } = await import('jspdf');
204
+ const autoTable = (await import('jspdf-autotable')).default;
205
+ const doc = new jsPDF({ orientation: options?.landscape ? 'landscape' : 'portrait' });
206
+ autoTable(doc, { head: [columns], body: rows.map(r => columns.map(k => r[k])), margin: options?.margins });
207
+ doc.save(`${fileName}.pdf`);
208
+ }
209
+ }
210
+
211
+ class XlsxAdapter {
212
+ async export({ fileName, sheetName, columns, rows }) {
213
+ const XLSX = await import('xlsx');
214
+ const header = [columns.map(c => c.title)];
215
+ const data = rows.map(r => columns.map(c => r[c.key]));
216
+ const ws = XLSX.utils.aoa_to_sheet([...header, ...data]);
217
+ const wb = XLSX.utils.book_new();
218
+ XLSX.utils.book_append_sheet(wb, ws, sheetName);
219
+ XLSX.writeFile(wb, `${fileName}.xlsx`);
220
+ }
221
+ }
222
+
223
+ // eslint-disable-next-line @angular-eslint/directive-selector
224
+ class ExportButtonDirective {
225
+ // Expose the TemplateRef so the grid can render it with a context
226
+ templateRef = inject(TemplateRef);
227
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ExportButtonDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
228
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: ExportButtonDirective, isStandalone: true, selector: "[ngbExportButton]", ngImport: i0 });
229
+ }
230
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ExportButtonDirective, decorators: [{
231
+ type: Directive,
232
+ args: [{ selector: '[ngbExportButton]' }]
233
+ }] });
234
+
235
+ const EMAIL_RX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
236
+ class Datagrid {
237
+ /** Column definitions to render */
238
+ columns = [];
239
+ /** Row data to display */
240
+ data = [];
241
+ enableSorting = false;
242
+ enableFiltering = false; // per-column + global
243
+ enableGlobalFilter = false; // global search bar
244
+ enablePagination = false;
245
+ enableEdit = false;
246
+ enableDelete = false;
247
+ pageSizeOptions = [5, 10, 25, 50];
248
+ enableAdd = false;
249
+ /** Accessible label (aria-label) applied to the add-row button. */
250
+ addButtonAriaLabel = 'Add row';
251
+ /** Visible text rendered inside the add-row button. */
252
+ addButtonText = '+ Add';
253
+ /** Accessible label for the global filter input. */
254
+ globalFilterAriaLabel = 'Search all columns';
255
+ /** Accessible label announced when expanding a row. */
256
+ expandRowAriaLabel = 'Expand row';
257
+ /** Accessible label announced when collapsing a row. */
258
+ collapseRowAriaLabel = 'Collapse row';
259
+ /** Accessible label for the PDF export button. */
260
+ exportPdfAriaLabel = 'Export to PDF';
261
+ /** Accessible label for the Excel export button. */
262
+ exportExcelAriaLabel = 'Export to Excel';
263
+ /** Optional defaults for new rows */
264
+ newRowDefaults = null;
265
+ strictEmail = false; // turn off if needed (e.g., intranet emails)
266
+ editOnRowClick = false;
267
+ singleExpand = false; // accordion mode: one row expanded at a time
268
+ exportOptions = {
269
+ enabled: false,
270
+ type: 'both',
271
+ pages: 'current',
272
+ fileName: 'export'
273
+ };
274
+ theme = 'bootstrap';
275
+ responsive = false;
276
+ // Data hooks for export
277
+ dataProviderAll; // used when pages='all'
278
+ dataProviderSelection; // used when pages='selection'
279
+ // Grab the directive and its TemplateRef
280
+ exportButtonDir;
281
+ exporting = false;
282
+ rowAdd = new EventEmitter();
283
+ // events
284
+ rowEdit = new EventEmitter();
285
+ rowSave = new EventEmitter();
286
+ rowCancel = new EventEmitter();
287
+ rowDelete = new EventEmitter();
288
+ sortChange = new EventEmitter();
289
+ filtersChange = new EventEmitter();
290
+ pageChange = new EventEmitter();
291
+ rowDetailTpl;
292
+ exporter = inject(NgbExportService); // instead of constructor(private exporter: NgbExportService) {}
293
+ expanded = new Set();
294
+ fb = inject(FormBuilder);
295
+ filterForm = this.fb.group({});
296
+ globalFilterCtrl = new FormControl('', { nonNullable: true });
297
+ addingNew = false;
298
+ draftNew = null;
299
+ errorsNew = {};
300
+ // --- sorting (from previous step)
301
+ sort = { active: null, direction: '' };
302
+ // --- filtering
303
+ globalFilter = '';
304
+ filters = {}; // per-column text
305
+ page = 1;
306
+ pageSize = 10;
307
+ /** Inline editing state */
308
+ editingIndex = null; // index in `paged` view
309
+ editForm = this.fb.group({});
310
+ saveAttemptedEdit = false;
311
+ addForm = this.fb.group({});
312
+ saveAttemptedNew = false;
313
+ norm(v) {
314
+ return (v ?? '').toString().toLowerCase().trim();
315
+ }
316
+ keyOf(col) {
317
+ return col.field;
318
+ }
319
+ passesRowFilters(row) {
320
+ // per-column
321
+ for (const col of this.columns) {
322
+ if (!col.filterable)
323
+ continue;
324
+ const key = col.field;
325
+ const q = this.norm(this.filters[key]);
326
+ if (!q)
327
+ continue;
328
+ const cell = this.norm(row[col.field]);
329
+ if (!cell.includes(q))
330
+ return false;
331
+ }
332
+ // global
333
+ const g = this.norm(this.globalFilter);
334
+ if (!g)
335
+ return true;
336
+ return this.columns.some(c => this.norm(row[c.field]).includes(g));
337
+ }
338
+ getDefaults() {
339
+ return typeof this.newRowDefaults === 'function'
340
+ ? this.newRowDefaults() ?? {}
341
+ : this.newRowDefaults ?? {};
342
+ }
343
+ defaultFor(col) {
344
+ const d = this.getDefaults();
345
+ const key = col.field;
346
+ if (key in d)
347
+ return d[key];
348
+ switch (col.type) {
349
+ case 'boolean': return false;
350
+ case 'number': return '';
351
+ case 'date': return '';
352
+ default: return '';
353
+ }
354
+ }
355
+ numberValidator() {
356
+ const rx = /^-?\d+(\.\d+)?$/;
357
+ return (c) => c.value === '' || c.value === null || c.value === undefined || rx.test(String(c.value))
358
+ ? null
359
+ : { number: true };
360
+ }
361
+ dateValidator() {
362
+ return (c) => {
363
+ const v = c.value;
364
+ if (v === '' || v === null || v === undefined)
365
+ return null;
366
+ return isNaN(Date.parse(String(v))) ? { date: true } : null;
367
+ };
368
+ }
369
+ buildFormFromRow(row) {
370
+ const group = {};
371
+ for (const col of this.columns) {
372
+ if (col.editable === false)
373
+ continue; // read-only columns not in form
374
+ const key = col.field;
375
+ const initial = row && key in row ? row[key] : this.defaultFor(col);
376
+ const v = [];
377
+ if (col.required && col.type !== 'boolean')
378
+ v.push(Validators.required);
379
+ if (col.type === 'email') {
380
+ v.push(this.strictEmail ? this.strictEmailValidator() : Validators.email);
381
+ }
382
+ if (col.type === 'number')
383
+ v.push(this.numberValidator());
384
+ if (col.type === 'date')
385
+ v.push(this.dateValidator());
386
+ // NOTE: If you need "boolean must be true", add Validators.requiredTrue here.
387
+ group[key] = [initial, v];
388
+ }
389
+ return this.fb.group(group);
390
+ }
391
+ rowIsValidAgainst(targetErrors) {
392
+ for (const col of this.columns) {
393
+ if (col.editable === false)
394
+ continue;
395
+ const key = this.keyOf(col);
396
+ if (targetErrors[key])
397
+ return false;
398
+ }
399
+ return true;
400
+ }
401
+ strictEmailValidator() {
402
+ // simple, pragmatic: local@domain.tld (tld >= 2)
403
+ const rx = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
404
+ return (c) => {
405
+ const v = c.value;
406
+ if (v === null || v === undefined || v === '')
407
+ return null; // let "required" handle empties
408
+ return rx.test(String(v)) ? null : { email: true };
409
+ };
410
+ }
411
+ //Getter for the ng-template
412
+ get exportButtonTpl() {
413
+ return this.exportButtonDir?.templateRef ?? null;
414
+ }
415
+ // pipeline respects toggles
416
+ get filtered() {
417
+ if (!this.enableFiltering)
418
+ return this.data ?? [];
419
+ const src = this.data ?? [];
420
+ return src.filter(r => {
421
+ // per-column
422
+ for (const col of this.columns) {
423
+ if (!col.filterable)
424
+ continue;
425
+ const q = (this.filters[col.field] ?? '').toString().toLowerCase().trim();
426
+ if (!q)
427
+ continue;
428
+ const cell = (r[col.field] ?? '').toString().toLowerCase().trim();
429
+ if (!cell.includes(q))
430
+ return false;
431
+ }
432
+ // global
433
+ if (this.enableGlobalFilter) {
434
+ const g = (this.globalFilter ?? '').toString().toLowerCase().trim();
435
+ if (g)
436
+ return this.columns.some(c => (r[c.field] ?? '').toString().toLowerCase().includes(g));
437
+ }
438
+ return true;
439
+ });
440
+ }
441
+ compare(a, b) {
442
+ if (a == null && b == null)
443
+ return 0;
444
+ if (a == null)
445
+ return -1;
446
+ if (b == null)
447
+ return 1;
448
+ if (typeof a === 'string' && typeof b === 'string') {
449
+ return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' });
450
+ }
451
+ return a < b ? -1 : a > b ? 1 : 0;
452
+ }
453
+ get sorted() {
454
+ if (!this.enableSorting || !this.sort.active || !this.sort.direction)
455
+ return this.filtered;
456
+ const copy = [...this.filtered];
457
+ const { active, direction } = this.sort;
458
+ copy.sort((a, b) => {
459
+ const av = a?.[active];
460
+ const bv = b?.[active];
461
+ const cmp = typeof av === 'string' && typeof bv === 'string'
462
+ ? av.localeCompare(bv, undefined, { numeric: true, sensitivity: 'base' })
463
+ : av < bv ? -1 : av > bv ? 1 : 0;
464
+ return direction === 'asc' ? cmp : -cmp;
465
+ });
466
+ return copy;
467
+ }
468
+ // pagination (filled in next step)
469
+ get paged() {
470
+ if (!this.enablePagination)
471
+ return this.sorted;
472
+ const start = (this.page - 1) * this.pageSize;
473
+ return this.sorted.slice(start, start + this.pageSize);
474
+ }
475
+ // getters for template conditions / display
476
+ get anyFilterable() {
477
+ return this.enableFiltering && !!this.columns?.some(c => c.filterable);
478
+ }
479
+ get startIndex() { const total = this.sorted.length; return total ? (this.page - 1) * this.pageSize + 1 : 0; }
480
+ get endIndex() { return Math.min(this.page * this.pageSize, this.sorted.length); }
481
+ get detailColspan() {
482
+ const actionCols = (this.enableEdit || this.enableDelete) ? 1 : 0;
483
+ const caretCol = this.rowDetailTpl ? 1 : 0;
484
+ return this.columns.length + actionCols + caretCol;
485
+ }
486
+ headerText(col) {
487
+ return (col.header ?? col.field ?? '').toString();
488
+ }
489
+ columnFilterAriaLabel(col) {
490
+ return `${this.headerText(col)} filter`;
491
+ }
492
+ inputAriaLabel(col) {
493
+ return this.headerText(col);
494
+ }
495
+ ariaSortFor(field) {
496
+ if (!this.enableSorting || this.sort.active !== field || !this.sort.direction) {
497
+ return 'none';
498
+ }
499
+ return this.sort.direction === 'asc' ? 'ascending' : 'descending';
500
+ }
501
+ sortButtonAriaLabel(col) {
502
+ const header = this.headerText(col);
503
+ const state = this.sort.active === col.field
504
+ ? (this.sort.direction === 'asc'
505
+ ? 'ascending'
506
+ : this.sort.direction === 'desc'
507
+ ? 'descending'
508
+ : 'unsorted')
509
+ : 'unsorted';
510
+ return `Sort by ${header}. Current sort ${state}.`;
511
+ }
512
+ exportAriaLabel(kind) {
513
+ return kind === 'pdf' ? this.exportPdfAriaLabel : this.exportExcelAriaLabel;
514
+ }
515
+ // Helpers to map from paged index → original data index
516
+ // helper used above
517
+ dataIndexFromPaged(i) {
518
+ const row = this.paged[i];
519
+ return this.data.indexOf(row);
520
+ }
521
+ rebuildFilterForm() {
522
+ const group = {};
523
+ for (const c of this.columns)
524
+ if (c.filterable)
525
+ group[c.field] = [''];
526
+ this.filterForm = this.fb.group(group);
527
+ this.filterForm.valueChanges.subscribe(v => {
528
+ this.page = 1;
529
+ this.filtersChange.emit({ global: this.globalFilterCtrl.value, columns: v });
530
+ });
531
+ this.globalFilterCtrl.valueChanges.subscribe(() => {
532
+ this.page = 1;
533
+ this.filtersChange.emit({ global: this.globalFilterCtrl.value, columns: this.filterForm.value });
534
+ });
535
+ }
536
+ cellTplQ;
537
+ editTplQ;
538
+ filterTplQ;
539
+ globalTplQ;
540
+ /** Internal lookup maps */
541
+ cellTpls = {};
542
+ editTpls = {};
543
+ filterTpls = {};
544
+ globalTpl = null;
545
+ toRecord(items) {
546
+ const arr = Array.isArray(items)
547
+ ? items
548
+ : items instanceof QueryList
549
+ ? items.toArray()
550
+ : [];
551
+ return arr.reduce((acc, t) => (acc[t.field] = t, acc), {});
552
+ }
553
+ async export(kind) {
554
+ if (!this.exportOptions?.enabled)
555
+ return;
556
+ this.exporting = true;
557
+ try {
558
+ const data = await this.resolveDataset();
559
+ const cols = this.columns.map(c => ({ key: c.field, title: c.header }));
560
+ if (kind === 'pdf') {
561
+ await this.exporter.exportPdf({
562
+ fileName: this.exportOptions.fileName || 'export',
563
+ columns: cols.map(c => c.key),
564
+ rows: data,
565
+ options: this.exportOptions.pdf
566
+ });
567
+ }
568
+ else {
569
+ await this.exporter.exportExcel({
570
+ fileName: this.exportOptions.fileName || 'export',
571
+ sheetName: this.exportOptions.excel?.sheetName || 'Sheet1',
572
+ columns: cols,
573
+ rows: data
574
+ });
575
+ }
576
+ }
577
+ finally {
578
+ this.exporting = false;
579
+ }
580
+ }
581
+ async resolveDataset() {
582
+ const mode = this.exportOptions?.pages || 'current';
583
+ if (mode === 'all') {
584
+ if (!this.dataProviderAll)
585
+ throw new Error('dataProviderAll required when pages="all"');
586
+ const d = this.dataProviderAll();
587
+ return isObservable(d) ? firstValueFrom(d) : (d instanceof Promise ? d : d);
588
+ }
589
+ if (mode === 'selection') {
590
+ if (!this.dataProviderSelection)
591
+ throw new Error('dataProviderSelection required when pages="selection"');
592
+ return this.dataProviderSelection();
593
+ }
594
+ // current page == your existing `paged` array
595
+ return this.paged;
596
+ }
597
+ ngAfterContentInit() {
598
+ const rebuild = () => {
599
+ this.cellTpls = this.toRecord(this.cellTplQ);
600
+ this.editTpls = this.toRecord(this.editTplQ);
601
+ this.filterTpls = this.toRecord(this.filterTplQ);
602
+ this.globalTpl = this.globalTplQ?.first ?? null;
603
+ };
604
+ rebuild();
605
+ this.cellTplQ?.changes.subscribe(rebuild);
606
+ this.editTplQ?.changes.subscribe(rebuild);
607
+ this.filterTplQ?.changes.subscribe(rebuild);
608
+ this.globalTplQ?.changes.subscribe(rebuild);
609
+ }
610
+ ngOnChanges(ch) {
611
+ if (ch['columns'])
612
+ this.rebuildFilterForm();
613
+ }
614
+ startAdd() {
615
+ if (!this.enableAdd || this.addingNew)
616
+ return;
617
+ this.page = 1;
618
+ this.editingIndex = null;
619
+ this.addingNew = true;
620
+ this.addForm = this.buildFormFromRow(); // defaults
621
+ this.saveAttemptedNew = false;
622
+ }
623
+ saveAdd() {
624
+ if (!this.addingNew || !this.addForm)
625
+ return;
626
+ this.saveAttemptedNew = true;
627
+ this.addForm.markAllAsTouched();
628
+ this.addForm.updateValueAndValidity();
629
+ if (this.addForm.invalid)
630
+ return;
631
+ const newRow = { ...this.addForm.value };
632
+ this.rowAdd.emit({ newRow });
633
+ this.addingNew = false;
634
+ this.addForm = this.fb.group({});
635
+ ;
636
+ this.saveAttemptedNew = false;
637
+ }
638
+ cancelAdd() {
639
+ this.addingNew = false;
640
+ this.addForm = this.fb.group({});
641
+ ;
642
+ this.saveAttemptedNew = false;
643
+ }
644
+ startEdit(i) {
645
+ if (!this.enableEdit)
646
+ return;
647
+ this.addingNew = false;
648
+ this.editingIndex = i;
649
+ const row = this.paged[i];
650
+ this.editForm = this.buildFormFromRow(this.paged[i]);
651
+ this.saveAttemptedEdit = false;
652
+ const di = this.data.indexOf(row);
653
+ this.rowEdit.emit({ row: this.data[di], index: di });
654
+ }
655
+ saveEdit(i) {
656
+ if (this.editingIndex !== i || !this.editForm)
657
+ return;
658
+ this.saveAttemptedEdit = true;
659
+ this.editForm.markAllAsTouched();
660
+ this.editForm.updateValueAndValidity();
661
+ if (this.editForm.invalid)
662
+ return;
663
+ const di = this.data.indexOf(this.paged[i]);
664
+ const original = this.data[di];
665
+ const updated = { ...original, ...this.editForm.value };
666
+ this.rowSave.emit({ original, updated, index: di });
667
+ this.editingIndex = null;
668
+ this.editForm = this.fb.group({});
669
+ this.saveAttemptedEdit = false;
670
+ }
671
+ cancelEdit(i) {
672
+ const di = this.data.indexOf(this.paged[i]);
673
+ this.rowCancel.emit({ row: this.data[di], index: di });
674
+ this.editingIndex = null;
675
+ this.editForm = this.fb.group({}); // empty group
676
+ this.saveAttemptedEdit = false;
677
+ }
678
+ // For the "Add row" draft
679
+ onNewDraftChange(col) {
680
+ this.validateInto(col, this.draftNew, this.errorsNew);
681
+ }
682
+ validateInto(col, targetDraft, targetErrors) {
683
+ if (!targetDraft)
684
+ return;
685
+ const key = this.keyOf(col);
686
+ const val = targetDraft[key];
687
+ let err = '';
688
+ // required
689
+ if (col.required) {
690
+ const empty = val == null || (typeof val === 'string' && val.trim() === '');
691
+ if (empty)
692
+ err = 'Required';
693
+ }
694
+ // type checks
695
+ const EMAIL_RX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
696
+ if (!err && col.type === 'email' && val && !EMAIL_RX.test(String(val)))
697
+ err = 'Invalid email';
698
+ if (!err && col.type === 'number' && val !== '' && val != null && Number.isNaN(Number(val)))
699
+ err = 'Invalid number';
700
+ if (!err && col.type === 'date' && val && Number.isNaN(Date.parse(String(val))))
701
+ err = 'Invalid date';
702
+ if (!err && col.type === 'boolean' && col.required && val !== true && val !== false)
703
+ err = 'Required';
704
+ if (err)
705
+ targetErrors[key] = err;
706
+ else
707
+ delete targetErrors[key];
708
+ }
709
+ deleteRow(i) {
710
+ if (!this.enableDelete)
711
+ return;
712
+ const di = this.dataIndexFromPaged(i);
713
+ this.rowDelete.emit({ row: this.data[di], index: di });
714
+ }
715
+ // (optional) better *ngFor performance
716
+ trackRow = (_, row) => row;
717
+ toggleSort(field) {
718
+ if (!this.enableSorting)
719
+ return;
720
+ if (this.sort.active !== field)
721
+ this.sort = { active: field, direction: 'asc' };
722
+ else
723
+ this.sort = {
724
+ active: field,
725
+ direction: this.sort.direction === 'asc' ? 'desc' : this.sort.direction === 'desc' ? '' : 'asc'
726
+ };
727
+ this.page = 1;
728
+ this.sortChange.emit({ active: this.sort.active, direction: this.sort.direction });
729
+ }
730
+ onGlobalFilterChange() {
731
+ if (!this.enableFiltering || !this.enableGlobalFilter)
732
+ return;
733
+ this.page = 1;
734
+ this.filtersChange.emit({ global: this.globalFilter, columns: { ...this.filters } });
735
+ }
736
+ onColumnFilterChange() {
737
+ if (!this.enableFiltering)
738
+ return;
739
+ this.page = 1;
740
+ this.filtersChange.emit({ global: this.globalFilter, columns: { ...this.filters } });
741
+ }
742
+ onPageChange(p) {
743
+ this.page = p;
744
+ this.pageChange.emit({ page: this.page, pageSize: this.pageSize });
745
+ }
746
+ onPageSizeChange() {
747
+ this.page = 1;
748
+ this.pageChange.emit({ page: this.page, pageSize: this.pageSize });
749
+ }
750
+ toggleExpand(i) {
751
+ const isOpen = this.expanded.has(i);
752
+ if (this.singleExpand) {
753
+ this.expanded.clear();
754
+ if (!isOpen)
755
+ this.expanded.add(i); // open the clicked row, or close all if it was open
756
+ }
757
+ else {
758
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
759
+ isOpen ? this.expanded.delete(i) : this.expanded.add(i);
760
+ }
761
+ }
762
+ isExpanded(i) {
763
+ return this.expanded.has(i);
764
+ }
765
+ onRowClick(ev, i) {
766
+ if (!this.editOnRowClick)
767
+ return;
768
+ if (this.addingNew || this.editingIndex === i)
769
+ return;
770
+ const el = ev.target;
771
+ if (el.closest('button, a, input, select, textarea, label, .no-edit-trigger'))
772
+ return;
773
+ this.startEdit(i);
774
+ }
775
+ onPage(p) {
776
+ if (p < 1)
777
+ return;
778
+ this.page = p;
779
+ this.pageChange.emit({ page: this.page, pageSize: this.pageSize });
780
+ }
781
+ onPageSize(sz) {
782
+ this.pageSize = Number(sz) || this.pageSize;
783
+ this.page = 1; // reset to first page when size changes
784
+ this.pageChange.emit({ page: this.page, pageSize: this.pageSize });
785
+ }
786
+ asBool(v) {
787
+ return typeof v === 'boolean' ? v : !!v?.enabled;
788
+ }
789
+ triggerExport(kind) {
790
+ this.export(kind);
791
+ }
792
+ isResponsiveEnabled() {
793
+ const r = this.responsive;
794
+ return r === true || (!!r && r.enabled === true);
795
+ }
796
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: Datagrid, deps: [], target: i0.ɵɵFactoryTarget.Component });
797
+ 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", dataProviderAll: "dataProviderAll", dataProviderSelection: "dataProviderSelection", pageSize: "pageSize" }, outputs: { rowAdd: "rowAdd", rowEdit: "rowEdit", rowSave: "rowSave", rowCancel: "rowCancel", rowDelete: "rowDelete", sortChange: "sortChange", filtersChange: "filtersChange", pageChange: "pageChange" }, providers: [
798
+ { provide: PdfExportAdapter, useClass: JsPdfAdapter },
799
+ { provide: ExcelExportAdapter, useClass: XlsxAdapter }
800
+ ], 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 });
801
+ }
802
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: Datagrid, decorators: [{
803
+ type: Component,
804
+ args: [{ selector: 'ngb-datagrid', imports: [CommonModule, FormsModule, NgbPaginationComponent, ReactiveFormsModule], providers: [
805
+ { provide: PdfExportAdapter, useClass: JsPdfAdapter },
806
+ { provide: ExcelExportAdapter, useClass: XlsxAdapter }
807
+ ], 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"] }]
808
+ }], propDecorators: { columns: [{
809
+ type: Input
810
+ }], data: [{
811
+ type: Input
812
+ }], enableSorting: [{
813
+ type: Input
814
+ }], enableFiltering: [{
815
+ type: Input
816
+ }], enableGlobalFilter: [{
817
+ type: Input
818
+ }], enablePagination: [{
819
+ type: Input
820
+ }], enableEdit: [{
821
+ type: Input
822
+ }], enableDelete: [{
823
+ type: Input
824
+ }], pageSizeOptions: [{
825
+ type: Input
826
+ }], enableAdd: [{
827
+ type: Input
828
+ }], addButtonAriaLabel: [{
829
+ type: Input
830
+ }], addButtonText: [{
831
+ type: Input
832
+ }], globalFilterAriaLabel: [{
833
+ type: Input
834
+ }], expandRowAriaLabel: [{
835
+ type: Input
836
+ }], collapseRowAriaLabel: [{
837
+ type: Input
838
+ }], exportPdfAriaLabel: [{
839
+ type: Input
840
+ }], exportExcelAriaLabel: [{
841
+ type: Input
842
+ }], newRowDefaults: [{
843
+ type: Input
844
+ }], strictEmail: [{
845
+ type: Input
846
+ }], editOnRowClick: [{
847
+ type: Input
848
+ }], singleExpand: [{
849
+ type: Input
850
+ }], exportOptions: [{
851
+ type: Input
852
+ }], theme: [{
853
+ type: Input
854
+ }], responsive: [{
855
+ type: Input
856
+ }], dataProviderAll: [{
857
+ type: Input
858
+ }], dataProviderSelection: [{
859
+ type: Input
860
+ }], exportButtonDir: [{
861
+ type: ContentChild,
862
+ args: [ExportButtonDirective]
863
+ }], rowAdd: [{
864
+ type: Output
865
+ }], rowEdit: [{
866
+ type: Output
867
+ }], rowSave: [{
868
+ type: Output
869
+ }], rowCancel: [{
870
+ type: Output
871
+ }], rowDelete: [{
872
+ type: Output
873
+ }], sortChange: [{
874
+ type: Output
875
+ }], filtersChange: [{
876
+ type: Output
877
+ }], pageChange: [{
878
+ type: Output
879
+ }], rowDetailTpl: [{
880
+ type: ContentChild,
881
+ args: [NgbRowDetailTemplate]
882
+ }], pageSize: [{
883
+ type: Input
884
+ }], cellTplQ: [{
885
+ type: ContentChildren,
886
+ args: [NgbCellTemplate]
887
+ }], editTplQ: [{
888
+ type: ContentChildren,
889
+ args: [NgbEditorTemplate]
890
+ }], filterTplQ: [{
891
+ type: ContentChildren,
892
+ args: [NgbFilterTemplate]
893
+ }], globalTplQ: [{
894
+ type: ContentChildren,
895
+ args: [NgbGlobalFilterTemplate]
896
+ }] } });
897
+
898
+ class NgbLiveAnnouncer {
899
+ node = null;
900
+ announce(message) {
901
+ if (!this.node) {
902
+ this.node = document.createElement('div');
903
+ this.node.setAttribute('aria-live', 'polite');
904
+ this.node.setAttribute('aria-atomic', 'true');
905
+ this.node.style.position = 'fixed';
906
+ this.node.style.width = '1px';
907
+ this.node.style.height = '1px';
908
+ this.node.style.overflow = 'hidden';
909
+ this.node.style.clip = 'rect(1px,1px,1px,1px)';
910
+ this.node.style.whiteSpace = 'nowrap';
911
+ document.body.appendChild(this.node);
912
+ }
913
+ this.node.textContent = message;
914
+ }
915
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbLiveAnnouncer, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
916
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbLiveAnnouncer, providedIn: 'root' });
917
+ }
918
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbLiveAnnouncer, decorators: [{
919
+ type: Injectable,
920
+ args: [{ providedIn: 'root' }]
921
+ }] });
922
+ const DND_LIVE_ANNOUNCE = new InjectionToken('DND_LIVE_ANNOUNCE', { providedIn: 'root', factory: () => null });
923
+
924
+ const DND_I18N = new InjectionToken('DND_I18N');
925
+ const defaultDndI18n = {
926
+ listLabel: (n) => n ?? 'List',
927
+ pickedUp: () => 'Picked up. Use Arrow keys to move, Enter to drop, Escape to cancel.',
928
+ dropped: () => 'Dropped.',
929
+ moveToIndex: (i, t) => `Moved to position ${i} of ${t}.`,
930
+ dropHere: () => 'Drop here',
931
+ cannotDropHere: () => 'Cannot drop here'
932
+ };
933
+
934
+ // drag-state.service.ts
935
+ class NgbDndState {
936
+ active = signal(false, ...(ngDevMode ? [{ debugName: "active" }] : []));
937
+ store = new Map();
938
+ currentId = null; // ✅ track current session id
939
+ // Public so directives can read strings and announce
940
+ i18n = defaultDndI18n;
941
+ announce;
942
+ constructor(announceFn, i18n) {
943
+ if (i18n)
944
+ this.i18n = i18n;
945
+ if (announceFn)
946
+ this.announce = announceFn;
947
+ }
948
+ createSession(session) {
949
+ const id = 'ngb-dnd-' + (crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2));
950
+ this.store.set(id, session);
951
+ this.currentId = id; // ✅ remember current
952
+ this.active.set(true);
953
+ return id;
954
+ }
955
+ get(id) {
956
+ if (!id)
957
+ return null;
958
+ return this.store.get(id) ?? null;
959
+ }
960
+ getCurrent() {
961
+ return this.currentId ? (this.store.get(this.currentId) ?? null) : null;
962
+ }
963
+ clear(id) {
964
+ if (id)
965
+ this.store.delete(id);
966
+ if (this.currentId === id)
967
+ this.currentId = null;
968
+ if (this.store.size === 0)
969
+ this.active.set(false);
970
+ }
971
+ 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 });
972
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbDndState, providedIn: 'root' });
973
+ }
974
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbDndState, decorators: [{
975
+ type: Injectable,
976
+ args: [{ providedIn: 'root' }]
977
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
978
+ type: Optional
979
+ }, {
980
+ type: Inject,
981
+ args: [DND_LIVE_ANNOUNCE]
982
+ }] }, { type: undefined, decorators: [{
983
+ type: Optional
984
+ }, {
985
+ type: Inject,
986
+ args: [DND_I18N]
987
+ }] }] });
988
+
989
+ class NgbDndListDirective {
990
+ el = inject((ElementRef));
991
+ state = inject(NgbDndState);
992
+ dndChildrenKey = 'children';
993
+ /** Bind your array here: <div [ngbDndList]="items"> */
994
+ list;
995
+ /** Optional: group barrier across lists */
996
+ dndGroup;
997
+ /** Disable this list */
998
+ dndDisabled = false;
999
+ /** NEW: clone if source wasn't a list (e.g. palette) */
1000
+ dndCloneOnDrop = false;
1001
+ /** NEW: custom clone function */
1002
+ dndCloneFn;
1003
+ dndIsPalette = false;
1004
+ dndCanDrop = true;
1005
+ // (optional) visual feedback while hovering a denied list
1006
+ _denied = false;
1007
+ get denied() { return this._denied; }
1008
+ dndAriaLabel;
1009
+ get ariaLabel() { return this.dndAriaLabel || null; }
1010
+ /** Fires after item is inserted */
1011
+ dndDropped = new EventEmitter();
1012
+ role = 'list';
1013
+ hostClass = true;
1014
+ // NEW: when the pointer is over this list while dragging
1015
+ hover = false;
1016
+ get isOver() { return this.hover; }
1017
+ // NEW: reflect whether the current drag can drop here (for CSS)
1018
+ canDrop = null;
1019
+ get dataDropValid() {
1020
+ // values: "true" | "false" | null (attribute omitted)
1021
+ return this.canDrop === true ? 'true' : this.canDrop === false ? 'false' : null;
1022
+ }
1023
+ placeholder;
1024
+ lastIndex = -1;
1025
+ onDragEnter(ev) {
1026
+ if (this.dndDisabled)
1027
+ return;
1028
+ const s = this.getSession(ev);
1029
+ // If there is an active drag and groups match, indicate hover
1030
+ if (s) {
1031
+ ev.preventDefault();
1032
+ this.hover = true;
1033
+ this.canDrop = true; // group check already done in getSession
1034
+ }
1035
+ else {
1036
+ // Drag exists but not compatible with this list (group mismatch)
1037
+ this.hover = true;
1038
+ this.canDrop = false;
1039
+ }
1040
+ }
1041
+ onDragOver(ev) {
1042
+ if (this.dndDisabled)
1043
+ return;
1044
+ const s = this.getSession(ev);
1045
+ if (!s) {
1046
+ // Not a compatible session → show invalid
1047
+ this.hover = true;
1048
+ this.canDrop = false;
1049
+ return;
1050
+ }
1051
+ ev.preventDefault();
1052
+ this.hover = true;
1053
+ this.canDrop = true;
1054
+ try {
1055
+ if (ev.dataTransfer)
1056
+ ev.dataTransfer.dropEffect = this.dndCloneOnDrop ? 'copy' : 'move';
1057
+ }
1058
+ catch { }
1059
+ this.ensurePlaceholder();
1060
+ const idx = this.indexFromPointer(ev);
1061
+ if (idx !== this.lastIndex) {
1062
+ this.lastIndex = idx;
1063
+ this.positionPlaceholderAt(idx);
1064
+ }
1065
+ }
1066
+ onDragLeave(ev) {
1067
+ // Only reset when actually leaving this list (not moving between children)
1068
+ if (!this.el.nativeElement.contains(ev.relatedTarget)) {
1069
+ this.removePlaceholder();
1070
+ this.lastIndex = -1;
1071
+ this.hover = false;
1072
+ this.canDrop = null;
1073
+ }
1074
+ }
1075
+ onDocumentDrop(ev) {
1076
+ const host = this.el.nativeElement;
1077
+ if (!host.contains(ev.target))
1078
+ return;
1079
+ const anyEv = ev;
1080
+ if (anyEv.__ngbHandled)
1081
+ return;
1082
+ anyEv.__ngbHandled = true;
1083
+ ev.preventDefault();
1084
+ this.performDrop(ev);
1085
+ // reset visual state
1086
+ this.hover = false;
1087
+ this.canDrop = null;
1088
+ }
1089
+ onDrop(ev) {
1090
+ if (this.dndDisabled)
1091
+ return;
1092
+ const anyEv = ev;
1093
+ if (anyEv.__ngbHandled)
1094
+ return;
1095
+ anyEv.__ngbHandled = true;
1096
+ ev.preventDefault();
1097
+ ev.stopPropagation();
1098
+ this.performDrop(ev);
1099
+ // reset visual state
1100
+ this.hover = false;
1101
+ this.canDrop = null;
1102
+ }
1103
+ // helpers
1104
+ getSession(ev) {
1105
+ // Chrome often returns "" during dragover/enter; try service fallback
1106
+ const sid = ev.dataTransfer?.getData('text/ngb-dnd') || null;
1107
+ let s = sid ? this.state.get(sid) : null;
1108
+ if (!s)
1109
+ s = this.state.getCurrent(); // ✅ fallback
1110
+ if (!s)
1111
+ return null;
1112
+ if (this.dndGroup && s.group && s.group !== this.dndGroup)
1113
+ return null;
1114
+ return s;
1115
+ }
1116
+ ensurePlaceholder() {
1117
+ if (!this.placeholder) {
1118
+ this.placeholder = document.createElement('div');
1119
+ this.placeholder.className = 'ngb-dnd-placeholder';
1120
+ this.placeholder.setAttribute('aria-hidden', 'true');
1121
+ this.el.nativeElement.appendChild(this.placeholder);
1122
+ }
1123
+ }
1124
+ removePlaceholder() {
1125
+ if (this.placeholder?.parentElement)
1126
+ this.placeholder.parentElement.removeChild(this.placeholder);
1127
+ this.placeholder = undefined;
1128
+ }
1129
+ positionPlaceholderAt(index) {
1130
+ const host = this.el.nativeElement;
1131
+ const children = Array.from(host.children).filter(c => c !== this.placeholder);
1132
+ const before = children[index];
1133
+ if (!before)
1134
+ host.appendChild(this.placeholder);
1135
+ else
1136
+ host.insertBefore(this.placeholder, before);
1137
+ }
1138
+ indexFromPointer(ev) {
1139
+ const host = this.el.nativeElement;
1140
+ const y = ev.clientY;
1141
+ const children = Array.from(host.children).filter(c => c !== this.placeholder);
1142
+ for (let i = 0; i < children.length; i++) {
1143
+ const r = children[i].getBoundingClientRect();
1144
+ const mid = r.top + r.height / 2;
1145
+ if (y < mid)
1146
+ return i;
1147
+ }
1148
+ return children.length;
1149
+ }
1150
+ cloneItem(i) {
1151
+ if (this.dndCloneFn)
1152
+ return this.dndCloneFn(i);
1153
+ // fallback shallow clone
1154
+ if (Array.isArray(i))
1155
+ return i.slice();
1156
+ if (i && typeof i === 'object')
1157
+ return { ...i };
1158
+ return i; // primitive
1159
+ }
1160
+ isDroppingIntoOwnDescendant(item, targetList) {
1161
+ const key = this.dndChildrenKey;
1162
+ if (!key || !item)
1163
+ return false;
1164
+ const stack = [];
1165
+ if (Array.isArray(item[key]))
1166
+ stack.push(item[key]);
1167
+ while (stack.length) {
1168
+ const arr = stack.pop();
1169
+ if (arr === targetList)
1170
+ return true;
1171
+ for (const child of arr) {
1172
+ const kids = child?.[key];
1173
+ if (Array.isArray(kids))
1174
+ stack.push(kids);
1175
+ }
1176
+ }
1177
+ return false;
1178
+ }
1179
+ /**
1180
+ * Build a guard payload using whatever drag context you already store.
1181
+ * If some fields don’t exist in your code, the fallbacks keep it working.
1182
+ */
1183
+ _buildGuardPayload(dstIndex) {
1184
+ const anyThis = this;
1185
+ // Try your known drag context fields; fall back harmlessly if absent
1186
+ const dragItem = anyThis._dragCtx?.item ?? anyThis.dragItem ?? anyThis._drag?.item ?? null;
1187
+ const srcList = anyThis._dragCtx?.src?.list ?? anyThis.srcList ?? anyThis._drag?.srcList ?? [];
1188
+ const srcIndex = anyThis._dragCtx?.src?.index ?? anyThis.srcIndex ?? anyThis._drag?.srcIndex ?? -1;
1189
+ const dstList = this.list;
1190
+ return {
1191
+ dragItem,
1192
+ srcList,
1193
+ srcIndex,
1194
+ dstList,
1195
+ dstIndex,
1196
+ isExternal: srcList !== dstList,
1197
+ };
1198
+ }
1199
+ /**
1200
+ * Compute destination index from the event position (no placeholder var needed).
1201
+ * Uses vertical list heuristics; adjust for horizontal if your list is horizontal.
1202
+ */
1203
+ _computeDropIndex(ev) {
1204
+ const host = this._host?.nativeElement
1205
+ ?? this._el?.nativeElement
1206
+ ?? this.el?.nativeElement
1207
+ ?? this.elementRef?.nativeElement
1208
+ ?? null;
1209
+ if (!host)
1210
+ return this.list?.length ?? 0;
1211
+ const children = Array.from(host.children);
1212
+ const y = ev.clientY;
1213
+ for (let i = 0; i < children.length; i++) {
1214
+ const rect = children[i].getBoundingClientRect();
1215
+ const midY = rect.top + rect.height / 2;
1216
+ if (y < midY)
1217
+ return i;
1218
+ }
1219
+ return children.length;
1220
+ }
1221
+ // Evaluate the guard safely
1222
+ _allowDrop(payload) {
1223
+ const guard = this.dndCanDrop;
1224
+ if (typeof guard === 'boolean')
1225
+ return guard;
1226
+ try {
1227
+ return guard ? !!guard(payload) : true;
1228
+ }
1229
+ catch {
1230
+ return false;
1231
+ }
1232
+ }
1233
+ // Extract your existing onDrop logic into a single method:
1234
+ performDrop(ev) {
1235
+ const session = this.getSession(ev);
1236
+ if (!session)
1237
+ return;
1238
+ const dstIndex = this._computeDropIndex(ev);
1239
+ // Guard check (block drop if not allowed)
1240
+ const payload = this._buildGuardPayload(dstIndex);
1241
+ if (!this._allowDrop(payload)) {
1242
+ ev.preventDefault();
1243
+ ev.stopPropagation();
1244
+ this._denied = true; // cosmetic: adds .ngb-dnd-denied for styling
1245
+ return; // ⛔ do not mutate arrays
1246
+ }
1247
+ this._denied = false;
1248
+ // ❗ block cycles: don't allow dropping an item into its own descendants
1249
+ if (this.isDroppingIntoOwnDescendant(session.item, this.list)) {
1250
+ // optional: visual/aural feedback
1251
+ this.state.announce?.(this.state.i18n.cannotDropHere());
1252
+ this.removePlaceholder();
1253
+ this.lastIndex = -1;
1254
+ return;
1255
+ }
1256
+ const targetIndex = this.lastIndex < 0 ? this.list.length : this.lastIndex;
1257
+ const fromList = session.fromList ?? undefined;
1258
+ const fromIndex = session.fromIndex ?? -1;
1259
+ const sameList = fromList === this.list;
1260
+ const isExternal = session.fromIsPalette === true || !fromList || fromIndex === -1;
1261
+ // ✅ External (palette): always transform/clone into canvas, and DO NOT remove from source.
1262
+ // ✅ Canvas↔Canvas: move by default; if you want to clone across canvas, set dndCloneOnDrop=true.
1263
+ const shouldCloneAcrossCanvas = this.dndCloneOnDrop && !sameList && !isExternal;
1264
+ const itemToInsert = isExternal
1265
+ ? this.cloneItem(session.item) // transform palette item to Panel
1266
+ : (shouldCloneAcrossCanvas ? this.cloneItem(session.item) : session.item);
1267
+ // Remove from source ONLY for real moves within canvas
1268
+ if (!isExternal && !shouldCloneAcrossCanvas && fromIndex > -1) {
1269
+ fromList.splice(fromIndex, 1);
1270
+ }
1271
+ // adjust index when reordering within same list and moving forward
1272
+ const insertAt = sameList && fromIndex > -1 && targetIndex > fromIndex
1273
+ ? targetIndex - 1
1274
+ : targetIndex;
1275
+ this.list.splice(insertAt, 0, itemToInsert);
1276
+ this.removePlaceholder();
1277
+ this.lastIndex = -1;
1278
+ this.dndDropped.emit({
1279
+ item: itemToInsert,
1280
+ fromIndex,
1281
+ toIndex: insertAt,
1282
+ fromList: fromList ?? [],
1283
+ toList: this.list,
1284
+ sameList
1285
+ });
1286
+ this.state.announce?.(this.state.i18n.dropped());
1287
+ }
1288
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbDndListDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1289
+ 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 });
1290
+ }
1291
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbDndListDirective, decorators: [{
1292
+ type: Directive,
1293
+ args: [{
1294
+ selector: '[ngbDndList]',
1295
+ standalone: true
1296
+ }]
1297
+ }], propDecorators: { dndChildrenKey: [{
1298
+ type: Input
1299
+ }], list: [{
1300
+ type: Input,
1301
+ args: ['ngbDndList']
1302
+ }], dndGroup: [{
1303
+ type: Input
1304
+ }], dndDisabled: [{
1305
+ type: Input
1306
+ }], dndCloneOnDrop: [{
1307
+ type: Input
1308
+ }], dndCloneFn: [{
1309
+ type: Input
1310
+ }], dndIsPalette: [{
1311
+ type: Input
1312
+ }], dndCanDrop: [{
1313
+ type: Input
1314
+ }], denied: [{
1315
+ type: HostBinding,
1316
+ args: ['class.ngb-dnd-denied']
1317
+ }], dndAriaLabel: [{
1318
+ type: Input
1319
+ }], ariaLabel: [{
1320
+ type: HostBinding,
1321
+ args: ['attr.aria-label']
1322
+ }], dndDropped: [{
1323
+ type: Output
1324
+ }], role: [{
1325
+ type: HostBinding,
1326
+ args: ['attr.role']
1327
+ }], hostClass: [{
1328
+ type: HostBinding,
1329
+ args: ['class.ngb-dnd-list']
1330
+ }], isOver: [{
1331
+ type: HostBinding,
1332
+ args: ['class.ngb-dnd-over']
1333
+ }], dataDropValid: [{
1334
+ type: HostBinding,
1335
+ args: ['attr.data-drop-valid']
1336
+ }], onDragEnter: [{
1337
+ type: HostListener,
1338
+ args: ['dragenter', ['$event']]
1339
+ }], onDragOver: [{
1340
+ type: HostListener,
1341
+ args: ['dragover', ['$event']]
1342
+ }], onDragLeave: [{
1343
+ type: HostListener,
1344
+ args: ['dragleave', ['$event']]
1345
+ }], onDocumentDrop: [{
1346
+ type: HostListener,
1347
+ args: ['document:drop', ['$event']]
1348
+ }], onDrop: [{
1349
+ type: HostListener,
1350
+ args: ['drop', ['$event']]
1351
+ }] } });
1352
+
1353
+ class NgbDndItemDirective {
1354
+ parentList;
1355
+ el = inject((ElementRef));
1356
+ state = inject(NgbDndState);
1357
+ constructor(parentList) {
1358
+ this.parentList = parentList;
1359
+ }
1360
+ /** required: the value carried by this row/panel */
1361
+ item;
1362
+ /** optional: constrain cross-list drops */
1363
+ dndGroup;
1364
+ /** index inside its parent list (bind to *ngFor index) */
1365
+ dndIndex;
1366
+ dndDisabled = false;
1367
+ dndDragStart = new EventEmitter();
1368
+ dndDragEnd = new EventEmitter();
1369
+ get draggable() { return !this.dndDisabled; }
1370
+ dragging = false;
1371
+ get ariaGrabbed() { return this.dragging ? 'true' : 'false'; }
1372
+ role = 'listitem';
1373
+ tabIndex = 0; // keyboard focusable
1374
+ sessionId = null;
1375
+ onDragStart(ev) {
1376
+ if (this.dndDisabled || this.item == null) {
1377
+ ev.preventDefault();
1378
+ return;
1379
+ }
1380
+ this.sessionId = this.state.createSession({
1381
+ item: this.item,
1382
+ group: this.dndGroup ?? this.parentList?.dndGroup,
1383
+ fromList: this.parentList?.list,
1384
+ fromIndex: this.dndIndex,
1385
+ fromIsPalette: this.parentList?.dndIsPalette === true
1386
+ });
1387
+ // ✅ set both a custom and a plain text payload; keep plain text non-empty
1388
+ ev.dataTransfer?.setData('text/ngb-dnd', this.sessionId);
1389
+ ev.dataTransfer?.setData('text/plain', 'ngb'); // some browsers require non-empty
1390
+ if (ev.dataTransfer)
1391
+ ev.dataTransfer.effectAllowed = 'copyMove';
1392
+ ev.dataTransfer?.setDragImage(this.el.nativeElement, 16, 16);
1393
+ this.dragging = true;
1394
+ this.dndDragStart.emit(this.item);
1395
+ }
1396
+ onDragEnd() {
1397
+ this.dragging = false;
1398
+ this.state.clear(this.sessionId);
1399
+ this.sessionId = null;
1400
+ this.dndDragEnd.emit();
1401
+ }
1402
+ onKeydown(ev) {
1403
+ if (this.dndDisabled)
1404
+ return;
1405
+ // start dragging (keyboard mode)
1406
+ if (ev.code === 'Space' && !this.dragging) {
1407
+ ev.preventDefault();
1408
+ this.onDragStart(new DragEvent('dragstart')); // reuse logic
1409
+ return;
1410
+ }
1411
+ if (!this.dragging)
1412
+ return;
1413
+ // move within same list
1414
+ const parent = this.parentList;
1415
+ if (!parent || typeof this.dndIndex !== 'number')
1416
+ return;
1417
+ if (ev.code === 'ArrowUp' || ev.code === 'ArrowDown') {
1418
+ ev.preventDefault();
1419
+ const list = parent.list;
1420
+ const from = this.dndIndex;
1421
+ let to = ev.code === 'ArrowUp' ? from - 1 : from + 1;
1422
+ to = Math.max(0, Math.min(list.length - 1, to));
1423
+ if (to !== from) {
1424
+ const [it] = list.splice(from, 1);
1425
+ list.splice(to, 0, it);
1426
+ this.dndIndex = to;
1427
+ // announce (see live region below)
1428
+ this.state.announce?.(this.state.i18n.moveToIndex(to + 1, list.length));
1429
+ }
1430
+ }
1431
+ if (ev.code === 'Enter') {
1432
+ ev.preventDefault();
1433
+ this.onDragEnd();
1434
+ } // drop
1435
+ if (ev.code === 'Escape') {
1436
+ ev.preventDefault();
1437
+ this.onDragEnd();
1438
+ } // cancel
1439
+ }
1440
+ 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 });
1441
+ 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 });
1442
+ }
1443
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbDndItemDirective, decorators: [{
1444
+ type: Directive,
1445
+ args: [{
1446
+ selector: '[ngbDndItem]',
1447
+ standalone: true
1448
+ }]
1449
+ }], ctorParameters: () => [{ type: NgbDndListDirective, decorators: [{
1450
+ type: Optional
1451
+ }] }], propDecorators: { item: [{
1452
+ type: Input,
1453
+ args: ['ngbDndItem']
1454
+ }], dndGroup: [{
1455
+ type: Input
1456
+ }], dndIndex: [{
1457
+ type: Input
1458
+ }], dndDisabled: [{
1459
+ type: Input
1460
+ }], dndDragStart: [{
1461
+ type: Output
1462
+ }], dndDragEnd: [{
1463
+ type: Output
1464
+ }], draggable: [{
1465
+ type: HostBinding,
1466
+ args: ['attr.draggable']
1467
+ }], dragging: [{
1468
+ type: HostBinding,
1469
+ args: ['class.ngb-dnd-dragging']
1470
+ }], ariaGrabbed: [{
1471
+ type: HostBinding,
1472
+ args: ['attr.aria-grabbed']
1473
+ }], role: [{
1474
+ type: HostBinding,
1475
+ args: ['attr.role']
1476
+ }], tabIndex: [{
1477
+ type: HostBinding,
1478
+ args: ['attr.tabindex']
1479
+ }], onDragStart: [{
1480
+ type: HostListener,
1481
+ args: ['dragstart', ['$event']]
1482
+ }], onDragEnd: [{
1483
+ type: HostListener,
1484
+ args: ['dragend']
1485
+ }], onKeydown: [{
1486
+ type: HostListener,
1487
+ args: ['keydown', ['$event']]
1488
+ }] } });
1489
+
1490
+ class NgbStepLabelDirective {
1491
+ template;
1492
+ for;
1493
+ constructor(template) {
1494
+ this.template = template;
1495
+ }
1496
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbStepLabelDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
1497
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: NgbStepLabelDirective, isStandalone: true, selector: "[ngbStepLabel]", inputs: { for: ["ngbStepLabel", "for"] }, ngImport: i0 });
1498
+ }
1499
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbStepLabelDirective, decorators: [{
1500
+ type: Directive,
1501
+ args: [{
1502
+ selector: '[ngbStepLabel]',
1503
+ standalone: true,
1504
+ }]
1505
+ }], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { for: [{
1506
+ type: Input,
1507
+ args: ['ngbStepLabel']
1508
+ }] } });
1509
+
1510
+ class NgbStepperComponent {
1511
+ /** Steps definition */
1512
+ steps = [];
1513
+ /** Orientation of the stepper */
1514
+ orientation = 'horizontal';
1515
+ /** Position of labels around the indicator */
1516
+ labelPosition = 'right';
1517
+ /** Position of content relative to header (horizontal only) */
1518
+ contentPosition = 'bottom';
1519
+ /** Allow navigating back to previous steps by clicking header */
1520
+ allowRevisit = true;
1521
+ /** Enable lazy loading of step content (default true) */
1522
+ lazy = true;
1523
+ /** Theme keyword (only CSS classes, no external deps) */
1524
+ theme = 'bootstrap';
1525
+ /** Whether to apply responsive helper class */
1526
+ responsive = true;
1527
+ /** Disable transition / animation helpers */
1528
+ disableAnimation = false;
1529
+ /** Animation duration (ms) for step transitions when enabled */
1530
+ animationDuration = 150;
1531
+ /** Label strings (for i18n) */
1532
+ nextLabel = 'Next';
1533
+ previousLabel = 'Previous';
1534
+ resetLabel = 'Reset';
1535
+ cancelLabel = 'Cancel';
1536
+ optionalLabel = 'Optional';
1537
+ /** Custom mapping from state -> icon text */
1538
+ stateIcons = {
1539
+ number: '#',
1540
+ done: '✓',
1541
+ active: '•',
1542
+ error: '!',
1543
+ disabled: '×',
1544
+ };
1545
+ /** Current selected index */
1546
+ selectedIndex = 0;
1547
+ /** Emits when selectedIndex changes (programmatically or by user) */
1548
+ selectedIndexChange = new EventEmitter();
1549
+ /** Emits when selection changes with rich event */
1550
+ selectionChange = new EventEmitter();
1551
+ /** Navigation events */
1552
+ nextClicked = new EventEmitter();
1553
+ prevClicked = new EventEmitter();
1554
+ resetClicked = new EventEmitter();
1555
+ cancelClicked = new EventEmitter();
1556
+ /** Custom label template (applied for all steps) */
1557
+ stepLabelTplDir;
1558
+ /** Custom icon template for step indicator */
1559
+ customIconTpl;
1560
+ /** Content template; user will typically use <ng-template #stepContent ...> */
1561
+ stepContentTpl;
1562
+ /** Track which steps have been visited */
1563
+ visited = new Set();
1564
+ /** Used when allowRevisit=false to block earlier steps */
1565
+ visitedFrom = 0;
1566
+ ngAfterContentInit() {
1567
+ if (this.selectedIndex < 0)
1568
+ this.selectedIndex = 0;
1569
+ if (this.selectedIndex > this.steps.length - 1 && this.steps.length > 0) {
1570
+ this.selectedIndex = this.steps.length - 1;
1571
+ }
1572
+ this.markVisited(this.selectedIndex);
1573
+ }
1574
+ get stepLabelTpl() {
1575
+ return this.stepLabelTplDir?.template ?? null;
1576
+ }
1577
+ get labelPositionClass() {
1578
+ switch (this.labelPosition) {
1579
+ case 'top':
1580
+ return 'ngb-stepper-label-top';
1581
+ case 'bottom':
1582
+ return 'ngb-stepper-label-bottom';
1583
+ case 'left':
1584
+ return 'ngb-stepper-label-left';
1585
+ default:
1586
+ return 'ngb-stepper-label-right';
1587
+ }
1588
+ }
1589
+ get currentErrorMessage() {
1590
+ const step = this.steps[this.selectedIndex];
1591
+ return step?.errorMessage ?? null;
1592
+ }
1593
+ stepId(i) {
1594
+ const s = this.steps[i];
1595
+ const base = s?.id || `ngb-step-${i}`;
1596
+ return `${base}-label`;
1597
+ }
1598
+ contentId(i) {
1599
+ const s = this.steps[i];
1600
+ const base = s?.id || `ngb-step-${i}`;
1601
+ return `${base}-content`;
1602
+ }
1603
+ markVisited(i) {
1604
+ this.visited.add(i);
1605
+ this.visitedFrom = Math.max(this.visitedFrom, i);
1606
+ }
1607
+ stepState(i) {
1608
+ const s = this.steps[i];
1609
+ if (!s)
1610
+ return 'number';
1611
+ if (s.state)
1612
+ return s.state;
1613
+ if (this.isStepInError(i))
1614
+ return 'error';
1615
+ if (i === this.selectedIndex)
1616
+ return 'active';
1617
+ if (i < this.selectedIndex)
1618
+ return 'done';
1619
+ if (s.disabled)
1620
+ return 'disabled';
1621
+ return 'number';
1622
+ }
1623
+ isStepInError(i) {
1624
+ const s = this.steps[i];
1625
+ return !!s?.errorMessage && this.visited.has(i);
1626
+ }
1627
+ indicatorClass(i) {
1628
+ const state = this.stepState(i);
1629
+ return state;
1630
+ }
1631
+ defaultIconForState(state, index) {
1632
+ if (state === 'number') {
1633
+ return String(index + 1);
1634
+ }
1635
+ return this.stateIcons[state] ?? this.stateIcons['number'] ?? String(index + 1);
1636
+ }
1637
+ onHeaderClick(i) {
1638
+ if (i === this.selectedIndex)
1639
+ return;
1640
+ const step = this.steps[i];
1641
+ if (!step || step.disabled)
1642
+ return;
1643
+ if (!this.allowRevisit && i < this.visitedFrom)
1644
+ return;
1645
+ this.changeIndex(i);
1646
+ }
1647
+ onHeaderKeydown(event, i) {
1648
+ event.preventDefault();
1649
+ this.onHeaderClick(i);
1650
+ }
1651
+ next() {
1652
+ const target = Math.min(this.steps.length - 1, this.selectedIndex + 1);
1653
+ if (target === this.selectedIndex)
1654
+ return;
1655
+ this.nextClicked.emit(target);
1656
+ this.changeIndex(target);
1657
+ }
1658
+ prev() {
1659
+ if (!this.allowRevisit)
1660
+ return;
1661
+ const target = Math.max(0, this.selectedIndex - 1);
1662
+ if (target === this.selectedIndex)
1663
+ return;
1664
+ this.prevClicked.emit(target);
1665
+ this.changeIndex(target);
1666
+ }
1667
+ onReset() {
1668
+ this.resetClicked.emit();
1669
+ this.selectedIndex = 0;
1670
+ this.selectedIndexChange.emit(this.selectedIndex);
1671
+ this.selectionChange.emit({
1672
+ previousIndex: 0,
1673
+ currentIndex: 0,
1674
+ step: this.steps[0],
1675
+ });
1676
+ this.visited.clear();
1677
+ this.visitedFrom = 0;
1678
+ this.markVisited(0);
1679
+ }
1680
+ onCancel() {
1681
+ this.cancelClicked.emit();
1682
+ }
1683
+ changeIndex(nextIndex) {
1684
+ const prev = this.selectedIndex;
1685
+ this.selectedIndex = nextIndex;
1686
+ this.markVisited(nextIndex);
1687
+ this.selectedIndexChange.emit(this.selectedIndex);
1688
+ this.selectionChange.emit({
1689
+ previousIndex: prev,
1690
+ currentIndex: nextIndex,
1691
+ step: this.steps[nextIndex],
1692
+ });
1693
+ }
1694
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbStepperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1695
+ 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 });
1696
+ }
1697
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbStepperComponent, decorators: [{
1698
+ type: Component,
1699
+ 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"] }]
1700
+ }], propDecorators: { steps: [{
1701
+ type: Input
1702
+ }], orientation: [{
1703
+ type: Input
1704
+ }], labelPosition: [{
1705
+ type: Input
1706
+ }], contentPosition: [{
1707
+ type: Input
1708
+ }], allowRevisit: [{
1709
+ type: Input
1710
+ }], lazy: [{
1711
+ type: Input
1712
+ }], theme: [{
1713
+ type: Input
1714
+ }], responsive: [{
1715
+ type: Input
1716
+ }], disableAnimation: [{
1717
+ type: Input
1718
+ }], animationDuration: [{
1719
+ type: Input
1720
+ }], nextLabel: [{
1721
+ type: Input
1722
+ }], previousLabel: [{
1723
+ type: Input
1724
+ }], resetLabel: [{
1725
+ type: Input
1726
+ }], cancelLabel: [{
1727
+ type: Input
1728
+ }], optionalLabel: [{
1729
+ type: Input
1730
+ }], stateIcons: [{
1731
+ type: Input
1732
+ }], selectedIndex: [{
1733
+ type: Input
1734
+ }], selectedIndexChange: [{
1735
+ type: Output
1736
+ }], selectionChange: [{
1737
+ type: Output
1738
+ }], nextClicked: [{
1739
+ type: Output
1740
+ }], prevClicked: [{
1741
+ type: Output
1742
+ }], resetClicked: [{
1743
+ type: Output
1744
+ }], cancelClicked: [{
1745
+ type: Output
1746
+ }], stepLabelTplDir: [{
1747
+ type: ContentChild,
1748
+ args: [NgbStepLabelDirective]
1749
+ }], customIconTpl: [{
1750
+ type: ContentChild,
1751
+ args: ['ngbStepperIcon', { read: TemplateRef }]
1752
+ }], stepContentTpl: [{
1753
+ type: ContentChild,
1754
+ args: ['stepContent', { read: TemplateRef }]
1755
+ }] } });
1756
+
1757
+ class NgbSplitterPaneComponent {
1758
+ templateRef;
1759
+ size;
1760
+ min;
1761
+ max;
1762
+ collapsible = false;
1763
+ collapsed = false;
1764
+ collapsedChange = new EventEmitter();
1765
+ toggleCollapsed(next) {
1766
+ const value = next ?? !this.collapsed;
1767
+ if (value !== this.collapsed) {
1768
+ this.collapsed = value;
1769
+ this.collapsedChange.emit(this.collapsed);
1770
+ }
1771
+ }
1772
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbSplitterPaneComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1773
+ 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", collapsible: "collapsible", collapsed: "collapsed" }, outputs: { collapsedChange: "collapsedChange" }, viewQueries: [{ propertyName: "templateRef", first: true, predicate: ["paneTpl"], descendants: true, static: true }], ngImport: i0, template: `<ng-template #paneTpl><ng-content></ng-content></ng-template>`, isInline: true });
1774
+ }
1775
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbSplitterPaneComponent, decorators: [{
1776
+ type: Component,
1777
+ args: [{
1778
+ selector: 'ngb-splitter-pane',
1779
+ standalone: true,
1780
+ template: `<ng-template #paneTpl><ng-content></ng-content></ng-template>`,
1781
+ }]
1782
+ }], propDecorators: { templateRef: [{
1783
+ type: ViewChild,
1784
+ args: ['paneTpl', { static: true }]
1785
+ }], size: [{
1786
+ type: Input
1787
+ }], min: [{
1788
+ type: Input
1789
+ }], max: [{
1790
+ type: Input
1791
+ }], collapsible: [{
1792
+ type: Input
1793
+ }], collapsed: [{
1794
+ type: Input
1795
+ }], collapsedChange: [{
1796
+ type: Output
1797
+ }] } });
1798
+
1799
+ class NgbSplitterComponent {
1800
+ panes;
1801
+ container;
1802
+ orientation = 'horizontal';
1803
+ handleThickness = 12;
1804
+ paneStates = [];
1805
+ resizeSub;
1806
+ currentDrag;
1807
+ ngAfterContentInit() {
1808
+ this.initStates();
1809
+ this.panes.changes.subscribe(() => this.initStates());
1810
+ }
1811
+ ngOnChanges(changes) {
1812
+ if (changes['orientation'] && !changes['orientation'].firstChange) {
1813
+ this.initStates();
1814
+ }
1815
+ }
1816
+ ngOnDestroy() {
1817
+ this.detachDragListeners();
1818
+ }
1819
+ onWindowResize() {
1820
+ this.initStates();
1821
+ }
1822
+ startResize(index, event) {
1823
+ event.preventDefault();
1824
+ const total = this.getContainerSize();
1825
+ const sizes = this.computePaneSizes(total);
1826
+ this.currentDrag = {
1827
+ index,
1828
+ startPos: this.pointerPosition(event),
1829
+ startSizes: [sizes[index], sizes[index + 1]],
1830
+ };
1831
+ const move = (e) => this.onDrag(e);
1832
+ const up = () => this.stopDrag();
1833
+ document.addEventListener('mousemove', move);
1834
+ document.addEventListener('mouseup', up, { once: true });
1835
+ this.resizeSub = { move, up };
1836
+ }
1837
+ onHandleKeydown(index, event) {
1838
+ const isHorizontal = this.orientation === 'horizontal';
1839
+ const delta = this.handleThickness;
1840
+ if ((isHorizontal && (event.key === 'ArrowLeft' || event.key === 'ArrowRight')) ||
1841
+ (!isHorizontal && (event.key === 'ArrowUp' || event.key === 'ArrowDown'))) {
1842
+ event.preventDefault();
1843
+ const direction = event.key === 'ArrowLeft' || event.key === 'ArrowUp' ? -1 : 1;
1844
+ this.adjustPaneSizes(index, delta * direction);
1845
+ }
1846
+ if (event.key === 'Enter' || event.key === ' ') {
1847
+ event.preventDefault();
1848
+ this.toggleFromHandle(index);
1849
+ }
1850
+ }
1851
+ toggleFromHandle(index) {
1852
+ const panesArray = this.panes.toArray();
1853
+ const target = panesArray[index].collapsible ? panesArray[index] : panesArray[index + 1];
1854
+ if (target?.collapsible) {
1855
+ target.toggleCollapsed();
1856
+ this.initStates();
1857
+ }
1858
+ }
1859
+ onDrag(event) {
1860
+ if (!this.currentDrag) {
1861
+ return;
1862
+ }
1863
+ event.preventDefault();
1864
+ const delta = this.pointerPosition(event) - this.currentDrag.startPos;
1865
+ this.adjustPaneSizes(this.currentDrag.index, delta, this.currentDrag.startSizes);
1866
+ }
1867
+ stopDrag() {
1868
+ this.detachDragListeners();
1869
+ this.currentDrag = undefined;
1870
+ }
1871
+ adjustPaneSizes(index, delta, startSizes) {
1872
+ const total = this.getContainerSize();
1873
+ const sizes = startSizes ?? this.computePaneSizes(total);
1874
+ const panesArray = this.panes.toArray();
1875
+ const first = panesArray[index];
1876
+ const second = panesArray[index + 1];
1877
+ if (first.collapsed || second.collapsed) {
1878
+ return;
1879
+ }
1880
+ let firstSize = sizes[0] + delta;
1881
+ let secondSize = sizes[1] - delta;
1882
+ const firstLimits = this.getLimits(first, total);
1883
+ const secondLimits = this.getLimits(second, total);
1884
+ firstSize = this.clamp(firstSize, firstLimits.minPx, firstLimits.maxPx);
1885
+ secondSize = this.clamp(secondSize, secondLimits.minPx, secondLimits.maxPx);
1886
+ if (firstSize + secondSize > 0) {
1887
+ this.paneStates[index].sizePx = firstSize;
1888
+ this.paneStates[index + 1].sizePx = secondSize;
1889
+ }
1890
+ }
1891
+ initStates() {
1892
+ const total = this.getContainerSize();
1893
+ const sizes = this.computePaneSizes(total);
1894
+ const panesArray = this.panes.toArray();
1895
+ this.paneStates = panesArray.map((pane, idx) => {
1896
+ const limits = this.getLimits(pane, total);
1897
+ return {
1898
+ id: `ngb-splitter-pane-${idx}`,
1899
+ sizePx: pane.collapsed ? 0 : sizes[idx],
1900
+ minPx: limits.minPx,
1901
+ maxPx: limits.maxPx,
1902
+ };
1903
+ });
1904
+ }
1905
+ computePaneSizes(total) {
1906
+ const panesArray = this.panes?.toArray() ?? [];
1907
+ if (!panesArray.length)
1908
+ return [];
1909
+ const resolved = panesArray.map((pane) => pane.collapsed ? 0 : this.resolveSize(pane.size, total));
1910
+ const specifiedTotal = resolved.reduce((sum, val) => sum + (val ?? 0), 0);
1911
+ const unspecified = resolved.filter((v) => v == null).length;
1912
+ const remaining = Math.max(total - specifiedTotal, 0);
1913
+ const autoSize = unspecified > 0 ? remaining / unspecified : 0;
1914
+ return resolved.map((val) => {
1915
+ if (val != null) {
1916
+ return val;
1917
+ }
1918
+ if (autoSize) {
1919
+ return autoSize;
1920
+ }
1921
+ return total / panesArray.length;
1922
+ });
1923
+ }
1924
+ resolveSize(value, total) {
1925
+ if (!value)
1926
+ return undefined;
1927
+ const trimmed = value.trim();
1928
+ if (trimmed.endsWith('%')) {
1929
+ const pct = Number.parseFloat(trimmed.slice(0, -1));
1930
+ return (pct / 100) * total;
1931
+ }
1932
+ if (trimmed.endsWith('px')) {
1933
+ return Number.parseFloat(trimmed.replace('px', ''));
1934
+ }
1935
+ const asNumber = Number.parseFloat(trimmed);
1936
+ return Number.isFinite(asNumber) ? asNumber : undefined;
1937
+ }
1938
+ getLimits(pane, total) {
1939
+ return {
1940
+ minPx: pane.collapsed ? 0 : this.resolveSize(pane.min, total),
1941
+ maxPx: pane.collapsed ? 0 : this.resolveSize(pane.max, total),
1942
+ };
1943
+ }
1944
+ clamp(value, min, max) {
1945
+ let next = value;
1946
+ if (min != null)
1947
+ next = Math.max(min, next);
1948
+ if (max != null)
1949
+ next = Math.min(max, next);
1950
+ return next;
1951
+ }
1952
+ pointerPosition(event) {
1953
+ return this.orientation === 'horizontal' ? event.clientX : event.clientY;
1954
+ }
1955
+ getContainerSize() {
1956
+ const rect = this.container?.nativeElement.getBoundingClientRect();
1957
+ return rect && this.orientation === 'horizontal' ? rect.width : rect?.height || 0;
1958
+ }
1959
+ detachDragListeners() {
1960
+ if (this.resizeSub) {
1961
+ document.removeEventListener('mousemove', this.resizeSub.move);
1962
+ document.removeEventListener('mouseup', this.resizeSub.up);
1963
+ this.resizeSub = undefined;
1964
+ }
1965
+ }
1966
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbSplitterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1967
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: NgbSplitterComponent, isStandalone: true, selector: "ngb-splitter", inputs: { orientation: "orientation", handleThickness: "handleThickness" }, host: { listeners: { "window:resize": "onWindowResize()" } }, queries: [{ propertyName: "panes", predicate: NgbSplitterPaneComponent }], viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: `
1968
+ <div
1969
+ #container
1970
+ class="d-flex"
1971
+ [class.flex-row]="orientation === 'horizontal'"
1972
+ [class.flex-column]="orientation === 'vertical'"
1973
+ role="group"
1974
+ >
1975
+ <ng-container *ngFor="let pane of panes.toArray(); let i = index">
1976
+ <div
1977
+ class="d-flex flex-column overflow-auto"
1978
+ [attr.data-pane-id]="paneStates[i]?.id"
1979
+ [class.d-none]="pane.collapsed"
1980
+ [style.flex-basis.px]="paneStates[i]?.sizePx"
1981
+ [style.min-width.px]="orientation === 'horizontal' ? paneStates[i]?.minPx : undefined"
1982
+ [style.max-width.px]="orientation === 'horizontal' ? paneStates[i]?.maxPx : undefined"
1983
+ [style.min-height.px]="orientation === 'vertical' ? paneStates[i]?.minPx : undefined"
1984
+ [style.max-height.px]="orientation === 'vertical' ? paneStates[i]?.maxPx : undefined"
1985
+ >
1986
+ <ng-container *ngTemplateOutlet="pane.templateRef"></ng-container>
1987
+ </div>
1988
+
1989
+ <button
1990
+ *ngIf="i < panes.length - 1"
1991
+ type="button"
1992
+ class="bg-transparent border-0 p-0 d-flex align-items-center justify-content-center"
1993
+ [style.width.px]="orientation === 'horizontal' ? handleThickness : undefined"
1994
+ [style.height.px]="orientation === 'vertical' ? handleThickness : undefined"
1995
+ [class.w-100]="orientation === 'vertical'"
1996
+ [class.h-100]="orientation === 'horizontal'"
1997
+ role="separator"
1998
+ [attr.aria-orientation]="orientation"
1999
+ [attr.aria-controls]="paneStates[i]?.id + ' ' + paneStates[i + 1]?.id"
2000
+ tabindex="0"
2001
+ (mousedown)="startResize(i, $event)"
2002
+ (keydown)="onHandleKeydown(i, $event)"
2003
+ (dblclick)="toggleFromHandle(i)"
2004
+ aria-label="Splitter resize handle"
2005
+ >
2006
+ <span class="border bg-light" [style.width.px]="orientation === 'horizontal' ? 2 : handleThickness" [style.height.px]="orientation === 'vertical' ? 2 : handleThickness"></span>
2007
+ </button>
2008
+ </ng-container>
2009
+ </div>
2010
+ `, 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"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2011
+ }
2012
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbSplitterComponent, decorators: [{
2013
+ type: Component,
2014
+ args: [{
2015
+ selector: 'ngb-splitter',
2016
+ standalone: true,
2017
+ imports: [CommonModule, NgbSplitterPaneComponent],
2018
+ changeDetection: ChangeDetectionStrategy.OnPush,
2019
+ template: `
2020
+ <div
2021
+ #container
2022
+ class="d-flex"
2023
+ [class.flex-row]="orientation === 'horizontal'"
2024
+ [class.flex-column]="orientation === 'vertical'"
2025
+ role="group"
2026
+ >
2027
+ <ng-container *ngFor="let pane of panes.toArray(); let i = index">
2028
+ <div
2029
+ class="d-flex flex-column overflow-auto"
2030
+ [attr.data-pane-id]="paneStates[i]?.id"
2031
+ [class.d-none]="pane.collapsed"
2032
+ [style.flex-basis.px]="paneStates[i]?.sizePx"
2033
+ [style.min-width.px]="orientation === 'horizontal' ? paneStates[i]?.minPx : undefined"
2034
+ [style.max-width.px]="orientation === 'horizontal' ? paneStates[i]?.maxPx : undefined"
2035
+ [style.min-height.px]="orientation === 'vertical' ? paneStates[i]?.minPx : undefined"
2036
+ [style.max-height.px]="orientation === 'vertical' ? paneStates[i]?.maxPx : undefined"
2037
+ >
2038
+ <ng-container *ngTemplateOutlet="pane.templateRef"></ng-container>
2039
+ </div>
2040
+
2041
+ <button
2042
+ *ngIf="i < panes.length - 1"
2043
+ type="button"
2044
+ class="bg-transparent border-0 p-0 d-flex align-items-center justify-content-center"
2045
+ [style.width.px]="orientation === 'horizontal' ? handleThickness : undefined"
2046
+ [style.height.px]="orientation === 'vertical' ? handleThickness : undefined"
2047
+ [class.w-100]="orientation === 'vertical'"
2048
+ [class.h-100]="orientation === 'horizontal'"
2049
+ role="separator"
2050
+ [attr.aria-orientation]="orientation"
2051
+ [attr.aria-controls]="paneStates[i]?.id + ' ' + paneStates[i + 1]?.id"
2052
+ tabindex="0"
2053
+ (mousedown)="startResize(i, $event)"
2054
+ (keydown)="onHandleKeydown(i, $event)"
2055
+ (dblclick)="toggleFromHandle(i)"
2056
+ aria-label="Splitter resize handle"
2057
+ >
2058
+ <span class="border bg-light" [style.width.px]="orientation === 'horizontal' ? 2 : handleThickness" [style.height.px]="orientation === 'vertical' ? 2 : handleThickness"></span>
2059
+ </button>
2060
+ </ng-container>
2061
+ </div>
2062
+ `,
2063
+ }]
2064
+ }], propDecorators: { panes: [{
2065
+ type: ContentChildren,
2066
+ args: [NgbSplitterPaneComponent]
2067
+ }], container: [{
2068
+ type: ViewChild,
2069
+ args: ['container', { static: true }]
2070
+ }], orientation: [{
2071
+ type: Input
2072
+ }], handleThickness: [{
2073
+ type: Input
2074
+ }], onWindowResize: [{
2075
+ type: HostListener,
2076
+ args: ['window:resize']
2077
+ }] } });
2078
+
2079
+ class NgbTypeaheadComponent {
2080
+ data = [];
2081
+ debounceTime = 200;
2082
+ characterTyped = 1;
2083
+ limit = 10;
2084
+ selectExact = false;
2085
+ multiSelect = false;
2086
+ i18n;
2087
+ itemTemplate;
2088
+ selectedItems = new EventEmitter();
2089
+ selectionChange = new EventEmitter();
2090
+ onChange = new EventEmitter();
2091
+ onScrollEvent = new EventEmitter();
2092
+ scroller;
2093
+ inputEl;
2094
+ itemButtons;
2095
+ query = '';
2096
+ filtered = [];
2097
+ visible = [];
2098
+ selected = [];
2099
+ viewportHeight = 240;
2100
+ itemHeight = 40;
2101
+ beforePadding = 0;
2102
+ afterPadding = 0;
2103
+ debounceId;
2104
+ ngAfterViewInit() {
2105
+ this.applyFilter('');
2106
+ }
2107
+ ngOnChanges(changes) {
2108
+ if (changes['data'] && !changes['data'].firstChange) {
2109
+ this.applyFilter(this.query);
2110
+ }
2111
+ }
2112
+ ngOnDestroy() {
2113
+ if (this.debounceId) {
2114
+ clearTimeout(this.debounceId);
2115
+ }
2116
+ }
2117
+ get selectionSummary() {
2118
+ if (!this.selected.length)
2119
+ return '';
2120
+ if (this.selected.length <= 2) {
2121
+ return this.selected.map((s) => s.label).join(', ');
2122
+ }
2123
+ const firstTwo = this.selected.slice(0, 2).map((s) => s.label).join(', ');
2124
+ return `${firstTwo} (+${this.selected.length - 2})`;
2125
+ }
2126
+ onInput(value) {
2127
+ this.query = value;
2128
+ this.onChange.emit(value);
2129
+ if (this.debounceId)
2130
+ clearTimeout(this.debounceId);
2131
+ if (this.debounceTime <= 0) {
2132
+ this.applyFilter(value);
2133
+ }
2134
+ else {
2135
+ this.debounceId = setTimeout(() => this.applyFilter(value), this.debounceTime);
2136
+ }
2137
+ }
2138
+ onKeydown(event) {
2139
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
2140
+ event.preventDefault();
2141
+ this.focusSibling(event.key === 'ArrowDown' ? 1 : -1);
2142
+ }
2143
+ else if (event.key === 'Enter') {
2144
+ event.preventDefault();
2145
+ const first = this.visible[0];
2146
+ if (first)
2147
+ this.selectItem(first);
2148
+ }
2149
+ }
2150
+ onScroll(event) {
2151
+ const target = event.target;
2152
+ const scrollTop = target.scrollTop;
2153
+ this.onScrollEvent.emit();
2154
+ this.updateViewport(scrollTop);
2155
+ }
2156
+ selectItem(item) {
2157
+ if (!item || item.disabled)
2158
+ return;
2159
+ this.validateItem(item);
2160
+ if (this.multiSelect) {
2161
+ const exists = this.selected.find((s) => s.id === item.id);
2162
+ if (exists) {
2163
+ this.selected = this.selected.filter((s) => s.id !== item.id);
2164
+ }
2165
+ else {
2166
+ this.selected = [...this.selected, item];
2167
+ }
2168
+ }
2169
+ else {
2170
+ this.selected = [item];
2171
+ }
2172
+ this.selectedItems.emit(this.selected);
2173
+ this.selectionChange.emit(this.selected);
2174
+ }
2175
+ clearSelection() {
2176
+ this.selected = [];
2177
+ this.selectedItems.emit(this.selected);
2178
+ this.selectionChange.emit(this.selected);
2179
+ }
2180
+ isSelected(item) {
2181
+ return this.selected.some((s) => s.id === item.id);
2182
+ }
2183
+ trackById = (_, item) => item.id;
2184
+ applyFilter(value) {
2185
+ const term = (value || '').toLowerCase();
2186
+ const meetsThreshold = term.length >= this.characterTyped;
2187
+ const base = meetsThreshold ? this.data.filter((d) => (d.label || '').toLowerCase().includes(term)) : [];
2188
+ const limited = this.limit ? base.slice(0, this.limit) : base;
2189
+ this.filtered = limited;
2190
+ if (this.selectExact && limited.length === 1) {
2191
+ this.selectItem(limited[0]);
2192
+ }
2193
+ this.updateViewport(this.scroller?.nativeElement.scrollTop || 0);
2194
+ }
2195
+ updateViewport(scrollTop) {
2196
+ const total = this.filtered.length;
2197
+ const visibleCount = Math.ceil(this.viewportHeight / this.itemHeight) + 2;
2198
+ const start = Math.max(Math.floor(scrollTop / this.itemHeight), 0);
2199
+ const end = Math.min(start + visibleCount, total);
2200
+ this.beforePadding = start * this.itemHeight;
2201
+ this.afterPadding = Math.max(total - end, 0) * this.itemHeight;
2202
+ this.visible = this.filtered.slice(start, end);
2203
+ }
2204
+ focusSibling(direction) {
2205
+ const buttons = this.scroller?.nativeElement.querySelectorAll('button.list-group-item');
2206
+ if (!buttons || buttons.length === 0)
2207
+ return;
2208
+ const active = document.activeElement;
2209
+ const index = Array.prototype.indexOf.call(buttons, active);
2210
+ const next = buttons[index + direction] || buttons[(index + direction + buttons.length) % buttons.length];
2211
+ next?.focus();
2212
+ }
2213
+ validateItem(item) {
2214
+ if (this.itemTemplate) {
2215
+ if (item.id == null || (item.value == null && item.value !== 0)) {
2216
+ throw new Error('Custom templates require items to provide both id and value.');
2217
+ }
2218
+ }
2219
+ }
2220
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbTypeaheadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2221
+ 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", i18n: "i18n" }, outputs: { selectedItems: "selectedItems", selectionChange: "selectionChange", onChange: "onChange", onScrollEvent: "onScrollEvent" }, queries: [{ propertyName: "itemTemplate", first: true, predicate: TemplateRef, descendants: true }], viewQueries: [{ propertyName: "scroller", first: true, predicate: ["scroller"], descendants: true, static: true }, { propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true, static: true }, { propertyName: "itemButtons", predicate: ["itemButton"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
2222
+ <div class="mb-2">
2223
+ <input
2224
+ #inputEl
2225
+ type="text"
2226
+ class="form-control"
2227
+ [attr.placeholder]="i18n?.placeholder || 'Type to search'"
2228
+ [ngModel]="query"
2229
+ (ngModelChange)="onInput($event)"
2230
+ (keydown)="onKeydown($event)"
2231
+ aria-autocomplete="list"
2232
+ role="combobox"
2233
+ [attr.aria-expanded]="filtered.length > 0"
2234
+ />
2235
+ </div>
2236
+
2237
+ <div
2238
+ #scroller
2239
+ class="border rounded list-group"
2240
+ style="max-height: 240px; overflow: auto;"
2241
+ role="listbox"
2242
+ (scroll)="onScroll($event)"
2243
+ >
2244
+ <div [style.height.px]="beforePadding"></div>
2245
+ <button
2246
+ *ngFor="let item of visible; trackBy: trackById"
2247
+ type="button"
2248
+ class="list-group-item list-group-item-action d-flex align-items-center justify-content-between"
2249
+ [class.active]="isSelected(item)"
2250
+ [attr.aria-selected]="isSelected(item)"
2251
+ (click)="selectItem(item)"
2252
+ [disabled]="item.disabled"
2253
+ >
2254
+ <ng-container *ngIf="itemTemplate; else defaultTpl" [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item }"></ng-container>
2255
+ <ng-template #defaultTpl>
2256
+ <span>{{ item.label }}</span>
2257
+ <span *ngIf="multiSelect && isSelected(item)" class="badge bg-secondary">✓</span>
2258
+ </ng-template>
2259
+ </button>
2260
+ <div [style.height.px]="afterPadding"></div>
2261
+
2262
+ <div *ngIf="!filtered.length" class="list-group-item text-muted text-center">
2263
+ {{ i18n?.noResults || 'No results' }}
2264
+ </div>
2265
+ </div>
2266
+
2267
+ <div *ngIf="multiSelect && selected.length" class="mt-2 small text-muted">
2268
+ {{ selectionSummary }}
2269
+ <button type="button" class="btn btn-link btn-sm ps-1" (click)="clearSelection()">
2270
+ {{ i18n?.clearSelection || 'Clear' }}
2271
+ </button>
2272
+ </div>
2273
+ `, 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"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { 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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2274
+ }
2275
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbTypeaheadComponent, decorators: [{
2276
+ type: Component,
2277
+ args: [{
2278
+ selector: 'ngb-typeahead',
2279
+ standalone: true,
2280
+ imports: [CommonModule, FormsModule],
2281
+ changeDetection: ChangeDetectionStrategy.OnPush,
2282
+ template: `
2283
+ <div class="mb-2">
2284
+ <input
2285
+ #inputEl
2286
+ type="text"
2287
+ class="form-control"
2288
+ [attr.placeholder]="i18n?.placeholder || 'Type to search'"
2289
+ [ngModel]="query"
2290
+ (ngModelChange)="onInput($event)"
2291
+ (keydown)="onKeydown($event)"
2292
+ aria-autocomplete="list"
2293
+ role="combobox"
2294
+ [attr.aria-expanded]="filtered.length > 0"
2295
+ />
2296
+ </div>
2297
+
2298
+ <div
2299
+ #scroller
2300
+ class="border rounded list-group"
2301
+ style="max-height: 240px; overflow: auto;"
2302
+ role="listbox"
2303
+ (scroll)="onScroll($event)"
2304
+ >
2305
+ <div [style.height.px]="beforePadding"></div>
2306
+ <button
2307
+ *ngFor="let item of visible; trackBy: trackById"
2308
+ type="button"
2309
+ class="list-group-item list-group-item-action d-flex align-items-center justify-content-between"
2310
+ [class.active]="isSelected(item)"
2311
+ [attr.aria-selected]="isSelected(item)"
2312
+ (click)="selectItem(item)"
2313
+ [disabled]="item.disabled"
2314
+ >
2315
+ <ng-container *ngIf="itemTemplate; else defaultTpl" [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item }"></ng-container>
2316
+ <ng-template #defaultTpl>
2317
+ <span>{{ item.label }}</span>
2318
+ <span *ngIf="multiSelect && isSelected(item)" class="badge bg-secondary">✓</span>
2319
+ </ng-template>
2320
+ </button>
2321
+ <div [style.height.px]="afterPadding"></div>
2322
+
2323
+ <div *ngIf="!filtered.length" class="list-group-item text-muted text-center">
2324
+ {{ i18n?.noResults || 'No results' }}
2325
+ </div>
2326
+ </div>
2327
+
2328
+ <div *ngIf="multiSelect && selected.length" class="mt-2 small text-muted">
2329
+ {{ selectionSummary }}
2330
+ <button type="button" class="btn btn-link btn-sm ps-1" (click)="clearSelection()">
2331
+ {{ i18n?.clearSelection || 'Clear' }}
2332
+ </button>
2333
+ </div>
2334
+ `,
2335
+ }]
2336
+ }], propDecorators: { data: [{
2337
+ type: Input
2338
+ }], debounceTime: [{
2339
+ type: Input
2340
+ }], characterTyped: [{
2341
+ type: Input
2342
+ }], limit: [{
2343
+ type: Input
2344
+ }], selectExact: [{
2345
+ type: Input
2346
+ }], multiSelect: [{
2347
+ type: Input
2348
+ }], i18n: [{
2349
+ type: Input
2350
+ }], itemTemplate: [{
2351
+ type: ContentChild,
2352
+ args: [TemplateRef]
2353
+ }], selectedItems: [{
2354
+ type: Output
2355
+ }], selectionChange: [{
2356
+ type: Output
2357
+ }], onChange: [{
2358
+ type: Output
2359
+ }], onScrollEvent: [{
2360
+ type: Output
2361
+ }], scroller: [{
2362
+ type: ViewChild,
2363
+ args: ['scroller', { static: true }]
2364
+ }], inputEl: [{
2365
+ type: ViewChild,
2366
+ args: ['inputEl', { static: true }]
2367
+ }], itemButtons: [{
2368
+ type: ViewChildren,
2369
+ args: ['itemButton']
2370
+ }] } });
2371
+
2372
+ class NgbTreeComponent {
2373
+ nodes = [];
2374
+ type = 'text';
2375
+ showCheckbox = false;
2376
+ i18n;
2377
+ expand = new EventEmitter();
2378
+ collapse = new EventEmitter();
2379
+ expandAll = new EventEmitter();
2380
+ collapseAll = new EventEmitter();
2381
+ selectionChange = new EventEmitter();
2382
+ nodeButtons;
2383
+ trackById = (_, node) => node.id;
2384
+ hasChildren(node) {
2385
+ return Array.isArray(node.children) && node.children.length > 0;
2386
+ }
2387
+ toggleNode(node) {
2388
+ if (!this.hasChildren(node))
2389
+ return;
2390
+ node.expanded = !node.expanded;
2391
+ node.expanded ? this.expand.emit(node) : this.collapse.emit(node);
2392
+ }
2393
+ toggleSelection(node, value) {
2394
+ node.selected = value;
2395
+ this.selectionChange.emit(this.collectSelection(this.nodes));
2396
+ }
2397
+ expandAllNodes() {
2398
+ this.setExpanded(this.nodes, true);
2399
+ this.expandAll.emit();
2400
+ }
2401
+ collapseAllNodes() {
2402
+ this.setExpanded(this.nodes, false);
2403
+ this.collapseAll.emit();
2404
+ }
2405
+ onNodeKeydown(event, node) {
2406
+ const key = event.key;
2407
+ const target = event.target;
2408
+ if (key === 'Enter' || key === ' ') {
2409
+ event.preventDefault();
2410
+ this.toggleNode(node);
2411
+ }
2412
+ else if (key === 'ArrowRight') {
2413
+ if (this.hasChildren(node) && !node.expanded) {
2414
+ event.preventDefault();
2415
+ node.expanded = true;
2416
+ this.expand.emit(node);
2417
+ }
2418
+ }
2419
+ else if (key === 'ArrowLeft') {
2420
+ if (this.hasChildren(node) && node.expanded) {
2421
+ event.preventDefault();
2422
+ node.expanded = false;
2423
+ this.collapse.emit(node);
2424
+ }
2425
+ }
2426
+ else if (key === 'ArrowDown' || key === 'ArrowUp') {
2427
+ event.preventDefault();
2428
+ this.focusSibling(target, key === 'ArrowDown' ? 1 : -1);
2429
+ }
2430
+ }
2431
+ focusSibling(current, direction) {
2432
+ const buttons = this.nodeButtons?.toArray().map((b) => b.nativeElement) || [];
2433
+ const idx = buttons.indexOf(current);
2434
+ if (idx === -1)
2435
+ return;
2436
+ const next = buttons[idx + direction];
2437
+ next?.focus();
2438
+ }
2439
+ setExpanded(nodes, expanded) {
2440
+ for (const n of nodes) {
2441
+ if (this.hasChildren(n)) {
2442
+ n.expanded = expanded;
2443
+ this.setExpanded(n.children || [], expanded);
2444
+ }
2445
+ }
2446
+ }
2447
+ collectSelection(nodes, acc = []) {
2448
+ for (const n of nodes) {
2449
+ if (n.selected)
2450
+ acc.push(n);
2451
+ if (this.hasChildren(n))
2452
+ this.collectSelection(n.children, acc);
2453
+ }
2454
+ return acc;
2455
+ }
2456
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2457
+ 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" }, outputs: { expand: "expand", collapse: "collapse", expandAll: "expandAll", collapseAll: "collapseAll", selectionChange: "selectionChange" }, viewQueries: [{ propertyName: "nodeButtons", predicate: ["nodeBtn"], descendants: true, read: ElementRef }], ngImport: i0, template: `
2458
+ <div class="d-flex gap-2 mb-2">
2459
+ <button type="button" class="btn btn-sm btn-outline-secondary" (click)="expandAllNodes()" [attr.aria-label]="i18n?.expandAll || 'Expand all'">
2460
+ {{ i18n?.expandAll || 'Expand all' }}
2461
+ </button>
2462
+ <button type="button" class="btn btn-sm btn-outline-secondary" (click)="collapseAllNodes()" [attr.aria-label]="i18n?.collapseAll || 'Collapse all'">
2463
+ {{ i18n?.collapseAll || 'Collapse all' }}
2464
+ </button>
2465
+ </div>
2466
+
2467
+ <ul class="list-unstyled mb-0" role="tree" [attr.aria-label]="i18n?.treeLabel || 'Tree'">
2468
+ <ng-container *ngFor="let node of nodes; let i = index; trackBy: trackById">
2469
+ <ng-container *ngTemplateOutlet="renderNode; context: { $implicit: node, level: 1, index: i }"></ng-container>
2470
+ </ng-container>
2471
+ </ul>
2472
+
2473
+ <ng-template #renderNode let-node let-level="level" let-index="index">
2474
+ <li
2475
+ class="d-flex align-items-start"
2476
+ role="treeitem"
2477
+ [attr.aria-expanded]="hasChildren(node) ? !!node.expanded : null"
2478
+ [attr.aria-level]="level"
2479
+ >
2480
+ <button
2481
+ #nodeBtn
2482
+ type="button"
2483
+ class="btn btn-sm btn-link px-1"
2484
+ [disabled]="!hasChildren(node)"
2485
+ (click)="toggleNode(node)"
2486
+ (keydown)="onNodeKeydown($event, node)"
2487
+ [attr.aria-label]="(node.expanded ? i18n?.collapse || 'Collapse' : i18n?.expand || 'Expand') + ' ' + node.label"
2488
+ >
2489
+ <span *ngIf="hasChildren(node)">
2490
+ <span *ngIf="type === 'json'">{{ node.expanded ? '-' : '+' }}</span>
2491
+ <span *ngIf="type !== 'json'">{{ node.expanded ? '▾' : '▸' }}</span>
2492
+ </span>
2493
+ <span *ngIf="!hasChildren(node)" class="text-muted">•</span>
2494
+ </button>
2495
+
2496
+ <label class="form-check d-inline-flex align-items-center gap-2 flex-grow-1 mb-0">
2497
+ <input
2498
+ *ngIf="showCheckbox"
2499
+ type="checkbox"
2500
+ class="form-check-input"
2501
+ [checked]="!!node.selected"
2502
+ [disabled]="node.disabled"
2503
+ (change)="toggleSelection(node, $event.target.checked)"
2504
+ [attr.aria-label]="(i18n?.select || 'Select') + ' ' + node.label"
2505
+ />
2506
+ <span class="form-check-label">{{ node.label }}</span>
2507
+ </label>
2508
+ </li>
2509
+
2510
+ <ul
2511
+ *ngIf="node.expanded && hasChildren(node)"
2512
+ class="list-unstyled ms-4"
2513
+ role="group"
2514
+ >
2515
+ <ng-container *ngFor="let child of node.children; let ci = index; trackBy: trackById">
2516
+ <ng-container *ngTemplateOutlet="renderNode; context: { $implicit: child, level: level + 1, index: ci }"></ng-container>
2517
+ </ng-container>
2518
+ </ul>
2519
+ </ng-template>
2520
+ `, 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"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2521
+ }
2522
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgbTreeComponent, decorators: [{
2523
+ type: Component,
2524
+ args: [{
2525
+ selector: 'ngb-tree',
2526
+ standalone: true,
2527
+ imports: [CommonModule],
2528
+ changeDetection: ChangeDetectionStrategy.OnPush,
2529
+ template: `
2530
+ <div class="d-flex gap-2 mb-2">
2531
+ <button type="button" class="btn btn-sm btn-outline-secondary" (click)="expandAllNodes()" [attr.aria-label]="i18n?.expandAll || 'Expand all'">
2532
+ {{ i18n?.expandAll || 'Expand all' }}
2533
+ </button>
2534
+ <button type="button" class="btn btn-sm btn-outline-secondary" (click)="collapseAllNodes()" [attr.aria-label]="i18n?.collapseAll || 'Collapse all'">
2535
+ {{ i18n?.collapseAll || 'Collapse all' }}
2536
+ </button>
2537
+ </div>
2538
+
2539
+ <ul class="list-unstyled mb-0" role="tree" [attr.aria-label]="i18n?.treeLabel || 'Tree'">
2540
+ <ng-container *ngFor="let node of nodes; let i = index; trackBy: trackById">
2541
+ <ng-container *ngTemplateOutlet="renderNode; context: { $implicit: node, level: 1, index: i }"></ng-container>
2542
+ </ng-container>
2543
+ </ul>
2544
+
2545
+ <ng-template #renderNode let-node let-level="level" let-index="index">
2546
+ <li
2547
+ class="d-flex align-items-start"
2548
+ role="treeitem"
2549
+ [attr.aria-expanded]="hasChildren(node) ? !!node.expanded : null"
2550
+ [attr.aria-level]="level"
2551
+ >
2552
+ <button
2553
+ #nodeBtn
2554
+ type="button"
2555
+ class="btn btn-sm btn-link px-1"
2556
+ [disabled]="!hasChildren(node)"
2557
+ (click)="toggleNode(node)"
2558
+ (keydown)="onNodeKeydown($event, node)"
2559
+ [attr.aria-label]="(node.expanded ? i18n?.collapse || 'Collapse' : i18n?.expand || 'Expand') + ' ' + node.label"
2560
+ >
2561
+ <span *ngIf="hasChildren(node)">
2562
+ <span *ngIf="type === 'json'">{{ node.expanded ? '-' : '+' }}</span>
2563
+ <span *ngIf="type !== 'json'">{{ node.expanded ? '▾' : '▸' }}</span>
2564
+ </span>
2565
+ <span *ngIf="!hasChildren(node)" class="text-muted">•</span>
2566
+ </button>
2567
+
2568
+ <label class="form-check d-inline-flex align-items-center gap-2 flex-grow-1 mb-0">
2569
+ <input
2570
+ *ngIf="showCheckbox"
2571
+ type="checkbox"
2572
+ class="form-check-input"
2573
+ [checked]="!!node.selected"
2574
+ [disabled]="node.disabled"
2575
+ (change)="toggleSelection(node, $event.target.checked)"
2576
+ [attr.aria-label]="(i18n?.select || 'Select') + ' ' + node.label"
2577
+ />
2578
+ <span class="form-check-label">{{ node.label }}</span>
2579
+ </label>
2580
+ </li>
2581
+
2582
+ <ul
2583
+ *ngIf="node.expanded && hasChildren(node)"
2584
+ class="list-unstyled ms-4"
2585
+ role="group"
2586
+ >
2587
+ <ng-container *ngFor="let child of node.children; let ci = index; trackBy: trackById">
2588
+ <ng-container *ngTemplateOutlet="renderNode; context: { $implicit: child, level: level + 1, index: ci }"></ng-container>
2589
+ </ng-container>
2590
+ </ul>
2591
+ </ng-template>
2592
+ `,
2593
+ }]
2594
+ }], propDecorators: { nodes: [{
2595
+ type: Input
2596
+ }], type: [{
2597
+ type: Input
2598
+ }], showCheckbox: [{
2599
+ type: Input
2600
+ }], i18n: [{
2601
+ type: Input
2602
+ }], expand: [{
2603
+ type: Output
2604
+ }], collapse: [{
2605
+ type: Output
2606
+ }], expandAll: [{
2607
+ type: Output
2608
+ }], collapseAll: [{
2609
+ type: Output
2610
+ }], selectionChange: [{
2611
+ type: Output
2612
+ }], nodeButtons: [{
2613
+ type: ViewChildren,
2614
+ args: ['nodeBtn', { read: ElementRef }]
2615
+ }] } });
2616
+
2617
+ /// <reference path="./types/optional-peer-deps.d.ts" />
2618
+
2619
+ /**
2620
+ * Generated bundle index. Do not edit.
2621
+ */
2622
+
2623
+ export { DATAGRID_TEMPLATE_DIRECTIVES, DND_I18N, DND_LIVE_ANNOUNCE, Datagrid, ExcelExportAdapter, ExportButtonDirective, JsPdfAdapter, NgbCellTemplate, NgbDndItemDirective, NgbDndListDirective, NgbDndState, NgbEditorTemplate, NgbExportService, NgbFilterTemplate, NgbGlobalFilterTemplate, NgbLiveAnnouncer, NgbPaginationComponent, NgbRowDetailTemplate, NgbSplitterComponent, NgbStepLabelDirective, NgbStepperComponent, NgbTreeComponent, NgbTypeaheadComponent, PdfExportAdapter, XlsxAdapter, defaultDndI18n };
2624
+ //# sourceMappingURL=angular-bootstrap-ngbootstrap.mjs.map