@oneteme/jquery-table 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,1618 @@
1
+ import * as i1 from '@angular/common';
2
+ import { CommonModule } from '@angular/common';
3
+ import * as i0 from '@angular/core';
4
+ import { Directive, Input, EventEmitter, inject, ChangeDetectorRef, Component, ChangeDetectionStrategy, Output, ViewChild, ElementRef, ContentChildren } from '@angular/core';
5
+ import * as i2$1 from '@angular/forms';
6
+ import { FormsModule } from '@angular/forms';
7
+ import { MatButtonModule } from '@angular/material/button';
8
+ import * as i2 from '@angular/material/icon';
9
+ import { MatIconModule } from '@angular/material/icon';
10
+ import * as i6 from '@angular/material/menu';
11
+ import { MatMenuModule } from '@angular/material/menu';
12
+ import * as i4 from '@angular/material/paginator';
13
+ import { MatPaginatorIntl, MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
14
+ import * as i9 from '@angular/material/select';
15
+ import { MatSelectModule } from '@angular/material/select';
16
+ import * as i5 from '@angular/material/sort';
17
+ import { MatSort, MatSortModule } from '@angular/material/sort';
18
+ import * as i3 from '@angular/material/table';
19
+ import { MatTableDataSource, MatTableModule } from '@angular/material/table';
20
+ import { Subject, take, takeUntil } from 'rxjs';
21
+ import * as i8 from '@angular/material/form-field';
22
+ import * as i10 from '@angular/material/core';
23
+
24
+ /**
25
+ * Directive permettant de définir un template de cellule personnalisé pour une colonne.
26
+ *
27
+ * Usage:
28
+ * ```html
29
+ * <jquery-table [config]="tableConfig">
30
+ * <ng-template jqtCellDef="monKey" let-row>
31
+ * <span>{{ row.monChamp }}</span>
32
+ * </ng-template>
33
+ * </jquery-table>
34
+ * ```
35
+ * Le contexte expose `$implicit` (ligne brute `T`) et `index` (index global).
36
+ */
37
+ class JqtCellDefDirective {
38
+ constructor(template) {
39
+ this.template = template;
40
+ /** Clé de la colonne à laquelle ce template s'applique. */
41
+ this.columnKey = '';
42
+ }
43
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: JqtCellDefDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); }
44
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: JqtCellDefDirective, isStandalone: true, selector: "[jqtCellDef]", inputs: { columnKey: ["jqtCellDef", "columnKey"] }, ngImport: i0 }); }
45
+ }
46
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: JqtCellDefDirective, decorators: [{
47
+ type: Directive,
48
+ args: [{
49
+ selector: '[jqtCellDef]',
50
+ standalone: true,
51
+ }]
52
+ }], ctorParameters: function () { return [{ type: i0.TemplateRef }]; }, propDecorators: { columnKey: [{
53
+ type: Input,
54
+ args: ['jqtCellDef']
55
+ }] } });
56
+
57
+ const EMPTY_CATEGORY_KEY = '__empty__';
58
+ const EMPTY_CATEGORY_LABEL = 'Vide';
59
+ class SlicePanelComponent {
60
+ constructor() {
61
+ this.sliceConfigs = [];
62
+ this.columns = [];
63
+ this.data = [];
64
+ this.lazyData = new Map();
65
+ this.lazyStatus = new Map();
66
+ this.showToggle = true;
67
+ this.showCounts = true;
68
+ this.collapsedByDefault = false;
69
+ this.alwaysShow = false;
70
+ this.filterChange = new EventEmitter();
71
+ this.activeKeysChange = new EventEmitter();
72
+ this.dynamicSliceKeysChange = new EventEmitter();
73
+ this.collapsedChange = new EventEmitter();
74
+ this._cdr = inject(ChangeDetectorRef);
75
+ this.isCollapsed = false;
76
+ this.activeKeysBySlice = new Map();
77
+ this._expandedSlices = new Set();
78
+ this._dynamicSlices = [];
79
+ this._hiddenStaticSliceKeys = new Set();
80
+ // ── Cache ─────────────────────────────────────────────────────
81
+ this._cachedSlices = [];
82
+ this._countCache = new Map();
83
+ this.trackBySliceIndex = (index, _) => index;
84
+ this.trackByCategoryKey = (_, cat) => cat.key;
85
+ }
86
+ // ── Lifecycle ─────────────────────────────────────────────────
87
+ ngOnInit() {
88
+ this.isCollapsed = this.collapsedByDefault;
89
+ const initiallyHidden = (this.sliceConfigs || [])
90
+ .filter(s => s.hidden === true && !!s.columnKey)
91
+ .map(s => s.columnKey);
92
+ if (initiallyHidden.length) {
93
+ this._hiddenStaticSliceKeys = new Set([...this._hiddenStaticSliceKeys, ...initiallyHidden]);
94
+ }
95
+ }
96
+ ngOnChanges(changes) {
97
+ if (changes['sliceConfigs'] || changes['data'] || changes['columns'] || changes['lazyData'] || changes['lazyStatus']) {
98
+ this._refreshDynamicSlices();
99
+ this._rebuildCache();
100
+ this._cdr.markForCheck();
101
+ }
102
+ }
103
+ // ── Computed ──────────────────────────────────────────────────
104
+ get showPanel() {
105
+ const visibleStaticCount = (this.sliceConfigs || []).filter((s) => !s.columnKey || !this._hiddenStaticSliceKeys.has(s.columnKey)).length;
106
+ const hasCfg = visibleStaticCount > 0 || this._dynamicSlices.length > 0;
107
+ if (this.alwaysShow)
108
+ return hasCfg;
109
+ return hasCfg && (this.data?.length ?? 0) > 0;
110
+ }
111
+ get slices() {
112
+ return this._cachedSlices;
113
+ }
114
+ get staticSliceCount() {
115
+ return (this.sliceConfigs || []).filter((s) => !s.columnKey || !this._hiddenStaticSliceKeys.has(s.columnKey)).length;
116
+ }
117
+ get staticSlicesForMenu() {
118
+ return (this.sliceConfigs || [])
119
+ .filter((s) => !!s.columnKey)
120
+ .map((s) => ({ key: s.columnKey, title: s.title || s.columnKey }));
121
+ }
122
+ get activeDynamicSliceColumns() {
123
+ return this._dynamicSlices
124
+ .map((d) => (this.columns || []).find((c) => c.key === d.key))
125
+ .filter((c) => !!c);
126
+ }
127
+ get availableColumnsForDynamicSlice() {
128
+ const dynamicKeys = new Set(this._dynamicSlices.map((d) => d.key));
129
+ const staticKeys = new Set((this.sliceConfigs || []).map((s) => s.columnKey).filter((k) => !!k));
130
+ const existingKeys = new Set([...dynamicKeys, ...staticKeys]);
131
+ const seen = new Set();
132
+ return (this.columns || []).filter((c) => {
133
+ if (existingKeys.has(c.key) || seen.has(c.key))
134
+ return false;
135
+ seen.add(c.key);
136
+ return true;
137
+ });
138
+ }
139
+ get activeSliceByLabel() {
140
+ const staticActiveCount = (this.sliceConfigs || []).filter((s) => !s.columnKey || !this._hiddenStaticSliceKeys.has(s.columnKey)).length;
141
+ const total = staticActiveCount + this._dynamicSlices.length;
142
+ if (total === 0)
143
+ return 'Aucun';
144
+ if (total === 1) {
145
+ const first = (this.sliceConfigs || []).find((s) => !s.columnKey || !this._hiddenStaticSliceKeys.has(s.columnKey));
146
+ if (first)
147
+ return first.title || first.columnKey || '1';
148
+ return this.activeDynamicSliceColumns[0]?.header || '1';
149
+ }
150
+ return `${total} actifs`;
151
+ }
152
+ // ── UI state ──────────────────────────────────────────────────
153
+ isSliceCollapsed(sliceIndex) {
154
+ if (this._cachedSlices.length === 1) {
155
+ return this._expandedSlices.has(-1 - sliceIndex);
156
+ }
157
+ return !this._expandedSlices.has(sliceIndex);
158
+ }
159
+ toggleSliceCollapse(sliceIndex) {
160
+ if (this.slices.length === 1) {
161
+ const closedKey = -1 - sliceIndex;
162
+ if (this._expandedSlices.has(closedKey)) {
163
+ this._expandedSlices.delete(closedKey);
164
+ }
165
+ else {
166
+ this._expandedSlices.add(closedKey);
167
+ this._scrollSliceTitleIntoView(sliceIndex);
168
+ }
169
+ }
170
+ else {
171
+ if (this._expandedSlices.has(sliceIndex)) {
172
+ this._expandedSlices.delete(sliceIndex);
173
+ }
174
+ else {
175
+ this._expandedSlices.clear();
176
+ this._expandedSlices.add(sliceIndex);
177
+ this._scrollSliceTitleIntoView(sliceIndex);
178
+ }
179
+ }
180
+ this._cdr.markForCheck();
181
+ }
182
+ togglePanel() {
183
+ this.isCollapsed = !this.isCollapsed;
184
+ this.collapsedChange.emit(this.isCollapsed);
185
+ this._cdr.markForCheck();
186
+ }
187
+ isSliceDynamic(sliceIndex) {
188
+ return sliceIndex >= this.staticSliceCount;
189
+ }
190
+ isSliceLoading(sliceIndex) {
191
+ const staticSlice = (this.sliceConfigs || [])[sliceIndex];
192
+ if (staticSlice?.columnKey) {
193
+ const colDef = (this.columns || []).find(c => c.key === staticSlice.columnKey);
194
+ if (colDef?.lazy) {
195
+ const status = this.lazyStatus.get(staticSlice.columnKey) ?? 'idle';
196
+ return status === 'loading' || status === 'idle';
197
+ }
198
+ return false;
199
+ }
200
+ const key = this._getDynamicSliceColumnKey(sliceIndex);
201
+ if (!key)
202
+ return false;
203
+ const colDef = (this.columns || []).find(c => c.key === key);
204
+ if (!colDef?.lazy)
205
+ return false;
206
+ const status = this.lazyStatus.get(key) ?? 'idle';
207
+ return status === 'loading' || status === 'idle';
208
+ }
209
+ isStaticSliceVisible(columnKey) {
210
+ return !this._hiddenStaticSliceKeys.has(columnKey);
211
+ }
212
+ toggleStaticSlice(columnKey) {
213
+ const next = new Set(this._hiddenStaticSliceKeys);
214
+ if (next.has(columnKey))
215
+ next.delete(columnKey);
216
+ else
217
+ next.add(columnKey);
218
+ this._hiddenStaticSliceKeys = next;
219
+ this._rebuildCache();
220
+ this._emitFilter();
221
+ this._cdr.markForCheck();
222
+ }
223
+ // ── Selection ─────────────────────────────────────────────────
224
+ getSliceIcon(slice) {
225
+ if (slice.icon)
226
+ return slice.icon;
227
+ if (slice.columnKey) {
228
+ return (this.columns || []).find(c => c.key === slice.columnKey)?.icon;
229
+ }
230
+ return undefined;
231
+ }
232
+ isSliceCategoryActive(sliceIndex, categoryKey) {
233
+ return this.activeKeysBySlice.get(sliceIndex)?.has(categoryKey) ?? false;
234
+ }
235
+ isSliceAllActive(sliceIndex) {
236
+ return !this.activeKeysBySlice.has(sliceIndex) || this.activeKeysBySlice.get(sliceIndex).size === 0;
237
+ }
238
+ sliceCategoryCount(sliceIndex, categoryKey) {
239
+ const cacheKey = `${sliceIndex}:${categoryKey}`;
240
+ if (this._countCache.has(cacheKey)) {
241
+ return this._countCache.get(cacheKey);
242
+ }
243
+ const slice = this._cachedSlices[sliceIndex];
244
+ if (!slice)
245
+ return 0;
246
+ const category = (slice.categories || []).find((c) => c.key === categoryKey);
247
+ const count = category ? (this.data || []).filter((row) => category.filter(row)).length : 0;
248
+ this._countCache.set(cacheKey, count);
249
+ return count;
250
+ }
251
+ isSliceEmpty(sliceIndex) {
252
+ const slice = this._cachedSlices[sliceIndex];
253
+ if (!slice)
254
+ return true;
255
+ return (slice.categories || []).every(c => this.sliceCategoryCount(sliceIndex, c.key) === 0);
256
+ }
257
+ onSliceCategorySelected(sliceIndex, categoryKey) {
258
+ const slice = this.slices[sliceIndex];
259
+ if (!slice)
260
+ return;
261
+ if (!this.activeKeysBySlice.has(sliceIndex)) {
262
+ this.activeKeysBySlice.set(sliceIndex, new Set());
263
+ }
264
+ const activeKeys = this.activeKeysBySlice.get(sliceIndex);
265
+ if (slice.multiSelect === false) {
266
+ if (activeKeys.has(categoryKey)) {
267
+ activeKeys.clear();
268
+ }
269
+ else {
270
+ activeKeys.clear();
271
+ activeKeys.add(categoryKey);
272
+ }
273
+ }
274
+ else {
275
+ if (activeKeys.has(categoryKey)) {
276
+ activeKeys.delete(categoryKey);
277
+ }
278
+ else {
279
+ activeKeys.add(categoryKey);
280
+ }
281
+ }
282
+ this._emitFilter();
283
+ this._cdr.markForCheck();
284
+ }
285
+ onSliceAllSelected(sliceIndex) {
286
+ if (this.activeKeysBySlice.has(sliceIndex)) {
287
+ this.activeKeysBySlice.get(sliceIndex).clear();
288
+ }
289
+ this._emitFilter();
290
+ this._cdr.markForCheck();
291
+ }
292
+ // ── Dynamic slice management ──────────────────────────────────
293
+ addDynamicSlice(column) {
294
+ const distinctValues = this._computeDistinctValues(column.key);
295
+ const newSlice = {
296
+ title: column.header,
297
+ columnKey: column.key,
298
+ categories: distinctValues.map((v) => this._buildCategoryFromValue(column.key, v)),
299
+ };
300
+ this._dynamicSlices = [...this._dynamicSlices, { key: column.key, slice: newSlice }];
301
+ this._rebuildCache();
302
+ this.dynamicSliceKeysChange.emit(this._dynamicSlices.map(d => d.key));
303
+ this._emitFilter();
304
+ this._cdr.markForCheck();
305
+ }
306
+ removeDynamicSlice(sliceIndex) {
307
+ const dynamicIndex = sliceIndex - this.staticSliceCount;
308
+ if (dynamicIndex < 0)
309
+ return;
310
+ const newActiveKeys = new Map();
311
+ this.activeKeysBySlice.forEach((keys, idx) => {
312
+ if (idx < sliceIndex)
313
+ newActiveKeys.set(idx, keys);
314
+ else if (idx > sliceIndex)
315
+ newActiveKeys.set(idx - 1, keys);
316
+ });
317
+ this.activeKeysBySlice = newActiveKeys;
318
+ this._dynamicSlices = this._dynamicSlices.filter((_, i) => i !== dynamicIndex);
319
+ this._rebuildCache();
320
+ this.dynamicSliceKeysChange.emit(this._dynamicSlices.map(d => d.key));
321
+ this._emitFilter();
322
+ this._cdr.markForCheck();
323
+ }
324
+ removeDynamicSliceByKey(key) {
325
+ const dynamicIndex = this._dynamicSlices.findIndex((d) => d.key === key);
326
+ if (dynamicIndex < 0)
327
+ return;
328
+ const sliceIndex = this.staticSliceCount + dynamicIndex;
329
+ this.removeDynamicSlice(sliceIndex);
330
+ }
331
+ // ── Private helpers ───────────────────────────────────────────
332
+ _emitFilter() {
333
+ const slices = this.slices;
334
+ const activeKeysBySlice = this.activeKeysBySlice;
335
+ this.activeKeysChange.emit(slices.map((_, i) => {
336
+ const keys = activeKeysBySlice.get(i);
337
+ return keys ? [...keys] : [];
338
+ }));
339
+ const pred = (row) => slices.every((slice, sliceIndex) => {
340
+ const activeKeys = activeKeysBySlice.get(sliceIndex);
341
+ if (!activeKeys || activeKeys.size === 0)
342
+ return true;
343
+ return [...activeKeys].some((key) => {
344
+ const category = (slice.categories || []).find((c) => c.key === key);
345
+ return category ? category.filter(row) : false;
346
+ });
347
+ });
348
+ this.filterChange.emit(pred);
349
+ }
350
+ _getDynamicSliceColumnKey(sliceIndex) {
351
+ const dynamicIndex = sliceIndex - this.staticSliceCount;
352
+ return this._dynamicSlices[dynamicIndex]?.key ?? null;
353
+ }
354
+ _materializeSlice(slice) {
355
+ if (!slice.columnKey || (slice.categories && slice.categories.length > 0)) {
356
+ return slice;
357
+ }
358
+ const col = (this.columns || []).find(c => c.key === slice.columnKey);
359
+ const isLazy = !!col?.lazy;
360
+ if (isLazy && this.lazyStatus.get(slice.columnKey) !== 'loaded') {
361
+ return { ...slice, categories: [] };
362
+ }
363
+ if (slice.bucket) {
364
+ const distinctValues = [...new Set((this.data || []).map(slice.bucket))].sort();
365
+ return {
366
+ ...slice,
367
+ categories: distinctValues.map((v) => ({
368
+ key: v,
369
+ label: v,
370
+ filter: (row) => slice.bucket(row) === v,
371
+ })),
372
+ };
373
+ }
374
+ const distinctValues = this._computeDistinctValues(slice.columnKey);
375
+ return {
376
+ ...slice,
377
+ categories: distinctValues.map((v) => this._buildCategoryFromValue(slice.columnKey, v)),
378
+ };
379
+ }
380
+ _buildCategoryFromValue(columnKey, v) {
381
+ if (v === EMPTY_CATEGORY_KEY) {
382
+ return {
383
+ key: EMPTY_CATEGORY_KEY,
384
+ label: EMPTY_CATEGORY_LABEL,
385
+ filter: (row) => {
386
+ const val = this._getLazyAwareValue(columnKey, row);
387
+ return val == null || val === '';
388
+ },
389
+ };
390
+ }
391
+ return {
392
+ key: v,
393
+ label: v,
394
+ filter: (row) => String(this._getLazyAwareValue(columnKey, row) ?? '') === v,
395
+ };
396
+ }
397
+ _computeDistinctValues(columnKey) {
398
+ const rawValues = (this.data || []).map((row) => {
399
+ const val = this._getLazyAwareValue(columnKey, row);
400
+ return val != null && val !== '' ? String(val) : null;
401
+ });
402
+ const distinct = [...new Set(rawValues.filter((v) => v !== null))].sort();
403
+ if (rawValues.some(v => v === null)) {
404
+ distinct.push(EMPTY_CATEGORY_KEY);
405
+ }
406
+ return distinct;
407
+ }
408
+ _getLazyAwareValue(columnKey, row) {
409
+ const colDef = (this.columns || []).find(c => c.key === columnKey);
410
+ if (colDef?.lazy) {
411
+ return this.lazyData.get(columnKey)?.get(row) ?? null;
412
+ }
413
+ if (colDef?.value) {
414
+ return colDef.value(row, 0);
415
+ }
416
+ return row[columnKey];
417
+ }
418
+ _rebuildCache() {
419
+ const staticSlices = (this.sliceConfigs || []).filter((s) => !s.columnKey || !this._hiddenStaticSliceKeys.has(s.columnKey));
420
+ const allSlices = [...staticSlices, ...this._dynamicSlices.map((d) => d.slice)];
421
+ this._cachedSlices = allSlices.map((slice) => this._materializeSlice(slice));
422
+ this._countCache.clear();
423
+ }
424
+ _refreshDynamicSlices() {
425
+ this._dynamicSlices = this._dynamicSlices.map(({ key, slice }) => {
426
+ const distinctValues = this._computeDistinctValues(key);
427
+ return {
428
+ key,
429
+ slice: {
430
+ ...slice,
431
+ categories: distinctValues.map((v) => this._buildCategoryFromValue(key, v)),
432
+ },
433
+ };
434
+ });
435
+ }
436
+ _scrollSliceTitleIntoView(sliceIndex) {
437
+ setTimeout(() => {
438
+ const container = this.slicePanelBodyRef?.nativeElement;
439
+ if (!container)
440
+ return;
441
+ const row = container.querySelector(`[data-slice-index="${sliceIndex}"]`);
442
+ if (!row)
443
+ return;
444
+ row.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
445
+ }, 0);
446
+ }
447
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SlicePanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
448
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: SlicePanelComponent, isStandalone: true, selector: "slice-panel", inputs: { sliceConfigs: "sliceConfigs", columns: "columns", data: "data", lazyData: "lazyData", lazyStatus: "lazyStatus", showToggle: "showToggle", showCounts: "showCounts", collapsedByDefault: "collapsedByDefault", alwaysShow: "alwaysShow" }, outputs: { filterChange: "filterChange", activeKeysChange: "activeKeysChange", dynamicSliceKeysChange: "dynamicSliceKeysChange", collapsedChange: "collapsedChange" }, viewQueries: [{ propertyName: "slicePanelBodyRef", first: true, predicate: ["slicePanelBody"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"slice-panel-wrapper\" [class.collapsed]=\"isCollapsed\">\n <aside class=\"slice-panel\" [class.collapsed]=\"isCollapsed\">\n <div class=\"slice-panel-header\">\n <span *ngIf=\"!isCollapsed\" class=\"slice-panel-title\">\n <mat-icon class=\"slice-panel-title-icon\" fontSet=\"material-icons-outlined\">filter_list</mat-icon>\n {{ slices.length > 1 ? 'Filtres' : 'Filtre' }}\n </span>\n <button\n *ngIf=\"showToggle\"\n type=\"button\"\n class=\"slice-toggle\"\n [attr.aria-label]=\"isCollapsed ? 'D\u00E9plier le filtre' : 'Replier le filtre'\"\n (click)=\"togglePanel()\"\n >\n <span class=\"slice-toggle-icon\">{{ isCollapsed ? '\u203A' : '\u2039' }}</span>\n </button>\n </div>\n\n <div class=\"slice-panel-body\" *ngIf=\"!isCollapsed\" #slicePanelBody>\n <ng-container *ngFor=\"let slice of slices; let si = index; trackBy: trackBySliceIndex\">\n <div class=\"slice-title-row\" [attr.data-slice-index]=\"si\" (click)=\"toggleSliceCollapse(si)\">\n <span class=\"slice-title-chevron\" [class.expanded]=\"!isSliceCollapsed(si)\">\u203A</span>\n <mat-icon *ngIf=\"getSliceIcon(slice)\" class=\"slice-title-icon\" fontSet=\"material-icons-outlined\">{{ getSliceIcon(slice) }}</mat-icon>\n <p class=\"slice-title\">{{ slice.title || 'Filtrer' }}</p>\n <button\n *ngIf=\"isSliceDynamic(si)\"\n type=\"button\"\n class=\"slice-remove-btn\"\n (click)=\"$event.stopPropagation(); removeDynamicSlice(si)\"\n >\u00D7</button>\n </div>\n\n <div class=\"slice-section-items\" *ngIf=\"!isSliceCollapsed(si)\">\n <ng-container *ngIf=\"isSliceLoading(si); else sliceContent\">\n <div class=\"slice-lazy-loading\">\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n </div>\n </ng-container>\n <ng-template #sliceContent>\n <ng-container *ngFor=\"let category of slice.categories; trackBy: trackByCategoryKey\">\n <button\n *ngIf=\"sliceCategoryCount(si, category.key) > 0\"\n class=\"slice-item\"\n type=\"button\"\n [class.active]=\"isSliceCategoryActive(si, category.key)\"\n [attr.data-category-key]=\"category.key\"\n [title]=\"category.label\"\n (click)=\"onSliceCategorySelected(si, category.key)\"\n >\n <span>{{ category.label }}</span>\n <span *ngIf=\"showCounts\">{{ sliceCategoryCount(si, category.key) }}</span>\n </button>\n </ng-container>\n <div *ngIf=\"isSliceEmpty(si)\" class=\"slice-empty-state\">Aucune donn\u00E9e</div>\n </ng-template>\n </div>\n\n <hr *ngIf=\"si < slices.length - 1\" class=\"slice-divider\" />\n </ng-container>\n </div>\n </aside>\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;box-sizing:border-box}.slice-panel-wrapper{height:100%;max-height:100%;overflow-y:auto;box-sizing:border-box}.slice-panel{margin-top:0;border:1px solid var(--jqt-border-color);border-radius:10px;background:var(--jqt-surface-color);display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden;box-sizing:border-box}.slice-panel-header{flex-shrink:0;display:flex;justify-content:flex-end;align-items:center;height:34px;padding:0 8px;margin-bottom:8px}.slice-panel-header:has(.slice-panel-title){justify-content:space-between;padding-left:12px}.slice-panel-body{flex:1 1 0;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:4px;padding:4px 10px 10px;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-panel-body::-webkit-scrollbar{width:2px}.slice-panel-body::-webkit-scrollbar-thumb{background:var(--jqt-primary-color);border-radius:4px}.slice-panel-title{margin:0;padding-top:2px;font-size:11px;font-weight:700;line-height:1;text-transform:uppercase;letter-spacing:.08em;color:var(--jqt-text-color);-webkit-user-select:none;user-select:none;align-self:center;display:inline-flex;align-items:center;gap:5px}.slice-panel-title .slice-panel-title-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0}.slice-section-items{display:flex;flex-direction:column;gap:2px;flex:0 0 auto;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-section-items::-webkit-scrollbar{width:2px}.slice-section-items .slice-empty-state{padding:8px 10px;font-size:12px;color:var(--jqt-text-muted, #9ca3af);font-style:italic;text-align:center}.slice-section-items::-webkit-scrollbar-thumb{background:var(--jqt-border-color);border-radius:4px}.slice-toggle{border:0;background:var(--jqt-surface-color);color:var(--jqt-text-color);border-radius:6px;width:22px;height:22px;padding:0;font-size:14px;text-align:center;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;line-height:1}.slice-toggle-icon{width:100%;height:100%;display:inline-flex;align-items:center;justify-content:center;line-height:1}.slice-panel.collapsed{border-color:transparent;background:transparent;overflow:visible}.slice-panel.collapsed .slice-panel-header{height:34px;padding:0;justify-content:center;align-items:center}.slice-panel.collapsed .slice-toggle{width:34px;height:34px;border:1px solid var(--jqt-border-color);background:var(--jqt-surface-color);border-radius:8px;font-size:16px}.slice-panel.collapsed .slice-toggle:hover{border-color:var(--jqt-text-muted)}.slice-title-row{display:flex;align-items:center;justify-content:space-between;gap:6px;margin:4px 0 6px;cursor:pointer;-webkit-user-select:none;user-select:none}.slice-title-row:hover .slice-title{color:var(--jqt-text-color)}.slice-title-row:hover .slice-title-chevron{color:var(--jqt-text-secondary)}.slice-title-row:first-of-type .slice-title{margin-top:0}.slice-title-chevron{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;font-size:16px;line-height:1;color:var(--jqt-text-muted);transition:transform .15s ease;transform:rotate(0);position:relative;top:-1px}.slice-title-chevron.expanded{transform:rotate(90deg)}.slice-title{flex:1 1 auto;margin:0;font-size:14px;font-weight:600;color:var(--jqt-title-color)}.slice-title-icon{flex-shrink:0;font-size:14px!important;width:14px!important;height:14px!important;color:var(--jqt-title-color)}.slice-remove-btn{background:none;border:none;cursor:pointer;color:var(--jqt-border-color);font-size:12px;padding:0 2px;line-height:1;flex-shrink:0;opacity:.5;transition:opacity .15s,color .15s}.slice-remove-btn:hover{color:var(--jqt-text-secondary);opacity:1}.slice-title-row:hover .slice-remove-btn{opacity:.8}.slice-item{display:flex;justify-content:space-between;align-items:center;text-align:left;width:100%;padding:5px 10px 5px 16px;background:transparent;border:0;cursor:pointer;border-radius:8px;color:var(--jqt-text-color);box-sizing:border-box;min-width:0;gap:8px}.slice-item.active{background:var(--jqt-row-hover-bg);font-weight:600}.slice-item.slice-multi{padding-left:6px}.slice-item span:first-child{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.slice-item span:last-child{flex:0 0 auto;min-width:1.2em;text-align:right}.slice-check-box{flex:0 0 16px;width:16px;height:16px;border:1.5px solid var(--jqt-text-muted);border-radius:4px;background:var(--jqt-surface-color);display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;transition:background .15s,border-color .15s}.slice-check-box.checked{background:var(--jqt-text-secondary);border-color:var(--jqt-text-secondary)}.slice-check-box.checked:after{content:\"\";display:block;width:9px;height:5px;border-left:2px solid #fff;border-bottom:2px solid #fff;transform:rotate(-45deg) translateY(-1px)}.slice-divider{border:none;border-top:1px solid var(--jqt-border-color);margin:6px 0}.lazy-skeleton{display:block;height:12px;width:70%;border-radius:4px;background:linear-gradient(90deg,var(--jqt-border-color) 25%,var(--jqt-surface-alt-color) 50%,var(--jqt-border-color) 75%);background-size:200% 100%;animation:lazy-shimmer 1.4s ease-in-out infinite}.slice-lazy-loading{display:flex;flex-direction:column;gap:8px;padding:8px 10px}.slice-lazy-loading .lazy-skeleton{width:80%}.slice-lazy-loading .lazy-skeleton:nth-child(2){width:60%}.slice-lazy-loading .lazy-skeleton:nth-child(3){width:45%}@keyframes lazy-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}\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: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
449
+ }
450
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SlicePanelComponent, decorators: [{
451
+ type: Component,
452
+ args: [{ standalone: true, selector: 'slice-panel', imports: [CommonModule, MatIconModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"slice-panel-wrapper\" [class.collapsed]=\"isCollapsed\">\n <aside class=\"slice-panel\" [class.collapsed]=\"isCollapsed\">\n <div class=\"slice-panel-header\">\n <span *ngIf=\"!isCollapsed\" class=\"slice-panel-title\">\n <mat-icon class=\"slice-panel-title-icon\" fontSet=\"material-icons-outlined\">filter_list</mat-icon>\n {{ slices.length > 1 ? 'Filtres' : 'Filtre' }}\n </span>\n <button\n *ngIf=\"showToggle\"\n type=\"button\"\n class=\"slice-toggle\"\n [attr.aria-label]=\"isCollapsed ? 'D\u00E9plier le filtre' : 'Replier le filtre'\"\n (click)=\"togglePanel()\"\n >\n <span class=\"slice-toggle-icon\">{{ isCollapsed ? '\u203A' : '\u2039' }}</span>\n </button>\n </div>\n\n <div class=\"slice-panel-body\" *ngIf=\"!isCollapsed\" #slicePanelBody>\n <ng-container *ngFor=\"let slice of slices; let si = index; trackBy: trackBySliceIndex\">\n <div class=\"slice-title-row\" [attr.data-slice-index]=\"si\" (click)=\"toggleSliceCollapse(si)\">\n <span class=\"slice-title-chevron\" [class.expanded]=\"!isSliceCollapsed(si)\">\u203A</span>\n <mat-icon *ngIf=\"getSliceIcon(slice)\" class=\"slice-title-icon\" fontSet=\"material-icons-outlined\">{{ getSliceIcon(slice) }}</mat-icon>\n <p class=\"slice-title\">{{ slice.title || 'Filtrer' }}</p>\n <button\n *ngIf=\"isSliceDynamic(si)\"\n type=\"button\"\n class=\"slice-remove-btn\"\n (click)=\"$event.stopPropagation(); removeDynamicSlice(si)\"\n >\u00D7</button>\n </div>\n\n <div class=\"slice-section-items\" *ngIf=\"!isSliceCollapsed(si)\">\n <ng-container *ngIf=\"isSliceLoading(si); else sliceContent\">\n <div class=\"slice-lazy-loading\">\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n </div>\n </ng-container>\n <ng-template #sliceContent>\n <ng-container *ngFor=\"let category of slice.categories; trackBy: trackByCategoryKey\">\n <button\n *ngIf=\"sliceCategoryCount(si, category.key) > 0\"\n class=\"slice-item\"\n type=\"button\"\n [class.active]=\"isSliceCategoryActive(si, category.key)\"\n [attr.data-category-key]=\"category.key\"\n [title]=\"category.label\"\n (click)=\"onSliceCategorySelected(si, category.key)\"\n >\n <span>{{ category.label }}</span>\n <span *ngIf=\"showCounts\">{{ sliceCategoryCount(si, category.key) }}</span>\n </button>\n </ng-container>\n <div *ngIf=\"isSliceEmpty(si)\" class=\"slice-empty-state\">Aucune donn\u00E9e</div>\n </ng-template>\n </div>\n\n <hr *ngIf=\"si < slices.length - 1\" class=\"slice-divider\" />\n </ng-container>\n </div>\n </aside>\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;box-sizing:border-box}.slice-panel-wrapper{height:100%;max-height:100%;overflow-y:auto;box-sizing:border-box}.slice-panel{margin-top:0;border:1px solid var(--jqt-border-color);border-radius:10px;background:var(--jqt-surface-color);display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden;box-sizing:border-box}.slice-panel-header{flex-shrink:0;display:flex;justify-content:flex-end;align-items:center;height:34px;padding:0 8px;margin-bottom:8px}.slice-panel-header:has(.slice-panel-title){justify-content:space-between;padding-left:12px}.slice-panel-body{flex:1 1 0;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:4px;padding:4px 10px 10px;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-panel-body::-webkit-scrollbar{width:2px}.slice-panel-body::-webkit-scrollbar-thumb{background:var(--jqt-primary-color);border-radius:4px}.slice-panel-title{margin:0;padding-top:2px;font-size:11px;font-weight:700;line-height:1;text-transform:uppercase;letter-spacing:.08em;color:var(--jqt-text-color);-webkit-user-select:none;user-select:none;align-self:center;display:inline-flex;align-items:center;gap:5px}.slice-panel-title .slice-panel-title-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0}.slice-section-items{display:flex;flex-direction:column;gap:2px;flex:0 0 auto;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-section-items::-webkit-scrollbar{width:2px}.slice-section-items .slice-empty-state{padding:8px 10px;font-size:12px;color:var(--jqt-text-muted, #9ca3af);font-style:italic;text-align:center}.slice-section-items::-webkit-scrollbar-thumb{background:var(--jqt-border-color);border-radius:4px}.slice-toggle{border:0;background:var(--jqt-surface-color);color:var(--jqt-text-color);border-radius:6px;width:22px;height:22px;padding:0;font-size:14px;text-align:center;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;line-height:1}.slice-toggle-icon{width:100%;height:100%;display:inline-flex;align-items:center;justify-content:center;line-height:1}.slice-panel.collapsed{border-color:transparent;background:transparent;overflow:visible}.slice-panel.collapsed .slice-panel-header{height:34px;padding:0;justify-content:center;align-items:center}.slice-panel.collapsed .slice-toggle{width:34px;height:34px;border:1px solid var(--jqt-border-color);background:var(--jqt-surface-color);border-radius:8px;font-size:16px}.slice-panel.collapsed .slice-toggle:hover{border-color:var(--jqt-text-muted)}.slice-title-row{display:flex;align-items:center;justify-content:space-between;gap:6px;margin:4px 0 6px;cursor:pointer;-webkit-user-select:none;user-select:none}.slice-title-row:hover .slice-title{color:var(--jqt-text-color)}.slice-title-row:hover .slice-title-chevron{color:var(--jqt-text-secondary)}.slice-title-row:first-of-type .slice-title{margin-top:0}.slice-title-chevron{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;font-size:16px;line-height:1;color:var(--jqt-text-muted);transition:transform .15s ease;transform:rotate(0);position:relative;top:-1px}.slice-title-chevron.expanded{transform:rotate(90deg)}.slice-title{flex:1 1 auto;margin:0;font-size:14px;font-weight:600;color:var(--jqt-title-color)}.slice-title-icon{flex-shrink:0;font-size:14px!important;width:14px!important;height:14px!important;color:var(--jqt-title-color)}.slice-remove-btn{background:none;border:none;cursor:pointer;color:var(--jqt-border-color);font-size:12px;padding:0 2px;line-height:1;flex-shrink:0;opacity:.5;transition:opacity .15s,color .15s}.slice-remove-btn:hover{color:var(--jqt-text-secondary);opacity:1}.slice-title-row:hover .slice-remove-btn{opacity:.8}.slice-item{display:flex;justify-content:space-between;align-items:center;text-align:left;width:100%;padding:5px 10px 5px 16px;background:transparent;border:0;cursor:pointer;border-radius:8px;color:var(--jqt-text-color);box-sizing:border-box;min-width:0;gap:8px}.slice-item.active{background:var(--jqt-row-hover-bg);font-weight:600}.slice-item.slice-multi{padding-left:6px}.slice-item span:first-child{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.slice-item span:last-child{flex:0 0 auto;min-width:1.2em;text-align:right}.slice-check-box{flex:0 0 16px;width:16px;height:16px;border:1.5px solid var(--jqt-text-muted);border-radius:4px;background:var(--jqt-surface-color);display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;transition:background .15s,border-color .15s}.slice-check-box.checked{background:var(--jqt-text-secondary);border-color:var(--jqt-text-secondary)}.slice-check-box.checked:after{content:\"\";display:block;width:9px;height:5px;border-left:2px solid #fff;border-bottom:2px solid #fff;transform:rotate(-45deg) translateY(-1px)}.slice-divider{border:none;border-top:1px solid var(--jqt-border-color);margin:6px 0}.lazy-skeleton{display:block;height:12px;width:70%;border-radius:4px;background:linear-gradient(90deg,var(--jqt-border-color) 25%,var(--jqt-surface-alt-color) 50%,var(--jqt-border-color) 75%);background-size:200% 100%;animation:lazy-shimmer 1.4s ease-in-out infinite}.slice-lazy-loading{display:flex;flex-direction:column;gap:8px;padding:8px 10px}.slice-lazy-loading .lazy-skeleton{width:80%}.slice-lazy-loading .lazy-skeleton:nth-child(2){width:60%}.slice-lazy-loading .lazy-skeleton:nth-child(3){width:45%}@keyframes lazy-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}\n"] }]
453
+ }], propDecorators: { sliceConfigs: [{
454
+ type: Input
455
+ }], columns: [{
456
+ type: Input
457
+ }], data: [{
458
+ type: Input
459
+ }], lazyData: [{
460
+ type: Input
461
+ }], lazyStatus: [{
462
+ type: Input
463
+ }], showToggle: [{
464
+ type: Input
465
+ }], showCounts: [{
466
+ type: Input
467
+ }], collapsedByDefault: [{
468
+ type: Input
469
+ }], alwaysShow: [{
470
+ type: Input
471
+ }], filterChange: [{
472
+ type: Output
473
+ }], activeKeysChange: [{
474
+ type: Output
475
+ }], dynamicSliceKeysChange: [{
476
+ type: Output
477
+ }], collapsedChange: [{
478
+ type: Output
479
+ }], slicePanelBodyRef: [{
480
+ type: ViewChild,
481
+ args: ['slicePanelBody']
482
+ }] } });
483
+
484
+ /** Traductions françaises du paginator Material. */
485
+ function getFrenchPaginatorIntl() {
486
+ const intl = new MatPaginatorIntl();
487
+ intl.itemsPerPageLabel = 'Éléments par page :';
488
+ intl.nextPageLabel = 'Page suivante';
489
+ intl.previousPageLabel = 'Page précédente';
490
+ intl.firstPageLabel = 'Première page';
491
+ intl.lastPageLabel = 'Dernière page';
492
+ intl.getRangeLabel = (page, pageSize, length) => {
493
+ if (length === 0 || pageSize === 0)
494
+ return `0 sur ${length}`;
495
+ length = Math.max(length, 0);
496
+ const start = page * pageSize;
497
+ const end = start < length ? Math.min(start + pageSize, length) : start + pageSize;
498
+ return `${start + 1} – ${end} sur ${length}`;
499
+ };
500
+ return intl;
501
+ }
502
+ /**
503
+ * Convertit une valeur brute en chaîne affichable.
504
+ * - null/undefined → ''
505
+ * - Date → format locale fr-FR
506
+ * - Tableau → valeurs jointes par ', '
507
+ * - Objet avec label/name → sa valeur string
508
+ * - Autres → String(value)
509
+ */
510
+ function normalizeCellValue(value) {
511
+ if (value === null || value === undefined) {
512
+ return '';
513
+ }
514
+ if (value instanceof Date) {
515
+ return value.toLocaleString('fr-FR');
516
+ }
517
+ if (Array.isArray(value)) {
518
+ return value.map((item) => normalizeCellValue(item)).join(', ');
519
+ }
520
+ if (typeof value === 'object') {
521
+ if ('label' in value && value.label !== undefined) {
522
+ return String(value.label);
523
+ }
524
+ if ('name' in value && value.name !== undefined) {
525
+ return String(value.name);
526
+ }
527
+ return JSON.stringify(value);
528
+ }
529
+ return value;
530
+ }
531
+ /**
532
+ * Transforme une clé camelCase/snake_case en libellé lisible.
533
+ * Ex: 'createdAt' → 'Created at', 'my_field' → 'My field'
534
+ */
535
+ function humanizeKey(key) {
536
+ return key
537
+ .replaceAll(/([a-z0-9])([A-Z])/g, '$1 $2')
538
+ .replaceAll(/[_-]+/g, ' ')
539
+ .replace(/^./, (value) => value.toUpperCase());
540
+ }
541
+
542
+ class TableComponent {
543
+ constructor() {
544
+ this.isLoading = false;
545
+ this.addRequested = new EventEmitter();
546
+ this.columnAdded = new EventEmitter();
547
+ this.columnRemoved = new EventEmitter();
548
+ this.categorySelected = new EventEmitter();
549
+ this.rowSelected = new EventEmitter();
550
+ this.renderedColumns = [];
551
+ this.tableDataSource = new MatTableDataSource([]);
552
+ this.activeColumns = [];
553
+ this.activeSliceFilter = () => true;
554
+ // Propriétés stables pour les bindings du template (contre erreur NG0100)
555
+ this._sliceConfigs = [];
556
+ this._sliceColumns = [];
557
+ this._sliceData = [];
558
+ this._sliceShowToggle = true;
559
+ this._staticSlicesForMenu = [];
560
+ this._activeDynamicSliceColumns = [];
561
+ this._availableColumnsForDynamicSlice = [];
562
+ this._allDynamicSliceColumns = [];
563
+ this._activeSliceByLabel = 'Aucun';
564
+ this.searchQuery = '';
565
+ this.activeGroupByKey = null;
566
+ this._matSortActive = '';
567
+ this._matSortDirection = '';
568
+ this._collapsedGroups = new Set();
569
+ this._activeSort = { active: '', direction: '' };
570
+ this._sortSubscribed = null;
571
+ this._destroy$ = new Subject();
572
+ this._defaultGroupCollapsed = false;
573
+ this._groupPages = new Map();
574
+ this._totalFilteredCount = 0;
575
+ this._lazyColumnStatus = new Map();
576
+ this._lazyColumnData = new Map();
577
+ this._lazyFetchCancels = new Map();
578
+ this._cdr = inject(ChangeDetectorRef);
579
+ this._el = inject((ElementRef));
580
+ this.isGroupHeader = (_, row) => row.__groupHeader === true;
581
+ // ── Lifecycle ──────────────────────────────────────────────────────────────
582
+ this.pageSize = 5;
583
+ this.pageSizeOptions = [5, 10, 20];
584
+ this.pageSizeOptionsGroupBy = [5, 10, 20];
585
+ this.groupPageSize = 5;
586
+ this.groupSortOrder = 'asc';
587
+ this.draggedColumnKey = null;
588
+ this.dragOverColumnKey = null;
589
+ this.userCustomizedColumns = false;
590
+ this.resolvedConfig = { columns: [] };
591
+ this._columnMap = new Map();
592
+ this._resolvedData = [];
593
+ this._preservePageIndex = null;
594
+ this._initialSearchApplied = false;
595
+ // ── Optimisation performance ─────────────────────────────────────────────
596
+ this._allFilteredRows = [];
597
+ this._paginatorSubscribed = null;
598
+ // Cache groupBy : invalide uniquement quand les données/tri/groupKey changent
599
+ this._groupCacheRows = null;
600
+ this._groupCacheKey = null;
601
+ this._groupCacheSortOrder = 'asc';
602
+ this._groupCacheSortActive = '';
603
+ this._groupCacheSortDir = '';
604
+ this._groupCacheOrder = [];
605
+ this._groupCacheMap = new Map();
606
+ this._pendingRender = null;
607
+ this.trackRowFn = (_index, row) => row.__groupHeader ? `__group_${row.__groupKey}` : row.__index ?? _index;
608
+ }
609
+ set paginator(value) {
610
+ this._paginator = value;
611
+ if (value) {
612
+ this.attachPaginator();
613
+ this._scheduleRender();
614
+ }
615
+ }
616
+ get paginator() { return this._paginator; }
617
+ // Seuil au-delà duquel le rendu groupé est bloqué pour protéger le navigateur
618
+ static { this.MAX_GROUP_COUNT = 500; }
619
+ ngOnChanges(changes) {
620
+ const dataChanged = !!(changes['config'] || changes['data'] || changes['dataSource']);
621
+ if (dataChanged) {
622
+ this._lazyFetchCancels.forEach(s => { s.next(); s.complete(); });
623
+ this._lazyFetchCancels.clear();
624
+ this._lazyColumnStatus = new Map();
625
+ this._lazyColumnData = new Map();
626
+ }
627
+ if (changes['config'] && !this._initialSearchApplied) {
628
+ const initial = this.config?.search?.initialQuery;
629
+ if (initial != null) {
630
+ this.searchQuery = initial;
631
+ this._initialSearchApplied = true;
632
+ }
633
+ }
634
+ this.refreshViewModel();
635
+ if (dataChanged) {
636
+ this.activeColumns.filter(c => c.lazy).forEach(c => this.triggerLazyFetch(c));
637
+ }
638
+ }
639
+ ngAfterViewInit() {
640
+ this.attachPaginator();
641
+ this.attachSort();
642
+ this._measureHeaderHeight();
643
+ }
644
+ ngOnDestroy() {
645
+ if (this._pendingRender !== null) {
646
+ clearTimeout(this._pendingRender);
647
+ }
648
+ this._destroy$.next();
649
+ this._destroy$.complete();
650
+ }
651
+ _measureHeaderHeight() {
652
+ const headerRow = this._el.nativeElement.querySelector('tr.mat-mdc-header-row');
653
+ if (headerRow) {
654
+ const h = headerRow.getBoundingClientRect().height;
655
+ if (h > 0) {
656
+ this._el.nativeElement.style.setProperty('--actual-header-height', h + 'px');
657
+ }
658
+ }
659
+ }
660
+ // ── Getters d'affichage ────────────────────────────────────────────────────
661
+ get title() {
662
+ return this.resolvedConfig.title ?? '';
663
+ }
664
+ get showTitle() {
665
+ return !!this.resolvedConfig.title;
666
+ }
667
+ get visibleColumns() {
668
+ return this.activeColumns;
669
+ }
670
+ get showSearchBar() { return this.resolvedConfig.search?.enabled === true; }
671
+ get showViewButton() { return this.resolvedConfig.view?.enabled === true; }
672
+ get showToolbar() { return this.showSearchBar || this.showViewButton; }
673
+ get showFields() { return this.showViewButton; }
674
+ get showGroupBySection() { return this.showViewButton && this.groupByColumns.length > 0; }
675
+ get filteredRowCount() {
676
+ return this.activeGroupByKey ? this._totalFilteredCount : this._allFilteredRows.length;
677
+ }
678
+ get activeGroupByLabel() {
679
+ if (!this.activeGroupByKey)
680
+ return 'Aucun';
681
+ const col = this.groupByColumns.find((c) => c.key === this.activeGroupByKey);
682
+ return col?.header || this.activeGroupByKey;
683
+ }
684
+ get totalColumnCount() {
685
+ return new Set((this.resolvedConfig.columns || []).map((c) => c.key)).size;
686
+ }
687
+ get activeSliceByLabel() {
688
+ return this._activeSliceByLabel;
689
+ }
690
+ get activeDynamicSliceColumns() {
691
+ return this._activeDynamicSliceColumns;
692
+ }
693
+ removeDynamicSliceByKey(key) {
694
+ this.slicePanelRef?.removeDynamicSliceByKey(key);
695
+ }
696
+ get groupByColumns() {
697
+ const all = this.resolvedConfig.columns || [];
698
+ const seen = new Set();
699
+ return all.filter((c) => {
700
+ if (c.groupable === false || seen.has(c.key))
701
+ return false;
702
+ if (!c.header && c.groupable !== true)
703
+ return false;
704
+ seen.add(c.key);
705
+ return true;
706
+ });
707
+ }
708
+ colLabel(col) {
709
+ return col.header || humanizeKey(col.key);
710
+ }
711
+ // ── Actions utilisateur ────────────────────────────────────────────────────
712
+ onSearchChange() {
713
+ this._preservePageIndex = 0;
714
+ this.refreshViewModel();
715
+ this.paginator?.firstPage();
716
+ }
717
+ onGroupByChange(key) {
718
+ this.activeGroupByKey = key;
719
+ this._defaultGroupCollapsed = key !== null;
720
+ this._collapsedGroups.clear();
721
+ this._groupPages.clear();
722
+ this.groupPageSize = this.resolveDefaultGroupPageSize();
723
+ this.refreshViewModel();
724
+ this.paginator?.firstPage();
725
+ if (key) {
726
+ this.viewMenuTrigger?.closeMenu();
727
+ const colDef = (this.resolvedConfig.columns || []).find(c => c.key === key);
728
+ if (colDef?.lazy && this._lazyColumnStatus.get(key) !== 'loaded') {
729
+ this.triggerLazyFetch(colDef);
730
+ }
731
+ }
732
+ else {
733
+ setTimeout(() => {
734
+ this.tableBodyScrollRef?.nativeElement.scrollTo({ top: 0, behavior: 'smooth' });
735
+ }, 0);
736
+ }
737
+ }
738
+ onGroupPageSizeChange(size) {
739
+ this.groupPageSize = Number(size);
740
+ this._groupPages.clear();
741
+ this.refreshViewModel();
742
+ }
743
+ onGroupSortToggle(dir) {
744
+ if (this.groupSortOrder === dir)
745
+ return;
746
+ this.groupSortOrder = dir;
747
+ this._groupPages.clear();
748
+ this.refreshViewModel();
749
+ const openGroup = this._defaultGroupCollapsed
750
+ ? (this._collapsedGroups.size === 1 ? [...this._collapsedGroups][0] : null)
751
+ : null;
752
+ if (openGroup) {
753
+ setTimeout(() => this.scrollToGroupHeader(openGroup), 0);
754
+ }
755
+ }
756
+ // ── Lazy loading ─────────────────────────────────────────────────────────
757
+ getLazyColumnStatus(key) {
758
+ return this._lazyColumnStatus.get(key) ?? 'idle';
759
+ }
760
+ getLazyRenderType(rowValue) {
761
+ if (rowValue === '__lazy_loading__')
762
+ return 'loading';
763
+ if (rowValue === '__lazy_error__')
764
+ return 'error';
765
+ return 'value';
766
+ }
767
+ retryLazyColumn(column) {
768
+ const k = column.key;
769
+ const prev = this._lazyFetchCancels.get(k);
770
+ if (prev) {
771
+ prev.next();
772
+ prev.complete();
773
+ }
774
+ this._lazyFetchCancels.delete(k);
775
+ const newStatus = new Map(this._lazyColumnStatus);
776
+ newStatus.delete(k);
777
+ this._lazyColumnStatus = newStatus;
778
+ const newData = new Map(this._lazyColumnData);
779
+ newData.delete(k);
780
+ this._lazyColumnData = newData;
781
+ this.triggerLazyFetch(column);
782
+ }
783
+ triggerLazyFetch(column) {
784
+ if (!column.lazy)
785
+ return;
786
+ const k = column.key;
787
+ if (this._lazyColumnStatus.get(k) === 'loading')
788
+ return;
789
+ const prev = this._lazyFetchCancels.get(k);
790
+ if (prev) {
791
+ prev.next();
792
+ prev.complete();
793
+ }
794
+ const cancel$ = new Subject();
795
+ this._lazyFetchCancels.set(k, cancel$);
796
+ const newStatus1 = new Map(this._lazyColumnStatus);
797
+ newStatus1.set(k, 'loading');
798
+ this._lazyColumnStatus = newStatus1;
799
+ this.refreshViewModel();
800
+ column.lazy.fetchFn().pipe(take(1), takeUntil(cancel$)).subscribe({
801
+ next: (values) => {
802
+ const rowMap = new Map();
803
+ const data = this._resolvedData;
804
+ values.forEach((value, i) => {
805
+ if (i < data.length)
806
+ rowMap.set(data[i], value);
807
+ });
808
+ const newData = new Map(this._lazyColumnData);
809
+ newData.set(k, rowMap);
810
+ this._lazyColumnData = newData;
811
+ const newStatus2 = new Map(this._lazyColumnStatus);
812
+ newStatus2.set(k, 'loaded');
813
+ this._lazyColumnStatus = newStatus2;
814
+ this.refreshViewModel();
815
+ this._cdr.markForCheck();
816
+ },
817
+ error: () => {
818
+ const newStatusErr = new Map(this._lazyColumnStatus);
819
+ newStatusErr.set(k, 'error');
820
+ this._lazyColumnStatus = newStatusErr;
821
+ this.refreshViewModel();
822
+ this._cdr.markForCheck();
823
+ },
824
+ });
825
+ }
826
+ // ── Groupement ────────────────────────────────────────────────────────────
827
+ toggleGroupCollapse(groupKey) {
828
+ if (this._collapsedGroups.has(groupKey)) {
829
+ this._collapsedGroups.delete(groupKey);
830
+ }
831
+ else {
832
+ this._collapsedGroups.clear();
833
+ this._collapsedGroups.add(groupKey);
834
+ setTimeout(() => this.scrollToGroupHeader(groupKey), 0);
835
+ }
836
+ // Court-circuit de refreshViewModel : la data et les colonnes n'ont pas changé,
837
+ // seul l'état collapsed change — reprojection directe sans rebuild complet
838
+ this._projectCurrentPage();
839
+ this._cdr.markForCheck();
840
+ }
841
+ scrollToGroupHeader(groupKey) {
842
+ const container = this.tableBodyScrollRef?.nativeElement;
843
+ if (!container)
844
+ return;
845
+ const row = container.querySelector(`tr[data-group-key="${CSS.escape(groupKey)}"]`);
846
+ if (!row)
847
+ return;
848
+ const headerHeight = parseFloat(this._el.nativeElement.style.getPropertyValue('--actual-header-height') || '56') || 56;
849
+ const containerRect = container.getBoundingClientRect();
850
+ const rowRect = row.getBoundingClientRect();
851
+ const currentScrollTop = container.scrollTop;
852
+ const targetScrollTop = currentScrollTop + (rowRect.top - containerRect.top) - headerHeight;
853
+ container.scrollTo({ top: targetScrollTop, behavior: 'smooth' });
854
+ }
855
+ isGroupCollapsed(groupKey) {
856
+ return this._defaultGroupCollapsed ? !this._collapsedGroups.has(groupKey) : this._collapsedGroups.has(groupKey);
857
+ }
858
+ groupPageBack(groupKey) {
859
+ const current = this._groupPages.get(groupKey) ?? 0;
860
+ if (current > 0) {
861
+ this._groupPages.set(groupKey, current - 1);
862
+ this._projectCurrentPage();
863
+ this._cdr.markForCheck();
864
+ }
865
+ }
866
+ groupPageForward(groupKey, totalCount) {
867
+ const maxPage = Math.ceil(totalCount / this.groupPageSize) - 1;
868
+ const current = this._groupPages.get(groupKey) ?? 0;
869
+ if (current < maxPage) {
870
+ this._groupPages.set(groupKey, current + 1);
871
+ this._projectCurrentPage();
872
+ this._cdr.markForCheck();
873
+ }
874
+ }
875
+ // ── Colonnes ──────────────────────────────────────────────────────────────
876
+ get hasAddableColumns() {
877
+ return this.menuBaseColumns.length > 0 || this.menuOptionalColumns.length > 0;
878
+ }
879
+ get menuBaseColumns() {
880
+ return this.uniqueColumnsByKey((this.resolvedConfig.columns || []).filter((c) => !c.optional));
881
+ }
882
+ get menuOptionalColumns() {
883
+ return this.uniqueColumnsByKey((this.resolvedConfig.columns || []).filter((c) => c.optional));
884
+ }
885
+ get availableColumnsToAdd() {
886
+ const activeKeys = new Set(this.activeColumns.map((column) => column.key));
887
+ return (this.resolvedConfig.columns || []).filter((c) => c.optional && !activeKeys.has(c.key));
888
+ }
889
+ get allowColumnRemoval() {
890
+ return this.resolvedConfig.view?.enableColumnRemoval !== false;
891
+ }
892
+ // ── Slices ────────────────────────────────────────────────────────────────
893
+ get hasSliceConfig() {
894
+ return (this.resolvedConfig.slices?.length ?? 0) > 0;
895
+ }
896
+ get showSlicePanel() {
897
+ return this.slicePanelRef?.showPanel ?? (this.hasSliceConfig && this._resolvedData.length > 0);
898
+ }
899
+ get showAddSliceButton() {
900
+ return this.showViewButton && ((this.resolvedConfig.slices?.length ?? 0) > 0 || this._allDynamicSliceColumns.length > 0);
901
+ }
902
+ get staticSlicesForMenu() {
903
+ return this._staticSlicesForMenu;
904
+ }
905
+ get availableColumnsForDynamicSlice() {
906
+ return this._availableColumnsForDynamicSlice;
907
+ }
908
+ isStaticSliceVisible(columnKey) {
909
+ return this.slicePanelRef?.isStaticSliceVisible(columnKey) ?? true;
910
+ }
911
+ toggleStaticSlice(columnKey) {
912
+ this.slicePanelRef?.toggleStaticSlice(columnKey);
913
+ }
914
+ addDynamicSlice(column) {
915
+ this.slicePanelRef?.addDynamicSlice(column);
916
+ if (column.lazy && this._lazyColumnStatus.get(column.key) !== 'loaded') {
917
+ this.triggerLazyFetch(column);
918
+ }
919
+ }
920
+ get allDynamicSliceColumns() {
921
+ return this._allDynamicSliceColumns;
922
+ }
923
+ isDynamicSliceActive(key) {
924
+ return this._activeDynamicSliceColumns.some(c => c.key === key);
925
+ }
926
+ toggleDynamicSlice(col) {
927
+ if (this.isDynamicSliceActive(col.key)) {
928
+ this.removeDynamicSliceByKey(col.key);
929
+ }
930
+ else {
931
+ this.addDynamicSlice(col);
932
+ }
933
+ }
934
+ onSliceFilterChange(pred) {
935
+ this.activeSliceFilter = pred;
936
+ this._preservePageIndex = 0;
937
+ this.refreshViewModel();
938
+ this.paginator?.firstPage();
939
+ }
940
+ onDynamicSliceKeysChange(keys) {
941
+ const allCols = this.resolvedConfig.columns ?? [];
942
+ const staticKeys = new Set((this.resolvedConfig.slices ?? []).map(s => s.columnKey).filter((k) => !!k));
943
+ const dynamicKeySet = new Set(keys);
944
+ const existingKeys = new Set([...staticKeys, ...dynamicKeySet]);
945
+ this._activeDynamicSliceColumns = keys
946
+ .map(k => allCols.find(c => c.key === k))
947
+ .filter((c) => !!c);
948
+ const seen = new Set();
949
+ this._availableColumnsForDynamicSlice = allCols.filter(c => {
950
+ if (c.sliceable === false || existingKeys.has(c.key) || seen.has(c.key))
951
+ return false;
952
+ seen.add(c.key);
953
+ return true;
954
+ });
955
+ this._computeSliceLabel();
956
+ this._cdr.markForCheck();
957
+ }
958
+ get emptyStateLabel() {
959
+ return this.resolvedConfig.labels?.empty || 'Aucune donnée';
960
+ }
961
+ get loadingStateLabel() {
962
+ return this.resolvedConfig.labels?.loading || 'Chargement des données...';
963
+ }
964
+ get effectiveIsLoading() {
965
+ return this.isLoading || this._pendingRender !== null;
966
+ }
967
+ get isEmpty() {
968
+ return !this.effectiveIsLoading && this._allFilteredRows.length === 0;
969
+ }
970
+ get showPagination() {
971
+ if (this.activeGroupByKey !== null)
972
+ return false;
973
+ return this.resolvedConfig.pagination?.enabled !== false;
974
+ }
975
+ get isColumnDragDropEnabled() {
976
+ return this.resolvedConfig.view?.enableColumnDragDrop === true;
977
+ }
978
+ get totalRows() {
979
+ return this.activeGroupByKey
980
+ ? this.tableDataSource.data.filter((r) => !r.__groupHeader).length
981
+ : this._allFilteredRows.length;
982
+ }
983
+ // ── Gestion des colonnes ─────────────────────────────────────────────────
984
+ onAddColumn(column) {
985
+ this.userCustomizedColumns = true;
986
+ const configCols = this.resolvedConfig.columns ?? [];
987
+ const configIndex = configCols.findIndex(c => c.key === column.key);
988
+ if (configIndex === -1) {
989
+ this.activeColumns = [...this.activeColumns, column];
990
+ }
991
+ else {
992
+ const insertBefore = this.activeColumns.findIndex(c => {
993
+ const ci = configCols.findIndex(cc => cc.key === c.key);
994
+ return ci !== -1 && ci > configIndex;
995
+ });
996
+ if (insertBefore === -1) {
997
+ this.activeColumns = [...this.activeColumns, column];
998
+ }
999
+ else {
1000
+ const copy = [...this.activeColumns];
1001
+ copy.splice(insertBefore, 0, column);
1002
+ this.activeColumns = copy;
1003
+ }
1004
+ }
1005
+ this._preservePageIndex = this.paginator?.pageIndex ?? null;
1006
+ this.refreshViewModel();
1007
+ if (column.lazy) {
1008
+ this.triggerLazyFetch(column);
1009
+ }
1010
+ this.addRequested.emit();
1011
+ this.columnAdded.emit(column);
1012
+ }
1013
+ onColumnVisibilityChange(column, checked) {
1014
+ const isVisible = this.isColumnVisible(column.key);
1015
+ if (checked && !isVisible) {
1016
+ this.onAddColumn(column);
1017
+ return;
1018
+ }
1019
+ if (!checked && isVisible) {
1020
+ if (!column.optional && !this.allowColumnRemoval) {
1021
+ return;
1022
+ }
1023
+ if (this.activeColumns.length <= 1) {
1024
+ return;
1025
+ }
1026
+ this.userCustomizedColumns = true;
1027
+ this.activeColumns = this.activeColumns.filter((item) => item.key !== column.key);
1028
+ this._preservePageIndex = this.paginator?.pageIndex ?? null;
1029
+ this.refreshViewModel();
1030
+ this.columnRemoved.emit(column);
1031
+ }
1032
+ }
1033
+ isColumnVisible(columnKey) {
1034
+ return this.activeColumns.some((column) => column.key === columnKey);
1035
+ }
1036
+ onRemoveColumn(columnKey, event) {
1037
+ event?.stopPropagation();
1038
+ if (!this.allowColumnRemoval) {
1039
+ return;
1040
+ }
1041
+ const column = this.activeColumns.find((item) => item.key === columnKey);
1042
+ if (!column || !this.canRemoveColumn(column)) {
1043
+ return;
1044
+ }
1045
+ this.userCustomizedColumns = true;
1046
+ this.activeColumns = this.activeColumns.filter((item) => item.key !== columnKey);
1047
+ this.refreshViewModel();
1048
+ this.columnRemoved.emit(column);
1049
+ }
1050
+ // ── Lignes ────────────────────────────────────────────────────────────────
1051
+ getCellTemplate(key) {
1052
+ return this._cellDefs?.find((d) => d.columnKey === key)?.template ?? null;
1053
+ }
1054
+ get hasCustomCellDefs() {
1055
+ return (this._cellDefs?.length ?? 0) > 0;
1056
+ }
1057
+ get hasRowClickHandler() {
1058
+ return this.rowSelected.observed || !!this.config?.onRowSelected;
1059
+ }
1060
+ getRowClass(row) {
1061
+ const fn = this.resolvedConfig.rowClass;
1062
+ if (!fn || row?.__groupHeader)
1063
+ return {};
1064
+ return fn(row.__raw ?? row, row.__index ?? 0);
1065
+ }
1066
+ onRowClick(row, event) {
1067
+ if (row?.__groupHeader)
1068
+ return;
1069
+ if (!this.hasRowClickHandler)
1070
+ return;
1071
+ const resolved = (row?.__raw ?? row);
1072
+ this.rowSelected.emit(resolved);
1073
+ this.config?.onRowSelected?.(resolved, event);
1074
+ }
1075
+ // ── Drag & drop ───────────────────────────────────────────────────────────
1076
+ onHeaderDragStart(columnKey, event) {
1077
+ if (!this.isColumnDragDropEnabled) {
1078
+ return;
1079
+ }
1080
+ this.draggedColumnKey = columnKey;
1081
+ this.dragOverColumnKey = null;
1082
+ if (event.dataTransfer) {
1083
+ event.dataTransfer.effectAllowed = 'move';
1084
+ event.dataTransfer.setData('text/plain', columnKey);
1085
+ }
1086
+ }
1087
+ onHeaderDragOver(columnKey, event) {
1088
+ if (!this.isColumnDragDropEnabled || !this.draggedColumnKey) {
1089
+ return;
1090
+ }
1091
+ event.preventDefault();
1092
+ this.dragOverColumnKey = columnKey;
1093
+ }
1094
+ onHeaderDrop(targetColumnKey, event) {
1095
+ if (!this.isColumnDragDropEnabled || !this.draggedColumnKey) {
1096
+ return;
1097
+ }
1098
+ event.preventDefault();
1099
+ const previousIndex = this.activeColumns.findIndex((column) => column.key === this.draggedColumnKey);
1100
+ const currentIndex = this.activeColumns.findIndex((column) => column.key === targetColumnKey);
1101
+ if (previousIndex === -1 || currentIndex === -1 || previousIndex === currentIndex) {
1102
+ this.onHeaderDragEnd();
1103
+ return;
1104
+ }
1105
+ this.userCustomizedColumns = true;
1106
+ const reordered = [...this.activeColumns];
1107
+ const [moved] = reordered.splice(previousIndex, 1);
1108
+ reordered.splice(currentIndex, 0, moved);
1109
+ this.activeColumns = reordered;
1110
+ this.refreshViewModel();
1111
+ this.onHeaderDragEnd();
1112
+ }
1113
+ onHeaderDragEnd() {
1114
+ this.draggedColumnKey = null;
1115
+ this.dragOverColumnKey = null;
1116
+ }
1117
+ isHeaderDropTarget(columnKey) {
1118
+ return !!this.draggedColumnKey && this.dragOverColumnKey === columnKey && this.draggedColumnKey !== columnKey;
1119
+ }
1120
+ canRemoveColumn(column) {
1121
+ return (this.allowColumnRemoval &&
1122
+ column.removable !== false &&
1123
+ this.activeColumns.length > 1);
1124
+ }
1125
+ isCellEmpty(value) {
1126
+ return value === '' || value == null;
1127
+ }
1128
+ asTitle(value) {
1129
+ if (value === null || value === undefined) {
1130
+ return '';
1131
+ }
1132
+ return String(value);
1133
+ }
1134
+ // ── Pipeline de données (privé) ──────────────────────────────────────────
1135
+ refreshViewModel() {
1136
+ this._resolvedData = this.resolveData();
1137
+ this.resolvedConfig = this.buildEffectiveConfig();
1138
+ this._columnMap = new Map((this.resolvedConfig.columns || []).map(c => [c.key, c]));
1139
+ const defaultColumns = (this.resolvedConfig.columns || []).filter(c => !c.optional);
1140
+ if (!this.userCustomizedColumns || this.activeColumns.length === 0) {
1141
+ this.activeColumns = [...defaultColumns];
1142
+ }
1143
+ else {
1144
+ const allowedKeys = new Set((this.resolvedConfig.columns || []).map((column) => column.key));
1145
+ this.activeColumns = this.activeColumns.filter((column) => allowedKeys.has(column.key));
1146
+ }
1147
+ this.renderedColumns = this.activeColumns.map((column) => column.key);
1148
+ this.pageSize = this.paginator?.pageSize || this.resolvePageSize();
1149
+ this.pageSizeOptions = this.resolvePageSizeOptions();
1150
+ this.pageSizeOptionsGroupBy = this.resolvePageSizeOptionsGroupBy();
1151
+ if (this.groupPageSize !== 0 && !this.pageSizeOptionsGroupBy.includes(this.groupPageSize)) {
1152
+ this.groupPageSize = this.resolveDefaultGroupPageSize();
1153
+ }
1154
+ this.attachPaginator();
1155
+ this.attachSort();
1156
+ // Mise à jour des propriétés stables pour les bindings du template
1157
+ this._sliceConfigs = this.resolvedConfig.slices ?? [];
1158
+ this._sliceColumns = this.resolvedConfig.columns ?? [];
1159
+ this._sliceData = this._resolvedData;
1160
+ this._sliceShowToggle = this.resolvedConfig.enableSliceToggle !== false;
1161
+ this._staticSlicesForMenu = (this.resolvedConfig.slices ?? [])
1162
+ .filter(s => !!s.columnKey)
1163
+ .map(s => {
1164
+ const colIcon = (this.resolvedConfig.columns ?? []).find(c => c.key === s.columnKey)?.icon;
1165
+ return { key: s.columnKey, title: s.title || humanizeKey(s.columnKey), icon: s.icon ?? colIcon };
1166
+ });
1167
+ if (!this._activeSort.active && this.resolvedConfig.defaultSort) {
1168
+ this._matSortActive = this.resolvedConfig.defaultSort.active;
1169
+ this._matSortDirection = this.resolvedConfig.defaultSort.direction;
1170
+ }
1171
+ const staticKeys = new Set((this.resolvedConfig.slices ?? []).map(s => s.columnKey).filter((k) => !!k));
1172
+ const activeDynKeys = new Set(this._activeDynamicSliceColumns.map(c => c.key));
1173
+ const seen = new Set();
1174
+ this._availableColumnsForDynamicSlice = (this.resolvedConfig.columns ?? []).filter(c => {
1175
+ if (c.sliceable === false || staticKeys.has(c.key) || activeDynKeys.has(c.key) || seen.has(c.key))
1176
+ return false;
1177
+ if (!c.header && c.sliceable !== true)
1178
+ return false;
1179
+ seen.add(c.key);
1180
+ return true;
1181
+ });
1182
+ const seen2 = new Set();
1183
+ this._allDynamicSliceColumns = (this.resolvedConfig.columns ?? []).filter(c => {
1184
+ if (c.sliceable === false || staticKeys.has(c.key) || seen2.has(c.key))
1185
+ return false;
1186
+ if (!c.header && c.sliceable !== true)
1187
+ return false;
1188
+ seen2.add(c.key);
1189
+ return true;
1190
+ });
1191
+ this._computeSliceLabel();
1192
+ this._scheduleRender();
1193
+ }
1194
+ _computeSliceLabel() {
1195
+ const visibleStaticCount = this.slicePanelRef?.staticSliceCount
1196
+ ?? this._sliceConfigs.length;
1197
+ const total = visibleStaticCount + this._activeDynamicSliceColumns.length;
1198
+ if (total === 0) {
1199
+ this._activeSliceByLabel = 'Aucun';
1200
+ return;
1201
+ }
1202
+ if (total === 1) {
1203
+ if (visibleStaticCount === 1) {
1204
+ const first = this._sliceConfigs.find(s => !s.columnKey || this.isStaticSliceVisible(s.columnKey));
1205
+ this._activeSliceByLabel = first?.title || first?.columnKey || '1';
1206
+ return;
1207
+ }
1208
+ this._activeSliceByLabel = this._activeDynamicSliceColumns[0]?.header || '1';
1209
+ return;
1210
+ }
1211
+ this._activeSliceByLabel = `${total} actifs`;
1212
+ }
1213
+ _scheduleRender() {
1214
+ if (this._pendingRender !== null) {
1215
+ clearTimeout(this._pendingRender);
1216
+ this._pendingRender = null;
1217
+ }
1218
+ this._pendingRender = setTimeout(() => {
1219
+ this._pendingRender = null;
1220
+ this._executeRender();
1221
+ }, 0);
1222
+ }
1223
+ _executeRender() {
1224
+ const categoryRows = this.getCategoryFilteredData();
1225
+ if (this.activeGroupByKey) {
1226
+ // En mode groupBy, projectRows gère tout le tri internement — pas de sort global inutile
1227
+ this._allFilteredRows = categoryRows;
1228
+ }
1229
+ else {
1230
+ const compareFn = this._buildSortComparator();
1231
+ this._allFilteredRows = compareFn ? [...categoryRows].sort(compareFn) : categoryRows;
1232
+ }
1233
+ // Mettre à jour le paginator avec le nouveau total
1234
+ if (this.paginator) {
1235
+ const savedIndex = this._preservePageIndex ?? this.paginator.pageIndex;
1236
+ const maxPage = Math.max(0, Math.ceil(this._allFilteredRows.length / this.pageSize) - 1);
1237
+ this.paginator.length = this._allFilteredRows.length;
1238
+ this.paginator.pageIndex = Math.min(savedIndex, maxPage);
1239
+ this._preservePageIndex = null;
1240
+ }
1241
+ this._projectCurrentPage();
1242
+ this._cdr.markForCheck();
1243
+ }
1244
+ _projectCurrentPage() {
1245
+ if (this.activeGroupByKey) {
1246
+ this.tableDataSource.data = this.projectRows(this._allFilteredRows);
1247
+ return;
1248
+ }
1249
+ const pageIndex = this.paginator?.pageIndex ?? 0;
1250
+ const start = pageIndex * this.pageSize;
1251
+ const pageRows = this._allFilteredRows.slice(start, start + this.pageSize);
1252
+ const projected = [];
1253
+ pageRows.forEach((row, i) => {
1254
+ const idx = start + i;
1255
+ const p = { __raw: row, __index: idx };
1256
+ this.activeColumns.forEach((col) => {
1257
+ p[col.key] = this.resolveCellValue(col, row, idx);
1258
+ });
1259
+ projected.push(p);
1260
+ });
1261
+ this.tableDataSource.data = projected;
1262
+ }
1263
+ buildEffectiveConfig() {
1264
+ const config = this.config || {};
1265
+ const rows = this._resolvedData;
1266
+ const columns = config.columns ||
1267
+ this.columnsConfig ||
1268
+ this.resolveColumnsFromCompat(rows);
1269
+ return {
1270
+ ...config,
1271
+ columns,
1272
+ slices: config.slices || [],
1273
+ enableSliceToggle: config.enableSliceToggle ?? true,
1274
+ };
1275
+ }
1276
+ resolveData() {
1277
+ if (Array.isArray(this.data)) {
1278
+ return this.data;
1279
+ }
1280
+ if (Array.isArray(this.dataSource)) {
1281
+ return this.dataSource;
1282
+ }
1283
+ if (this.dataSource && Array.isArray(this.dataSource.data)) {
1284
+ return this.dataSource.data;
1285
+ }
1286
+ return [];
1287
+ }
1288
+ resolveColumnsFromCompat(rows) {
1289
+ if (this.displayedColumns?.length) {
1290
+ return this.displayedColumns.map((key) => ({
1291
+ key,
1292
+ header: this.columnLabels?.[key] || humanizeKey(key),
1293
+ }));
1294
+ }
1295
+ const firstRow = rows[0];
1296
+ if (!firstRow || typeof firstRow !== 'object') {
1297
+ return [];
1298
+ }
1299
+ return Object.keys(firstRow).map((key) => ({
1300
+ key,
1301
+ header: this.columnLabels?.[key] || humanizeKey(key),
1302
+ }));
1303
+ }
1304
+ getCategoryFilteredData() {
1305
+ const data = this._resolvedData;
1306
+ let filtered = data.filter(this.activeSliceFilter);
1307
+ const query = this.searchQuery?.trim().toLowerCase();
1308
+ if (query) {
1309
+ filtered = filtered.filter((row) => this.activeColumns.some((col) => {
1310
+ const val = this.getLazyAwareValue(col.key, row);
1311
+ return val != null && String(val).toLowerCase().includes(query);
1312
+ }));
1313
+ }
1314
+ return filtered;
1315
+ }
1316
+ projectRows(rows) {
1317
+ const groupKey = this.activeGroupByKey;
1318
+ if (!groupKey) {
1319
+ const result = [];
1320
+ rows.forEach((row, index) => {
1321
+ const projected = { __raw: row, __index: index };
1322
+ this.activeColumns.forEach((col) => {
1323
+ projected[col.key] = this.resolveCellValue(col, row, index);
1324
+ });
1325
+ result.push(projected);
1326
+ });
1327
+ return result;
1328
+ }
1329
+ const groupColDef = this._columnMap.get(groupKey);
1330
+ const groupLazyStatus = groupColDef?.lazy ? (this._lazyColumnStatus.get(groupKey) ?? 'idle') : 'loaded';
1331
+ if (groupLazyStatus !== 'loaded') {
1332
+ this._totalFilteredCount = rows.length;
1333
+ return [{ __groupHeader: true, __groupKey: '__lazy_loading__', __groupCount: rows.length, __groupPage: 0, __groupPageCount: 1 }];
1334
+ }
1335
+ const effectiveSort = this._activeSort.active && this._activeSort.direction
1336
+ ? this._activeSort
1337
+ : this.resolvedConfig.defaultSort ?? { active: '', direction: '' };
1338
+ // Vérifie si le cache est encore valide (même référence de rows + même paramètres de tri/groupe)
1339
+ const cacheValid = this._groupCacheRows === rows &&
1340
+ this._groupCacheKey === groupKey &&
1341
+ this._groupCacheSortOrder === this.groupSortOrder &&
1342
+ this._groupCacheSortActive === (effectiveSort.active ?? '') &&
1343
+ this._groupCacheSortDir === (effectiveSort.direction ?? '');
1344
+ let groupOrder;
1345
+ let groupMap;
1346
+ if (cacheValid) {
1347
+ groupOrder = this._groupCacheOrder;
1348
+ groupMap = this._groupCacheMap;
1349
+ }
1350
+ else {
1351
+ // Pré-calcul des valeurs de groupe en un seul passage (évite O(n) find() par comparaison)
1352
+ const groupValueCache = new Map();
1353
+ rows.forEach(row => groupValueCache.set(row, String(this.getLazyAwareValue(groupKey, row) ?? '')));
1354
+ // Pré-calcul des valeurs de tri (évite O(n log n) × getLazyAwareValue dans le comparateur)
1355
+ let fastCompareFn = null;
1356
+ if (effectiveSort.active && effectiveSort.direction) {
1357
+ const sortValueCache = new Map();
1358
+ rows.forEach(row => sortValueCache.set(row, this.getLazyAwareValue(effectiveSort.active, row)));
1359
+ const dir = effectiveSort.direction;
1360
+ fastCompareFn = (a, b) => {
1361
+ const va = sortValueCache.get(a);
1362
+ const vb = sortValueCache.get(b);
1363
+ let cmp = 0;
1364
+ if (typeof va === 'number' && typeof vb === 'number') {
1365
+ cmp = va < vb ? -1 : va > vb ? 1 : 0;
1366
+ }
1367
+ else {
1368
+ const sa = va == null ? '' : String(va).toLowerCase();
1369
+ const sb = vb == null ? '' : String(vb).toLowerCase();
1370
+ cmp = sa < sb ? -1 : sa > sb ? 1 : 0;
1371
+ }
1372
+ return dir === 'asc' ? cmp : -cmp;
1373
+ };
1374
+ }
1375
+ const sorted = [...rows].sort((a, b) => {
1376
+ const va = groupValueCache.get(a);
1377
+ const vb = groupValueCache.get(b);
1378
+ const cmp = va.localeCompare(vb);
1379
+ return this.groupSortOrder === 'desc' ? -cmp : cmp;
1380
+ });
1381
+ groupOrder = [];
1382
+ groupMap = new Map();
1383
+ sorted.forEach((row) => {
1384
+ const groupValue = groupValueCache.get(row);
1385
+ if (!groupMap.has(groupValue)) {
1386
+ groupOrder.push(groupValue);
1387
+ groupMap.set(groupValue, []);
1388
+ }
1389
+ groupMap.get(groupValue).push(row);
1390
+ });
1391
+ // Tri intra-groupe stocké dans le cache — évite le re-tri sur collapse/expand/pagination
1392
+ if (fastCompareFn) {
1393
+ const fn = fastCompareFn;
1394
+ groupMap.forEach((groupRows, key) => {
1395
+ groupMap.set(key, [...groupRows].sort(fn));
1396
+ });
1397
+ }
1398
+ // Mise en cache
1399
+ this._groupCacheRows = rows;
1400
+ this._groupCacheKey = groupKey;
1401
+ this._groupCacheSortOrder = this.groupSortOrder;
1402
+ this._groupCacheSortActive = effectiveSort.active ?? '';
1403
+ this._groupCacheSortDir = effectiveSort.direction ?? '';
1404
+ this._groupCacheOrder = groupOrder;
1405
+ this._groupCacheMap = groupMap;
1406
+ }
1407
+ this._totalFilteredCount = rows.length;
1408
+ // Protection contre les jeux de données à trop haute cardinalité
1409
+ if (groupOrder.length > TableComponent.MAX_GROUP_COUNT) {
1410
+ return [{ __groupHeader: true, __groupKey: '__too_many_groups__', __groupCount: groupOrder.length, __groupPage: 0, __groupPageCount: 1 }];
1411
+ }
1412
+ const pageSize = this.groupPageSize;
1413
+ const result = [];
1414
+ groupOrder.forEach((groupValue) => {
1415
+ const groupRows = groupMap.get(groupValue);
1416
+ const totalCount = groupRows.length;
1417
+ const currentPage = pageSize === 0 ? 0 : (this._groupPages.get(groupValue) ?? 0);
1418
+ const pageCount = pageSize === 0 ? 1 : Math.max(1, Math.ceil(totalCount / pageSize));
1419
+ const collapsed = this.isGroupCollapsed(groupValue);
1420
+ result.push({ __groupHeader: true, __groupKey: groupValue, __groupCount: totalCount, __groupPage: currentPage, __groupPageCount: pageCount });
1421
+ if (!collapsed) {
1422
+ const start = pageSize === 0 ? 0 : currentPage * pageSize;
1423
+ const end = pageSize === 0 ? totalCount : Math.min(start + pageSize, totalCount);
1424
+ groupRows.slice(start, end).forEach((row, i) => {
1425
+ const idx = start + i;
1426
+ const projected = { __raw: row, __index: idx };
1427
+ this.activeColumns.forEach((col) => {
1428
+ projected[col.key] = this.resolveCellValue(col, row, idx);
1429
+ });
1430
+ result.push(projected);
1431
+ });
1432
+ }
1433
+ });
1434
+ return result;
1435
+ }
1436
+ _buildSortComparator() {
1437
+ const sort = this._activeSort.active && this._activeSort.direction
1438
+ ? this._activeSort
1439
+ : this.resolvedConfig.defaultSort ?? { active: '', direction: '' };
1440
+ const { active, direction } = sort;
1441
+ if (!active || !direction)
1442
+ return null;
1443
+ return (a, b) => {
1444
+ const va = this.getLazyAwareValue(active, a);
1445
+ const vb = this.getLazyAwareValue(active, b);
1446
+ let cmp = 0;
1447
+ if (typeof va === 'number' && typeof vb === 'number') {
1448
+ cmp = va < vb ? -1 : va > vb ? 1 : 0;
1449
+ }
1450
+ else {
1451
+ const sa = va == null ? '' : String(va).toLowerCase();
1452
+ const sb = vb == null ? '' : String(vb).toLowerCase();
1453
+ cmp = sa < sb ? -1 : sa > sb ? 1 : 0;
1454
+ }
1455
+ return direction === 'asc' ? cmp : -cmp;
1456
+ };
1457
+ }
1458
+ getLazyAwareValue(columnKey, row) {
1459
+ const colDef = this._columnMap.get(columnKey);
1460
+ if (colDef?.lazy) {
1461
+ return this._lazyColumnData.get(columnKey)?.get(row) ?? null;
1462
+ }
1463
+ if (colDef?.sortValue) {
1464
+ return normalizeCellValue(colDef.sortValue(row, 0));
1465
+ }
1466
+ if (colDef?.value) {
1467
+ return normalizeCellValue(colDef.value(row, 0));
1468
+ }
1469
+ return row[columnKey];
1470
+ }
1471
+ resolveCellValue(col, row, index) {
1472
+ if (col.lazy) {
1473
+ const status = this._lazyColumnStatus.get(col.key) ?? 'idle';
1474
+ if (status === 'loading')
1475
+ return '__lazy_loading__';
1476
+ if (status === 'error')
1477
+ return '__lazy_error__';
1478
+ if (status === 'loaded') {
1479
+ const rowMap = this._lazyColumnData.get(col.key);
1480
+ return normalizeCellValue(rowMap?.get(row) ?? '');
1481
+ }
1482
+ return '';
1483
+ }
1484
+ const valueProvider = col.value;
1485
+ const rawValue = valueProvider ? valueProvider(row, index) : row?.[col.key];
1486
+ return normalizeCellValue(rawValue);
1487
+ }
1488
+ resolvePageSize() {
1489
+ return Math.max(1, this.resolvedConfig.pagination?.pageSize || this.pageSize || 5);
1490
+ }
1491
+ resolvePageSizeOptions() {
1492
+ const options = this.resolvedConfig.pagination?.pageSizeOptions || [5, 10, 20];
1493
+ const unique = [...new Set(options.filter((value) => value > 0))];
1494
+ if (!unique.includes(this.pageSize)) {
1495
+ unique.push(this.pageSize);
1496
+ }
1497
+ return unique.sort((a, b) => a - b);
1498
+ }
1499
+ resolvePageSizeOptionsGroupBy() {
1500
+ const options = this.resolvedConfig.pagination?.pageSizeOptionsGroupBy || this.resolvedConfig.pagination?.pageSizeOptions || [5, 10, 20];
1501
+ const unique = [...new Set(options.filter((v) => v > 0))];
1502
+ if (this.groupPageSize > 0 && !unique.includes(this.groupPageSize)) {
1503
+ unique.push(this.groupPageSize);
1504
+ }
1505
+ return unique.sort((a, b) => a - b);
1506
+ }
1507
+ resolveDefaultGroupPageSize() {
1508
+ const cfg = this.resolvedConfig;
1509
+ const options = cfg.pagination?.pageSizeOptionsGroupBy || cfg.pagination?.pageSizeOptions;
1510
+ if (options?.length)
1511
+ return options[0];
1512
+ return cfg.pagination?.pageSize || 5;
1513
+ }
1514
+ uniqueColumnsByKey(columns) {
1515
+ const seen = new Set();
1516
+ return columns.filter((column) => {
1517
+ if (seen.has(column.key)) {
1518
+ return false;
1519
+ }
1520
+ seen.add(column.key);
1521
+ return true;
1522
+ });
1523
+ }
1524
+ attachPaginator() {
1525
+ if (!this.showPagination) {
1526
+ this.tableDataSource.paginator = null;
1527
+ return;
1528
+ }
1529
+ if (!this.paginator)
1530
+ return;
1531
+ this.tableDataSource.paginator = null;
1532
+ this.paginator.pageSize = this.pageSize;
1533
+ if (this._paginatorSubscribed !== this.paginator) {
1534
+ this._paginatorSubscribed = this.paginator;
1535
+ this.paginator.page.pipe(takeUntil(this._destroy$)).subscribe(() => {
1536
+ this.pageSize = this.paginator.pageSize;
1537
+ if (this._pendingRender !== null)
1538
+ return;
1539
+ this._projectCurrentPage();
1540
+ this._cdr.markForCheck();
1541
+ });
1542
+ }
1543
+ }
1544
+ attachSort() {
1545
+ if (!this.sort)
1546
+ return;
1547
+ this.tableDataSource.sort = null;
1548
+ if (this._sortSubscribed !== this.sort) {
1549
+ this._sortSubscribed = this.sort;
1550
+ this.sort.sortChange.pipe(takeUntil(this._destroy$)).subscribe(() => {
1551
+ this._activeSort = {
1552
+ active: this.sort.active,
1553
+ direction: this.sort.direction,
1554
+ };
1555
+ this._scheduleRender();
1556
+ });
1557
+ }
1558
+ }
1559
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1560
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: TableComponent, isStandalone: true, selector: "jquery-table", inputs: { config: "config", dataSource: "dataSource", data: "data", displayedColumns: "displayedColumns", columnsConfig: "columnsConfig", columnLabels: "columnLabels", isLoading: "isLoading" }, outputs: { addRequested: "addRequested", columnAdded: "columnAdded", columnRemoved: "columnRemoved", categorySelected: "categorySelected", rowSelected: "rowSelected" }, providers: [{ provide: MatPaginatorIntl, useFactory: getFrenchPaginatorIntl }], queries: [{ propertyName: "_cellDefs", predicate: JqtCellDefDirective }], viewQueries: [{ propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }, { propertyName: "tableBodyScrollRef", first: true, predicate: ["tableBodyScroll"], descendants: true }, { propertyName: "slicePanelRef", first: true, predicate: SlicePanelComponent, descendants: true }, { propertyName: "viewMenuTrigger", first: true, predicate: ["viewMenuTrigger"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<section class=\"jquery-table-wrapper\">\n <header class=\"table-header\" *ngIf=\"showTitle\">\n <h3>{{ title }}</h3>\n </header>\n\n <div\n class=\"table-layout\"\n [class.slice-collapsed]=\"showSlicePanel && (slicePanelRef?.isCollapsed ?? false)\"\n [class.no-slice]=\"!showSlicePanel\"\n >\n <slice-panel\n [style.display]=\"showSlicePanel ? null : 'none'\"\n [sliceConfigs]=\"_sliceConfigs\"\n [columns]=\"_sliceColumns\"\n [data]=\"_sliceData\"\n [lazyData]=\"_lazyColumnData\"\n [lazyStatus]=\"_lazyColumnStatus\"\n [showToggle]=\"_sliceShowToggle\"\n (filterChange)=\"onSliceFilterChange($event)\"\n (dynamicSliceKeysChange)=\"onDynamicSliceKeysChange($event)\"\n ></slice-panel>\n\n <div class=\"table-shell\">\n <div class=\"table-toolbar\" *ngIf=\"showToolbar\">\n <div class=\"table-toolbar-left\">\n <div *ngIf=\"showSearchBar\" class=\"search-input-wrapper\">\n <svg class=\"search-icon\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <circle cx=\"8.5\" cy=\"8.5\" r=\"5.5\" stroke=\"currentColor\" stroke-width=\"1.5\"/>\n <path d=\"M13 13L17 17\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n </svg>\n <input\n class=\"search-input\"\n type=\"text\"\n [(ngModel)]=\"searchQuery\"\n (ngModelChange)=\"onSearchChange()\"\n placeholder=\"Rechercher...\"\n autocomplete=\"off\"\n />\n <button *ngIf=\"searchQuery\" type=\"button\" class=\"search-clear-btn\" (click)=\"searchQuery = ''; onSearchChange()\">\u00D7</button>\n <span class=\"search-count\">{{ filteredRowCount }}</span>\n </div>\n </div>\n <div *ngIf=\"showViewButton\" class=\"table-toolbar-right\">\n <button type=\"button\" class=\"view-btn\" [matMenuTriggerFor]=\"viewMenu\" #viewMenuTrigger=\"matMenuTrigger\">\n <mat-icon class=\"view-btn-icon\">settings</mat-icon>\n View\n </button>\n\n <mat-menu #viewMenu=\"matMenu\" [panelClass]=\"'view-menu-panel'\">\n <button *ngIf=\"showFields\" mat-menu-item [matMenuTriggerFor]=\"fieldsSubMenu\" class=\"view-menu-sub-trigger\">\n <span class=\"view-menu-sub-label\">Champs</span>\n <span class=\"view-menu-sub-value\">{{ visibleColumns.length }}/{{ totalColumnCount }}</span>\n </button>\n <button *ngIf=\"showGroupBySection\" mat-menu-item [matMenuTriggerFor]=\"groupBySubMenu\" class=\"view-menu-sub-trigger\">\n <span class=\"view-menu-sub-label\">Group by</span>\n <span class=\"view-menu-sub-value\">{{ activeGroupByLabel }}</span>\n </button>\n <button *ngIf=\"showAddSliceButton\" mat-menu-item [matMenuTriggerFor]=\"sliceBySubMenu\" class=\"view-menu-sub-trigger\">\n <span class=\"view-menu-sub-label\">Slice by</span>\n <span class=\"view-menu-sub-value\">{{ activeSliceByLabel }}</span>\n </button>\n </mat-menu>\n\n <mat-menu #fieldsSubMenu=\"matMenu\" [panelClass]=\"'view-submenu-panel'\">\n <div class=\"view-submenu\" (mousedown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"view-menu-item\" *ngFor=\"let column of menuBaseColumns\"\n [disabled]=\"!column.optional && !allowColumnRemoval\"\n (click)=\"onColumnVisibilityChange(column, !isColumnVisible(column.key))\">\n <span class=\"view-menu-check\">{{ isColumnVisible(column.key) ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"column.icon\" class=\"view-menu-col-icon\" fontSet=\"material-icons-outlined\">{{ column.icon }}</mat-icon>\n {{ colLabel(column) }}\n </button>\n <ng-container *ngIf=\"menuOptionalColumns.length > 0\">\n <button type=\"button\" class=\"view-menu-item\" *ngFor=\"let column of menuOptionalColumns\"\n [disabled]=\"(isColumnVisible(column.key) && visibleColumns.length === 1) || getLazyColumnStatus(column.key) === 'loading'\"\n (click)=\"onColumnVisibilityChange(column, !isColumnVisible(column.key))\">\n <span class=\"view-menu-check\">{{ isColumnVisible(column.key) ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"column.icon\" class=\"view-menu-col-icon\" fontSet=\"material-icons-outlined\">{{ column.icon }}</mat-icon>\n {{ colLabel(column) }}\n <span *ngIf=\"column.lazy && getLazyColumnStatus(column.key) === 'loading'\" class=\"lazy-menu-spinner\">\u21BB</span>\n <span *ngIf=\"column.lazy && getLazyColumnStatus(column.key) === 'error'\" class=\"lazy-menu-error\" title=\"Erreur de chargement\">\u26A0</span>\n </button>\n </ng-container>\n </div>\n </mat-menu>\n\n <mat-menu #groupBySubMenu=\"matMenu\" [panelClass]=\"'view-submenu-panel'\">\n <div class=\"view-submenu\" (mousedown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"view-menu-item\" *ngFor=\"let col of groupByColumns\"\n [class.active]=\"activeGroupByKey === col.key\"\n (click)=\"onGroupByChange(activeGroupByKey === col.key ? null : col.key)\">\n <span class=\"view-menu-check\">{{ activeGroupByKey === col.key ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"col.icon\" class=\"view-menu-col-icon\" fontSet=\"material-icons-outlined\">{{ col.icon }}</mat-icon>\n {{ colLabel(col) }}\n </button>\n </div>\n </mat-menu>\n\n <mat-menu #sliceBySubMenu=\"matMenu\" [panelClass]=\"'view-submenu-panel'\">\n <div class=\"view-submenu\" (mousedown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"view-menu-item\" *ngFor=\"let s of staticSlicesForMenu\"\n (click)=\"toggleStaticSlice(s.key)\">\n <span class=\"view-menu-check\">{{ isStaticSliceVisible(s.key) ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"s.icon\" class=\"view-menu-col-icon\" fontSet=\"material-icons-outlined\">{{ s.icon }}</mat-icon>\n {{ s.title }}\n </button>\n <ng-container *ngIf=\"allDynamicSliceColumns.length > 0\">\n <button type=\"button\" class=\"view-menu-item\" *ngFor=\"let col of allDynamicSliceColumns\"\n [class.active]=\"isDynamicSliceActive(col.key)\"\n (click)=\"toggleDynamicSlice(col)\">\n <span class=\"view-menu-check\">{{ isDynamicSliceActive(col.key) ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"col.icon\" class=\"view-menu-col-icon\" fontSet=\"material-icons-outlined\">{{ col.icon }}</mat-icon>\n {{ colLabel(col) }}\n </button>\n </ng-container>\n </div>\n </mat-menu>\n </div>\n </div>\n\n <div class=\"table-body-wrapper\">\n <div class=\"table-body-scroll\" #tableBodyScroll>\n <table mat-table matSort [dataSource]=\"tableDataSource\" class=\"jquery-table\"\n [trackBy]=\"trackRowFn\"\n [matSortActive]=\"_matSortActive\" [matSortDirection]=\"_matSortDirection\">\n <ng-container *ngFor=\"let column of visibleColumns\" [matColumnDef]=\"column.key\">\n <th\n mat-header-cell\n *matHeaderCellDef\n [mat-sort-header]=\"column.sortable !== false ? column.key : ''\"\n [disabled]=\"column.sortable === false\"\n [style.width]=\"column.width || null\"\n [style.min-width]=\"column.width || null\"\n [class.drag-enabled]=\"isColumnDragDropEnabled\"\n [class.drop-target]=\"isHeaderDropTarget(column.key)\"\n (dragover)=\"onHeaderDragOver(column.key, $event)\"\n (drop)=\"onHeaderDrop(column.key, $event)\"\n >\n <div class=\"head-cell\">\n <mat-icon *ngIf=\"column.icon\" class=\"head-icon\" fontSet=\"material-icons-outlined\">{{ column.icon }}</mat-icon>\n <span class=\"head-text\" [title]=\"column.header\">{{ column.header }}</span>\n <span\n *ngIf=\"isColumnDragDropEnabled\"\n class=\"drag-handle\"\n draggable=\"true\"\n (dragstart)=\"onHeaderDragStart(column.key, $event)\"\n (dragend)=\"onHeaderDragEnd()\"\n (click)=\"$event.stopPropagation()\"\n >\n <mat-icon>drag_indicator</mat-icon>\n </span>\n </div>\n </th>\n <td mat-cell *matCellDef=\"let row\"\n [style.width]=\"column.width || null\"\n [style.min-width]=\"column.width || null\">\n <ng-container *ngIf=\"getCellTemplate(column.key) as customTpl; else defaultCellTpl\">\n <ng-container *ngTemplateOutlet=\"customTpl; context: { $implicit: row.__raw, index: row.__index }\"></ng-container>\n </ng-container>\n <ng-template #defaultCellTpl>\n <ng-container [ngSwitch]=\"column.lazy ? getLazyRenderType(row[column.key]) : 'value'\">\n <span *ngSwitchCase=\"'loading'\" class=\"lazy-skeleton\"></span>\n <button *ngSwitchCase=\"'error'\" type=\"button\" class=\"lazy-error-btn\" (click)=\"retryLazyColumn(column)\" title=\"Erreur de chargement \u2014 cliquer pour r\u00E9essayer\">\n <span class=\"lazy-error-icon\">\u26A0</span>\n <span class=\"lazy-error-label\">R\u00E9essayer</span>\n </button>\n <span *ngSwitchDefault class=\"cell-text\" [class.cell-empty]=\"isCellEmpty(row[column.key])\" [title]=\"asTitle(row[column.key])\">{{ isCellEmpty(row[column.key]) ? 'N/A' : row[column.key] }}</span>\n </ng-container>\n </ng-template>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"renderedColumns; sticky: true\"></tr>\n <tr\n mat-row\n *matRowDef=\"let row; columns: renderedColumns; when: !isGroupHeader\"\n [attr.tabindex]=\"hasRowClickHandler ? 0 : null\"\n [ngClass]=\"getRowClass(row)\"\n [class.no-pointer]=\"!hasRowClickHandler\"\n (click)=\"onRowClick(row, $event)\"\n (keydown.enter)=\"onRowClick(row, null)\"\n (keydown.space)=\"onRowClick(row, null)\"\n ></tr>\n <ng-container matColumnDef=\"__group_row\">\n <td mat-cell *matCellDef=\"let row\" [attr.colspan]=\"renderedColumns.length\" class=\"group-header-cell\">\n <div class=\"group-header-label\">\n <span class=\"group-chevron\" [class.collapsed]=\"isGroupCollapsed(row.__groupKey)\" *ngIf=\"row.__groupKey !== '__lazy_loading__' && row.__groupKey !== '__too_many_groups__'\">\u203A</span>\n <span *ngIf=\"row.__groupKey !== '__lazy_loading__' && row.__groupKey !== '__too_many_groups__'\" class=\"group-key-label\">{{ row.__groupKey }}</span>\n <span *ngIf=\"row.__groupKey === '__lazy_loading__'\" class=\"lazy-skeleton group-lazy-key-skeleton\"></span>\n <span *ngIf=\"row.__groupKey === '__too_many_groups__'\" class=\"group-too-many-label\">\u26A0 Trop de groupes ({{ row.__groupCount }}) \u2014 affinez les donn\u00E9es ou choisissez une autre colonne</span>\n <span class=\"group-count-badge\">{{ row.__groupCount }}</span>\n <div class=\"group-pagination\" *ngIf=\"!isGroupCollapsed(row.__groupKey) && row.__groupPageCount > 1\" (click)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"group-page-btn\" [disabled]=\"row.__groupPage === 0\" (click)=\"groupPageBack(row.__groupKey)\">\u2039</button>\n <span class=\"group-page-info\">{{ row.__groupPage + 1 }}\u00A0/\u00A0{{ row.__groupPageCount }}</span>\n <button type=\"button\" class=\"group-page-btn\" [disabled]=\"row.__groupPage >= row.__groupPageCount - 1\" (click)=\"groupPageForward(row.__groupKey, row.__groupCount)\">\u203A</button>\n </div>\n </div>\n </td>\n </ng-container>\n <tr mat-row *matRowDef=\"let row; columns: ['__group_row']; when: isGroupHeader\"\n class=\"group-header-row\"\n [class.group-header-loading]=\"row.__groupKey === '__lazy_loading__'\"\n [class.group-header-too-many]=\"row.__groupKey === '__too_many_groups__'\"\n [attr.data-group-key]=\"row.__groupKey\"\n (click)=\"row.__groupKey !== '__lazy_loading__' && row.__groupKey !== '__too_many_groups__' && toggleGroupCollapse(row.__groupKey)\"></tr>\n <tr class=\"mat-mdc-row no-data-row\" *matNoDataRow>\n <td class=\"mat-mdc-cell no-data-cell\" [attr.colspan]=\"renderedColumns.length\"></td>\n </tr>\n </table>\n </div><!-- /table-body-scroll -->\n <div class=\"table-body-overlay\" *ngIf=\"effectiveIsLoading || isEmpty\">\n <span class=\"table-body-overlay-text\">{{ effectiveIsLoading ? loadingStateLabel : emptyStateLabel }}</span>\n </div>\n </div><!-- /table-body-wrapper -->\n\n <div class=\"bottom-bar\" *ngIf=\"showPagination\">\n <mat-paginator\n [length]=\"totalRows\"\n [pageSize]=\"pageSize\"\n [pageSizeOptions]=\"pageSizeOptions\"\n ></mat-paginator>\n </div>\n\n <div class=\"group-bottom-bar\" *ngIf=\"activeGroupByKey\">\n <div class=\"group-bottom-bar-container\">\n <div class=\"group-bottom-bar-left\">\n <button type=\"button\" class=\"group-reset-btn\" (click)=\"onGroupByChange(null)\">\n <svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" class=\"group-reset-icon\">\n <path d=\"M3 8a5 5 0 1 0 5-5 5 5 0 0 0-3.54 1.46L3 3\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n <path d=\"M3 3v2.5H5.5\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n Retirer le groupe\n </button>\n <div class=\"group-bar-separator\"></div>\n <div class=\"group-sort-controls\">\n <span class=\"group-sort-label\">Trier :</span>\n <button type=\"button\" class=\"group-sort-btn\" [class.active]=\"groupSortOrder === 'asc'\" (click)=\"onGroupSortToggle('asc')\" title=\"A \u2192 Z\">\n <mat-icon>arrow_upward</mat-icon>\n </button>\n <button type=\"button\" class=\"group-sort-btn\" [class.active]=\"groupSortOrder === 'desc'\" (click)=\"onGroupSortToggle('desc')\" title=\"Z \u2192 A\">\n <mat-icon>arrow_downward</mat-icon>\n </button>\n </div>\n </div>\n <div class=\"mat-mdc-paginator-page-size\">\n <div class=\"mat-mdc-paginator-page-size-label\">Lignes par groupe :</div>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"mat-mdc-paginator-page-size-select\">\n <mat-select\n [(ngModel)]=\"groupPageSize\"\n (ngModelChange)=\"onGroupPageSizeChange($event)\"\n panelClass=\"mat-mdc-paginator-select-page-size group-page-size-panel\"\n [panelWidth]=\"'auto'\"\n >\n <mat-select-trigger>{{ groupPageSize === 0 ? 'Toutes' : groupPageSize }}</mat-select-trigger>\n <mat-option *ngFor=\"let opt of pageSizeOptionsGroupBy\" [value]=\"opt\">{{ opt }}</mat-option>\n <mat-option [value]=\"0\">Toutes</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n </div>\n </div>\n </div>\n</section>\n", styles: [":host{display:block;max-height:100%;min-height:inherit;--jqt-title-color: #1f2937;--jqt-text-color: #111827;--jqt-text-secondary: #6b7280;--jqt-text-muted: #9ca3af;--jqt-surface-color: #ffffff;--jqt-surface-alt-color: #f9fafb;--jqt-row-hover-bg: #f3f4f6;--jqt-border-color: #e5e7eb;--jqt-group-bg: #eef2f7;--jqt-group-bg-hover: #e4ecf4;--jqt-group-accent: #94a3b8;--jqt-group-border: #d1d9e4;--jqt-primary-color: #1d4ed8;--jqt-danger-color: #dc2626;--jqt-danger-hover-bg: #fef2f2;--jqt-danger-border: #fca5a5;--jqt-header-bg: #f5f5f5;--jqt-header-text-color: #374151;--jqt-cell-bg: #ffffff;--jqt-footer-bg: #f5f5f5;--jqt-footer-text-color: #374151;--jqt-footer-icon-color: #374151}.jquery-table-wrapper{display:flex;flex-direction:column;width:100%;height:100%;min-height:inherit;padding:2px 2px 0;box-sizing:border-box}.table-header{display:flex;align-items:center;justify-content:flex-start;gap:8px;margin-bottom:12px}.table-header h3{margin:0;font-size:20px;font-weight:600;color:var(--jqt-title-color)}.table-layout{display:grid;grid-template-columns:minmax(155px,190px) minmax(0,1fr);gap:12px;align-items:stretch;min-height:inherit;height:100%}.table-layout.no-slice{grid-template-columns:minmax(0,1fr)}.table-layout.slice-collapsed{grid-template-columns:40px minmax(0,1fr)}.table-shell{display:flex;flex-direction:column;flex:1 1 0;min-height:0;min-width:0;width:100%;max-width:100%}.table-body-wrapper{position:relative;flex:1 1 0;min-height:0;display:flex;flex-direction:column}.table-body-scroll{flex:1 1 0;overflow-y:auto;min-height:0;border-top:1px solid var(--jqt-border-color);border-right:1px solid var(--jqt-border-color);border-left:1px solid var(--jqt-border-color);border-radius:8px 8px 0 0;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.table-body-scroll::-webkit-scrollbar{width:6px}.table-body-scroll::-webkit-scrollbar-thumb{background:var(--jqt-border-color);border-radius:4px}.table-body-overlay{position:absolute;top:var(--actual-header-height, 56px);left:0;right:0;bottom:0;z-index:3;display:flex;align-items:center;justify-content:center;background:var(--jqt-surface-color);border-right:1px solid var(--jqt-border-color);border-left:1px solid var(--jqt-border-color);pointer-events:none}.table-body-overlay-text{font-size:14px;color:var(--jqt-text-secondary)}.table-toolbar{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:0 0 8px}.table-toolbar-left{flex:1 1 auto;min-width:0}.table-toolbar-right{flex:0 0 auto}.search-input-wrapper{position:relative;display:inline-flex;align-items:center;width:100%;height:34px;border:1px solid var(--jqt-border-color);border-radius:8px;background:var(--jqt-surface-color);padding:0 8px;box-sizing:border-box;gap:6px}.search-input-wrapper:focus-within{border-color:var(--jqt-text-muted)}.search-input-wrapper .search-icon{flex-shrink:0;width:14px;height:14px;color:var(--jqt-text-muted)}.search-input{flex:1;border:none;outline:none;font-size:13px;color:var(--jqt-text-color);background:transparent;padding:7px 0;min-width:0}.search-input::placeholder{color:var(--jqt-text-muted)}.search-count{flex-shrink:0;font-size:12px;font-weight:600;color:var(--jqt-text-secondary);background:var(--jqt-row-hover-bg);border-radius:10px;padding:1px 7px;min-width:20px;text-align:center;line-height:18px}.search-clear-btn{background:none;border:none;cursor:pointer;color:var(--jqt-text-muted);font-size:16px;padding:0;line-height:1;flex-shrink:0}.search-clear-btn:hover{color:var(--jqt-text-color)}.view-btn{display:inline-flex;align-items:center;gap:5px;height:34px;border:1px solid var(--jqt-border-color);border-radius:8px;background:var(--jqt-surface-color);padding:0 12px;font-size:13px;font-weight:500;color:var(--jqt-text-color);cursor:pointer;white-space:nowrap;box-sizing:border-box}.view-btn:hover{border-color:var(--jqt-text-muted)}.view-btn-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0}.group-header-row{cursor:pointer}:host ::ng-deep .jquery-table{border-spacing:0}:host ::ng-deep .group-header-row td{padding:0!important;background:var(--jqt-group-bg);border-bottom:1px solid var(--jqt-group-border);border-top:1px solid var(--jqt-group-border);border-left:3px solid var(--jqt-group-accent);position:sticky;top:var(--actual-header-height, 56px);z-index:2;box-shadow:-4px 0 0 0 var(--jqt-group-bg)}:host ::ng-deep .group-header-row:hover td{background:var(--jqt-group-bg-hover);box-shadow:-4px 0 0 0 var(--jqt-group-bg-hover)}:host ::ng-deep .group-header-loading td,:host ::ng-deep .group-header-loading:hover td,:host ::ng-deep .group-header-too-many td,:host ::ng-deep .group-header-too-many:hover td{cursor:default;background:var(--jqt-surface-alt-color)}.group-too-many-label{font-size:12px;font-weight:500;color:var(--jqt-warn-color, #b45309);font-style:italic}.group-header-cell{padding:0!important}.group-header-label{display:flex;align-items:center;gap:6px;padding:6px 16px;font-size:12px;font-weight:700;color:var(--jqt-text-color);text-transform:uppercase;letter-spacing:.04em}.group-key-label{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.group-count-badge{flex-shrink:0;font-size:11px;line-height:1;font-weight:600;color:#fff;background:var(--jqt-text-secondary, #6b7280);border-radius:10px;padding:3px 7px;min-width:20px;text-align:center;text-transform:none;letter-spacing:0}.group-pagination{display:inline-flex;align-items:center;gap:4px;flex-shrink:0;margin-left:auto}.group-page-btn{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border:1px solid var(--jqt-border-color);border-radius:5px;background:var(--jqt-surface-color);cursor:pointer;font-size:14px;color:var(--jqt-text-color);line-height:1;padding:0 0 3px;text-transform:none;letter-spacing:0}.group-page-btn:hover:not(:disabled){border-color:var(--jqt-text-muted);background:var(--jqt-surface-alt-color)}.group-page-btn:disabled{opacity:.35;cursor:default}.group-page-info{font-size:11px;color:var(--jqt-text-secondary);min-width:36px;text-align:center;font-weight:500;text-transform:none;letter-spacing:0}.group-chevron{flex-shrink:0;font-size:14px;font-weight:700;padding-bottom:3px;transition:transform .15s ease;transform:rotate(90deg)}.group-chevron.collapsed{transform:rotate(0)}.no-data-row{cursor:default!important}.no-data-cell{padding:0!important;height:0!important;border:none!important}.jquery-table{width:100%;table-layout:fixed}:host ::ng-deep .mat-mdc-table{width:100%;background:transparent}:host ::ng-deep .mat-mdc-header-cell,:host ::ng-deep .mat-mdc-cell{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ::ng-deep .mat-mdc-header-row{background:var(--jqt-header-bg)}:host ::ng-deep .mat-mdc-header-cell{background:var(--jqt-header-bg);color:var(--jqt-header-text-color)}:host ::ng-deep .mat-mdc-row,:host ::ng-deep .mat-mdc-cell{background:var(--jqt-cell-bg)}:host ::ng-deep .mat-mdc-row{cursor:pointer;border-bottom-color:var(--jqt-border-color)}:host ::ng-deep .mat-mdc-row.no-pointer{cursor:auto}:host ::ng-deep .mat-mdc-row.no-data-row{cursor:default}:host ::ng-deep .mat-mdc-header-cell.drag-enabled{-webkit-user-select:none;user-select:none}:host ::ng-deep .mat-mdc-header-cell.drag-enabled:active{cursor:default}:host ::ng-deep .mat-mdc-header-cell.drop-target{background:var(--jqt-row-hover-bg)}.head-cell{display:flex;align-items:center;justify-content:space-between;gap:6px;min-width:0}.drag-handle{display:inline-flex;align-items:center;cursor:grab;color:var(--jqt-border-color);flex-shrink:0;padding:0 2px;border-radius:4px;line-height:1}.drag-handle:active{cursor:grabbing}.drag-handle:hover{color:var(--jqt-text-muted)}.drag-handle mat-icon{font-size:16px;width:16px;height:16px;line-height:1}.head-icon{font-size:14px;width:14px;height:14px;line-height:1;flex-shrink:0;color:var(--jqt-text-muted)}.head-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-transform:uppercase;font-size:11px;font-weight:600;letter-spacing:.05em}.head-actions{display:inline-flex;align-items:center;gap:6px}.cell-text{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cell-empty{color:var(--jqt-empty-cell-color, #9ca3af);font-style:italic}.bottom-bar{display:flex;align-items:center;flex:0 0 auto;background:var(--jqt-footer-bg);border-top:1px solid var(--jqt-border-color);border-right:1px solid var(--jqt-border-color);border-bottom:1px solid var(--jqt-border-color);border-left:1px solid var(--jqt-border-color);border-radius:0 0 8px 8px}:host ::ng-deep .bottom-bar mat-paginator{flex:1 1 auto;background:transparent;color:var(--jqt-footer-text-color)}:host ::ng-deep .bottom-bar .mat-mdc-paginator-outer-container{display:flex}:host ::ng-deep .bottom-bar .mat-mdc-paginator-container{display:flex;align-items:center;flex-wrap:nowrap;min-height:0;height:var(--actual-header-height, 56px);padding:0 8px;box-sizing:border-box}:host ::ng-deep .bottom-bar .mat-mdc-paginator-range-label,:host ::ng-deep .bottom-bar .mat-mdc-paginator-page-size-label{font-size:12px;color:var(--jqt-footer-text-color)}:host ::ng-deep .bottom-bar .mat-mdc-icon-button{color:var(--jqt-footer-icon-color);width:28px;height:28px;padding:0;line-height:1;display:inline-flex;align-items:center;justify-content:center;--mdc-icon-button-state-layer-size: 28px}:host ::ng-deep .bottom-bar .mat-mdc-icon-button .mat-mdc-button-persistent-ripple,:host ::ng-deep .bottom-bar .mat-mdc-icon-button .mdc-icon-button__state-layer{width:28px;height:28px;top:50%;left:50%;transform:translate(-50%,-50%)}:host ::ng-deep .bottom-bar .mat-mdc-icon-button .mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}:host ::ng-deep .bottom-bar .mat-mdc-icon-button:disabled{opacity:.35}:host ::ng-deep .bottom-bar .mat-mdc-paginator-page-size-select{width:72px;font-size:12px}:host ::ng-deep .bottom-bar .mat-mdc-text-field-wrapper{padding:0 8px}:host ::ng-deep .bottom-bar .mdc-text-field--outlined{height:28px}:host ::ng-deep .bottom-bar .mat-mdc-form-field-infix{padding-top:0;padding-bottom:0;min-height:0;height:28px;display:flex;align-items:center}:host ::ng-deep .bottom-bar .mat-mdc-select-value-text{font-size:12px;color:var(--jqt-text-color)}:host ::ng-deep .bottom-bar .mdc-notched-outline__leading,:host ::ng-deep .bottom-bar .mdc-notched-outline__notch,:host ::ng-deep .bottom-bar .mdc-notched-outline__trailing{border-color:var(--jqt-border-color)}.group-bottom-bar{display:flex;align-items:center;flex:0 0 auto;background:var(--jqt-footer-bg);border-top:1px solid var(--jqt-border-color);border-right:1px solid var(--jqt-border-color);border-bottom:1px solid var(--jqt-border-color);border-left:1px solid var(--jqt-border-color);border-radius:0 0 8px 8px}.group-bottom-bar-container{display:flex;align-items:center;justify-content:space-between;flex-wrap:nowrap;height:var(--actual-header-height, 56px);padding:0 16px;gap:16px;width:100%;box-sizing:border-box}.group-bar-separator{width:1px;height:20px;background:#9ca3af;flex-shrink:0;margin:0 4px}.group-bottom-bar-left{display:flex;align-items:center;gap:8px;flex-shrink:0}.group-sort-controls{display:flex;align-items:center;gap:2px}.group-sort-label{font-size:12px;color:var(--jqt-footer-text-color);white-space:nowrap;margin-right:2px}.group-sort-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--jqt-footer-icon-color);cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease}.group-sort-btn mat-icon{font-size:16px;width:16px;height:16px;line-height:16px}.group-sort-btn:hover{background:#0000000a}.group-sort-btn.active{color:var(--jqt-primary-color, #1976d2);border-color:var(--jqt-primary-color, #1976d2);background:#1976d214}:host ::ng-deep .group-bottom-bar .mat-mdc-paginator-page-size{display:flex;align-items:center;gap:8px}:host ::ng-deep .group-bottom-bar .mat-mdc-paginator-page-size-label{font-size:12px;color:var(--jqt-footer-text-color);white-space:nowrap}:host ::ng-deep .group-bottom-bar .mat-mdc-paginator-page-size-select{width:72px;font-size:12px}:host ::ng-deep .group-bottom-bar .mat-mdc-text-field-wrapper{padding:0 8px}:host ::ng-deep .group-bottom-bar .mdc-text-field--outlined{height:28px}:host ::ng-deep .group-bottom-bar .mat-mdc-form-field-infix{padding-top:0;padding-bottom:0;min-height:0;height:28px;display:flex;align-items:center}:host ::ng-deep .group-bottom-bar .mat-mdc-select-value-text{font-size:12px;color:var(--jqt-text-color)}:host ::ng-deep .group-bottom-bar .mdc-notched-outline__leading,:host ::ng-deep .group-bottom-bar .mdc-notched-outline__notch,:host ::ng-deep .group-bottom-bar .mdc-notched-outline__trailing{border-color:var(--jqt-border-color)}:host ::ng-deep .group-bottom-bar .mat-mdc-select-arrow{color:var(--jqt-footer-text-color)}.group-reset-btn{display:inline-flex;align-items:center;gap:6px;height:30px;padding:0 10px;border:1px solid var(--jqt-border-color);border-radius:8px;background:var(--jqt-surface-color);color:var(--jqt-text-secondary);font-size:12px;font-weight:500;font-family:inherit;cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease;white-space:nowrap;flex-shrink:0}.group-reset-btn:hover{background:var(--jqt-danger-hover-bg);color:var(--jqt-danger-color);border-color:var(--jqt-danger-border)}.group-reset-icon{width:14px;height:14px;flex-shrink:0}::ng-deep .mat-mdc-paginator-select-page-size .mat-pseudo-checkbox{display:none!important}::ng-deep .group-page-size-panel{min-width:90px!important}::ng-deep .group-page-size-panel .mat-mdc-option{justify-content:center;min-height:40px}::ng-deep .group-page-size-panel .mdc-list-item__primary-text{text-align:center;width:100%}@media (max-width: 960px){.table-layout{grid-template-columns:1fr}}::ng-deep .view-menu-panel .mat-mdc-menu-panel{min-width:220px!important;overflow:visible!important}::ng-deep .view-menu-panel .mat-mdc-menu-content{padding:4px 0!important}::ng-deep .view-submenu-panel .mat-mdc-menu-panel{min-width:200px!important}::ng-deep .view-submenu-panel .mat-mdc-menu-content{padding:0!important}::ng-deep .view-menu-panel .view-menu-sub-trigger.mat-mdc-menu-item{min-height:42px;padding:0 14px;display:flex;align-items:center;box-sizing:border-box}::ng-deep .view-menu-panel .view-menu-sub-trigger .mdc-list-item__primary-text{display:flex;align-items:center;width:100%;gap:0;overflow:hidden}::ng-deep .view-menu-sub-label{font-size:13px;font-weight:500;color:var(--jqt-text-color);flex:0 0 auto;white-space:nowrap;padding-right:8px}::ng-deep .view-menu-sub-value{font-size:12px;color:var(--jqt-text-secondary);flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:right}::ng-deep .view-submenu{padding:4px 6px;min-width:180px;box-sizing:border-box}::ng-deep .view-submenu .view-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;color:var(--jqt-text-color);cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}::ng-deep .view-submenu .view-menu-item:hover{background:var(--jqt-row-hover-bg)}::ng-deep .view-submenu .view-menu-item.active{font-weight:600}::ng-deep .view-submenu .view-menu-item:disabled{opacity:.4;cursor:default}::ng-deep .view-menu-col-icon{flex-shrink:0;font-size:14px!important;width:14px!important;height:14px!important;color:var(--jqt-text-muted)}::ng-deep .view-menu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:var(--jqt-text-color)}::ng-deep .view-menu-separator{border:none;border-top:1px solid var(--jqt-border-color);margin:4px 0}.lazy-skeleton{display:block;height:12px;width:70%;border-radius:4px;background:linear-gradient(90deg,var(--jqt-border-color) 25%,var(--jqt-surface-alt-color) 50%,var(--jqt-border-color) 75%);background-size:200% 100%;animation:lazy-shimmer 1.4s ease-in-out infinite}.group-lazy-key-skeleton{flex:1 1 auto;min-width:80px;height:13px;border-radius:4px}@keyframes lazy-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}.lazy-error-btn{display:inline-flex;align-items:center;gap:4px;background:none;border:1px solid #fca5a5;border-radius:4px;padding:2px 6px;cursor:pointer;color:#ef4444;font-size:12px;opacity:.85;transition:opacity .15s}.lazy-error-btn:hover{opacity:1;background:#fef2f2}.lazy-error-icon{font-size:12px}.lazy-error-label{font-size:11px;font-weight:500}::ng-deep .lazy-menu-spinner{display:inline-block;margin-left:auto;font-size:13px;color:var(--jqt-text-secondary);animation:lazy-spin 1s linear infinite}::ng-deep .lazy-menu-error{display:inline-block;margin-left:auto;font-size:13px;color:#ef4444}@keyframes lazy-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.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$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i3.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i3.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i3.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i3.MatColumnDef, selector: "[matColumnDef]", inputs: ["sticky", "matColumnDef"] }, { kind: "directive", type: i3.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i3.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i3.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i3.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i3.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i3.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "directive", type: i3.MatNoDataRow, selector: "ng-template[matNoDataRow]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i4.MatPaginator, selector: "mat-paginator", inputs: ["disabled"], exportAs: ["matPaginator"] }, { kind: "ngmodule", type: MatSortModule }, { kind: "directive", type: i5.MatSort, selector: "[matSort]", inputs: ["matSortDisabled", "matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i5.MatSortHeader, selector: "[mat-sort-header]", inputs: ["disabled", "mat-sort-header", "arrowPosition", "start", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i6.MatMenu, selector: "mat-menu", exportAs: ["matMenu"] }, { kind: "component", type: i6.MatMenuItem, selector: "[mat-menu-item]", inputs: ["disabled", "disableRipple", "role"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i6.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i8.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "component", type: i9.MatSelect, selector: "mat-select", inputs: ["disabled", "disableRipple", "tabIndex", "panelWidth", "hideSingleSelectionIndicator"], exportAs: ["matSelect"] }, { kind: "directive", type: i9.MatSelectTrigger, selector: "mat-select-trigger" }, { kind: "component", type: i10.MatOption, selector: "mat-option", exportAs: ["matOption"] }, { kind: "component", type: SlicePanelComponent, selector: "slice-panel", inputs: ["sliceConfigs", "columns", "data", "lazyData", "lazyStatus", "showToggle", "showCounts", "collapsedByDefault", "alwaysShow"], outputs: ["filterChange", "activeKeysChange", "dynamicSliceKeysChange", "collapsedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1561
+ }
1562
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TableComponent, decorators: [{
1563
+ type: Component,
1564
+ args: [{ standalone: true, selector: 'jquery-table', imports: [CommonModule, FormsModule, MatTableModule, MatButtonModule, MatPaginatorModule, MatSortModule, MatMenuModule, MatIconModule, MatSelectModule, SlicePanelComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [{ provide: MatPaginatorIntl, useFactory: getFrenchPaginatorIntl }], template: "<section class=\"jquery-table-wrapper\">\n <header class=\"table-header\" *ngIf=\"showTitle\">\n <h3>{{ title }}</h3>\n </header>\n\n <div\n class=\"table-layout\"\n [class.slice-collapsed]=\"showSlicePanel && (slicePanelRef?.isCollapsed ?? false)\"\n [class.no-slice]=\"!showSlicePanel\"\n >\n <slice-panel\n [style.display]=\"showSlicePanel ? null : 'none'\"\n [sliceConfigs]=\"_sliceConfigs\"\n [columns]=\"_sliceColumns\"\n [data]=\"_sliceData\"\n [lazyData]=\"_lazyColumnData\"\n [lazyStatus]=\"_lazyColumnStatus\"\n [showToggle]=\"_sliceShowToggle\"\n (filterChange)=\"onSliceFilterChange($event)\"\n (dynamicSliceKeysChange)=\"onDynamicSliceKeysChange($event)\"\n ></slice-panel>\n\n <div class=\"table-shell\">\n <div class=\"table-toolbar\" *ngIf=\"showToolbar\">\n <div class=\"table-toolbar-left\">\n <div *ngIf=\"showSearchBar\" class=\"search-input-wrapper\">\n <svg class=\"search-icon\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <circle cx=\"8.5\" cy=\"8.5\" r=\"5.5\" stroke=\"currentColor\" stroke-width=\"1.5\"/>\n <path d=\"M13 13L17 17\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n </svg>\n <input\n class=\"search-input\"\n type=\"text\"\n [(ngModel)]=\"searchQuery\"\n (ngModelChange)=\"onSearchChange()\"\n placeholder=\"Rechercher...\"\n autocomplete=\"off\"\n />\n <button *ngIf=\"searchQuery\" type=\"button\" class=\"search-clear-btn\" (click)=\"searchQuery = ''; onSearchChange()\">\u00D7</button>\n <span class=\"search-count\">{{ filteredRowCount }}</span>\n </div>\n </div>\n <div *ngIf=\"showViewButton\" class=\"table-toolbar-right\">\n <button type=\"button\" class=\"view-btn\" [matMenuTriggerFor]=\"viewMenu\" #viewMenuTrigger=\"matMenuTrigger\">\n <mat-icon class=\"view-btn-icon\">settings</mat-icon>\n View\n </button>\n\n <mat-menu #viewMenu=\"matMenu\" [panelClass]=\"'view-menu-panel'\">\n <button *ngIf=\"showFields\" mat-menu-item [matMenuTriggerFor]=\"fieldsSubMenu\" class=\"view-menu-sub-trigger\">\n <span class=\"view-menu-sub-label\">Champs</span>\n <span class=\"view-menu-sub-value\">{{ visibleColumns.length }}/{{ totalColumnCount }}</span>\n </button>\n <button *ngIf=\"showGroupBySection\" mat-menu-item [matMenuTriggerFor]=\"groupBySubMenu\" class=\"view-menu-sub-trigger\">\n <span class=\"view-menu-sub-label\">Group by</span>\n <span class=\"view-menu-sub-value\">{{ activeGroupByLabel }}</span>\n </button>\n <button *ngIf=\"showAddSliceButton\" mat-menu-item [matMenuTriggerFor]=\"sliceBySubMenu\" class=\"view-menu-sub-trigger\">\n <span class=\"view-menu-sub-label\">Slice by</span>\n <span class=\"view-menu-sub-value\">{{ activeSliceByLabel }}</span>\n </button>\n </mat-menu>\n\n <mat-menu #fieldsSubMenu=\"matMenu\" [panelClass]=\"'view-submenu-panel'\">\n <div class=\"view-submenu\" (mousedown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"view-menu-item\" *ngFor=\"let column of menuBaseColumns\"\n [disabled]=\"!column.optional && !allowColumnRemoval\"\n (click)=\"onColumnVisibilityChange(column, !isColumnVisible(column.key))\">\n <span class=\"view-menu-check\">{{ isColumnVisible(column.key) ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"column.icon\" class=\"view-menu-col-icon\" fontSet=\"material-icons-outlined\">{{ column.icon }}</mat-icon>\n {{ colLabel(column) }}\n </button>\n <ng-container *ngIf=\"menuOptionalColumns.length > 0\">\n <button type=\"button\" class=\"view-menu-item\" *ngFor=\"let column of menuOptionalColumns\"\n [disabled]=\"(isColumnVisible(column.key) && visibleColumns.length === 1) || getLazyColumnStatus(column.key) === 'loading'\"\n (click)=\"onColumnVisibilityChange(column, !isColumnVisible(column.key))\">\n <span class=\"view-menu-check\">{{ isColumnVisible(column.key) ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"column.icon\" class=\"view-menu-col-icon\" fontSet=\"material-icons-outlined\">{{ column.icon }}</mat-icon>\n {{ colLabel(column) }}\n <span *ngIf=\"column.lazy && getLazyColumnStatus(column.key) === 'loading'\" class=\"lazy-menu-spinner\">\u21BB</span>\n <span *ngIf=\"column.lazy && getLazyColumnStatus(column.key) === 'error'\" class=\"lazy-menu-error\" title=\"Erreur de chargement\">\u26A0</span>\n </button>\n </ng-container>\n </div>\n </mat-menu>\n\n <mat-menu #groupBySubMenu=\"matMenu\" [panelClass]=\"'view-submenu-panel'\">\n <div class=\"view-submenu\" (mousedown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"view-menu-item\" *ngFor=\"let col of groupByColumns\"\n [class.active]=\"activeGroupByKey === col.key\"\n (click)=\"onGroupByChange(activeGroupByKey === col.key ? null : col.key)\">\n <span class=\"view-menu-check\">{{ activeGroupByKey === col.key ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"col.icon\" class=\"view-menu-col-icon\" fontSet=\"material-icons-outlined\">{{ col.icon }}</mat-icon>\n {{ colLabel(col) }}\n </button>\n </div>\n </mat-menu>\n\n <mat-menu #sliceBySubMenu=\"matMenu\" [panelClass]=\"'view-submenu-panel'\">\n <div class=\"view-submenu\" (mousedown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"view-menu-item\" *ngFor=\"let s of staticSlicesForMenu\"\n (click)=\"toggleStaticSlice(s.key)\">\n <span class=\"view-menu-check\">{{ isStaticSliceVisible(s.key) ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"s.icon\" class=\"view-menu-col-icon\" fontSet=\"material-icons-outlined\">{{ s.icon }}</mat-icon>\n {{ s.title }}\n </button>\n <ng-container *ngIf=\"allDynamicSliceColumns.length > 0\">\n <button type=\"button\" class=\"view-menu-item\" *ngFor=\"let col of allDynamicSliceColumns\"\n [class.active]=\"isDynamicSliceActive(col.key)\"\n (click)=\"toggleDynamicSlice(col)\">\n <span class=\"view-menu-check\">{{ isDynamicSliceActive(col.key) ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"col.icon\" class=\"view-menu-col-icon\" fontSet=\"material-icons-outlined\">{{ col.icon }}</mat-icon>\n {{ colLabel(col) }}\n </button>\n </ng-container>\n </div>\n </mat-menu>\n </div>\n </div>\n\n <div class=\"table-body-wrapper\">\n <div class=\"table-body-scroll\" #tableBodyScroll>\n <table mat-table matSort [dataSource]=\"tableDataSource\" class=\"jquery-table\"\n [trackBy]=\"trackRowFn\"\n [matSortActive]=\"_matSortActive\" [matSortDirection]=\"_matSortDirection\">\n <ng-container *ngFor=\"let column of visibleColumns\" [matColumnDef]=\"column.key\">\n <th\n mat-header-cell\n *matHeaderCellDef\n [mat-sort-header]=\"column.sortable !== false ? column.key : ''\"\n [disabled]=\"column.sortable === false\"\n [style.width]=\"column.width || null\"\n [style.min-width]=\"column.width || null\"\n [class.drag-enabled]=\"isColumnDragDropEnabled\"\n [class.drop-target]=\"isHeaderDropTarget(column.key)\"\n (dragover)=\"onHeaderDragOver(column.key, $event)\"\n (drop)=\"onHeaderDrop(column.key, $event)\"\n >\n <div class=\"head-cell\">\n <mat-icon *ngIf=\"column.icon\" class=\"head-icon\" fontSet=\"material-icons-outlined\">{{ column.icon }}</mat-icon>\n <span class=\"head-text\" [title]=\"column.header\">{{ column.header }}</span>\n <span\n *ngIf=\"isColumnDragDropEnabled\"\n class=\"drag-handle\"\n draggable=\"true\"\n (dragstart)=\"onHeaderDragStart(column.key, $event)\"\n (dragend)=\"onHeaderDragEnd()\"\n (click)=\"$event.stopPropagation()\"\n >\n <mat-icon>drag_indicator</mat-icon>\n </span>\n </div>\n </th>\n <td mat-cell *matCellDef=\"let row\"\n [style.width]=\"column.width || null\"\n [style.min-width]=\"column.width || null\">\n <ng-container *ngIf=\"getCellTemplate(column.key) as customTpl; else defaultCellTpl\">\n <ng-container *ngTemplateOutlet=\"customTpl; context: { $implicit: row.__raw, index: row.__index }\"></ng-container>\n </ng-container>\n <ng-template #defaultCellTpl>\n <ng-container [ngSwitch]=\"column.lazy ? getLazyRenderType(row[column.key]) : 'value'\">\n <span *ngSwitchCase=\"'loading'\" class=\"lazy-skeleton\"></span>\n <button *ngSwitchCase=\"'error'\" type=\"button\" class=\"lazy-error-btn\" (click)=\"retryLazyColumn(column)\" title=\"Erreur de chargement \u2014 cliquer pour r\u00E9essayer\">\n <span class=\"lazy-error-icon\">\u26A0</span>\n <span class=\"lazy-error-label\">R\u00E9essayer</span>\n </button>\n <span *ngSwitchDefault class=\"cell-text\" [class.cell-empty]=\"isCellEmpty(row[column.key])\" [title]=\"asTitle(row[column.key])\">{{ isCellEmpty(row[column.key]) ? 'N/A' : row[column.key] }}</span>\n </ng-container>\n </ng-template>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"renderedColumns; sticky: true\"></tr>\n <tr\n mat-row\n *matRowDef=\"let row; columns: renderedColumns; when: !isGroupHeader\"\n [attr.tabindex]=\"hasRowClickHandler ? 0 : null\"\n [ngClass]=\"getRowClass(row)\"\n [class.no-pointer]=\"!hasRowClickHandler\"\n (click)=\"onRowClick(row, $event)\"\n (keydown.enter)=\"onRowClick(row, null)\"\n (keydown.space)=\"onRowClick(row, null)\"\n ></tr>\n <ng-container matColumnDef=\"__group_row\">\n <td mat-cell *matCellDef=\"let row\" [attr.colspan]=\"renderedColumns.length\" class=\"group-header-cell\">\n <div class=\"group-header-label\">\n <span class=\"group-chevron\" [class.collapsed]=\"isGroupCollapsed(row.__groupKey)\" *ngIf=\"row.__groupKey !== '__lazy_loading__' && row.__groupKey !== '__too_many_groups__'\">\u203A</span>\n <span *ngIf=\"row.__groupKey !== '__lazy_loading__' && row.__groupKey !== '__too_many_groups__'\" class=\"group-key-label\">{{ row.__groupKey }}</span>\n <span *ngIf=\"row.__groupKey === '__lazy_loading__'\" class=\"lazy-skeleton group-lazy-key-skeleton\"></span>\n <span *ngIf=\"row.__groupKey === '__too_many_groups__'\" class=\"group-too-many-label\">\u26A0 Trop de groupes ({{ row.__groupCount }}) \u2014 affinez les donn\u00E9es ou choisissez une autre colonne</span>\n <span class=\"group-count-badge\">{{ row.__groupCount }}</span>\n <div class=\"group-pagination\" *ngIf=\"!isGroupCollapsed(row.__groupKey) && row.__groupPageCount > 1\" (click)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"group-page-btn\" [disabled]=\"row.__groupPage === 0\" (click)=\"groupPageBack(row.__groupKey)\">\u2039</button>\n <span class=\"group-page-info\">{{ row.__groupPage + 1 }}\u00A0/\u00A0{{ row.__groupPageCount }}</span>\n <button type=\"button\" class=\"group-page-btn\" [disabled]=\"row.__groupPage >= row.__groupPageCount - 1\" (click)=\"groupPageForward(row.__groupKey, row.__groupCount)\">\u203A</button>\n </div>\n </div>\n </td>\n </ng-container>\n <tr mat-row *matRowDef=\"let row; columns: ['__group_row']; when: isGroupHeader\"\n class=\"group-header-row\"\n [class.group-header-loading]=\"row.__groupKey === '__lazy_loading__'\"\n [class.group-header-too-many]=\"row.__groupKey === '__too_many_groups__'\"\n [attr.data-group-key]=\"row.__groupKey\"\n (click)=\"row.__groupKey !== '__lazy_loading__' && row.__groupKey !== '__too_many_groups__' && toggleGroupCollapse(row.__groupKey)\"></tr>\n <tr class=\"mat-mdc-row no-data-row\" *matNoDataRow>\n <td class=\"mat-mdc-cell no-data-cell\" [attr.colspan]=\"renderedColumns.length\"></td>\n </tr>\n </table>\n </div><!-- /table-body-scroll -->\n <div class=\"table-body-overlay\" *ngIf=\"effectiveIsLoading || isEmpty\">\n <span class=\"table-body-overlay-text\">{{ effectiveIsLoading ? loadingStateLabel : emptyStateLabel }}</span>\n </div>\n </div><!-- /table-body-wrapper -->\n\n <div class=\"bottom-bar\" *ngIf=\"showPagination\">\n <mat-paginator\n [length]=\"totalRows\"\n [pageSize]=\"pageSize\"\n [pageSizeOptions]=\"pageSizeOptions\"\n ></mat-paginator>\n </div>\n\n <div class=\"group-bottom-bar\" *ngIf=\"activeGroupByKey\">\n <div class=\"group-bottom-bar-container\">\n <div class=\"group-bottom-bar-left\">\n <button type=\"button\" class=\"group-reset-btn\" (click)=\"onGroupByChange(null)\">\n <svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" class=\"group-reset-icon\">\n <path d=\"M3 8a5 5 0 1 0 5-5 5 5 0 0 0-3.54 1.46L3 3\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n <path d=\"M3 3v2.5H5.5\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n Retirer le groupe\n </button>\n <div class=\"group-bar-separator\"></div>\n <div class=\"group-sort-controls\">\n <span class=\"group-sort-label\">Trier :</span>\n <button type=\"button\" class=\"group-sort-btn\" [class.active]=\"groupSortOrder === 'asc'\" (click)=\"onGroupSortToggle('asc')\" title=\"A \u2192 Z\">\n <mat-icon>arrow_upward</mat-icon>\n </button>\n <button type=\"button\" class=\"group-sort-btn\" [class.active]=\"groupSortOrder === 'desc'\" (click)=\"onGroupSortToggle('desc')\" title=\"Z \u2192 A\">\n <mat-icon>arrow_downward</mat-icon>\n </button>\n </div>\n </div>\n <div class=\"mat-mdc-paginator-page-size\">\n <div class=\"mat-mdc-paginator-page-size-label\">Lignes par groupe :</div>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"mat-mdc-paginator-page-size-select\">\n <mat-select\n [(ngModel)]=\"groupPageSize\"\n (ngModelChange)=\"onGroupPageSizeChange($event)\"\n panelClass=\"mat-mdc-paginator-select-page-size group-page-size-panel\"\n [panelWidth]=\"'auto'\"\n >\n <mat-select-trigger>{{ groupPageSize === 0 ? 'Toutes' : groupPageSize }}</mat-select-trigger>\n <mat-option *ngFor=\"let opt of pageSizeOptionsGroupBy\" [value]=\"opt\">{{ opt }}</mat-option>\n <mat-option [value]=\"0\">Toutes</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n </div>\n </div>\n </div>\n</section>\n", styles: [":host{display:block;max-height:100%;min-height:inherit;--jqt-title-color: #1f2937;--jqt-text-color: #111827;--jqt-text-secondary: #6b7280;--jqt-text-muted: #9ca3af;--jqt-surface-color: #ffffff;--jqt-surface-alt-color: #f9fafb;--jqt-row-hover-bg: #f3f4f6;--jqt-border-color: #e5e7eb;--jqt-group-bg: #eef2f7;--jqt-group-bg-hover: #e4ecf4;--jqt-group-accent: #94a3b8;--jqt-group-border: #d1d9e4;--jqt-primary-color: #1d4ed8;--jqt-danger-color: #dc2626;--jqt-danger-hover-bg: #fef2f2;--jqt-danger-border: #fca5a5;--jqt-header-bg: #f5f5f5;--jqt-header-text-color: #374151;--jqt-cell-bg: #ffffff;--jqt-footer-bg: #f5f5f5;--jqt-footer-text-color: #374151;--jqt-footer-icon-color: #374151}.jquery-table-wrapper{display:flex;flex-direction:column;width:100%;height:100%;min-height:inherit;padding:2px 2px 0;box-sizing:border-box}.table-header{display:flex;align-items:center;justify-content:flex-start;gap:8px;margin-bottom:12px}.table-header h3{margin:0;font-size:20px;font-weight:600;color:var(--jqt-title-color)}.table-layout{display:grid;grid-template-columns:minmax(155px,190px) minmax(0,1fr);gap:12px;align-items:stretch;min-height:inherit;height:100%}.table-layout.no-slice{grid-template-columns:minmax(0,1fr)}.table-layout.slice-collapsed{grid-template-columns:40px minmax(0,1fr)}.table-shell{display:flex;flex-direction:column;flex:1 1 0;min-height:0;min-width:0;width:100%;max-width:100%}.table-body-wrapper{position:relative;flex:1 1 0;min-height:0;display:flex;flex-direction:column}.table-body-scroll{flex:1 1 0;overflow-y:auto;min-height:0;border-top:1px solid var(--jqt-border-color);border-right:1px solid var(--jqt-border-color);border-left:1px solid var(--jqt-border-color);border-radius:8px 8px 0 0;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.table-body-scroll::-webkit-scrollbar{width:6px}.table-body-scroll::-webkit-scrollbar-thumb{background:var(--jqt-border-color);border-radius:4px}.table-body-overlay{position:absolute;top:var(--actual-header-height, 56px);left:0;right:0;bottom:0;z-index:3;display:flex;align-items:center;justify-content:center;background:var(--jqt-surface-color);border-right:1px solid var(--jqt-border-color);border-left:1px solid var(--jqt-border-color);pointer-events:none}.table-body-overlay-text{font-size:14px;color:var(--jqt-text-secondary)}.table-toolbar{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:0 0 8px}.table-toolbar-left{flex:1 1 auto;min-width:0}.table-toolbar-right{flex:0 0 auto}.search-input-wrapper{position:relative;display:inline-flex;align-items:center;width:100%;height:34px;border:1px solid var(--jqt-border-color);border-radius:8px;background:var(--jqt-surface-color);padding:0 8px;box-sizing:border-box;gap:6px}.search-input-wrapper:focus-within{border-color:var(--jqt-text-muted)}.search-input-wrapper .search-icon{flex-shrink:0;width:14px;height:14px;color:var(--jqt-text-muted)}.search-input{flex:1;border:none;outline:none;font-size:13px;color:var(--jqt-text-color);background:transparent;padding:7px 0;min-width:0}.search-input::placeholder{color:var(--jqt-text-muted)}.search-count{flex-shrink:0;font-size:12px;font-weight:600;color:var(--jqt-text-secondary);background:var(--jqt-row-hover-bg);border-radius:10px;padding:1px 7px;min-width:20px;text-align:center;line-height:18px}.search-clear-btn{background:none;border:none;cursor:pointer;color:var(--jqt-text-muted);font-size:16px;padding:0;line-height:1;flex-shrink:0}.search-clear-btn:hover{color:var(--jqt-text-color)}.view-btn{display:inline-flex;align-items:center;gap:5px;height:34px;border:1px solid var(--jqt-border-color);border-radius:8px;background:var(--jqt-surface-color);padding:0 12px;font-size:13px;font-weight:500;color:var(--jqt-text-color);cursor:pointer;white-space:nowrap;box-sizing:border-box}.view-btn:hover{border-color:var(--jqt-text-muted)}.view-btn-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0}.group-header-row{cursor:pointer}:host ::ng-deep .jquery-table{border-spacing:0}:host ::ng-deep .group-header-row td{padding:0!important;background:var(--jqt-group-bg);border-bottom:1px solid var(--jqt-group-border);border-top:1px solid var(--jqt-group-border);border-left:3px solid var(--jqt-group-accent);position:sticky;top:var(--actual-header-height, 56px);z-index:2;box-shadow:-4px 0 0 0 var(--jqt-group-bg)}:host ::ng-deep .group-header-row:hover td{background:var(--jqt-group-bg-hover);box-shadow:-4px 0 0 0 var(--jqt-group-bg-hover)}:host ::ng-deep .group-header-loading td,:host ::ng-deep .group-header-loading:hover td,:host ::ng-deep .group-header-too-many td,:host ::ng-deep .group-header-too-many:hover td{cursor:default;background:var(--jqt-surface-alt-color)}.group-too-many-label{font-size:12px;font-weight:500;color:var(--jqt-warn-color, #b45309);font-style:italic}.group-header-cell{padding:0!important}.group-header-label{display:flex;align-items:center;gap:6px;padding:6px 16px;font-size:12px;font-weight:700;color:var(--jqt-text-color);text-transform:uppercase;letter-spacing:.04em}.group-key-label{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.group-count-badge{flex-shrink:0;font-size:11px;line-height:1;font-weight:600;color:#fff;background:var(--jqt-text-secondary, #6b7280);border-radius:10px;padding:3px 7px;min-width:20px;text-align:center;text-transform:none;letter-spacing:0}.group-pagination{display:inline-flex;align-items:center;gap:4px;flex-shrink:0;margin-left:auto}.group-page-btn{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border:1px solid var(--jqt-border-color);border-radius:5px;background:var(--jqt-surface-color);cursor:pointer;font-size:14px;color:var(--jqt-text-color);line-height:1;padding:0 0 3px;text-transform:none;letter-spacing:0}.group-page-btn:hover:not(:disabled){border-color:var(--jqt-text-muted);background:var(--jqt-surface-alt-color)}.group-page-btn:disabled{opacity:.35;cursor:default}.group-page-info{font-size:11px;color:var(--jqt-text-secondary);min-width:36px;text-align:center;font-weight:500;text-transform:none;letter-spacing:0}.group-chevron{flex-shrink:0;font-size:14px;font-weight:700;padding-bottom:3px;transition:transform .15s ease;transform:rotate(90deg)}.group-chevron.collapsed{transform:rotate(0)}.no-data-row{cursor:default!important}.no-data-cell{padding:0!important;height:0!important;border:none!important}.jquery-table{width:100%;table-layout:fixed}:host ::ng-deep .mat-mdc-table{width:100%;background:transparent}:host ::ng-deep .mat-mdc-header-cell,:host ::ng-deep .mat-mdc-cell{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ::ng-deep .mat-mdc-header-row{background:var(--jqt-header-bg)}:host ::ng-deep .mat-mdc-header-cell{background:var(--jqt-header-bg);color:var(--jqt-header-text-color)}:host ::ng-deep .mat-mdc-row,:host ::ng-deep .mat-mdc-cell{background:var(--jqt-cell-bg)}:host ::ng-deep .mat-mdc-row{cursor:pointer;border-bottom-color:var(--jqt-border-color)}:host ::ng-deep .mat-mdc-row.no-pointer{cursor:auto}:host ::ng-deep .mat-mdc-row.no-data-row{cursor:default}:host ::ng-deep .mat-mdc-header-cell.drag-enabled{-webkit-user-select:none;user-select:none}:host ::ng-deep .mat-mdc-header-cell.drag-enabled:active{cursor:default}:host ::ng-deep .mat-mdc-header-cell.drop-target{background:var(--jqt-row-hover-bg)}.head-cell{display:flex;align-items:center;justify-content:space-between;gap:6px;min-width:0}.drag-handle{display:inline-flex;align-items:center;cursor:grab;color:var(--jqt-border-color);flex-shrink:0;padding:0 2px;border-radius:4px;line-height:1}.drag-handle:active{cursor:grabbing}.drag-handle:hover{color:var(--jqt-text-muted)}.drag-handle mat-icon{font-size:16px;width:16px;height:16px;line-height:1}.head-icon{font-size:14px;width:14px;height:14px;line-height:1;flex-shrink:0;color:var(--jqt-text-muted)}.head-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-transform:uppercase;font-size:11px;font-weight:600;letter-spacing:.05em}.head-actions{display:inline-flex;align-items:center;gap:6px}.cell-text{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cell-empty{color:var(--jqt-empty-cell-color, #9ca3af);font-style:italic}.bottom-bar{display:flex;align-items:center;flex:0 0 auto;background:var(--jqt-footer-bg);border-top:1px solid var(--jqt-border-color);border-right:1px solid var(--jqt-border-color);border-bottom:1px solid var(--jqt-border-color);border-left:1px solid var(--jqt-border-color);border-radius:0 0 8px 8px}:host ::ng-deep .bottom-bar mat-paginator{flex:1 1 auto;background:transparent;color:var(--jqt-footer-text-color)}:host ::ng-deep .bottom-bar .mat-mdc-paginator-outer-container{display:flex}:host ::ng-deep .bottom-bar .mat-mdc-paginator-container{display:flex;align-items:center;flex-wrap:nowrap;min-height:0;height:var(--actual-header-height, 56px);padding:0 8px;box-sizing:border-box}:host ::ng-deep .bottom-bar .mat-mdc-paginator-range-label,:host ::ng-deep .bottom-bar .mat-mdc-paginator-page-size-label{font-size:12px;color:var(--jqt-footer-text-color)}:host ::ng-deep .bottom-bar .mat-mdc-icon-button{color:var(--jqt-footer-icon-color);width:28px;height:28px;padding:0;line-height:1;display:inline-flex;align-items:center;justify-content:center;--mdc-icon-button-state-layer-size: 28px}:host ::ng-deep .bottom-bar .mat-mdc-icon-button .mat-mdc-button-persistent-ripple,:host ::ng-deep .bottom-bar .mat-mdc-icon-button .mdc-icon-button__state-layer{width:28px;height:28px;top:50%;left:50%;transform:translate(-50%,-50%)}:host ::ng-deep .bottom-bar .mat-mdc-icon-button .mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}:host ::ng-deep .bottom-bar .mat-mdc-icon-button:disabled{opacity:.35}:host ::ng-deep .bottom-bar .mat-mdc-paginator-page-size-select{width:72px;font-size:12px}:host ::ng-deep .bottom-bar .mat-mdc-text-field-wrapper{padding:0 8px}:host ::ng-deep .bottom-bar .mdc-text-field--outlined{height:28px}:host ::ng-deep .bottom-bar .mat-mdc-form-field-infix{padding-top:0;padding-bottom:0;min-height:0;height:28px;display:flex;align-items:center}:host ::ng-deep .bottom-bar .mat-mdc-select-value-text{font-size:12px;color:var(--jqt-text-color)}:host ::ng-deep .bottom-bar .mdc-notched-outline__leading,:host ::ng-deep .bottom-bar .mdc-notched-outline__notch,:host ::ng-deep .bottom-bar .mdc-notched-outline__trailing{border-color:var(--jqt-border-color)}.group-bottom-bar{display:flex;align-items:center;flex:0 0 auto;background:var(--jqt-footer-bg);border-top:1px solid var(--jqt-border-color);border-right:1px solid var(--jqt-border-color);border-bottom:1px solid var(--jqt-border-color);border-left:1px solid var(--jqt-border-color);border-radius:0 0 8px 8px}.group-bottom-bar-container{display:flex;align-items:center;justify-content:space-between;flex-wrap:nowrap;height:var(--actual-header-height, 56px);padding:0 16px;gap:16px;width:100%;box-sizing:border-box}.group-bar-separator{width:1px;height:20px;background:#9ca3af;flex-shrink:0;margin:0 4px}.group-bottom-bar-left{display:flex;align-items:center;gap:8px;flex-shrink:0}.group-sort-controls{display:flex;align-items:center;gap:2px}.group-sort-label{font-size:12px;color:var(--jqt-footer-text-color);white-space:nowrap;margin-right:2px}.group-sort-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--jqt-footer-icon-color);cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease}.group-sort-btn mat-icon{font-size:16px;width:16px;height:16px;line-height:16px}.group-sort-btn:hover{background:#0000000a}.group-sort-btn.active{color:var(--jqt-primary-color, #1976d2);border-color:var(--jqt-primary-color, #1976d2);background:#1976d214}:host ::ng-deep .group-bottom-bar .mat-mdc-paginator-page-size{display:flex;align-items:center;gap:8px}:host ::ng-deep .group-bottom-bar .mat-mdc-paginator-page-size-label{font-size:12px;color:var(--jqt-footer-text-color);white-space:nowrap}:host ::ng-deep .group-bottom-bar .mat-mdc-paginator-page-size-select{width:72px;font-size:12px}:host ::ng-deep .group-bottom-bar .mat-mdc-text-field-wrapper{padding:0 8px}:host ::ng-deep .group-bottom-bar .mdc-text-field--outlined{height:28px}:host ::ng-deep .group-bottom-bar .mat-mdc-form-field-infix{padding-top:0;padding-bottom:0;min-height:0;height:28px;display:flex;align-items:center}:host ::ng-deep .group-bottom-bar .mat-mdc-select-value-text{font-size:12px;color:var(--jqt-text-color)}:host ::ng-deep .group-bottom-bar .mdc-notched-outline__leading,:host ::ng-deep .group-bottom-bar .mdc-notched-outline__notch,:host ::ng-deep .group-bottom-bar .mdc-notched-outline__trailing{border-color:var(--jqt-border-color)}:host ::ng-deep .group-bottom-bar .mat-mdc-select-arrow{color:var(--jqt-footer-text-color)}.group-reset-btn{display:inline-flex;align-items:center;gap:6px;height:30px;padding:0 10px;border:1px solid var(--jqt-border-color);border-radius:8px;background:var(--jqt-surface-color);color:var(--jqt-text-secondary);font-size:12px;font-weight:500;font-family:inherit;cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease;white-space:nowrap;flex-shrink:0}.group-reset-btn:hover{background:var(--jqt-danger-hover-bg);color:var(--jqt-danger-color);border-color:var(--jqt-danger-border)}.group-reset-icon{width:14px;height:14px;flex-shrink:0}::ng-deep .mat-mdc-paginator-select-page-size .mat-pseudo-checkbox{display:none!important}::ng-deep .group-page-size-panel{min-width:90px!important}::ng-deep .group-page-size-panel .mat-mdc-option{justify-content:center;min-height:40px}::ng-deep .group-page-size-panel .mdc-list-item__primary-text{text-align:center;width:100%}@media (max-width: 960px){.table-layout{grid-template-columns:1fr}}::ng-deep .view-menu-panel .mat-mdc-menu-panel{min-width:220px!important;overflow:visible!important}::ng-deep .view-menu-panel .mat-mdc-menu-content{padding:4px 0!important}::ng-deep .view-submenu-panel .mat-mdc-menu-panel{min-width:200px!important}::ng-deep .view-submenu-panel .mat-mdc-menu-content{padding:0!important}::ng-deep .view-menu-panel .view-menu-sub-trigger.mat-mdc-menu-item{min-height:42px;padding:0 14px;display:flex;align-items:center;box-sizing:border-box}::ng-deep .view-menu-panel .view-menu-sub-trigger .mdc-list-item__primary-text{display:flex;align-items:center;width:100%;gap:0;overflow:hidden}::ng-deep .view-menu-sub-label{font-size:13px;font-weight:500;color:var(--jqt-text-color);flex:0 0 auto;white-space:nowrap;padding-right:8px}::ng-deep .view-menu-sub-value{font-size:12px;color:var(--jqt-text-secondary);flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:right}::ng-deep .view-submenu{padding:4px 6px;min-width:180px;box-sizing:border-box}::ng-deep .view-submenu .view-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;color:var(--jqt-text-color);cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}::ng-deep .view-submenu .view-menu-item:hover{background:var(--jqt-row-hover-bg)}::ng-deep .view-submenu .view-menu-item.active{font-weight:600}::ng-deep .view-submenu .view-menu-item:disabled{opacity:.4;cursor:default}::ng-deep .view-menu-col-icon{flex-shrink:0;font-size:14px!important;width:14px!important;height:14px!important;color:var(--jqt-text-muted)}::ng-deep .view-menu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:var(--jqt-text-color)}::ng-deep .view-menu-separator{border:none;border-top:1px solid var(--jqt-border-color);margin:4px 0}.lazy-skeleton{display:block;height:12px;width:70%;border-radius:4px;background:linear-gradient(90deg,var(--jqt-border-color) 25%,var(--jqt-surface-alt-color) 50%,var(--jqt-border-color) 75%);background-size:200% 100%;animation:lazy-shimmer 1.4s ease-in-out infinite}.group-lazy-key-skeleton{flex:1 1 auto;min-width:80px;height:13px;border-radius:4px}@keyframes lazy-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}.lazy-error-btn{display:inline-flex;align-items:center;gap:4px;background:none;border:1px solid #fca5a5;border-radius:4px;padding:2px 6px;cursor:pointer;color:#ef4444;font-size:12px;opacity:.85;transition:opacity .15s}.lazy-error-btn:hover{opacity:1;background:#fef2f2}.lazy-error-icon{font-size:12px}.lazy-error-label{font-size:11px;font-weight:500}::ng-deep .lazy-menu-spinner{display:inline-block;margin-left:auto;font-size:13px;color:var(--jqt-text-secondary);animation:lazy-spin 1s linear infinite}::ng-deep .lazy-menu-error{display:inline-block;margin-left:auto;font-size:13px;color:#ef4444}@keyframes lazy-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
1565
+ }], propDecorators: { config: [{
1566
+ type: Input
1567
+ }], dataSource: [{
1568
+ type: Input
1569
+ }], data: [{
1570
+ type: Input
1571
+ }], displayedColumns: [{
1572
+ type: Input
1573
+ }], columnsConfig: [{
1574
+ type: Input
1575
+ }], columnLabels: [{
1576
+ type: Input
1577
+ }], isLoading: [{
1578
+ type: Input
1579
+ }], addRequested: [{
1580
+ type: Output
1581
+ }], columnAdded: [{
1582
+ type: Output
1583
+ }], columnRemoved: [{
1584
+ type: Output
1585
+ }], categorySelected: [{
1586
+ type: Output
1587
+ }], rowSelected: [{
1588
+ type: Output
1589
+ }], paginator: [{
1590
+ type: ViewChild,
1591
+ args: [MatPaginator]
1592
+ }], sort: [{
1593
+ type: ViewChild,
1594
+ args: [MatSort]
1595
+ }], tableBodyScrollRef: [{
1596
+ type: ViewChild,
1597
+ args: ['tableBodyScroll']
1598
+ }], slicePanelRef: [{
1599
+ type: ViewChild,
1600
+ args: [SlicePanelComponent]
1601
+ }], viewMenuTrigger: [{
1602
+ type: ViewChild,
1603
+ args: ['viewMenuTrigger']
1604
+ }], _cellDefs: [{
1605
+ type: ContentChildren,
1606
+ args: [JqtCellDefDirective]
1607
+ }] } });
1608
+
1609
+ /*
1610
+ * Public API Surface of jquery-table
1611
+ */
1612
+
1613
+ /**
1614
+ * Generated bundle index. Do not edit.
1615
+ */
1616
+
1617
+ export { JqtCellDefDirective, SlicePanelComponent, TableComponent };
1618
+ //# sourceMappingURL=oneteme-jquery-table.mjs.map