@tociva/tailng-ui 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,22 +1,807 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports$1) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports$1, p)) __createBinding(exports$1, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./lib"), exports);
1
+ import { NgTemplateOutlet, NgStyle } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { InjectionToken, Directive, input, inject, effect, ContentChild, Component, signal, EventEmitter, computed, Output, ContentChildren, HostListener, HostBinding, ElementRef } from '@angular/core';
4
+ import { TngOverlayRef, TngConnectedOverlay, TngOverlayPanel } from '@tociva/tailng-ui/popups-overlays';
5
+
6
+ const TNG_TABLE = new InjectionToken('TNG_TABLE');
7
+
8
+ class TngCellDef {
9
+ tpl;
10
+ constructor(tpl) {
11
+ this.tpl = tpl;
12
+ }
13
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngCellDef, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
14
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: TngCellDef, isStandalone: true, selector: "ng-template[tngCell]", ngImport: i0 });
15
+ }
16
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngCellDef, decorators: [{
17
+ type: Directive,
18
+ args: [{
19
+ selector: 'ng-template[tngCell]',
20
+ standalone: true,
21
+ }]
22
+ }], ctorParameters: () => [{ type: i0.TemplateRef }] });
23
+
24
+ class TngHeaderDef {
25
+ tpl;
26
+ constructor(tpl) {
27
+ this.tpl = tpl;
28
+ }
29
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngHeaderDef, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
30
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: TngHeaderDef, isStandalone: true, selector: "ng-template[tngHeader]", ngImport: i0 });
31
+ }
32
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngHeaderDef, decorators: [{
33
+ type: Directive,
34
+ args: [{
35
+ selector: 'ng-template[tngHeader]',
36
+ standalone: true,
37
+ }]
38
+ }], ctorParameters: () => [{ type: i0.TemplateRef }] });
39
+
40
+ class TngCol {
41
+ /** unique column id */
42
+ id = input.required(...(ngDevMode ? [{ debugName: "id" }] : []));
43
+ /** header label (fallback when no tngHeader template is provided) */
44
+ header = input('', ...(ngDevMode ? [{ debugName: "header" }] : []));
45
+ /** optional value resolver */
46
+ value = input(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
47
+ /** width (e.g. '120px', '10rem', '20%') */
48
+ width = input(null, ...(ngDevMode ? [{ debugName: "width" }] : []));
49
+ /** alignment */
50
+ align = input('left', ...(ngDevMode ? [{ debugName: "align" }] : []));
51
+ /** extra CSS classes applied to th/td */
52
+ klass = input(null, ...(ngDevMode ? [{ debugName: "klass" }] : []));
53
+ /** default filter meta (used by tng-filter-panel) */
54
+ filter = input(null, ...(ngDevMode ? [{ debugName: "filter" }] : []));
55
+ table = inject(TNG_TABLE);
56
+ // Projected templates
57
+ cellDef;
58
+ headerDef;
59
+ constructor() {
60
+ // Register column meta for default filter panel + any future features
61
+ effect((onCleanup) => {
62
+ const id = this.id();
63
+ this.table.registerColumn({
64
+ id,
65
+ label: this.resolveHeader(),
66
+ filter: this.filter() ?? undefined,
67
+ });
68
+ onCleanup(() => this.table.unregisterColumn(id));
69
+ });
70
+ }
71
+ resolveHeader() {
72
+ return this.header() || this.id();
73
+ }
74
+ resolveValue(row) {
75
+ const valueFn = this.value();
76
+ return valueFn ? valueFn(row) : row?.[this.id()];
77
+ }
78
+ resolveCellTpl() {
79
+ return this.cellDef?.tpl;
80
+ }
81
+ resolveHeaderTpl() {
82
+ return this.headerDef?.tpl;
83
+ }
84
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngCol, deps: [], target: i0.ɵɵFactoryTarget.Component });
85
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: TngCol, isStandalone: true, selector: "tng-col", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, header: { classPropertyName: "header", publicName: "header", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, klass: { classPropertyName: "klass", publicName: "klass", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "cellDef", first: true, predicate: TngCellDef, descendants: true }, { propertyName: "headerDef", first: true, predicate: TngHeaderDef, descendants: true }], ngImport: i0, template: '', isInline: true });
86
+ }
87
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngCol, decorators: [{
88
+ type: Component,
89
+ args: [{
90
+ selector: 'tng-col',
91
+ standalone: true,
92
+ template: '',
93
+ }]
94
+ }], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], header: [{ type: i0.Input, args: [{ isSignal: true, alias: "header", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], klass: [{ type: i0.Input, args: [{ isSignal: true, alias: "klass", required: false }] }], filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }], cellDef: [{
95
+ type: ContentChild,
96
+ args: [TngCellDef]
97
+ }], headerDef: [{
98
+ type: ContentChild,
99
+ args: [TngHeaderDef]
100
+ }] } });
101
+
102
+ // core/features/sort.feature.ts
103
+ class TngTableSortFeature {
104
+ sort = signal({ active: '', direction: '' }, ...(ngDevMode ? [{ debugName: "sort" }] : []));
105
+ toggleSort(active) {
106
+ const cur = this.sort();
107
+ const same = cur.active === active;
108
+ const nextDir = !same ? 'asc' : cur.direction === 'asc' ? 'desc' : cur.direction === 'desc' ? '' : 'asc';
109
+ this.sort.set({
110
+ active: nextDir ? active : '',
111
+ direction: nextDir,
112
+ });
113
+ }
114
+ setSort(sort) {
115
+ this.sort.set(sort);
116
+ }
117
+ clearSort() {
118
+ this.sort.set({ active: '', direction: '' });
119
+ }
120
+ directionFor(colId) {
121
+ const s = this.sort();
122
+ return s.active === colId ? s.direction : '';
123
+ }
124
+ isSorted(colId) {
125
+ return this.directionFor(colId) !== '';
126
+ }
127
+ }
128
+
129
+ class TngSortController {
130
+ featureId = 'sort';
131
+ feature = new TngTableSortFeature();
132
+ sort = this.feature.sort;
133
+ toggleSort(active) {
134
+ this.feature.toggleSort(active);
135
+ }
136
+ setSort(sort) {
137
+ this.feature.setSort(sort);
138
+ }
139
+ clearSort() {
140
+ this.feature.clearSort();
141
+ }
142
+ directionFor(colId) {
143
+ return this.feature.directionFor(colId);
144
+ }
145
+ isSorted(colId) {
146
+ return this.feature.isSorted(colId);
147
+ }
148
+ }
149
+
150
+ class TngTableFilterFeature {
151
+ filters = signal({}, ...(ngDevMode ? [{ debugName: "filters" }] : []));
152
+ /** Active filter UI column id ('' means closed) */
153
+ openFilterColId = signal('', ...(ngDevMode ? [{ debugName: "openFilterColId" }] : []));
154
+ /** Anchor element for positioning the overlay */
155
+ anchorEl = signal(null, ...(ngDevMode ? [{ debugName: "anchorEl" }] : []));
156
+ openFilter(colId, anchorEl) {
157
+ this.openFilterColId.set(colId);
158
+ this.anchorEl.set(anchorEl ?? null);
159
+ }
160
+ closeFilter() {
161
+ this.openFilterColId.set('');
162
+ this.anchorEl.set(null);
163
+ }
164
+ toggleFilter(colId, anchorEl) {
165
+ if (this.openFilterColId() === colId) {
166
+ this.closeFilter();
167
+ return;
168
+ }
169
+ this.openFilter(colId, anchorEl);
170
+ }
171
+ isFilterOpenFor(colId) {
172
+ return this.openFilterColId() === colId;
173
+ }
174
+ setFilter(colId, value) {
175
+ this.filters.update((cur) => ({ ...cur, [colId]: value }));
176
+ }
177
+ clearFilter(colId) {
178
+ this.filters.update((cur) => {
179
+ if (!(colId in cur))
180
+ return cur;
181
+ const next = { ...cur };
182
+ delete next[colId];
183
+ return next;
184
+ });
185
+ if (this.openFilterColId() === colId)
186
+ this.closeFilter();
187
+ }
188
+ clearAllFilters() {
189
+ this.filters.set({});
190
+ this.closeFilter();
191
+ }
192
+ filterValueFor(colId) {
193
+ return this.filters()[colId];
194
+ }
195
+ isFiltered(colId) {
196
+ return colId in this.filters();
197
+ }
198
+ }
199
+
200
+ class TngFilterController {
201
+ featureId = 'filter';
202
+ feature = new TngTableFilterFeature();
203
+ filters = this.feature.filters;
204
+ openFilterColId = this.feature.openFilterColId;
205
+ filterAnchorEl = this.feature.anchorEl;
206
+ _filterPanelKlass = signal('', ...(ngDevMode ? [{ debugName: "_filterPanelKlass" }] : []));
207
+ setFilterPanelKlass(v) {
208
+ this._filterPanelKlass.set(v);
209
+ }
210
+ filterPanelKlass() {
211
+ return this._filterPanelKlass();
212
+ }
213
+ openFilter(colId, anchorEl) {
214
+ this.feature.openFilter(colId, anchorEl);
215
+ }
216
+ closeFilter() {
217
+ this.feature.closeFilter();
218
+ }
219
+ toggleFilter(colId, anchorEl) {
220
+ this.feature.toggleFilter(colId, anchorEl);
221
+ }
222
+ isFilterOpenFor(colId) {
223
+ return this.feature.isFilterOpenFor(colId);
224
+ }
225
+ setFilter(colId, value) {
226
+ this.feature.setFilter(colId, value);
227
+ }
228
+ clearFilter(colId) {
229
+ this.feature.clearFilter(colId);
230
+ }
231
+ clearAllFilters() {
232
+ this.feature.clearAllFilters();
233
+ }
234
+ filterValueFor(colId) {
235
+ return this.feature.filterValueFor(colId);
236
+ }
237
+ isFiltered(colId) {
238
+ return this.feature.isFiltered(colId);
239
+ }
240
+ setFilters(filters) {
241
+ this.filters.set(filters);
242
+ }
243
+ }
244
+
245
+ class TngColumnMetaController {
246
+ featureId = 'column-meta';
247
+ colMeta = signal({}, ...(ngDevMode ? [{ debugName: "colMeta" }] : []));
248
+ registerColumn(meta) {
249
+ this.colMeta.update((cur) => ({ ...cur, [meta.id]: meta }));
250
+ }
251
+ unregisterColumn(colId) {
252
+ this.colMeta.update((cur) => {
253
+ if (!(colId in cur))
254
+ return cur;
255
+ const next = { ...cur };
256
+ delete next[colId];
257
+ return next;
258
+ });
259
+ }
260
+ metaFor(colId) {
261
+ return this.colMeta()[colId];
262
+ }
263
+ }
264
+
265
+ class TngTableController {
266
+ _featureId = 'table';
267
+ // Keep references to run lifecycle hooks if you need them later
268
+ features = [];
269
+ sortCtrl = new TngSortController();
270
+ filterCtrl = new TngFilterController();
271
+ colMetaCtrl = new TngColumnMetaController();
272
+ constructor() {
273
+ this.features = [this.sortCtrl, this.filterCtrl, this.colMetaCtrl];
274
+ this.features.forEach((f) => f.onInit?.());
275
+ }
276
+ destroy() {
277
+ this.features.forEach((f) => f.onDestroy?.());
278
+ }
279
+ // Optional: re-expose a flat API (so existing code doesn't change)
280
+ sort = this.sortCtrl.sort;
281
+ toggleSort = this.sortCtrl.toggleSort.bind(this.sortCtrl);
282
+ setSort = this.sortCtrl.setSort.bind(this.sortCtrl);
283
+ clearSort = this.sortCtrl.clearSort.bind(this.sortCtrl);
284
+ directionFor = this.sortCtrl.directionFor.bind(this.sortCtrl);
285
+ isSorted = this.sortCtrl.isSorted.bind(this.sortCtrl);
286
+ filters = this.filterCtrl.filters;
287
+ openFilterColId = this.filterCtrl.openFilterColId;
288
+ filterAnchorEl = this.filterCtrl.filterAnchorEl;
289
+ setFilterPanelKlass = this.filterCtrl.setFilterPanelKlass.bind(this.filterCtrl);
290
+ filterPanelKlass = this.filterCtrl.filterPanelKlass.bind(this.filterCtrl);
291
+ openFilter = this.filterCtrl.openFilter.bind(this.filterCtrl);
292
+ closeFilter = this.filterCtrl.closeFilter.bind(this.filterCtrl);
293
+ toggleFilter = this.filterCtrl.toggleFilter.bind(this.filterCtrl);
294
+ isFilterOpenFor = this.filterCtrl.isFilterOpenFor.bind(this.filterCtrl);
295
+ setFilter = this.filterCtrl.setFilter.bind(this.filterCtrl);
296
+ clearFilter = this.filterCtrl.clearFilter.bind(this.filterCtrl);
297
+ clearAllFilters = this.filterCtrl.clearAllFilters.bind(this.filterCtrl);
298
+ filterValueFor = this.filterCtrl.filterValueFor.bind(this.filterCtrl);
299
+ isFiltered = this.filterCtrl.isFiltered.bind(this.filterCtrl);
300
+ setFilters = this.filterCtrl.setFilters.bind(this.filterCtrl);
301
+ registerColumn = this.colMetaCtrl.registerColumn.bind(this.colMetaCtrl);
302
+ unregisterColumn = this.colMetaCtrl.unregisterColumn.bind(this.colMetaCtrl);
303
+ metaFor = this.colMetaCtrl.metaFor.bind(this.colMetaCtrl);
304
+ }
305
+
306
+ class TngTable {
307
+ colDefs;
308
+ controller = inject(TNG_TABLE);
309
+ /** Emits on any sort change (both client & server modes). */
310
+ sortChange = new EventEmitter();
311
+ /* =====================
312
+ * Inputs
313
+ * ===================== */
314
+ rows = input([], ...(ngDevMode ? [{ debugName: "rows" }] : []));
315
+ rowKey = input(null, ...(ngDevMode ? [{ debugName: "rowKey" }] : []));
316
+ /** Default: client (static) sort */
317
+ sortMode = input('client', ...(ngDevMode ? [{ debugName: "sortMode" }] : []));
318
+ /** styling */
319
+ tableKlass = input('w-full text-sm', ...(ngDevMode ? [{ debugName: "tableKlass" }] : []));
320
+ theadKlass = input('bg-alternate-background', ...(ngDevMode ? [{ debugName: "theadKlass" }] : []));
321
+ thKlass = input('px-3 py-2 text-left font-semibold border-b border-border', ...(ngDevMode ? [{ debugName: "thKlass" }] : []));
322
+ tdKlass = input('px-3 py-2 border-b border-border align-middle', ...(ngDevMode ? [{ debugName: "tdKlass" }] : []));
323
+ tbodyKlass = input('bg-bg', ...(ngDevMode ? [{ debugName: "tbodyKlass" }] : []));
324
+ /** empty */
325
+ emptyText = input('No data', ...(ngDevMode ? [{ debugName: "emptyText" }] : []));
326
+ /* =====================
327
+ * Internal projected columns signal
328
+ * ===================== */
329
+ projectedCols = signal([], ...(ngDevMode ? [{ debugName: "projectedCols" }] : []));
330
+ constructor() {
331
+ // Emit sortChange whenever controller.sort changes (skip initial emission)
332
+ let first = true;
333
+ effect(() => {
334
+ const s = this.controller.sort();
335
+ if (first) {
336
+ first = false;
337
+ return;
338
+ }
339
+ this.sortChange.emit(s);
340
+ });
341
+ }
342
+ ngAfterContentInit() {
343
+ const sync = () => this.projectedCols.set(this.colDefs?.toArray() ?? []);
344
+ sync();
345
+ this.colDefs.changes.subscribe(sync);
346
+ }
347
+ /* =====================
348
+ * Columns
349
+ * ===================== */
350
+ columns = computed(() => this.projectedCols().map((c) => ({
351
+ id: c.id(),
352
+ header: c.resolveHeader(),
353
+ align: c.align(),
354
+ width: c.width() ?? undefined,
355
+ klass: c.klass() ?? undefined,
356
+ value: (row) => c.resolveValue(row),
357
+ headerTpl: c.resolveHeaderTpl(),
358
+ cellTpl: c.resolveCellTpl(),
359
+ })), ...(ngDevMode ? [{ debugName: "columns" }] : []));
360
+ /* =====================
361
+ * Rows view (client sorting)
362
+ * ===================== */
363
+ viewRows = computed(() => {
364
+ const rows = this.rows();
365
+ const mode = this.sortMode();
366
+ const sort = this.controller.sort();
367
+ if (mode !== 'client')
368
+ return rows;
369
+ if (!sort.active || !sort.direction)
370
+ return rows;
371
+ const dir = sort.direction === 'asc' ? 1 : -1;
372
+ const colId = sort.active;
373
+ // stable sort
374
+ return rows
375
+ .map((row, i) => ({ row, i, key: this.sortValue(row, colId) }))
376
+ .sort((a, b) => {
377
+ const c = this.compare(a.key, b.key);
378
+ return c !== 0 ? c * dir : a.i - b.i;
379
+ })
380
+ .map((x) => x.row);
381
+ }, ...(ngDevMode ? [{ debugName: "viewRows" }] : []));
382
+ hasRows = computed(() => (this.viewRows()?.length ?? 0) > 0, ...(ngDevMode ? [{ debugName: "hasRows" }] : []));
383
+ trackRow = (index, row) => {
384
+ const key = this.rowKey();
385
+ return key ? row?.[key] ?? index : index;
386
+ };
387
+ /* =====================
388
+ * Rendering helpers
389
+ * ===================== */
390
+ alignClass(align) {
391
+ switch (align) {
392
+ case 'right':
393
+ return 'text-right';
394
+ case 'center':
395
+ return 'text-center';
396
+ default:
397
+ return 'text-left';
398
+ }
399
+ }
400
+ styleWidth(col) {
401
+ return col.width ? { width: col.width } : null;
402
+ }
403
+ cellCtx(row, rowIndex, col) {
404
+ const value = col.value ? col.value(row) : row?.[col.id];
405
+ return { $implicit: row, row, rowIndex, colId: col.id, value };
406
+ }
407
+ headerCtx(col) {
408
+ return { colId: col.id, header: col.header };
409
+ }
410
+ /* =====================
411
+ * Sort helpers
412
+ * ===================== */
413
+ sortValue(row, colId) {
414
+ const col = this.columns().find((c) => c.id === colId);
415
+ if (col?.value)
416
+ return col.value(row);
417
+ return row?.[colId];
418
+ }
419
+ compare(a, b) {
420
+ // null/undefined first
421
+ if (a == null && b == null)
422
+ return 0;
423
+ if (a == null)
424
+ return -1;
425
+ if (b == null)
426
+ return 1;
427
+ // numbers (avoid booleans)
428
+ if (typeof a === 'number' && typeof b === 'number') {
429
+ return a === b ? 0 : a < b ? -1 : 1;
430
+ }
431
+ const an = typeof a === 'string' ? Number(a) : NaN;
432
+ const bn = typeof b === 'string' ? Number(b) : NaN;
433
+ if (Number.isFinite(an) && Number.isFinite(bn)) {
434
+ return an === bn ? 0 : an < bn ? -1 : 1;
435
+ }
436
+ // ISO-ish dates (string)
437
+ if (typeof a === 'string' && typeof b === 'string') {
438
+ const at = Date.parse(a);
439
+ const bt = Date.parse(b);
440
+ if (!Number.isNaN(at) && !Number.isNaN(bt)) {
441
+ return at === bt ? 0 : at < bt ? -1 : 1;
442
+ }
443
+ }
444
+ // fallback: locale string
445
+ return String(a).localeCompare(String(b));
446
+ }
447
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
448
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TngTable, isStandalone: true, selector: "tng-table", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, rowKey: { classPropertyName: "rowKey", publicName: "rowKey", isSignal: true, isRequired: false, transformFunction: null }, sortMode: { classPropertyName: "sortMode", publicName: "sortMode", isSignal: true, isRequired: false, transformFunction: null }, tableKlass: { classPropertyName: "tableKlass", publicName: "tableKlass", isSignal: true, isRequired: false, transformFunction: null }, theadKlass: { classPropertyName: "theadKlass", publicName: "theadKlass", isSignal: true, isRequired: false, transformFunction: null }, thKlass: { classPropertyName: "thKlass", publicName: "thKlass", isSignal: true, isRequired: false, transformFunction: null }, tdKlass: { classPropertyName: "tdKlass", publicName: "tdKlass", isSignal: true, isRequired: false, transformFunction: null }, tbodyKlass: { classPropertyName: "tbodyKlass", publicName: "tbodyKlass", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sortChange: "sortChange" }, providers: [{ provide: TNG_TABLE, useFactory: () => new TngTableController() }], queries: [{ propertyName: "colDefs", predicate: TngCol }], ngImport: i0, template: "<div class=\"relative\">\n <table [class]=\"tableKlass()\">\n <thead [class]=\"theadKlass()\">\n <tr>\n @for (col of columns(); track col.id) {\n <th\n scope=\"col\"\n [class]=\"(thKlass() + ' ' + alignClass(col.align) + (col.klass ? ' ' + col.klass : '')).trim()\"\n [ngStyle]=\"styleWidth(col)\"\n >\n @if (col.headerTpl) {\n <ng-container\n [ngTemplateOutlet]=\"col.headerTpl\"\n [ngTemplateOutletContext]=\"headerCtx(col)\"\n />\n } @else {\n {{ col.header }}\n }\n </th>\n }\n </tr>\n </thead>\n\n <tbody [class]=\"tbodyKlass()\">\n @if (hasRows()) {\n @for (row of viewRows(); let rowIndex = $index; track row?.[rowKey()] ?? rowIndex) {\n <tr>\n @for (col of columns(); track col.id) {\n <td\n [class]=\"(tdKlass() + ' ' + alignClass(col.align) + (col.klass ? ' ' + col.klass : '')).trim()\"\n >\n @if (col.cellTpl) {\n <ng-container\n [ngTemplateOutlet]=\"col.cellTpl\"\n [ngTemplateOutletContext]=\"cellCtx(row, rowIndex, col)\"\n />\n } @else {\n {{ col.value?.(row) }}\n }\n </td>\n }\n </tr>\n }\n } @else {\n <tr>\n <td [attr.colspan]=\"columns().length || 1\" class=\"px-3 py-8 text-center text-disable\">\n {{ emptyText() }}\n </td>\n </tr>\n }\n </tbody>\n </table>\n\n <!-- Render projected extras like filter panel -->\n <ng-content />\n</div>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
449
+ }
450
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngTable, decorators: [{
451
+ type: Component,
452
+ args: [{ selector: 'tng-table', standalone: true, imports: [NgTemplateOutlet, NgStyle], providers: [{ provide: TNG_TABLE, useFactory: () => new TngTableController() }], template: "<div class=\"relative\">\n <table [class]=\"tableKlass()\">\n <thead [class]=\"theadKlass()\">\n <tr>\n @for (col of columns(); track col.id) {\n <th\n scope=\"col\"\n [class]=\"(thKlass() + ' ' + alignClass(col.align) + (col.klass ? ' ' + col.klass : '')).trim()\"\n [ngStyle]=\"styleWidth(col)\"\n >\n @if (col.headerTpl) {\n <ng-container\n [ngTemplateOutlet]=\"col.headerTpl\"\n [ngTemplateOutletContext]=\"headerCtx(col)\"\n />\n } @else {\n {{ col.header }}\n }\n </th>\n }\n </tr>\n </thead>\n\n <tbody [class]=\"tbodyKlass()\">\n @if (hasRows()) {\n @for (row of viewRows(); let rowIndex = $index; track row?.[rowKey()] ?? rowIndex) {\n <tr>\n @for (col of columns(); track col.id) {\n <td\n [class]=\"(tdKlass() + ' ' + alignClass(col.align) + (col.klass ? ' ' + col.klass : '')).trim()\"\n >\n @if (col.cellTpl) {\n <ng-container\n [ngTemplateOutlet]=\"col.cellTpl\"\n [ngTemplateOutletContext]=\"cellCtx(row, rowIndex, col)\"\n />\n } @else {\n {{ col.value?.(row) }}\n }\n </td>\n }\n </tr>\n }\n } @else {\n <tr>\n <td [attr.colspan]=\"columns().length || 1\" class=\"px-3 py-8 text-center text-disable\">\n {{ emptyText() }}\n </td>\n </tr>\n }\n </tbody>\n </table>\n\n <!-- Render projected extras like filter panel -->\n <ng-content />\n</div>\n" }]
453
+ }], ctorParameters: () => [], propDecorators: { colDefs: [{
454
+ type: ContentChildren,
455
+ args: [TngCol]
456
+ }], sortChange: [{
457
+ type: Output
458
+ }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], rowKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowKey", required: false }] }], sortMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortMode", required: false }] }], tableKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "tableKlass", required: false }] }], theadKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "theadKlass", required: false }] }], thKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "thKlass", required: false }] }], tdKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "tdKlass", required: false }] }], tbodyKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "tbodyKlass", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }] } });
459
+
460
+ class TngSortHeaderDirective {
461
+ colId = input.required(...(ngDevMode ? [{ debugName: "colId" }] : []));
462
+ table = inject(TNG_TABLE);
463
+ direction = computed(() => this.table.directionFor(this.colId()), ...(ngDevMode ? [{ debugName: "direction" }] : []));
464
+ isSorted = computed(() => this.direction() !== '', ...(ngDevMode ? [{ debugName: "isSorted" }] : []));
465
+ // a11y
466
+ role = 'button';
467
+ tabindex = 0;
468
+ get ariaSort() {
469
+ const dir = this.direction();
470
+ if (dir === 'asc')
471
+ return 'ascending';
472
+ if (dir === 'desc')
473
+ return 'descending';
474
+ return 'none';
475
+ }
476
+ onClick() {
477
+ this.table.toggleSort(this.colId());
478
+ }
479
+ onKey(ev) {
480
+ ev.preventDefault();
481
+ this.table.toggleSort(this.colId());
482
+ }
483
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngSortHeaderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
484
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: TngSortHeaderDirective, isStandalone: true, selector: "[tngSortHeader]", inputs: { colId: { classPropertyName: "colId", publicName: "colId", isSignal: true, isRequired: true, transformFunction: null } }, host: { listeners: { "click": "onClick()", "keydown.enter": "onKey($event)", "keydown.space": "onKey($event)" }, properties: { "attr.role": "this.role", "attr.tabindex": "this.tabindex", "attr.aria-sort": "this.ariaSort" } }, ngImport: i0 });
485
+ }
486
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngSortHeaderDirective, decorators: [{
487
+ type: Directive,
488
+ args: [{
489
+ selector: '[tngSortHeader]',
490
+ standalone: true,
491
+ }]
492
+ }], propDecorators: { colId: [{ type: i0.Input, args: [{ isSignal: true, alias: "colId", required: true }] }], role: [{
493
+ type: HostBinding,
494
+ args: ['attr.role']
495
+ }], tabindex: [{
496
+ type: HostBinding,
497
+ args: ['attr.tabindex']
498
+ }], ariaSort: [{
499
+ type: HostBinding,
500
+ args: ['attr.aria-sort']
501
+ }], onClick: [{
502
+ type: HostListener,
503
+ args: ['click']
504
+ }], onKey: [{
505
+ type: HostListener,
506
+ args: ['keydown.enter', ['$event']]
507
+ }, {
508
+ type: HostListener,
509
+ args: ['keydown.space', ['$event']]
510
+ }] } });
511
+
512
+ class TngSortIcon {
513
+ host = inject(TngSortHeaderDirective);
514
+ dir = this.host.direction;
515
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngSortIcon, deps: [], target: i0.ɵɵFactoryTarget.Component });
516
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TngSortIcon, isStandalone: true, selector: "tng-sort-icon", ngImport: i0, template: `
517
+ @switch (dir()) {
518
+ @case ('asc') { <span aria-hidden="true">▲</span> }
519
+ @case ('desc') { <span aria-hidden="true">▼</span> }
520
+ @default { <span aria-hidden="true" class="opacity-40">↕</span> }
521
+ }
522
+ `, isInline: true });
523
+ }
524
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngSortIcon, decorators: [{
525
+ type: Component,
526
+ args: [{
527
+ selector: 'tng-sort-icon',
528
+ standalone: true,
529
+ template: `
530
+ @switch (dir()) {
531
+ @case ('asc') { <span aria-hidden="true">▲</span> }
532
+ @case ('desc') { <span aria-hidden="true">▼</span> }
533
+ @default { <span aria-hidden="true" class="opacity-40">↕</span> }
534
+ }
535
+ `,
536
+ }]
537
+ }] });
538
+
539
+ class TngFilterTrigger {
540
+ colId = input.required(...(ngDevMode ? [{ debugName: "colId" }] : []));
541
+ table = inject(TNG_TABLE);
542
+ el = inject((ElementRef));
543
+ panelKlass = input('', ...(ngDevMode ? [{ debugName: "panelKlass" }] : []));
544
+ isFiltered = computed(() => this.table.isFiltered(this.colId()), ...(ngDevMode ? [{ debugName: "isFiltered" }] : []));
545
+ isOpen = computed(() => this.table.isFilterOpenFor(this.colId()), ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
546
+ // a11y
547
+ role = 'button';
548
+ tabindex = 0;
549
+ ariaHaspopup = 'dialog';
550
+ get ariaExpanded() {
551
+ return this.isOpen() ? 'true' : 'false';
552
+ }
553
+ onClick() {
554
+ this.table.setFilterPanelKlass(this.panelKlass()); // store it
555
+ this.table.toggleFilter(this.colId(), this.el.nativeElement);
556
+ }
557
+ onKey(ev) {
558
+ ev.preventDefault();
559
+ this.table.setFilterPanelKlass(this.panelKlass()); // store it
560
+ this.table.toggleFilter(this.colId(), this.el.nativeElement);
561
+ }
562
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngFilterTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive });
563
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: TngFilterTrigger, isStandalone: true, selector: "[tngFilterTrigger]", inputs: { colId: { classPropertyName: "colId", publicName: "colId", isSignal: true, isRequired: true, transformFunction: null }, panelKlass: { classPropertyName: "panelKlass", publicName: "panelKlass", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "onClick()", "keydown.enter": "onKey($event)", "keydown.space": "onKey($event)" }, properties: { "attr.role": "this.role", "attr.tabindex": "this.tabindex", "attr.aria-haspopup": "this.ariaHaspopup", "attr.aria-expanded": "this.ariaExpanded" } }, ngImport: i0 });
564
+ }
565
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngFilterTrigger, decorators: [{
566
+ type: Directive,
567
+ args: [{
568
+ selector: '[tngFilterTrigger]',
569
+ standalone: true,
570
+ }]
571
+ }], propDecorators: { colId: [{ type: i0.Input, args: [{ isSignal: true, alias: "colId", required: true }] }], panelKlass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelKlass", required: false }] }], role: [{
572
+ type: HostBinding,
573
+ args: ['attr.role']
574
+ }], tabindex: [{
575
+ type: HostBinding,
576
+ args: ['attr.tabindex']
577
+ }], ariaHaspopup: [{
578
+ type: HostBinding,
579
+ args: ['attr.aria-haspopup']
580
+ }], ariaExpanded: [{
581
+ type: HostBinding,
582
+ args: ['attr.aria-expanded']
583
+ }], onClick: [{
584
+ type: HostListener,
585
+ args: ['click']
586
+ }], onKey: [{
587
+ type: HostListener,
588
+ args: ['keydown.enter', ['$event']]
589
+ }, {
590
+ type: HostListener,
591
+ args: ['keydown.space', ['$event']]
592
+ }] } });
593
+
594
+ class TngFilterPanel {
595
+ table = inject(TNG_TABLE);
596
+ activeColId = computed(() => this.table.openFilterColId(), ...(ngDevMode ? [{ debugName: "activeColId" }] : []));
597
+ isOpen = computed(() => this.activeColId() !== '', ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
598
+ anchorEl = computed(() => this.table.filterAnchorEl(), ...(ngDevMode ? [{ debugName: "anchorEl" }] : []));
599
+ meta = computed(() => {
600
+ const id = this.activeColId();
601
+ return id ? this.table.metaFor(id) : undefined;
602
+ }, ...(ngDevMode ? [{ debugName: "meta" }] : []));
603
+ filterType = computed(() => this.meta()?.filter?.type ?? 'text', ...(ngDevMode ? [{ debugName: "filterType" }] : []));
604
+ titleSuffix = computed(() => {
605
+ const label = this.meta()?.label;
606
+ return label ? ` • ${label}` : '';
607
+ }, ...(ngDevMode ? [{ debugName: "titleSuffix" }] : []));
608
+ panelKlass = computed(() => {
609
+ const base = 'min-w-80 max-w-[360px] p-0';
610
+ const fromTrigger = this.table.filterPanelKlass();
611
+ return `${base} ${fromTrigger}`.trim();
612
+ }, ...(ngDevMode ? [{ debugName: "panelKlass" }] : []));
613
+ // ---- open/close wiring ----
614
+ onOverlayOpenChange(open) {
615
+ // if overlay closes, reflect that back to controller
616
+ if (!open && this.isOpen())
617
+ this.close();
618
+ }
619
+ onOverlayClosed() {
620
+ // overlay stack says it's closed (outside click / escape)
621
+ if (this.isOpen())
622
+ this.close();
623
+ }
624
+ close() {
625
+ this.table.closeFilter();
626
+ }
627
+ clear() {
628
+ const id = this.activeColId();
629
+ if (id)
630
+ this.table.clearFilter(id);
631
+ }
632
+ // ---------------- TEXT ----------------
633
+ textPlaceholder = computed(() => {
634
+ const f = this.meta()?.filter;
635
+ return f?.type === 'text' ? f.placeholder ?? 'Type to filter…' : 'Type to filter…';
636
+ }, ...(ngDevMode ? [{ debugName: "textPlaceholder" }] : []));
637
+ textValue() {
638
+ const id = this.activeColId();
639
+ const v = id ? this.table.filterValueFor(id) : undefined;
640
+ return typeof v === 'string' ? v : '';
641
+ }
642
+ onTextInput(ev) {
643
+ const value = ev.target?.value ?? '';
644
+ const id = this.activeColId();
645
+ if (!id)
646
+ return;
647
+ const trimmed = value.trim();
648
+ if (!trimmed)
649
+ this.table.clearFilter(id);
650
+ else
651
+ this.table.setFilter(id, trimmed);
652
+ }
653
+ // ---------------- NUMBER ----------------
654
+ numberValue() {
655
+ const id = this.activeColId();
656
+ const v = id ? this.table.filterValueFor(id) : undefined;
657
+ return v && typeof v === 'object' && !Array.isArray(v) ? v : {};
658
+ }
659
+ numberMin() {
660
+ const v = this.numberValue().min;
661
+ return typeof v === 'number' && Number.isFinite(v) ? String(v) : '';
662
+ }
663
+ numberMax() {
664
+ const v = this.numberValue().max;
665
+ return typeof v === 'number' && Number.isFinite(v) ? String(v) : '';
666
+ }
667
+ onNumberMinInput(ev) {
668
+ const raw = ev.target?.value ?? '';
669
+ const id = this.activeColId();
670
+ if (!id)
671
+ return;
672
+ const cur = this.numberValue();
673
+ const n = raw === '' ? undefined : Number(raw);
674
+ const next = { ...cur, min: Number.isFinite(n) ? n : undefined };
675
+ if (!next.min && !next.max)
676
+ this.table.clearFilter(id);
677
+ else
678
+ this.table.setFilter(id, next);
679
+ }
680
+ onNumberMaxInput(ev) {
681
+ const raw = ev.target?.value ?? '';
682
+ const id = this.activeColId();
683
+ if (!id)
684
+ return;
685
+ const cur = this.numberValue();
686
+ const n = raw === '' ? undefined : Number(raw);
687
+ const next = { ...cur, max: Number.isFinite(n) ? n : undefined };
688
+ if (!next.min && !next.max)
689
+ this.table.clearFilter(id);
690
+ else
691
+ this.table.setFilter(id, next);
692
+ }
693
+ // ---------------- DATE ----------------
694
+ dateValue() {
695
+ const id = this.activeColId();
696
+ const v = id ? this.table.filterValueFor(id) : undefined;
697
+ return v && typeof v === 'object' && !Array.isArray(v) ? v : {};
698
+ }
699
+ dateFrom() {
700
+ return this.dateValue().from ?? '';
701
+ }
702
+ dateTo() {
703
+ return this.dateValue().to ?? '';
704
+ }
705
+ onDateFromInput(ev) {
706
+ const from = ev.target?.value ?? '';
707
+ const id = this.activeColId();
708
+ if (!id)
709
+ return;
710
+ const cur = this.dateValue();
711
+ const next = { ...cur, from: from || undefined };
712
+ if (!next.from && !next.to)
713
+ this.table.clearFilter(id);
714
+ else
715
+ this.table.setFilter(id, next);
716
+ }
717
+ onDateToInput(ev) {
718
+ const to = ev.target?.value ?? '';
719
+ const id = this.activeColId();
720
+ if (!id)
721
+ return;
722
+ const cur = this.dateValue();
723
+ const next = { ...cur, to: to || undefined };
724
+ if (!next.from && !next.to)
725
+ this.table.clearFilter(id);
726
+ else
727
+ this.table.setFilter(id, next);
728
+ }
729
+ // ---------------- ENUM ----------------
730
+ enumOptions() {
731
+ const f = this.meta()?.filter;
732
+ return f?.type === 'enum' ? f.options : [];
733
+ }
734
+ enumValue() {
735
+ const id = this.activeColId();
736
+ const v = id ? this.table.filterValueFor(id) : undefined;
737
+ return Array.isArray(v) ? v : [];
738
+ }
739
+ enumChecked(value) {
740
+ return this.enumValue().includes(value);
741
+ }
742
+ toggleEnum(value) {
743
+ const id = this.activeColId();
744
+ if (!id)
745
+ return;
746
+ const cur = this.enumValue();
747
+ const next = cur.includes(value) ? cur.filter((v) => v !== value) : [...cur, value];
748
+ if (next.length === 0)
749
+ this.table.clearFilter(id);
750
+ else
751
+ this.table.setFilter(id, next);
752
+ }
753
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngFilterPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
754
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TngFilterPanel, isStandalone: true, selector: "tng-filter-panel", ngImport: i0, template: "<tng-overlay-ref\n #overlayRef=\"tngOverlayRef\"\n [open]=\"isOpen()\"\n (openChange)=\"onOverlayOpenChange($event)\"\n (closed)=\"onOverlayClosed()\"\n>\n <tng-connected-overlay\n [open]=\"overlayRef.isOpen()\"\n [anchor]=\"anchorEl()\"\n placement=\"bottom-start\"\n (closed)=\"overlayRef.close($event)\"\n [closeOnInsideClick]=\"false\"\n >\n <!-- set width at panel level (menu-style) -->\n <tng-overlay-panel [klass]=\"panelKlass()\">\n <div class=\"w-full\">\n <div class=\"flex items-center justify-between gap-3 border-b border-slate-200 px-3 py-2\">\n <div class=\"min-w-0\">\n <div class=\"truncate text-sm font-semibold text-slate-900\">\n Filter{{ titleSuffix() }}\n </div>\n <div class=\"truncate text-xs text-slate-500\">{{ activeColId() }}</div>\n </div>\n\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"rounded px-2 py-1 text-xs text-slate-600 hover:bg-slate-50\"\n (click)=\"clear()\"\n >\n Clear\n </button>\n <button\n type=\"button\"\n class=\"rounded px-2 py-1 text-xs text-slate-600 hover:bg-slate-50\"\n (click)=\"close()\"\n aria-label=\"Close filter\"\n >\n \u2715\n </button>\n </div>\n </div>\n\n <div class=\"p-3\">\n @switch (filterType()) {\n @case ('text') {\n <label class=\"text-xs font-medium text-slate-700\">Contains</label>\n <input\n class=\"mt-2 w-full rounded border border-slate-200 px-2 py-1 text-sm outline-none focus:border-slate-400\"\n [placeholder]=\"textPlaceholder()\"\n [value]=\"textValue()\"\n (input)=\"onTextInput($event)\"\n />\n }\n\n @case ('number') {\n <div class=\"grid grid-cols-2 gap-3\">\n <div>\n <label class=\"text-xs font-medium text-slate-700\">Min</label>\n <input\n type=\"number\"\n class=\"mt-2 w-full rounded border border-slate-200 px-2 py-1 text-sm outline-none focus:border-slate-400\"\n [value]=\"numberMin()\"\n (input)=\"onNumberMinInput($event)\"\n />\n </div>\n <div>\n <label class=\"text-xs font-medium text-slate-700\">Max</label>\n <input\n type=\"number\"\n class=\"mt-2 w-full rounded border border-slate-200 px-2 py-1 text-sm outline-none focus:border-slate-400\"\n [value]=\"numberMax()\"\n (input)=\"onNumberMaxInput($event)\"\n />\n </div>\n </div>\n }\n\n @case ('date') {\n <div class=\"grid grid-cols-2 gap-3\">\n <div>\n <label class=\"text-xs font-medium text-slate-700\">From</label>\n <input\n type=\"date\"\n class=\"mt-2 w-full rounded border border-slate-200 px-2 py-1 text-sm outline-none focus:border-slate-400\"\n [value]=\"dateFrom()\"\n (input)=\"onDateFromInput($event)\"\n />\n </div>\n <div>\n <label class=\"text-xs font-medium text-slate-700\">To</label>\n <input\n type=\"date\"\n class=\"mt-2 w-full rounded border border-slate-200 px-2 py-1 text-sm outline-none focus:border-slate-400\"\n [value]=\"dateTo()\"\n (input)=\"onDateToInput($event)\"\n />\n </div>\n </div>\n }\n\n @case ('enum') {\n <div class=\"space-y-2\">\n <div class=\"text-xs font-medium text-slate-700\">Select</div>\n\n <div class=\"max-h-56 overflow-auto rounded border border-slate-200\">\n @for (opt of enumOptions(); track opt.value) {\n <label class=\"flex cursor-pointer items-center gap-2 px-2 py-1 text-sm hover:bg-slate-50\">\n <input\n type=\"checkbox\"\n class=\"h-4 w-4\"\n [checked]=\"enumChecked(opt.value)\"\n (change)=\"toggleEnum(opt.value)\"\n />\n <span class=\"truncate\">{{ opt.label }}</span>\n </label>\n }\n </div>\n </div>\n }\n\n @default {\n <div class=\"text-sm text-slate-600\">\n No default filter configured for this column.\n </div>\n }\n }\n </div>\n </div>\n </tng-overlay-panel>\n </tng-connected-overlay>\n</tng-overlay-ref>\n", dependencies: [{ kind: "component", type: TngOverlayRef, selector: "tng-overlay-ref", inputs: ["open"], outputs: ["openChange", "opened", "closed"], exportAs: ["tngOverlayRef"] }, { kind: "component", type: TngConnectedOverlay, selector: "tng-connected-overlay", inputs: ["open", "anchor", "placement", "offset", "width", "closeOnOutsideClick", "closeOnInsideClick", "closeOnEscape", "hasBackdrop", "backdropClass"], outputs: ["opened", "closed", "backdropClick"] }, { kind: "component", type: TngOverlayPanel, selector: "tng-overlay-panel", inputs: ["klass", "modal", "role", "ariaLabel", "ariaLabelledby", "ariaDescribedby", "restoreFocus", "autoCapture", "deferCaptureElements"] }] });
755
+ }
756
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngFilterPanel, decorators: [{
757
+ type: Component,
758
+ args: [{ selector: 'tng-filter-panel', standalone: true, imports: [TngOverlayRef, TngConnectedOverlay, TngOverlayPanel], template: "<tng-overlay-ref\n #overlayRef=\"tngOverlayRef\"\n [open]=\"isOpen()\"\n (openChange)=\"onOverlayOpenChange($event)\"\n (closed)=\"onOverlayClosed()\"\n>\n <tng-connected-overlay\n [open]=\"overlayRef.isOpen()\"\n [anchor]=\"anchorEl()\"\n placement=\"bottom-start\"\n (closed)=\"overlayRef.close($event)\"\n [closeOnInsideClick]=\"false\"\n >\n <!-- set width at panel level (menu-style) -->\n <tng-overlay-panel [klass]=\"panelKlass()\">\n <div class=\"w-full\">\n <div class=\"flex items-center justify-between gap-3 border-b border-slate-200 px-3 py-2\">\n <div class=\"min-w-0\">\n <div class=\"truncate text-sm font-semibold text-slate-900\">\n Filter{{ titleSuffix() }}\n </div>\n <div class=\"truncate text-xs text-slate-500\">{{ activeColId() }}</div>\n </div>\n\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"rounded px-2 py-1 text-xs text-slate-600 hover:bg-slate-50\"\n (click)=\"clear()\"\n >\n Clear\n </button>\n <button\n type=\"button\"\n class=\"rounded px-2 py-1 text-xs text-slate-600 hover:bg-slate-50\"\n (click)=\"close()\"\n aria-label=\"Close filter\"\n >\n \u2715\n </button>\n </div>\n </div>\n\n <div class=\"p-3\">\n @switch (filterType()) {\n @case ('text') {\n <label class=\"text-xs font-medium text-slate-700\">Contains</label>\n <input\n class=\"mt-2 w-full rounded border border-slate-200 px-2 py-1 text-sm outline-none focus:border-slate-400\"\n [placeholder]=\"textPlaceholder()\"\n [value]=\"textValue()\"\n (input)=\"onTextInput($event)\"\n />\n }\n\n @case ('number') {\n <div class=\"grid grid-cols-2 gap-3\">\n <div>\n <label class=\"text-xs font-medium text-slate-700\">Min</label>\n <input\n type=\"number\"\n class=\"mt-2 w-full rounded border border-slate-200 px-2 py-1 text-sm outline-none focus:border-slate-400\"\n [value]=\"numberMin()\"\n (input)=\"onNumberMinInput($event)\"\n />\n </div>\n <div>\n <label class=\"text-xs font-medium text-slate-700\">Max</label>\n <input\n type=\"number\"\n class=\"mt-2 w-full rounded border border-slate-200 px-2 py-1 text-sm outline-none focus:border-slate-400\"\n [value]=\"numberMax()\"\n (input)=\"onNumberMaxInput($event)\"\n />\n </div>\n </div>\n }\n\n @case ('date') {\n <div class=\"grid grid-cols-2 gap-3\">\n <div>\n <label class=\"text-xs font-medium text-slate-700\">From</label>\n <input\n type=\"date\"\n class=\"mt-2 w-full rounded border border-slate-200 px-2 py-1 text-sm outline-none focus:border-slate-400\"\n [value]=\"dateFrom()\"\n (input)=\"onDateFromInput($event)\"\n />\n </div>\n <div>\n <label class=\"text-xs font-medium text-slate-700\">To</label>\n <input\n type=\"date\"\n class=\"mt-2 w-full rounded border border-slate-200 px-2 py-1 text-sm outline-none focus:border-slate-400\"\n [value]=\"dateTo()\"\n (input)=\"onDateToInput($event)\"\n />\n </div>\n </div>\n }\n\n @case ('enum') {\n <div class=\"space-y-2\">\n <div class=\"text-xs font-medium text-slate-700\">Select</div>\n\n <div class=\"max-h-56 overflow-auto rounded border border-slate-200\">\n @for (opt of enumOptions(); track opt.value) {\n <label class=\"flex cursor-pointer items-center gap-2 px-2 py-1 text-sm hover:bg-slate-50\">\n <input\n type=\"checkbox\"\n class=\"h-4 w-4\"\n [checked]=\"enumChecked(opt.value)\"\n (change)=\"toggleEnum(opt.value)\"\n />\n <span class=\"truncate\">{{ opt.label }}</span>\n </label>\n }\n </div>\n </div>\n }\n\n @default {\n <div class=\"text-sm text-slate-600\">\n No default filter configured for this column.\n </div>\n }\n }\n </div>\n </div>\n </tng-overlay-panel>\n </tng-connected-overlay>\n</tng-overlay-ref>\n" }]
759
+ }] });
760
+
761
+ class TngSortHeader {
762
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngSortHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
763
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: TngSortHeader, isStandalone: true, selector: "tng-sort-header", ngImport: i0, template: "<div class=\"p-4 border border-gray-200 rounded-md\">\n <p class=\"text-gray-600\">Welcome hello component - Sort Header</p>\n</div>\n" });
764
+ }
765
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngSortHeader, decorators: [{
766
+ type: Component,
767
+ args: [{ selector: 'tng-sort-header', standalone: true, template: "<div class=\"p-4 border border-gray-200 rounded-md\">\n <p class=\"text-gray-600\">Welcome hello component - Sort Header</p>\n</div>\n" }]
768
+ }] });
769
+
770
+ class TngTree {
771
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngTree, deps: [], target: i0.ɵɵFactoryTarget.Component });
772
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: TngTree, isStandalone: true, selector: "tng-tree", ngImport: i0, template: "<div class=\"p-4 border border-gray-200 rounded-md\">\n <p class=\"text-gray-600\">Welcome hello component - Tree</p>\n</div>\n" });
773
+ }
774
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngTree, decorators: [{
775
+ type: Component,
776
+ args: [{ selector: 'tng-tree', standalone: true, template: "<div class=\"p-4 border border-gray-200 rounded-md\">\n <p class=\"text-gray-600\">Welcome hello component - Tree</p>\n</div>\n" }]
777
+ }] });
778
+
779
+ class TngEmptyState {
780
+ title = input('No data available', ...(ngDevMode ? [{ debugName: "title" }] : []));
781
+ message = input('', ...(ngDevMode ? [{ debugName: "message" }] : []));
782
+ icon = input('', ...(ngDevMode ? [{ debugName: "icon" }] : []));
783
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngEmptyState, deps: [], target: i0.ɵɵFactoryTarget.Component });
784
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TngEmptyState, isStandalone: true, selector: "tng-empty-state", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"flex flex-col items-center justify-center p-8 text-center\">\n @if (icon()) {\n <div class=\"mb-4 text-4xl text-gray-400\">{{ icon() }}</div>\n }\n <h3 class=\"text-lg font-semibold text-gray-900 mb-2\">{{ title() }}</h3>\n @if (message()) {\n <p class=\"text-sm text-gray-600\">{{ message() }}</p>\n }\n</div>\n" });
785
+ }
786
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngEmptyState, decorators: [{
787
+ type: Component,
788
+ args: [{ selector: 'tng-empty-state', standalone: true, template: "<div class=\"flex flex-col items-center justify-center p-8 text-center\">\n @if (icon()) {\n <div class=\"mb-4 text-4xl text-gray-400\">{{ icon() }}</div>\n }\n <h3 class=\"text-lg font-semibold text-gray-900 mb-2\">{{ title() }}</h3>\n @if (message()) {\n <p class=\"text-sm text-gray-600\">{{ message() }}</p>\n }\n</div>\n" }]
789
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }] } });
790
+
791
+ class TngVirtualScroll {
792
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngVirtualScroll, deps: [], target: i0.ɵɵFactoryTarget.Component });
793
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: TngVirtualScroll, isStandalone: true, selector: "tng-virtual-scroll", ngImport: i0, template: "<div class=\"p-4 border border-gray-200 rounded-md\">\n <p class=\"text-gray-600\">Virtual Scroll component - placeholder</p>\n</div>\n" });
794
+ }
795
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TngVirtualScroll, decorators: [{
796
+ type: Component,
797
+ args: [{ selector: 'tng-virtual-scroll', standalone: true, template: "<div class=\"p-4 border border-gray-200 rounded-md\">\n <p class=\"text-gray-600\">Virtual Scroll component - placeholder</p>\n</div>\n" }]
798
+ }] });
799
+
800
+ // Table (components, defs, types, controllers, directives, features)
18
801
 
19
802
  /**
20
803
  * Generated bundle index. Do not edit.
21
804
  */
805
+
806
+ export { TNG_TABLE, TngCellDef, TngCol, TngColumnMetaController, TngEmptyState, TngFilterController, TngFilterPanel, TngFilterTrigger, TngHeaderDef, TngSortController, TngSortHeader, TngSortHeaderDirective, TngSortIcon, TngTable, TngTableController, TngTableFilterFeature, TngTableSortFeature, TngTree, TngVirtualScroll };
22
807
  //# sourceMappingURL=tociva-tailng-ui-data-table-structure.mjs.map