@oneteme/jquery-table 0.0.13 → 0.0.15
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.
- package/README.md +138 -23
- package/esm2022/lib/component/export.manager.mjs +6 -5
- package/esm2022/lib/component/table-preferences.manager.mjs +13 -3
- package/esm2022/lib/component/table.component.mjs +398 -143
- package/esm2022/lib/component/table.constants.mjs +3 -1
- package/esm2022/lib/directive/jqt-cell-def.directive.mjs +4 -2
- package/esm2022/lib/jquery-table.model.mjs +1 -1
- package/fesm2022/oneteme-jquery-table.mjs +418 -148
- package/fesm2022/oneteme-jquery-table.mjs.map +1 -1
- package/lib/component/table.component.d.ts +98 -24
- package/lib/component/table.constants.d.ts +2 -0
- package/lib/directive/jqt-cell-def.directive.d.ts +5 -1
- package/lib/jquery-table.model.d.ts +50 -9
- package/package.json +3 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, Directive, Input, EventEmitter, Component, ChangeDetectionStrategy, Output, inject, ChangeDetectorRef, ElementRef, NgZone, HostBinding, ViewChild, ContentChildren, HostListener } from '@angular/core';
|
|
2
|
+
import { InjectionToken, Directive, Input, EventEmitter, Component, ChangeDetectionStrategy, Output, inject, ChangeDetectorRef, ElementRef, NgZone, isDevMode, HostBinding, ViewChild, ContentChildren, HostListener } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
4
|
import { CommonModule } from '@angular/common';
|
|
5
5
|
import * as i2 from '@angular/forms';
|
|
@@ -17,8 +17,8 @@ import * as i5 from '@angular/material/sort';
|
|
|
17
17
|
import { MatSort, MatSortModule } from '@angular/material/sort';
|
|
18
18
|
import * as i3 from '@angular/material/table';
|
|
19
19
|
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
|
|
20
|
-
import { Subject, takeUntil } from 'rxjs';
|
|
21
|
-
import { OrganizerButtonComponent, OrganizerFacade, GroupByManager,
|
|
20
|
+
import { Subject, takeUntil, defer, take } from 'rxjs';
|
|
21
|
+
import { OrganizerButtonComponent, OrganizerFacade, GroupByManager, SlicePanelComponent } from '@oneteme/jquery-organizer';
|
|
22
22
|
export { OrganizerButtonComponent, SlicePanelComponent } from '@oneteme/jquery-organizer';
|
|
23
23
|
import { FullscreenManager, VisualSnapshotStorage, containsFunction, cloneSerializable } from '@oneteme/jquery-core';
|
|
24
24
|
import * as i7 from '@angular/material/form-field';
|
|
@@ -131,7 +131,9 @@ const JQT_I18N = new InjectionToken('JQT_I18N');
|
|
|
131
131
|
* </ng-template>
|
|
132
132
|
* </jquery-table>
|
|
133
133
|
* ```
|
|
134
|
-
* Le contexte expose `$implicit` (ligne brute `T`)
|
|
134
|
+
* Le contexte expose `$implicit` (ligne brute `T`), `index` (index global) et `value` (valeur affichée de la cellule,
|
|
135
|
+
* y compris pour une colonne `lazy` dont la valeur n'est pas présente dans la ligne brute).
|
|
136
|
+
* Pour une colonne `lazy`, le template n'est rendu qu'une fois la valeur chargée ; le chargement et l'erreur sont gérés par le tableau.
|
|
135
137
|
*/
|
|
136
138
|
class JqtCellDefDirective {
|
|
137
139
|
constructor(template) {
|
|
@@ -241,11 +243,21 @@ class TablePreferencesManager {
|
|
|
241
243
|
}
|
|
242
244
|
/** Supprime la configuration sauvegardée. */
|
|
243
245
|
clear() {
|
|
244
|
-
|
|
246
|
+
try {
|
|
247
|
+
localStorage.removeItem(this.key);
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// stockage indisponible : rien à effacer
|
|
251
|
+
}
|
|
245
252
|
}
|
|
246
253
|
/** Indique si une configuration est actuellement sauvegardée. */
|
|
247
254
|
hasSaved() {
|
|
248
|
-
|
|
255
|
+
try {
|
|
256
|
+
return localStorage.getItem(this.key) !== null;
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
249
261
|
}
|
|
250
262
|
}
|
|
251
263
|
|
|
@@ -268,7 +280,8 @@ class ExportManager {
|
|
|
268
280
|
return;
|
|
269
281
|
const rows = this.getRows();
|
|
270
282
|
const columns = this.getColumns();
|
|
271
|
-
const
|
|
283
|
+
const baseName = cfg.filename || 'export';
|
|
284
|
+
const filename = baseName.toLowerCase().endsWith('.csv') ? baseName : baseName + '.csv';
|
|
272
285
|
const header = columns.map(c => this._csvEscape(c.header || c.key)).join(',');
|
|
273
286
|
const lines = rows.map((row, idx) => {
|
|
274
287
|
if (cfg.transform) {
|
|
@@ -282,13 +295,13 @@ class ExportManager {
|
|
|
282
295
|
const url = URL.createObjectURL(blob);
|
|
283
296
|
const a = document.createElement('a');
|
|
284
297
|
a.href = url;
|
|
285
|
-
a.download = filename
|
|
298
|
+
a.download = filename;
|
|
286
299
|
a.click();
|
|
287
300
|
URL.revokeObjectURL(url);
|
|
288
301
|
}
|
|
289
302
|
_csvEscape(value) {
|
|
290
|
-
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
|
|
291
|
-
return '"' + value.
|
|
303
|
+
if (value.includes(',') || value.includes('"') || value.includes('\n') || value.includes('\r')) {
|
|
304
|
+
return '"' + value.replaceAll('"', '""') + '"';
|
|
292
305
|
}
|
|
293
306
|
return value;
|
|
294
307
|
}
|
|
@@ -306,6 +319,8 @@ const GROUP_ROW_COLUMN = '__group_row';
|
|
|
306
319
|
const GROUP_HEADER_MARKER = '__groupHeader';
|
|
307
320
|
/** Clé du groupe spéciale « chargement lazy en cours ». */
|
|
308
321
|
const GROUP_KEY_LAZY_LOADING = '__lazy_loading__';
|
|
322
|
+
/** Clé du groupe spéciale « chargement lazy en erreur ». */
|
|
323
|
+
const GROUP_KEY_LAZY_ERROR = '__lazy_error__';
|
|
309
324
|
/** Clé du groupe spéciale « trop de groupes ». */
|
|
310
325
|
const GROUP_KEY_TOO_MANY = '__too_many_groups__';
|
|
311
326
|
/** Propriété injectée dans chaque ligne projetée : référence vers la ligne brute d'origine. */
|
|
@@ -375,6 +390,11 @@ class TableComponent {
|
|
|
375
390
|
constructor() {
|
|
376
391
|
this._isFullscreen = false;
|
|
377
392
|
this.isLoading = false;
|
|
393
|
+
/**
|
|
394
|
+
* À passer à `true` quand la requête relancée après `lazyColumnsRequested` échoue : les colonnes en attente affichent un bouton
|
|
395
|
+
* « Réessayer ». Repasser à `false` au nouvel essai.
|
|
396
|
+
*/
|
|
397
|
+
this.lazyError = false;
|
|
378
398
|
this.copyFeedback = {};
|
|
379
399
|
this.addRequested = new EventEmitter();
|
|
380
400
|
this.columnAdded = new EventEmitter();
|
|
@@ -392,6 +412,11 @@ class TableComponent {
|
|
|
392
412
|
/** Émis quand la liste des colonnes visibles change (clés dans l’ordre d’affichage). */
|
|
393
413
|
this.columnsChange = new EventEmitter();
|
|
394
414
|
this.visualCopied = new EventEmitter();
|
|
415
|
+
/**
|
|
416
|
+
* Clés de toutes les colonnes `lazy` demandées jusqu'ici (cumul), émises quand l'une d'elles est activée pour la première fois.
|
|
417
|
+
* L'application relance sa requête complète en les incluant puis repasse les nouvelles `data`.
|
|
418
|
+
*/
|
|
419
|
+
this.lazyColumnsRequested = new EventEmitter();
|
|
395
420
|
/** Map clé→TemplateRef pré-calculée depuis _cellDefs. Mise à jour dans ngAfterContentInit et sur _cellDefs.changes. */
|
|
396
421
|
this._cellDefMap = new Map();
|
|
397
422
|
this.renderedColumns = [];
|
|
@@ -422,6 +447,7 @@ class TableComponent {
|
|
|
422
447
|
this._showSlicePanel = false;
|
|
423
448
|
/** Snapshot stable de l'état collapsed du panneau slice, mis à jour via (collapsedChange) */
|
|
424
449
|
this._slicePanelCollapsed = false;
|
|
450
|
+
this._initialSlicePanelCollapsedApplied = false;
|
|
425
451
|
this._sliceByMenuItems = [];
|
|
426
452
|
this.searchQuery = '';
|
|
427
453
|
this._matSortActive = '';
|
|
@@ -432,18 +458,30 @@ class TableComponent {
|
|
|
432
458
|
this._totalFilteredCount = 0;
|
|
433
459
|
/** Gestionnaire du mode Group By (état collapsed, pagination, tri des groupes). */
|
|
434
460
|
this._groupBy = new GroupByManager(() => { this._projectCurrentPage(); this._cdr.markForCheck(); }, () => this.refreshViewModel());
|
|
435
|
-
/**
|
|
436
|
-
|
|
461
|
+
/**
|
|
462
|
+
* Statut des colonnes `lazy` déjà demandées (absente = pas encore demandée).
|
|
463
|
+
* `loading` dure jusqu'à la réception de nouvelles `data` ; la référence change à chaque mise à jour (panneau de slices).
|
|
464
|
+
*/
|
|
465
|
+
this._lazyColumnStatus = new Map();
|
|
466
|
+
this._lazyFlushQueued = false;
|
|
467
|
+
/** Lignes retournées par `config.lazyLoader` : elles remplacent `data` jusqu'au prochain `data`. */
|
|
468
|
+
this._loaderRows = null;
|
|
469
|
+
this._lazyWarned = false;
|
|
470
|
+
this._optionalLazyColumns = new WeakMap();
|
|
437
471
|
this._cdr = inject(ChangeDetectorRef);
|
|
438
472
|
this._el = inject((ElementRef));
|
|
439
473
|
this._ngZone = inject(NgZone);
|
|
474
|
+
this._paginatorIntl = inject(MatPaginatorIntl);
|
|
440
475
|
this._i18nRaw = inject(JQT_I18N, { optional: true });
|
|
476
|
+
this._fullItemsPerPageLabel = this._paginatorIntl.itemsPerPageLabel;
|
|
441
477
|
this.copyFeedbackMessage = '';
|
|
442
478
|
/** Labels de l’interface. Fusionnés depuis JQT_I18N_DEFAULTS et le token JQT_I18N injecté. */
|
|
443
|
-
this.i18n =
|
|
479
|
+
this.i18n = this._i18nRaw
|
|
480
|
+
? { ...JQT_I18N_DEFAULTS, ...this._i18nRaw }
|
|
481
|
+
: { ...JQT_I18N_DEFAULTS };
|
|
444
482
|
this.isGroupHeader = (_, row) => row[GROUP_HEADER_MARKER] === true;
|
|
445
483
|
/** Gestionnaire export CSV. */
|
|
446
|
-
this._export = new ExportManager(() => this._allFilteredRows, () => this.activeFields, () => this.resolvedConfig.export, (col, row, idx) => this.
|
|
484
|
+
this._export = new ExportManager(() => this._allFilteredRows, () => this.activeFields, () => this.resolvedConfig.export, (col, row, idx) => this._resolveExportValue(col, row, idx));
|
|
447
485
|
// ── Lifecycle
|
|
448
486
|
this.pageSize = 5;
|
|
449
487
|
this.pageSizeOptions = [5, 10, 20];
|
|
@@ -478,12 +516,21 @@ class TableComponent {
|
|
|
478
516
|
/** Cache groupBy — invalidé dès que rows/key/tri changent. */
|
|
479
517
|
this._groupCache = null;
|
|
480
518
|
this._pendingRender = null;
|
|
519
|
+
this._deferredTimers = new Set();
|
|
520
|
+
this._headerMeasureFrame = null;
|
|
521
|
+
this._destroyed = false;
|
|
481
522
|
this._staticSliceHiddenKeys = new Set();
|
|
482
523
|
this._configHiddenKeys = new Set();
|
|
483
524
|
/** Clés des colonnes visibles du dernier émission columnsChange (pour éviter les émissions dupliquement). */
|
|
484
525
|
this._prevColumnKeys = [];
|
|
485
526
|
this._organizerConfigCache = null;
|
|
486
|
-
this.trackRowFn = (_index, row) =>
|
|
527
|
+
this.trackRowFn = (_index, row) => {
|
|
528
|
+
if (row[GROUP_HEADER_MARKER])
|
|
529
|
+
return `__group_${row[ROW_GROUP_KEY]}`;
|
|
530
|
+
if (row[ROW_GROUP_KEY] !== undefined)
|
|
531
|
+
return `__row_${row[ROW_GROUP_KEY]}_${row[ROW_INDEX_KEY] ?? _index}`;
|
|
532
|
+
return row[ROW_INDEX_KEY] ?? _index;
|
|
533
|
+
};
|
|
487
534
|
this._preferencesMessageTimer = null;
|
|
488
535
|
}
|
|
489
536
|
/** Paramètre de contrôle pour réinitialiser barre de recherche. */
|
|
@@ -513,16 +560,18 @@ class TableComponent {
|
|
|
513
560
|
set groupPageSize(v) { this._groupBy.groupPageSize = v; }
|
|
514
561
|
get groupSortOrder() { return this._groupBy.groupSortOrder; }
|
|
515
562
|
set groupSortOrder(v) { this._groupBy.groupSortOrder = v; }
|
|
516
|
-
/** Compatibilité template : retourne la map de statuts lazy. */
|
|
517
|
-
get _lazyColumnStatus() { return this._lazy.status; }
|
|
518
|
-
/** Compatibilité template : retourne la map de données lazy. */
|
|
519
|
-
get _lazyColumnData() { return this._lazy.data; }
|
|
520
563
|
/** Retourne true si la ligne de groupe est une ligne normale (ni lazy, ni too-many). */
|
|
521
564
|
isNormalGroupRow(row) {
|
|
522
|
-
|
|
565
|
+
const key = row[ROW_GROUP_KEY];
|
|
566
|
+
return key !== GROUP_KEY_LAZY_LOADING && key !== GROUP_KEY_LAZY_ERROR && key !== GROUP_KEY_TOO_MANY;
|
|
523
567
|
}
|
|
524
568
|
isGroupRowLoading(row) { return row[ROW_GROUP_KEY] === GROUP_KEY_LAZY_LOADING; }
|
|
569
|
+
isGroupRowError(row) { return row[ROW_GROUP_KEY] === GROUP_KEY_LAZY_ERROR; }
|
|
525
570
|
isGroupRowTooMany(row) { return row[ROW_GROUP_KEY] === GROUP_KEY_TOO_MANY; }
|
|
571
|
+
retryGroupByColumn(event) {
|
|
572
|
+
event?.stopPropagation();
|
|
573
|
+
this.retryLazyColumns();
|
|
574
|
+
}
|
|
526
575
|
/** Gère le clic sur une ligne d'en-tête de groupe (no-op pour les états spéciaux). */
|
|
527
576
|
onGroupHeaderClick(row) {
|
|
528
577
|
if (!this.isNormalGroupRow(row))
|
|
@@ -531,20 +580,28 @@ class TableComponent {
|
|
|
531
580
|
}
|
|
532
581
|
// Seuil au-delà duquel le rendu groupé est bloqué pour protéger le navigateur
|
|
533
582
|
static { this.MAX_GROUP_COUNT = 500; }
|
|
583
|
+
emitColumnsChangeIfNeeded() {
|
|
584
|
+
const nextKeys = [...this.renderedColumns];
|
|
585
|
+
if (nextKeys.length === this._prevColumnKeys.length
|
|
586
|
+
&& nextKeys.every((key, index) => key === this._prevColumnKeys[index]))
|
|
587
|
+
return;
|
|
588
|
+
this._prevColumnKeys = nextKeys;
|
|
589
|
+
this.columnsChange.emit(nextKeys);
|
|
590
|
+
}
|
|
534
591
|
ngOnChanges(changes) {
|
|
535
|
-
const dataChanged = !!(changes['
|
|
536
|
-
if (dataChanged)
|
|
537
|
-
this.
|
|
538
|
-
|
|
592
|
+
const dataChanged = !!(changes['data'] || changes['dataSource']);
|
|
593
|
+
if (dataChanged)
|
|
594
|
+
this._onNewData();
|
|
595
|
+
if (changes['lazyError'])
|
|
596
|
+
this._applyLazyError();
|
|
539
597
|
this.refreshViewModel();
|
|
540
598
|
if (changes['config']) {
|
|
541
599
|
this.applyConfigSearchReset();
|
|
542
600
|
this.applyInitialSearchQuery(changes['config'].firstChange);
|
|
543
601
|
this._invalidateOrganizerConfig(); // les colonnes ont pu changer
|
|
544
602
|
}
|
|
545
|
-
if (dataChanged)
|
|
546
|
-
this.
|
|
547
|
-
}
|
|
603
|
+
if (dataChanged && this._lazyColumnStatus.size && this._resolvedData.length)
|
|
604
|
+
this._runLazyLoader();
|
|
548
605
|
}
|
|
549
606
|
ngAfterContentInit() {
|
|
550
607
|
this._rebuildCellDefMap();
|
|
@@ -557,8 +614,26 @@ class TableComponent {
|
|
|
557
614
|
this.attachPaginator();
|
|
558
615
|
this.attachSort();
|
|
559
616
|
this._measureHeaderHeight();
|
|
617
|
+
this._observeTableShellWidth();
|
|
560
618
|
this._applyPendingDynamicSliceKeys();
|
|
561
619
|
}
|
|
620
|
+
_observeTableShellWidth() {
|
|
621
|
+
const tableShell = this.tableShellRef?.nativeElement;
|
|
622
|
+
if (!tableShell || typeof ResizeObserver === 'undefined')
|
|
623
|
+
return;
|
|
624
|
+
this._tableShellResizeObserver = new ResizeObserver(([entry]) => {
|
|
625
|
+
const compact = entry.contentRect.width < 660;
|
|
626
|
+
const label = compact ? 'Par page :' : this._fullItemsPerPageLabel;
|
|
627
|
+
if (this._paginatorIntl.itemsPerPageLabel === label)
|
|
628
|
+
return;
|
|
629
|
+
this._ngZone.run(() => {
|
|
630
|
+
this._paginatorIntl.itemsPerPageLabel = label;
|
|
631
|
+
this._paginatorIntl.changes.next();
|
|
632
|
+
this._cdr.markForCheck();
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
this._tableShellResizeObserver.observe(tableShell);
|
|
636
|
+
}
|
|
562
637
|
_applyPendingDynamicSliceKeys() {
|
|
563
638
|
const hasPendingDynamic = !!this._pendingDynamicSliceKeys;
|
|
564
639
|
const hasPendingFilters = !!this._pendingSliceFilters;
|
|
@@ -569,29 +644,28 @@ class TableComponent {
|
|
|
569
644
|
const filters = this._pendingSliceFilters ? { ...this._pendingSliceFilters } : null;
|
|
570
645
|
this._pendingDynamicSliceKeys = null;
|
|
571
646
|
this._pendingSliceFilters = null;
|
|
572
|
-
|
|
647
|
+
this._scheduleDeferred(() => {
|
|
573
648
|
if (this._resolvedData.length === 0) {
|
|
574
|
-
console.log('[SliceRestore] ngAfterViewInit: données absentes, restauration différée à _recomputeShowSlicePanel', { keys, filters });
|
|
575
649
|
if (filters)
|
|
576
650
|
this._pendingSliceFilters = filters;
|
|
577
651
|
if (keys.length > 0)
|
|
578
652
|
this._pendingDynamicSliceKeys = [...keys];
|
|
579
653
|
return;
|
|
580
654
|
}
|
|
581
|
-
console.log('[SliceRestore] ngAfterViewInit: données présentes, application immédiate', { keys, filters });
|
|
582
655
|
keys.forEach(key => {
|
|
583
656
|
const col = allCols.find(c => c.key === key);
|
|
584
657
|
if (col)
|
|
585
658
|
this.addDynamicSlice(col);
|
|
586
659
|
});
|
|
587
660
|
if (filters && this.slicePanelRef) {
|
|
588
|
-
console.log('[SliceRestore] restoreFilters depuis ngAfterViewInit', filters);
|
|
589
661
|
this.slicePanelRef.restoreFilters(filters);
|
|
590
662
|
}
|
|
591
663
|
this._cdr.markForCheck();
|
|
592
664
|
});
|
|
593
665
|
}
|
|
594
666
|
ngOnDestroy() {
|
|
667
|
+
this._destroyed = true;
|
|
668
|
+
this._tableShellResizeObserver?.disconnect();
|
|
595
669
|
if (this._copyFeedbackTimer !== undefined)
|
|
596
670
|
window.clearTimeout(this._copyFeedbackTimer);
|
|
597
671
|
if (this._pendingRender !== null) {
|
|
@@ -600,6 +674,10 @@ class TableComponent {
|
|
|
600
674
|
if (this._preferencesMessageTimer !== null) {
|
|
601
675
|
clearTimeout(this._preferencesMessageTimer);
|
|
602
676
|
}
|
|
677
|
+
this._deferredTimers.forEach(timer => clearTimeout(timer));
|
|
678
|
+
this._deferredTimers.clear();
|
|
679
|
+
if (this._headerMeasureFrame !== null)
|
|
680
|
+
cancelAnimationFrame(this._headerMeasureFrame);
|
|
603
681
|
// Nettoie un éventuel resize en cours (listeners window non retirés)
|
|
604
682
|
this._resizeState = null;
|
|
605
683
|
this._destroy$.next();
|
|
@@ -608,6 +686,8 @@ class TableComponent {
|
|
|
608
686
|
}
|
|
609
687
|
_measureHeaderHeight() {
|
|
610
688
|
const measure = () => {
|
|
689
|
+
if (this._destroyed)
|
|
690
|
+
return;
|
|
611
691
|
const headerRow = this._el.nativeElement.querySelector('tr.mat-mdc-header-row');
|
|
612
692
|
if (headerRow) {
|
|
613
693
|
const h = headerRow.getBoundingClientRect().height;
|
|
@@ -617,15 +697,32 @@ class TableComponent {
|
|
|
617
697
|
}
|
|
618
698
|
}
|
|
619
699
|
// Header pas encore rendu (ex: lazy Angular Material), on réessaie après le prochain frame
|
|
620
|
-
this.
|
|
700
|
+
this._scheduleHeaderMeasure(measure);
|
|
621
701
|
};
|
|
622
|
-
this.
|
|
702
|
+
this._scheduleHeaderMeasure(measure);
|
|
703
|
+
}
|
|
704
|
+
_scheduleHeaderMeasure(measure) {
|
|
705
|
+
this._ngZone.runOutsideAngular(() => {
|
|
706
|
+
this._headerMeasureFrame = requestAnimationFrame(() => {
|
|
707
|
+
this._headerMeasureFrame = null;
|
|
708
|
+
measure();
|
|
709
|
+
});
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
_scheduleDeferred(callback) {
|
|
713
|
+
const timer = setTimeout(() => {
|
|
714
|
+
this._deferredTimers.delete(timer);
|
|
715
|
+
if (!this._destroyed)
|
|
716
|
+
callback();
|
|
717
|
+
}, 0);
|
|
718
|
+
this._deferredTimers.add(timer);
|
|
623
719
|
}
|
|
624
720
|
_invalidateOrganizerConfig() { this._organizerConfigCache = null; }
|
|
625
721
|
get organizerConfig() {
|
|
626
722
|
if (this._organizerConfigCache)
|
|
627
723
|
return this._organizerConfigCache;
|
|
628
724
|
const allCols = [...this._organizer.menuBaseFields, ...this._organizer.menuOptionalFields];
|
|
725
|
+
const organizerOptions = this.resolvedConfig.organizer;
|
|
629
726
|
this._organizerConfigCache = {
|
|
630
727
|
fields: this._organizer.showFields ? allCols.map(c => ({
|
|
631
728
|
id: c.key,
|
|
@@ -643,6 +740,9 @@ class TableComponent {
|
|
|
643
740
|
label: item.title,
|
|
644
741
|
icon: item.icon,
|
|
645
742
|
})) : undefined,
|
|
743
|
+
buttonLabel: organizerOptions?.buttonLabel,
|
|
744
|
+
buttonIcon: organizerOptions?.buttonIcon,
|
|
745
|
+
showButtonIcon: organizerOptions?.showButtonIcon,
|
|
646
746
|
showExport: this.showExportButton,
|
|
647
747
|
onExport: () => this.onExport(),
|
|
648
748
|
showPreferences: this.showPreferencesMenu,
|
|
@@ -657,6 +757,9 @@ class TableComponent {
|
|
|
657
757
|
};
|
|
658
758
|
return this._organizerConfigCache;
|
|
659
759
|
}
|
|
760
|
+
get organizerHideMenuValues() {
|
|
761
|
+
return this.resolvedConfig.organizer?.hideMenuValues === true;
|
|
762
|
+
}
|
|
660
763
|
get organizerState() {
|
|
661
764
|
return {
|
|
662
765
|
visibleFields: this.visibleColumns.map(c => c.key),
|
|
@@ -771,30 +874,50 @@ class TableComponent {
|
|
|
771
874
|
}
|
|
772
875
|
get groupByColumns() { return this._organizer.groupByFields; }
|
|
773
876
|
colLabel(col) { return this._organizer.fieldLabel(col); }
|
|
877
|
+
/** Traduit les options de la vue table vers celles de l'Organizer. */
|
|
878
|
+
_organizerViewConfig() {
|
|
879
|
+
const view = this.resolvedConfig.view;
|
|
880
|
+
if (!view)
|
|
881
|
+
return undefined;
|
|
882
|
+
return {
|
|
883
|
+
enabled: view.enabled,
|
|
884
|
+
enableFieldRemoval: view.enableColumnRemoval,
|
|
885
|
+
enableFieldDragDrop: view.enableColumnDragDrop,
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
/** Ne change la référence que si le contenu change : le panneau recalcule tout à chaque nouvelle référence. */
|
|
889
|
+
_setSliceConfigs(next) {
|
|
890
|
+
const same = next.length === this._sliceConfigs.length && next.every((slice, i) => slice === this._sliceConfigs[i]);
|
|
891
|
+
if (!same)
|
|
892
|
+
this._sliceConfigs = next;
|
|
893
|
+
}
|
|
774
894
|
onSearchChange() {
|
|
895
|
+
this._runSearch();
|
|
896
|
+
this.searchChange.emit(this.searchQuery);
|
|
897
|
+
}
|
|
898
|
+
_runSearch() {
|
|
775
899
|
this._preservePageIndex = 0;
|
|
776
|
-
this.
|
|
900
|
+
this._scheduleRender();
|
|
777
901
|
this.paginator?.firstPage();
|
|
778
|
-
this.searchChange.emit(this.searchQuery);
|
|
779
902
|
}
|
|
780
|
-
|
|
903
|
+
setGroupByAndEmit(key) {
|
|
904
|
+
const previousKey = this._organizer.groupBy.activeKey ?? null;
|
|
781
905
|
this._organizer.setGroupBy(key);
|
|
906
|
+
const nextKey = this._organizer.groupBy.activeKey ?? null;
|
|
907
|
+
if (previousKey !== nextKey)
|
|
908
|
+
this.groupByChange.emit(nextKey);
|
|
909
|
+
}
|
|
910
|
+
onGroupByChange(key) {
|
|
911
|
+
this.setGroupByAndEmit(key);
|
|
782
912
|
this._groupBy.setDefaultCollapsed(key !== null);
|
|
783
913
|
this._groupBy.reset();
|
|
784
914
|
this._groupBy.groupPageSize = this.resolveDefaultGroupPageSize();
|
|
785
915
|
this.refreshViewModel();
|
|
786
916
|
this.paginator?.firstPage();
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
const colDef = (this.resolvedConfig.columns || []).find(c => c.key === key);
|
|
790
|
-
if (colDef?.lazy && this._lazyColumnStatus.get(key) !== 'loaded') {
|
|
791
|
-
this.triggerLazyFetch(colDef);
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
else {
|
|
795
|
-
this._ngZone.runOutsideAngular(() => setTimeout(() => {
|
|
917
|
+
if (!key) {
|
|
918
|
+
this._ngZone.runOutsideAngular(() => this._scheduleDeferred(() => {
|
|
796
919
|
this.tableBodyScrollRef?.nativeElement.scrollTo({ top: 0, behavior: 'smooth' });
|
|
797
|
-
}
|
|
920
|
+
}));
|
|
798
921
|
}
|
|
799
922
|
}
|
|
800
923
|
onGroupPageSizeChange(size) {
|
|
@@ -802,30 +925,126 @@ class TableComponent {
|
|
|
802
925
|
}
|
|
803
926
|
onGroupSortToggle(dir) {
|
|
804
927
|
this._groupBy.onGroupSortToggle(dir, (key) => {
|
|
805
|
-
this._ngZone.runOutsideAngular(() =>
|
|
928
|
+
this._ngZone.runOutsideAngular(() => this._scheduleDeferred(() => this.scrollToGroupHeader(key)));
|
|
806
929
|
});
|
|
807
930
|
}
|
|
808
931
|
getLazyColumnStatus(key) {
|
|
809
|
-
return this.
|
|
932
|
+
return this._lazyColumnStatus.get(key) ?? 'idle';
|
|
810
933
|
}
|
|
811
934
|
getLazyRenderType(rowValue) {
|
|
812
|
-
|
|
935
|
+
if (rowValue === LAZY_LOADING_VALUE)
|
|
936
|
+
return 'loading';
|
|
937
|
+
if (rowValue === LAZY_ERROR_VALUE)
|
|
938
|
+
return 'error';
|
|
939
|
+
return 'value';
|
|
940
|
+
}
|
|
941
|
+
/** Relance la demande des colonnes en erreur : le chargeur est rappelé, ou l'application refait sa requête complète. */
|
|
942
|
+
retryLazyColumns() {
|
|
943
|
+
const failed = [...this._lazyColumnStatus].filter(([, status]) => status === 'error').map(([key]) => key);
|
|
944
|
+
if (!failed.length)
|
|
945
|
+
return;
|
|
946
|
+
this._setLazyStatus(failed, 'loading');
|
|
947
|
+
this.lazyColumnsRequested.emit([...this._lazyColumnStatus.keys()]);
|
|
948
|
+
this._runLazyLoader();
|
|
949
|
+
this._scheduleRender();
|
|
950
|
+
}
|
|
951
|
+
/** Colonnes lazy dont la valeur est nécessaire : affichées, utilisées en Group by ou en Slice by dynamique. */
|
|
952
|
+
_usedLazyColumns() {
|
|
953
|
+
const used = new Map();
|
|
954
|
+
const add = (column) => { if (column?.lazy)
|
|
955
|
+
used.set(column.key, column); };
|
|
956
|
+
this.activeFields.forEach(add);
|
|
957
|
+
this._organizer.sliceBy.activeDynamicFields.forEach(add);
|
|
958
|
+
const groupKey = this.activeGroupByKey;
|
|
959
|
+
if (groupKey)
|
|
960
|
+
add(this._columnMap.get(groupKey));
|
|
961
|
+
return [...used.values()];
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* Quel que soit le chemin d'activation (Organizer, Group by, Slice by, préférences...), les colonnes lazy utilisées et pas encore
|
|
965
|
+
* demandées sont réclamées en un seul événement. Hors cycle de détection, et seulement une fois des lignes affichées :
|
|
966
|
+
* la première réponse de l'application ne doit pas être prise pour celle qui contient les colonnes.
|
|
967
|
+
*/
|
|
968
|
+
_requestLazyColumns() {
|
|
969
|
+
if (this._lazyFlushQueued)
|
|
970
|
+
return;
|
|
971
|
+
this._lazyFlushQueued = true;
|
|
972
|
+
queueMicrotask(() => {
|
|
973
|
+
this._lazyFlushQueued = false;
|
|
974
|
+
if (this._destroyed || !this._resolvedData.length)
|
|
975
|
+
return;
|
|
976
|
+
const idle = this._usedLazyColumns().filter(c => !this._lazyColumnStatus.has(c.key));
|
|
977
|
+
if (!idle.length)
|
|
978
|
+
return;
|
|
979
|
+
this._warnIfNoLazySource(idle);
|
|
980
|
+
this._setLazyStatus(idle.map(c => c.key), 'loading');
|
|
981
|
+
this.lazyColumnsRequested.emit([...this._lazyColumnStatus.keys()]);
|
|
982
|
+
this._runLazyLoader();
|
|
983
|
+
this._scheduleRender();
|
|
984
|
+
this._cdr.markForCheck();
|
|
985
|
+
});
|
|
813
986
|
}
|
|
814
|
-
|
|
815
|
-
|
|
987
|
+
/** Appelle `config.lazyLoader` avec toutes les colonnes lazy demandées ; sa réponse remplace `data`. */
|
|
988
|
+
_runLazyLoader() {
|
|
989
|
+
const loader = this.resolvedConfig.lazyLoader;
|
|
990
|
+
if (!loader)
|
|
991
|
+
return;
|
|
992
|
+
this._loaderSub?.unsubscribe();
|
|
993
|
+
const columns = [...this._lazyColumnStatus.keys()];
|
|
994
|
+
this._loaderSub = defer(() => loader(columns)).pipe(take(1), takeUntil(this._destroy$)).subscribe({
|
|
995
|
+
next: rows => {
|
|
996
|
+
this._loaderRows = rows;
|
|
997
|
+
this._settleLazyColumns();
|
|
998
|
+
this.refreshViewModel();
|
|
999
|
+
this._cdr.markForCheck();
|
|
1000
|
+
},
|
|
1001
|
+
error: () => {
|
|
1002
|
+
const loading = [...this._lazyColumnStatus].filter(([, status]) => status === 'loading').map(([key]) => key);
|
|
1003
|
+
this._setLazyStatus(loading, 'error');
|
|
1004
|
+
this._scheduleRender();
|
|
1005
|
+
this._cdr.markForCheck();
|
|
1006
|
+
},
|
|
1007
|
+
});
|
|
816
1008
|
}
|
|
817
|
-
|
|
818
|
-
|
|
1009
|
+
/** Nouveau `data` : il l'emporte sur les lignes du chargeur. Avec un chargeur, les colonnes déjà demandées sont rechargées. */
|
|
1010
|
+
_onNewData() {
|
|
1011
|
+
this._loaderSub?.unsubscribe();
|
|
1012
|
+
this._loaderRows = null;
|
|
1013
|
+
if (this.config?.lazyLoader && this._lazyColumnStatus.size) {
|
|
1014
|
+
this._setLazyStatus([...this._lazyColumnStatus.keys()], 'loading');
|
|
1015
|
+
}
|
|
1016
|
+
else {
|
|
1017
|
+
this._settleLazyColumns();
|
|
1018
|
+
}
|
|
819
1019
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
1020
|
+
/** Sans chargeur ni écouteur, la colonne demandée resterait en skeleton sans que rien ne l'explique. */
|
|
1021
|
+
_warnIfNoLazySource(columns) {
|
|
1022
|
+
if (this._lazyWarned || this.resolvedConfig.lazyLoader || this.lazyColumnsRequested.observed || !isDevMode())
|
|
1023
|
+
return;
|
|
1024
|
+
this._lazyWarned = true;
|
|
1025
|
+
console.warn(`[jquery-table] Colonne lazy activée (${columns.map(c => c.key).join(', ')}) mais ni config.lazyLoader ni (lazyColumnsRequested) ne sont définis : `
|
|
1026
|
+
+ 'aucune requête n\'est lancée et le skeleton restera affiché.');
|
|
1027
|
+
}
|
|
1028
|
+
/** De nouvelles `data` sont arrivées : toutes les colonnes demandées sont considérées comme prêtes. */
|
|
1029
|
+
_settleLazyColumns() {
|
|
1030
|
+
const waiting = [...this._lazyColumnStatus].filter(([, status]) => status !== 'loaded').map(([key]) => key);
|
|
1031
|
+
if (waiting.length)
|
|
1032
|
+
this._setLazyStatus(waiting, 'loaded');
|
|
1033
|
+
}
|
|
1034
|
+
_applyLazyError() {
|
|
1035
|
+
const from = this.lazyError ? 'loading' : 'error';
|
|
1036
|
+
const keys = [...this._lazyColumnStatus].filter(([, status]) => status === from).map(([key]) => key);
|
|
1037
|
+
if (keys.length)
|
|
1038
|
+
this._setLazyStatus(keys, this.lazyError ? 'error' : 'loading');
|
|
1039
|
+
}
|
|
1040
|
+
_setLazyStatus(keys, status) {
|
|
1041
|
+
const next = new Map(this._lazyColumnStatus);
|
|
1042
|
+
keys.forEach(key => next.set(key, status));
|
|
1043
|
+
this._lazyColumnStatus = next;
|
|
825
1044
|
}
|
|
826
1045
|
toggleGroupCollapse(groupKey) {
|
|
827
1046
|
this._groupBy.toggleGroupCollapse(groupKey, (key) => {
|
|
828
|
-
this._ngZone.runOutsideAngular(() =>
|
|
1047
|
+
this._ngZone.runOutsideAngular(() => this._scheduleDeferred(() => this.scrollToGroupHeader(key)));
|
|
829
1048
|
});
|
|
830
1049
|
}
|
|
831
1050
|
scrollToGroupHeader(groupKey) {
|
|
@@ -835,7 +1054,7 @@ class TableComponent {
|
|
|
835
1054
|
const row = container.querySelector(`tr[data-group-key="${CSS.escape(groupKey)}"]`);
|
|
836
1055
|
if (!row)
|
|
837
1056
|
return;
|
|
838
|
-
const headerHeight = parseFloat(this._el.nativeElement.style.getPropertyValue('--actual-header-height') || '56') || 56;
|
|
1057
|
+
const headerHeight = Number.parseFloat(this._el.nativeElement.style.getPropertyValue('--actual-header-height') || '56') || 56;
|
|
839
1058
|
const containerRect = container.getBoundingClientRect();
|
|
840
1059
|
const rowRect = row.getBoundingClientRect();
|
|
841
1060
|
const currentScrollTop = container.scrollTop;
|
|
@@ -892,8 +1111,7 @@ class TableComponent {
|
|
|
892
1111
|
this._pendingSliceFilters = null;
|
|
893
1112
|
this._pendingDynamicSliceKeys = null;
|
|
894
1113
|
const allCols = this.resolvedConfig.columns ?? [];
|
|
895
|
-
|
|
896
|
-
setTimeout(() => {
|
|
1114
|
+
this._scheduleDeferred(() => {
|
|
897
1115
|
if (dynamicKeys) {
|
|
898
1116
|
dynamicKeys.forEach(key => {
|
|
899
1117
|
const col = allCols.find(c => c.key === key);
|
|
@@ -902,7 +1120,6 @@ class TableComponent {
|
|
|
902
1120
|
});
|
|
903
1121
|
}
|
|
904
1122
|
if (filters && this.slicePanelRef) {
|
|
905
|
-
console.log('[SliceRestore] restoreFilters depuis _recomputeShowSlicePanel', filters);
|
|
906
1123
|
this.slicePanelRef.restoreFilters(filters);
|
|
907
1124
|
}
|
|
908
1125
|
this._cdr.markForCheck();
|
|
@@ -929,21 +1146,14 @@ class TableComponent {
|
|
|
929
1146
|
next.add(columnKey);
|
|
930
1147
|
this._staticSliceHiddenKeys = next;
|
|
931
1148
|
this._organizer.updateHiddenStaticKeys(this._staticSliceHiddenKeys);
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
// un filtre orphelin (la slice n'est plus visible mais son filtre resterait actif)
|
|
935
|
-
if (next.has(columnKey)) {
|
|
936
|
-
this.activeSliceFilter = () => true;
|
|
937
|
-
}
|
|
1149
|
+
// Le panneau retire la sélection du slice masqué et réémet le filtre des autres.
|
|
1150
|
+
this._setSliceConfigs(this._computeSliceConfigs());
|
|
938
1151
|
this._recomputeShowSlicePanel();
|
|
939
1152
|
this._scheduleRender();
|
|
940
1153
|
this._cdr.markForCheck();
|
|
941
1154
|
}
|
|
942
1155
|
addDynamicSlice(column) {
|
|
943
1156
|
this.slicePanelRef?.addDynamicSlice(column);
|
|
944
|
-
if (column.lazy && this._lazyColumnStatus.get(column.key) !== 'loaded') {
|
|
945
|
-
this.triggerLazyFetch(column);
|
|
946
|
-
}
|
|
947
1157
|
}
|
|
948
1158
|
get allDynamicSliceColumns() {
|
|
949
1159
|
return this._allDynamicSliceColumns;
|
|
@@ -968,6 +1178,7 @@ class TableComponent {
|
|
|
968
1178
|
onDynamicSliceKeysChange(keys) {
|
|
969
1179
|
this._organizer.onDynamicSliceKeysChange(keys);
|
|
970
1180
|
this._recomputeShowSlicePanel();
|
|
1181
|
+
this._requestLazyColumns();
|
|
971
1182
|
this._cdr.markForCheck();
|
|
972
1183
|
}
|
|
973
1184
|
get emptyStateLabel() {
|
|
@@ -1004,9 +1215,6 @@ class TableComponent {
|
|
|
1004
1215
|
this._organizer.addField(column);
|
|
1005
1216
|
this._preservePageIndex = this.paginator?.pageIndex ?? null;
|
|
1006
1217
|
this.refreshViewModel();
|
|
1007
|
-
if (column.lazy) {
|
|
1008
|
-
this.triggerLazyFetch(column);
|
|
1009
|
-
}
|
|
1010
1218
|
this.addRequested.emit();
|
|
1011
1219
|
this.columnAdded.emit(column);
|
|
1012
1220
|
}
|
|
@@ -1052,6 +1260,9 @@ class TableComponent {
|
|
|
1052
1260
|
get hasRowClickHandler() {
|
|
1053
1261
|
return this.rowSelected.observed || !!this.resolvedConfig.onRowSelected;
|
|
1054
1262
|
}
|
|
1263
|
+
rowActionLabel(row) {
|
|
1264
|
+
return `Sélectionner la ligne ${(row?.[ROW_INDEX_KEY] ?? 0) + 1}`;
|
|
1265
|
+
}
|
|
1055
1266
|
/** Largeur minimale du tableau en px, calculée depuis les largeurs explicites des colonnes actives.
|
|
1056
1267
|
* Retourne null si aucune colonne n'a de largeur définie (le tableau remplit alors 100% du conteneur).
|
|
1057
1268
|
* Seules les valeurs explicitement en `px` sont sommables ; les pourcentages ou autres unités sont ignorés. */
|
|
@@ -1071,9 +1282,6 @@ class TableComponent {
|
|
|
1071
1282
|
if (isPx && !Number.isNaN(px)) {
|
|
1072
1283
|
sum += px;
|
|
1073
1284
|
}
|
|
1074
|
-
else {
|
|
1075
|
-
sum += 150;
|
|
1076
|
-
}
|
|
1077
1285
|
}
|
|
1078
1286
|
else {
|
|
1079
1287
|
sum += 120; // largeur minimale estimée pour une colonne sans largeur explicite
|
|
@@ -1108,6 +1316,20 @@ class TableComponent {
|
|
|
1108
1316
|
event.dataTransfer.setData('text/plain', columnKey);
|
|
1109
1317
|
}
|
|
1110
1318
|
}
|
|
1319
|
+
onHeaderKeyDown(columnKey, event) {
|
|
1320
|
+
if ((!this.isColumnDragDropEnabled && !this._editMode)
|
|
1321
|
+
|| (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight'))
|
|
1322
|
+
return;
|
|
1323
|
+
const currentIndex = this.activeFields.findIndex(column => column.key === columnKey);
|
|
1324
|
+
const targetIndex = event.key === 'ArrowLeft' ? currentIndex - 1 : currentIndex + 1;
|
|
1325
|
+
if (currentIndex < 0 || targetIndex < 0 || targetIndex >= this.activeFields.length)
|
|
1326
|
+
return;
|
|
1327
|
+
event.preventDefault();
|
|
1328
|
+
const reordered = [...this.activeFields];
|
|
1329
|
+
[reordered[currentIndex], reordered[targetIndex]] = [reordered[targetIndex], reordered[currentIndex]];
|
|
1330
|
+
this._organizer.setActiveFields(reordered);
|
|
1331
|
+
this.refreshViewModel();
|
|
1332
|
+
}
|
|
1111
1333
|
onHeaderDragOver(columnKey, event) {
|
|
1112
1334
|
if ((!this.isColumnDragDropEnabled && !this._editMode) || !this.draggedColumnKey) {
|
|
1113
1335
|
return;
|
|
@@ -1158,7 +1380,6 @@ class TableComponent {
|
|
|
1158
1380
|
if (scrollEl) {
|
|
1159
1381
|
const allTh = scrollEl.querySelectorAll('th.mat-mdc-header-cell');
|
|
1160
1382
|
allTh.forEach(thEl => {
|
|
1161
|
-
const key = thEl.getAttribute('mat-column-header') || thEl.getAttribute('mat-sort-header') || '';
|
|
1162
1383
|
const colClass = Array.from(thEl.classList).find(c => c.startsWith('mat-column-'));
|
|
1163
1384
|
const colKey = colClass ? colClass.replace('mat-column-', '') : null;
|
|
1164
1385
|
if (colKey && this._columnWidths[colKey] == null) {
|
|
@@ -1281,7 +1502,7 @@ class TableComponent {
|
|
|
1281
1502
|
this._columnWidths = { ...saved.columnWidths };
|
|
1282
1503
|
// Group by
|
|
1283
1504
|
if (saved.groupBy !== undefined) {
|
|
1284
|
-
this.
|
|
1505
|
+
this.setGroupByAndEmit(saved.groupBy);
|
|
1285
1506
|
if (saved.groupBy) {
|
|
1286
1507
|
this._groupBy.setDefaultCollapsed(true);
|
|
1287
1508
|
this._groupBy.reset();
|
|
@@ -1410,7 +1631,7 @@ class TableComponent {
|
|
|
1410
1631
|
}
|
|
1411
1632
|
if (state.groupBy === null || typeof state.groupBy === 'string') {
|
|
1412
1633
|
const groupKey = state.groupBy && available.has(state.groupBy) ? state.groupBy : null;
|
|
1413
|
-
this.
|
|
1634
|
+
this.setGroupByAndEmit(groupKey);
|
|
1414
1635
|
restored.push('groupBy');
|
|
1415
1636
|
}
|
|
1416
1637
|
if (state.columnWidths) {
|
|
@@ -1431,6 +1652,10 @@ class TableComponent {
|
|
|
1431
1652
|
this._resolvedData = this.resolveData();
|
|
1432
1653
|
this.resolvedConfig = this.buildEffectiveConfig();
|
|
1433
1654
|
this._columnMap = new Map((this.resolvedConfig.columns || []).map(c => [c.key, c]));
|
|
1655
|
+
if (!this._initialSlicePanelCollapsedApplied) {
|
|
1656
|
+
this._slicePanelCollapsed = this.resolvedConfig.slicePanelCollapsed === true;
|
|
1657
|
+
this._initialSlicePanelCollapsedApplied = true;
|
|
1658
|
+
}
|
|
1434
1659
|
// ── Initialiser le gestionnaire de préférences si l'option est activée
|
|
1435
1660
|
const prefsId = this.resolvedConfig.preferences?.enabled
|
|
1436
1661
|
? this.resolvedConfig.preferences.tableId
|
|
@@ -1449,7 +1674,7 @@ class TableComponent {
|
|
|
1449
1674
|
}
|
|
1450
1675
|
// Délègue à la facade : mise à jour de l'état View (colonnes, groupBy meta, sliceBy meta)
|
|
1451
1676
|
this._organizer.update({
|
|
1452
|
-
config: this.
|
|
1677
|
+
config: this._organizerViewConfig(),
|
|
1453
1678
|
fields: this.resolvedConfig.columns ?? [],
|
|
1454
1679
|
sliceConfigs: this.resolvedConfig.slices ?? [],
|
|
1455
1680
|
defaultGroupBy: this.resolvedConfig.defaultGroupBy,
|
|
@@ -1459,10 +1684,12 @@ class TableComponent {
|
|
|
1459
1684
|
this._groupBy.setDefaultCollapsed(true);
|
|
1460
1685
|
this._groupBy.reset();
|
|
1461
1686
|
this._groupBy.groupPageSize = this.resolveDefaultGroupPageSize();
|
|
1687
|
+
this.groupByChange.emit(this._organizer.groupBy.activeKey);
|
|
1462
1688
|
}
|
|
1463
1689
|
// Synchronise la propriété stable (évite NG0100 — la facade est source de vérité)
|
|
1464
1690
|
this.activeFields = this._organizer.fields.activeFields;
|
|
1465
1691
|
this.renderedColumns = this.activeFields.map(c => c.key);
|
|
1692
|
+
this.emitColumnsChangeIfNeeded();
|
|
1466
1693
|
this.pageSize = this.paginator?.pageSize || this.resolvePageSize();
|
|
1467
1694
|
this.pageSizeOptions = this.resolvePageSizeOptions();
|
|
1468
1695
|
this.pageSizeOptionsGroupBy = this.resolvePageSizeOptionsGroupBy();
|
|
@@ -1487,12 +1714,15 @@ class TableComponent {
|
|
|
1487
1714
|
// mais avant _computeSliceConfigs() pour que hiddenSliceKeys soit déjà restauré.
|
|
1488
1715
|
if (pendingPrefs) {
|
|
1489
1716
|
this._applyPreferences(pendingPrefs);
|
|
1717
|
+
this.activeFields = this._organizer.fields.activeFields;
|
|
1718
|
+
this.renderedColumns = this.activeFields.map(c => c.key);
|
|
1719
|
+
this.emitColumnsChangeIfNeeded();
|
|
1490
1720
|
}
|
|
1491
1721
|
// Propriétés stables pour les bindings du template
|
|
1492
1722
|
// On recalcule _sliceConfigs en reflétant l'état courant des slices cachées,
|
|
1493
1723
|
// pour que SlicePanelComponent.ngOnInit reçoive la bonne valeur de `hidden`
|
|
1494
1724
|
// même quand le composant est créé pour la 1ère fois après un toggle.
|
|
1495
|
-
this.
|
|
1725
|
+
this._setSliceConfigs(this._computeSliceConfigs());
|
|
1496
1726
|
this._sliceColumns = this.resolvedConfig.columns ?? [];
|
|
1497
1727
|
this._sliceData = this._resolvedData;
|
|
1498
1728
|
this._sliceShowToggle = this.resolvedConfig.enableSliceToggle !== false;
|
|
@@ -1502,6 +1732,7 @@ class TableComponent {
|
|
|
1502
1732
|
this._matSortDirection = this.resolvedConfig.defaultSort.direction;
|
|
1503
1733
|
}
|
|
1504
1734
|
this._buildSliceByMenuItems();
|
|
1735
|
+
this._requestLazyColumns();
|
|
1505
1736
|
this._scheduleRender();
|
|
1506
1737
|
}
|
|
1507
1738
|
/** Retourne uniquement les slices visibles (non cachées par _staticSliceHiddenKeys).
|
|
@@ -1574,7 +1805,7 @@ class TableComponent {
|
|
|
1574
1805
|
this._allFilteredRows = categoryRows;
|
|
1575
1806
|
}
|
|
1576
1807
|
else {
|
|
1577
|
-
const compareFn = this._buildSortComparator();
|
|
1808
|
+
const compareFn = this._buildSortComparator(categoryRows);
|
|
1578
1809
|
this._allFilteredRows = compareFn ? [...categoryRows].sort(compareFn) : categoryRows;
|
|
1579
1810
|
}
|
|
1580
1811
|
// Mettre à jour le paginator avec le nouveau total
|
|
@@ -1612,9 +1843,9 @@ class TableComponent {
|
|
|
1612
1843
|
buildEffectiveConfig() {
|
|
1613
1844
|
const config = this.config || {};
|
|
1614
1845
|
const rows = this._resolvedData;
|
|
1615
|
-
const columns = config.columns ||
|
|
1846
|
+
const columns = this._normalizeColumns(config.columns ||
|
|
1616
1847
|
this.columnsConfig ||
|
|
1617
|
-
this.resolveColumnsFromCompat(rows);
|
|
1848
|
+
this.resolveColumnsFromCompat(rows));
|
|
1618
1849
|
// Mode HTML : [view] prend le dessus sur config.view si fourni (Phase 3).
|
|
1619
1850
|
const view = this.view ?? config.view;
|
|
1620
1851
|
const autoTableId = config.preferences?.tableId || TableComponent._hashColumns((columns || []).map(c => c.key));
|
|
@@ -1638,6 +1869,31 @@ class TableComponent {
|
|
|
1638
1869
|
preferences: prefsConfig,
|
|
1639
1870
|
};
|
|
1640
1871
|
}
|
|
1872
|
+
/** Une colonne lazy est forcément optionnelle : `optional` est implicite, et `optional: false` est refusé. */
|
|
1873
|
+
_normalizeColumns(columns) {
|
|
1874
|
+
if (columns === this._columnsSource)
|
|
1875
|
+
return this._columnsNormalized;
|
|
1876
|
+
const normalized = columns.map(column => this._withImpliedOptional(column));
|
|
1877
|
+
const result = normalized.some((column, index) => column !== columns[index]) ? normalized : columns;
|
|
1878
|
+
this._columnsSource = columns;
|
|
1879
|
+
this._columnsNormalized = result;
|
|
1880
|
+
return result;
|
|
1881
|
+
}
|
|
1882
|
+
_withImpliedOptional(column) {
|
|
1883
|
+
if (!column.lazy || column.optional)
|
|
1884
|
+
return column;
|
|
1885
|
+
if (column.optional === false) {
|
|
1886
|
+
throw new Error(`[jquery-table] La colonne '${column.key}' est lazy : elle est forcément optionnelle (optional: true est implicite). `
|
|
1887
|
+
+ 'optional: false la ferait demander dès le premier rendu, soit une requête de plus pour rien. '
|
|
1888
|
+
+ 'Retirez optional: false, ou lazy: true si la colonne doit toujours être affichée.');
|
|
1889
|
+
}
|
|
1890
|
+
let implied = this._optionalLazyColumns.get(column);
|
|
1891
|
+
if (!implied) {
|
|
1892
|
+
implied = { ...column, optional: true };
|
|
1893
|
+
this._optionalLazyColumns.set(column, implied);
|
|
1894
|
+
}
|
|
1895
|
+
return implied;
|
|
1896
|
+
}
|
|
1641
1897
|
applyConfigSearchReset() {
|
|
1642
1898
|
const clearToken = this.resolvedConfig.clearSearchInput;
|
|
1643
1899
|
const shouldClear = !!clearToken && clearToken !== this._lastConfigClearSearchInput;
|
|
@@ -1656,8 +1912,13 @@ class TableComponent {
|
|
|
1656
1912
|
}
|
|
1657
1913
|
this._lastInitialSearchQuery = initialQuery;
|
|
1658
1914
|
this._initialSearchQueryApplied = true;
|
|
1659
|
-
|
|
1660
|
-
this.
|
|
1915
|
+
const next = initialQuery ?? '';
|
|
1916
|
+
const changed = this.searchQuery !== next;
|
|
1917
|
+
this.searchQuery = next;
|
|
1918
|
+
if (changed)
|
|
1919
|
+
this.onSearchChange();
|
|
1920
|
+
else
|
|
1921
|
+
this._runSearch();
|
|
1661
1922
|
}
|
|
1662
1923
|
static _hashColumns(keys) {
|
|
1663
1924
|
const str = keys.join('|');
|
|
@@ -1669,6 +1930,9 @@ class TableComponent {
|
|
|
1669
1930
|
return h.toString(16).padStart(8, '0');
|
|
1670
1931
|
}
|
|
1671
1932
|
resolveData() {
|
|
1933
|
+
if (this._loaderRows) {
|
|
1934
|
+
return this._loaderRows;
|
|
1935
|
+
}
|
|
1672
1936
|
if (Array.isArray(this.data)) {
|
|
1673
1937
|
return this.data;
|
|
1674
1938
|
}
|
|
@@ -1729,7 +1993,8 @@ class TableComponent {
|
|
|
1729
1993
|
const groupLazyStatus = groupColDef?.lazy ? (this._lazyColumnStatus.get(groupKey) ?? 'idle') : 'loaded';
|
|
1730
1994
|
if (groupLazyStatus !== 'loaded') {
|
|
1731
1995
|
this._totalFilteredCount = rows.length;
|
|
1732
|
-
|
|
1996
|
+
const key = groupLazyStatus === 'error' ? GROUP_KEY_LAZY_ERROR : GROUP_KEY_LAZY_LOADING;
|
|
1997
|
+
return [{ [GROUP_HEADER_MARKER]: true, [ROW_GROUP_KEY]: key, [ROW_GROUP_COUNT]: rows.length, [ROW_GROUP_PAGE]: 0, [ROW_GROUP_PAGE_COUNT]: 1 }];
|
|
1733
1998
|
}
|
|
1734
1999
|
const effectiveSort = this._activeSort.active && this._activeSort.direction
|
|
1735
2000
|
? this._activeSort
|
|
@@ -1752,26 +2017,7 @@ class TableComponent {
|
|
|
1752
2017
|
const groupValueCache = new Map();
|
|
1753
2018
|
rows.forEach(row => groupValueCache.set(row, String(this.getLazyAwareValue(groupKey, row) ?? '')));
|
|
1754
2019
|
// Pré-calcul des valeurs de tri (évite O(n log n) × getLazyAwareValue dans le comparateur)
|
|
1755
|
-
|
|
1756
|
-
if (effectiveSort.active && effectiveSort.direction) {
|
|
1757
|
-
const sortValueCache = new Map();
|
|
1758
|
-
rows.forEach(row => sortValueCache.set(row, this.getLazyAwareValue(effectiveSort.active, row)));
|
|
1759
|
-
const dir = effectiveSort.direction;
|
|
1760
|
-
fastCompareFn = (a, b) => {
|
|
1761
|
-
const va = sortValueCache.get(a);
|
|
1762
|
-
const vb = sortValueCache.get(b);
|
|
1763
|
-
let cmp = 0;
|
|
1764
|
-
if (typeof va === 'number' && typeof vb === 'number') {
|
|
1765
|
-
cmp = va < vb ? -1 : va > vb ? 1 : 0;
|
|
1766
|
-
}
|
|
1767
|
-
else {
|
|
1768
|
-
const sa = va == null ? '' : String(va).toLowerCase();
|
|
1769
|
-
const sb = vb == null ? '' : String(vb).toLowerCase();
|
|
1770
|
-
cmp = sa < sb ? -1 : sa > sb ? 1 : 0;
|
|
1771
|
-
}
|
|
1772
|
-
return dir === 'asc' ? cmp : -cmp;
|
|
1773
|
-
};
|
|
1774
|
-
}
|
|
2020
|
+
const fastCompareFn = this._buildCachedSortComparator(rows, effectiveSort);
|
|
1775
2021
|
const sorted = [...rows].sort((a, b) => {
|
|
1776
2022
|
const va = groupValueCache.get(a);
|
|
1777
2023
|
const vb = groupValueCache.get(b);
|
|
@@ -1813,6 +2059,8 @@ class TableComponent {
|
|
|
1813
2059
|
}
|
|
1814
2060
|
const pageSize = this.groupPageSize;
|
|
1815
2061
|
const result = [];
|
|
2062
|
+
const rowIndexes = new Map();
|
|
2063
|
+
rows.forEach((row, index) => rowIndexes.set(row, index));
|
|
1816
2064
|
groupOrder.forEach((groupValue) => {
|
|
1817
2065
|
const groupRows = groupMap.get(groupValue);
|
|
1818
2066
|
const totalCount = groupRows.length;
|
|
@@ -1823,9 +2071,9 @@ class TableComponent {
|
|
|
1823
2071
|
if (!collapsed) {
|
|
1824
2072
|
const start = pageSize === 0 ? 0 : currentPage * pageSize;
|
|
1825
2073
|
const end = pageSize === 0 ? totalCount : Math.min(start + pageSize, totalCount);
|
|
1826
|
-
groupRows.slice(start, end).forEach((row
|
|
1827
|
-
const idx =
|
|
1828
|
-
const projected = { [ROW_RAW_KEY]: row, [ROW_INDEX_KEY]: idx };
|
|
2074
|
+
groupRows.slice(start, end).forEach((row) => {
|
|
2075
|
+
const idx = rowIndexes.get(row) ?? 0;
|
|
2076
|
+
const projected = { [ROW_RAW_KEY]: row, [ROW_INDEX_KEY]: idx, [ROW_GROUP_KEY]: groupValue };
|
|
1829
2077
|
this.activeFields.forEach((col) => {
|
|
1830
2078
|
projected[col.key] = this.resolveCellValue(col, row, idx);
|
|
1831
2079
|
});
|
|
@@ -1835,27 +2083,41 @@ class TableComponent {
|
|
|
1835
2083
|
});
|
|
1836
2084
|
return result;
|
|
1837
2085
|
}
|
|
1838
|
-
|
|
2086
|
+
/** Comparateur dont les valeurs de tri sont calculées une seule fois par ligne. */
|
|
2087
|
+
_buildSortComparator(rows) {
|
|
1839
2088
|
const sort = this._activeSort.active && this._activeSort.direction
|
|
1840
2089
|
? this._activeSort
|
|
1841
2090
|
: this.resolvedConfig.defaultSort ?? { active: '', direction: '' };
|
|
1842
|
-
|
|
1843
|
-
|
|
2091
|
+
return this._buildCachedSortComparator(rows, sort);
|
|
2092
|
+
}
|
|
2093
|
+
_buildCachedSortComparator(rows, sort) {
|
|
2094
|
+
if (!sort.active || !sort.direction)
|
|
1844
2095
|
return null;
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
2096
|
+
const sortKey = sort.active;
|
|
2097
|
+
const sortDirection = sort.direction;
|
|
2098
|
+
const sortValueCache = new Map();
|
|
2099
|
+
rows.forEach(row => sortValueCache.set(row, this.getLazyAwareValue(sortKey, row)));
|
|
2100
|
+
return (left, right) => this._applySortDirection(this._compareSortValues(sortValueCache.get(left), sortValueCache.get(right)), sortDirection);
|
|
2101
|
+
}
|
|
2102
|
+
static { this._collator = new Intl.Collator('fr', { sensitivity: 'accent', numeric: true }); }
|
|
2103
|
+
_compareSortValues(left, right) {
|
|
2104
|
+
if (typeof left === 'number' && typeof right === 'number') {
|
|
2105
|
+
if (left < right)
|
|
2106
|
+
return -1;
|
|
2107
|
+
if (left > right)
|
|
2108
|
+
return 1;
|
|
2109
|
+
return 0;
|
|
2110
|
+
}
|
|
2111
|
+
if (left instanceof Date && right instanceof Date) {
|
|
2112
|
+
return Math.sign(left.getTime() - right.getTime()) || 0;
|
|
2113
|
+
}
|
|
2114
|
+
return TableComponent._collator.compare(this._normalizeSortValue(left), this._normalizeSortValue(right));
|
|
2115
|
+
}
|
|
2116
|
+
_normalizeSortValue(value) {
|
|
2117
|
+
return value == null ? '' : String(value);
|
|
2118
|
+
}
|
|
2119
|
+
_applySortDirection(comparison, direction) {
|
|
2120
|
+
return direction === 'asc' ? comparison : -comparison;
|
|
1859
2121
|
}
|
|
1860
2122
|
/**
|
|
1861
2123
|
* Retourne la valeur telle qu'elle est affichée dans la cellule, en suivant exactement
|
|
@@ -1863,13 +2125,8 @@ class TableComponent {
|
|
|
1863
2125
|
* Ne passe pas par sortValue (valeur brute de tri) contrairement à getLazyAwareValue.
|
|
1864
2126
|
*/
|
|
1865
2127
|
getDisplayValueForSearch(col, row) {
|
|
1866
|
-
if (col.lazy)
|
|
1867
|
-
|
|
1868
|
-
if (status !== 'loaded')
|
|
1869
|
-
return null;
|
|
1870
|
-
const rowMap = this._lazyColumnData.get(col.key);
|
|
1871
|
-
return normalizeCellValue(rowMap?.get(row) ?? '');
|
|
1872
|
-
}
|
|
2128
|
+
if (col.lazy && this._lazyColumnStatus.get(col.key) !== 'loaded')
|
|
2129
|
+
return null;
|
|
1873
2130
|
if (col.searchValue) {
|
|
1874
2131
|
return col.searchValue(row, 0);
|
|
1875
2132
|
}
|
|
@@ -1878,29 +2135,35 @@ class TableComponent {
|
|
|
1878
2135
|
}
|
|
1879
2136
|
getLazyAwareValue(columnKey, row) {
|
|
1880
2137
|
const colDef = this._columnMap.get(columnKey);
|
|
1881
|
-
if (colDef?.lazy) {
|
|
1882
|
-
return
|
|
2138
|
+
if (colDef?.lazy && this._lazyColumnStatus.get(columnKey) !== 'loaded') {
|
|
2139
|
+
return null;
|
|
1883
2140
|
}
|
|
1884
2141
|
if (colDef?.sortValue) {
|
|
1885
|
-
return
|
|
2142
|
+
return this._toSortInput(colDef.sortValue(row, 0));
|
|
1886
2143
|
}
|
|
1887
2144
|
if (colDef?.value) {
|
|
1888
|
-
return
|
|
2145
|
+
return this._toSortInput(colDef.value(row, 0));
|
|
1889
2146
|
}
|
|
1890
2147
|
return row[columnKey];
|
|
1891
2148
|
}
|
|
2149
|
+
/** Les nombres et les dates gardent leur type pour un tri correct ; le reste est normalisé en texte. */
|
|
2150
|
+
_toSortInput(value) {
|
|
2151
|
+
return typeof value === 'number' || value instanceof Date ? value : normalizeCellValue(value);
|
|
2152
|
+
}
|
|
2153
|
+
/** Les statuts de chargement lazy ne doivent jamais apparaître dans un fichier exporté. */
|
|
2154
|
+
_resolveExportValue(col, row, index) {
|
|
2155
|
+
const value = this.resolveCellValue(col, row, index);
|
|
2156
|
+
return value === LAZY_LOADING_VALUE || value === LAZY_ERROR_VALUE ? '' : value;
|
|
2157
|
+
}
|
|
1892
2158
|
resolveCellValue(col, row, index) {
|
|
1893
2159
|
if (col.lazy) {
|
|
1894
|
-
const status = this._lazyColumnStatus.get(col.key)
|
|
2160
|
+
const status = this._lazyColumnStatus.get(col.key);
|
|
1895
2161
|
if (status === 'loading')
|
|
1896
2162
|
return LAZY_LOADING_VALUE;
|
|
1897
2163
|
if (status === 'error')
|
|
1898
2164
|
return LAZY_ERROR_VALUE;
|
|
1899
|
-
if (status
|
|
1900
|
-
|
|
1901
|
-
return normalizeCellValue(rowMap?.get(row) ?? '');
|
|
1902
|
-
}
|
|
1903
|
-
return '';
|
|
2165
|
+
if (!status)
|
|
2166
|
+
return '';
|
|
1904
2167
|
}
|
|
1905
2168
|
const valueProvider = col.value;
|
|
1906
2169
|
const rawValue = valueProvider ? valueProvider(row, index) : row?.[col.key];
|
|
@@ -1980,11 +2243,11 @@ class TableComponent {
|
|
|
1980
2243
|
}
|
|
1981
2244
|
}
|
|
1982
2245
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1983
|
-
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", view: "view", columnLabels: "columnLabels", isLoading: "isLoading", copyFeedback: "copyFeedback", clearSearchInput: "clearSearchInput" }, outputs: { addRequested: "addRequested", columnAdded: "columnAdded", columnRemoved: "columnRemoved", categorySelected: "categorySelected", rowSelected: "rowSelected", sortChange: "sortChange", pageChange: "pageChange", searchChange: "searchChange", groupByChange: "groupByChange", columnsChange: "columnsChange", visualCopied: "visualCopied" }, host: { listeners: { "document:fullscreenchange": "onFullscreenChange()" }, properties: { "class.visual-fullscreen": "this._isFullscreen" } }, 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 }], usesOnChanges: true, ngImport: i0, template: "<section class=\"jquery-table-wrapper\">\n <div class=\"visual-copy-feedback\" *ngIf=\"copyFeedbackMessage\" role=\"status\">{{ copyFeedbackMessage }}</div>\n <header class=\"table-header\" *ngIf=\"showTitle\">\n <h3>{{ title }}</h3>\n </header>\n\n <div\n class=\"table-layout\"\n [class.no-slice]=\"!_showSlicePanel || _slicePanelCollapsed\"\n >\n <slice-panel\n [style.display]=\"!_showSlicePanel || _slicePanelCollapsed ? 'none' : null\"\n [sliceConfigs]=\"_sliceConfigs\"\n [columns]=\"_sliceColumns\"\n [data]=\"_sliceData\"\n [lazyData]=\"_lazyColumnData\"\n [lazyStatus]=\"_lazyColumnStatus\"\n [showToggle]=\"_sliceShowToggle\"\n [collapsedByDefault]=\"_slicePanelCollapsed\"\n (filterChange)=\"onSliceFilterChange($event)\"\n (dynamicSliceKeysChange)=\"onDynamicSliceKeysChange($event)\"\n (collapsedChange)=\"onSlicePanelCollapsedChange($event)\"\n ></slice-panel>\n\n <div class=\"table-shell\">\n <div class=\"table-toolbar\" *ngIf=\"showToolbar\">\n <div class=\"table-toolbar-left\">\n <button\n *ngIf=\"showSliceExpandBtn\"\n type=\"button\"\n class=\"slice-expand-btn\"\n [title]=\"i18n.expandFilterTitle\"\n (click)=\"expandSlicePanel()\"\n ><mat-icon class=\"slice-expand-mat-icon\">filter_list</mat-icon></button>\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]=\"i18n.searchPlaceholder\"\n autocomplete=\"off\"\n />\n <button *ngIf=\"searchQuery\" type=\"button\" class=\"search-clear-btn\" (click)=\"searchQuery = ''; onSearchChange()\">\u00D7</button>\n <span class=\"search-count\" *ngIf=\"!effectiveIsLoading\">{{ filteredRowCount }}</span>\n <div class=\"search-count-loader\" *ngIf=\"effectiveIsLoading\">\n <div class=\"spinner\"></div>\n </div>\n </div>\n </div>\n <div *ngIf=\"showOrganizerButton\" class=\"table-toolbar-right\">\n <table-organizer-button\n [config]=\"organizerConfig\"\n [state]=\"organizerState\"\n (viewChange)=\"onOrganizerViewChange($event)\">\n </table-organizer-button>\n </div>\n </div>\n\n <div class=\"table-body-wrapper\">\n\n <!-- Banni\u00E8re message pr\u00E9f\u00E9rences -->\n <div *ngIf=\"_preferencesMessage\" class=\"preferences-message\">\n <mat-icon class=\"preferences-message-icon\">check_circle</mat-icon>\n <span>{{ _preferencesMessage }}</span>\n </div>\n\n <!-- Bouton de confirmation du mode \u00E9dition -->\n <button\n *ngIf=\"_editMode\"\n type=\"button\"\n class=\"edit-confirm-btn\"\n [title]=\"i18n.preferencesConfirmEdit\"\n (click)=\"confirmEditMode()\"\n >\n <mat-icon>check_circle</mat-icon>\n </button>\n <div class=\"table-body-scroll\" #tableBodyScroll>\n <table mat-table matSort [dataSource]=\"tableDataSource\" class=\"jquery-table\"\n [style.min-width]=\"tableMinWidth\"\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]=\"(!_editMode && column.sortable !== false) ? column.key : ''\"\n [disabled]=\"_editMode || column.sortable === false\"\n [style.width]=\"getColumnWidth(column.key)\"\n [class.drag-enabled]=\"isColumnDragDropEnabled || _editMode\"\n [class.drop-target]=\"isHeaderDropTarget(column.key)\"\n [class.edit-mode-header]=\"_editMode\"\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 || _editMode\"\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 <!-- Poign\u00E9e de resize (visible uniquement en mode \u00E9dition) -->\n <span\n *ngIf=\"_editMode\"\n class=\"col-resize-handle\"\n draggable=\"false\"\n (mousedown)=\"onResizeStart(column.key, $event)\"\n ></span>\n </th>\n <td mat-cell *matCellDef=\"let row\"\n [style.width]=\"getColumnWidth(column.key)\"\n [class.edit-mode-cell]=\"_editMode\">\n <ng-container *ngIf=\"!_editMode; else editModeCellTpl\">\n <ng-container *ngIf=\"getCellTemplate(column.key) as customTpl; else defaultCellTpl\">\n <ng-container *ngTemplateOutlet=\"customTpl; context: { $implicit: row[_ROW_RAW_KEY], index: row[_ROW_INDEX_KEY] }\"></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]=\"i18n.lazyErrorTitle\">\n <span class=\"lazy-error-icon\">\u26A0</span>\n <span class=\"lazy-error-label\">{{ i18n.lazyRetry }}</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]) ? i18n.cellEmpty : row[column.key] }}</span>\n </ng-container>\n </ng-template>\n </ng-container>\n <ng-template #editModeCellTpl>\n <span class=\"cell-text cell-edit-mode\" [title]=\"asTitle(row[column.key])\">{{ row[column.key] }}</span>\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 && !_editMode ? 0 : null\"\n [ngClass]=\"getRowClass(row)\"\n [class.no-pointer]=\"!hasRowClickHandler || _editMode\"\n [class.edit-mode-row]=\"_editMode\"\n (click)=\"!_editMode && onRowClick(row, $event)\"\n (keydown.enter)=\"!_editMode && onRowClick(row, null)\"\n (keydown.space)=\"!_editMode && onRowClick(row, null)\"\n ></tr>\n <ng-container [matColumnDef]=\"_GROUP_ROW_COLUMN\">\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[_ROW_GROUP_KEY])\" *ngIf=\"isNormalGroupRow(row)\">\u203A</span>\n <span *ngIf=\"isNormalGroupRow(row)\" class=\"group-key-label\">{{ row[_ROW_GROUP_KEY] }}</span>\n <span *ngIf=\"isGroupRowLoading(row)\" class=\"lazy-skeleton group-lazy-key-skeleton\"></span>\n <span *ngIf=\"isGroupRowTooMany(row)\" class=\"group-too-many-label\">{{ i18n.groupTooMany(row[_ROW_GROUP_COUNT]) }}</span>\n <span class=\"group-count-badge\">{{ row[_ROW_GROUP_COUNT] }}</span>\n <div class=\"group-pagination\" *ngIf=\"!isGroupCollapsed(row[_ROW_GROUP_KEY]) && row[_ROW_GROUP_PAGE_COUNT] > 1\" (click)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"group-page-btn\" [disabled]=\"row[_ROW_GROUP_PAGE] === 0\" (click)=\"groupPageBack(row[_ROW_GROUP_KEY])\">\u2039</button>\n <span class=\"group-page-info\">{{ row[_ROW_GROUP_PAGE] + 1 }} / {{ row[_ROW_GROUP_PAGE_COUNT] }}</span>\n <button type=\"button\" class=\"group-page-btn\" [disabled]=\"row[_ROW_GROUP_PAGE] >= row[_ROW_GROUP_PAGE_COUNT] - 1\" (click)=\"groupPageForward(row[_ROW_GROUP_KEY], row[_ROW_GROUP_COUNT])\">\u203A</button>\n </div>\n </div>\n </td>\n </ng-container>\n <tr mat-row *matRowDef=\"let row; columns: [_GROUP_ROW_COLUMN]; when: isGroupHeader\"\n class=\"group-header-row\"\n [class.group-header-loading]=\"isGroupRowLoading(row)\"\n [class.group-header-too-many]=\"isGroupRowTooMany(row)\"\n [attr.data-group-key]=\"row[_ROW_GROUP_KEY]\"\n (click)=\"onGroupHeaderClick(row)\"></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\" [class.is-loading]=\"effectiveIsLoading\" *ngIf=\"showPagination\">\n <mat-paginator\n [length]=\"totalRows\"\n [pageSize]=\"pageSize\"\n [pageSizeOptions]=\"pageSizeOptions\"\n [showFirstLastButtons]=\"resolvedPaginationShowFirstLastButtons\"\n ></mat-paginator>\n <div class=\"paginator-range-loader\" \n [class.paginator-range-loader--no-first-last]=\"!resolvedPaginationShowFirstLastButtons\"\n *ngIf=\"effectiveIsLoading\">\n <div class=\"spinner\"></div>\n </div>\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 {{ i18n.groupRemove }}\n </button>\n <div class=\"group-bar-separator\"></div>\n <div class=\"group-sort-controls\">\n <span class=\"group-sort-label\">{{ i18n.groupSortLabel }}</span>\n <button type=\"button\" class=\"group-sort-btn\" [class.active]=\"groupSortOrder === 'asc'\" (click)=\"onGroupSortToggle('asc')\" [title]=\"i18n.groupSortAsc\">\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]=\"i18n.groupSortDesc\">\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\">{{ i18n.groupRowsPerGroup }}</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 ? i18n.groupShowAll : groupPageSize }}</mat-select-trigger>\n <mat-option *ngFor=\"let opt of pageSizeOptionsGroupBy\" [value]=\"opt\">{{ opt }}</mat-option>\n <mat-option [value]=\"0\">{{ i18n.groupShowAll }}</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}:host.visual-fullscreen{display:block;width:100vw;height:100vh;background:#fff}.jquery-table-wrapper{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:inherit;padding:2px 2px 0;box-sizing:border-box}.visual-copy-feedback{position:absolute;top:8px;left:50%;z-index:20;transform:translate(-50%);padding:7px 12px;border:1px solid #c7dfd1;border-radius:5px;background:#f4fbf7f5;color:#24613b;font-size:12px;pointer-events:none}.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-x:auto;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;display:flex;align-items:center;gap:6px}.table-toolbar-right{flex:0 0 auto;display:flex;align-items:center;gap:6px}.table-action-btn{width:34px;height:34px;display:inline-flex;align-items:center;justify-content:center;border:0;border-radius:6px;background:transparent;color:inherit;cursor:pointer}.table-action-btn:hover{background:#00000014}.search-input-wrapper{position:relative;display:inline-flex;align-items:center;flex:1 1 auto;min-width:0;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-count-loader{flex-shrink:0;display:flex;align-items:center;justify-content:center;min-width:20px;height:20px}.spinner{width:14px;height:14px;border:2px solid #d1d5db;border-top-color:#374151;border-radius:50%;animation:spin .8s linear infinite}.search-count-loader .spinner{width:16px;height:16px;border:2.5px solid #e5e7eb;border-top-color:#a0a5ad}@keyframes spin{to{transform:rotate(360deg)}}.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)}.slice-expand-btn{flex-shrink:0;width:34px;height:34px;border:1px solid var(--jqt-border-color);border-radius:8px;background:var(--jqt-surface-color);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;color:var(--jqt-text-color);padding:0;line-height:1}.slice-expand-btn:hover{border-color:var(--jqt-text-muted)}.slice-expand-mat-icon{font-size:18px;width:18px;height:18px;line-height:1}.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}.export-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}.export-btn:hover{border-color:var(--jqt-text-muted)}.export-btn-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0}.view-menu-export-divider{margin:4px 0!important}.view-menu-export-item{display:flex;align-items:center;gap:6px;width:100%;padding:0 16px;height:40px;border:none;background:transparent;cursor:pointer;font-size:13px;font-weight:500;color:var(--jqt-text-color);text-align:left;box-sizing:border-box}.view-menu-export-item:hover{background:#0000000a}.view-menu-export-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0;color:var(--jqt-text-muted)}.edit-confirm-btn{position:absolute;top:10px;right:10px;z-index:200;display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:none;border-radius:50%;background:#16a34a;cursor:pointer;box-shadow:0 2px 8px #00000040;transition:background .15s}.edit-confirm-btn mat-icon{color:#fff;font-size:22px;width:22px;height:22px}.edit-confirm-btn:hover{background:#15803d}.preferences-message{display:flex;align-items:center;gap:8px;margin:0 0 8px;padding:8px 12px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:6px;font-size:12px;color:#15803d}.preferences-message .preferences-message-icon{font-size:16px;width:16px;height:16px}:host ::ng-deep .edit-mode-header{background:#3b82f60a!important;cursor:grab!important;-webkit-user-select:none;user-select:none;overflow:visible!important}.col-resize-handle{position:absolute;top:0;right:0;width:6px;height:100%;cursor:col-resize!important;background:#3b82f640;z-index:20}.col-resize-handle:hover{background:#3b82f6b3}.col-resize-handle:active{background:#3b82f6e6}:host ::ng-deep th.mat-mdc-header-cell{position:relative}:host ::ng-deep th.mat-mdc-header-cell.drag-enabled{overflow:visible!important}:host ::ng-deep .edit-mode-row{pointer-events:none;-webkit-user-select:none;user-select:none;opacity:.8}.cell-edit-mode{pointer-events:none;-webkit-user-select:none;user-select:none;color:var(--jqt-text-muted)}.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;position:relative;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)}:host ::ng-deep .bottom-bar.is-loading .mat-mdc-paginator-range-label{visibility:hidden}.paginator-range-loader{position:absolute;right:0;top:50%;transform:translateY(-50%);margin-right:145px;display:flex;align-items:center;justify-content:center;width:auto;padding:0 16px;height:24px}.paginator-range-loader--no-first-last{margin-right:80px}.paginator-range-loader .spinner{width:16px;height:16px;border:2.5px solid #e5e7eb;border-top-color:#a0a5ad}.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:wrap;min-height:var(--actual-header-height, 56px);height:auto;padding:8px 12px;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;flex:1 1 280px;min-width:0;flex-wrap:wrap;gap:8px}.group-sort-controls{display:flex;align-items:center;min-width:0;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;flex:0 1 auto;min-width:0;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;max-width:100%;overflow:hidden;text-overflow:ellipsis;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.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { 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: MatDividerModule }, { 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: "ngmodule", type: MatIconModule }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "component", type: i8.MatSelect, selector: "mat-select", inputs: ["disabled", "disableRipple", "tabIndex", "panelWidth", "hideSingleSelectionIndicator"], exportAs: ["matSelect"] }, { kind: "directive", type: i8.MatSelectTrigger, selector: "mat-select-trigger" }, { kind: "component", type: i9.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"] }, { kind: "component", type: OrganizerButtonWrapperComponent, selector: "table-organizer-button", inputs: ["config", "state", "hideMenuValues"], outputs: ["viewChange", "sliceStateChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2246
|
+
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", view: "view", columnLabels: "columnLabels", isLoading: "isLoading", lazyError: "lazyError", copyFeedback: "copyFeedback", clearSearchInput: "clearSearchInput" }, outputs: { addRequested: "addRequested", columnAdded: "columnAdded", columnRemoved: "columnRemoved", categorySelected: "categorySelected", rowSelected: "rowSelected", sortChange: "sortChange", pageChange: "pageChange", searchChange: "searchChange", groupByChange: "groupByChange", columnsChange: "columnsChange", visualCopied: "visualCopied", lazyColumnsRequested: "lazyColumnsRequested" }, host: { listeners: { "document:fullscreenchange": "onFullscreenChange()" }, properties: { "class.visual-fullscreen": "this._isFullscreen" } }, 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: "tableShellRef", first: true, predicate: ["tableShell"], descendants: true }, { propertyName: "slicePanelRef", first: true, predicate: SlicePanelComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<section class=\"jquery-table-wrapper\">\n <output class=\"visual-copy-feedback\" *ngIf=\"copyFeedbackMessage\" aria-live=\"polite\">{{ copyFeedbackMessage }}</output>\n <header class=\"table-header\" *ngIf=\"showTitle\">\n <h3>{{ title }}</h3>\n </header>\n\n <div\n class=\"table-layout\"\n [class.no-slice]=\"!_showSlicePanel || _slicePanelCollapsed\"\n >\n <slice-panel\n [style.display]=\"!_showSlicePanel || _slicePanelCollapsed ? 'none' : null\"\n [sliceConfigs]=\"_sliceConfigs\"\n [columns]=\"_sliceColumns\"\n [data]=\"_sliceData\"\n [lazyStatus]=\"_lazyColumnStatus\"\n [showToggle]=\"_sliceShowToggle\"\n [collapsedByDefault]=\"_slicePanelCollapsed\"\n (filterChange)=\"onSliceFilterChange($event)\"\n (dynamicSliceKeysChange)=\"onDynamicSliceKeysChange($event)\"\n (collapsedChange)=\"onSlicePanelCollapsedChange($event)\"\n ></slice-panel>\n\n <div class=\"table-shell\" #tableShell>\n <div class=\"table-toolbar\" *ngIf=\"showToolbar\">\n <div class=\"table-toolbar-left\">\n <button\n *ngIf=\"showSliceExpandBtn\"\n type=\"button\"\n class=\"slice-expand-btn\"\n [title]=\"i18n.expandFilterTitle\"\n (click)=\"expandSlicePanel()\"\n ><mat-icon class=\"slice-expand-mat-icon\">filter_list</mat-icon></button>\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]=\"i18n.searchPlaceholder\"\n [attr.aria-label]=\"i18n.searchPlaceholder\"\n autocomplete=\"off\"\n />\n <button *ngIf=\"searchQuery\" type=\"button\" class=\"search-clear-btn\" (click)=\"searchQuery = ''; onSearchChange()\">\u00D7</button>\n <span class=\"search-count\" *ngIf=\"!effectiveIsLoading\">{{ filteredRowCount }}</span>\n <div class=\"search-count-loader\" *ngIf=\"effectiveIsLoading\">\n <div class=\"spinner\"></div>\n </div>\n </div>\n </div>\n <div *ngIf=\"showOrganizerButton\" class=\"table-toolbar-right\">\n <table-organizer-button\n [config]=\"organizerConfig\"\n [state]=\"organizerState\"\n [hideMenuValues]=\"organizerHideMenuValues\"\n (viewChange)=\"onOrganizerViewChange($event)\">\n </table-organizer-button>\n </div>\n </div>\n\n <div class=\"table-body-wrapper\">\n\n <!-- Banni\u00E8re message pr\u00E9f\u00E9rences -->\n <div *ngIf=\"_preferencesMessage\" class=\"preferences-message\">\n <mat-icon class=\"preferences-message-icon\">check_circle</mat-icon>\n <span>{{ _preferencesMessage }}</span>\n </div>\n\n <!-- Bouton de confirmation du mode \u00E9dition -->\n <button\n *ngIf=\"_editMode\"\n type=\"button\"\n class=\"edit-confirm-btn\"\n [title]=\"i18n.preferencesConfirmEdit\"\n (click)=\"confirmEditMode()\"\n >\n <mat-icon>check_circle</mat-icon>\n </button>\n <div class=\"table-body-scroll\" #tableBodyScroll>\n <table mat-table matSort [dataSource]=\"tableDataSource\" class=\"jquery-table\"\n [style.min-width]=\"tableMinWidth\"\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]=\"(!_editMode && column.sortable !== false) ? column.key : ''\"\n [disabled]=\"_editMode || column.sortable === false\"\n [style.width]=\"getColumnWidth(column.key)\"\n [class.drag-enabled]=\"isColumnDragDropEnabled || _editMode\"\n [class.drop-target]=\"isHeaderDropTarget(column.key)\"\n [class.edit-mode-header]=\"_editMode\"\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 <button\n type=\"button\"\n *ngIf=\"isColumnDragDropEnabled || _editMode\"\n class=\"drag-handle\"\n draggable=\"true\"\n tabindex=\"0\"\n [attr.aria-label]=\"'R\u00E9ordonner la colonne ' + column.header\"\n (dragstart)=\"onHeaderDragStart(column.key, $event)\"\n (dragend)=\"onHeaderDragEnd()\"\n (keydown)=\"onHeaderKeyDown(column.key, $event)\"\n (click)=\"$event.stopPropagation()\"\n >\n <mat-icon>drag_indicator</mat-icon>\n </button>\n </div>\n <!-- Poign\u00E9e de resize (visible uniquement en mode \u00E9dition) -->\n <span\n *ngIf=\"_editMode\"\n class=\"col-resize-handle\"\n draggable=\"false\"\n (mousedown)=\"onResizeStart(column.key, $event)\"\n ></span>\n </th>\n <td mat-cell *matCellDef=\"let row\"\n [style.width]=\"getColumnWidth(column.key)\"\n [class.edit-mode-cell]=\"_editMode\">\n <ng-container *ngIf=\"!_editMode; else editModeCellTpl\">\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)=\"$event.stopPropagation(); retryLazyColumns()\" [title]=\"i18n.lazyErrorTitle\">\n <span class=\"lazy-error-icon\">\u26A0</span>\n <span class=\"lazy-error-label\">{{ i18n.lazyRetry }}</span>\n </button>\n <ng-container *ngSwitchDefault>\n <ng-container *ngIf=\"getCellTemplate(column.key) as customTpl; else defaultCellTpl\">\n <ng-container *ngTemplateOutlet=\"customTpl; context: { $implicit: row[_ROW_RAW_KEY], index: row[_ROW_INDEX_KEY], value: row[column.key] }\"></ng-container>\n </ng-container>\n <ng-template #defaultCellTpl>\n <span class=\"cell-text\" [class.cell-empty]=\"isCellEmpty(row[column.key])\" [title]=\"asTitle(row[column.key])\">{{ isCellEmpty(row[column.key]) ? i18n.cellEmpty : row[column.key] }}</span>\n </ng-template>\n </ng-container>\n </ng-container>\n </ng-container>\n <ng-template #editModeCellTpl>\n <span class=\"cell-text cell-edit-mode\" [title]=\"asTitle(row[column.key])\">{{ row[column.key] }}</span>\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 && !_editMode ? 0 : null\"\n [attr.aria-label]=\"hasRowClickHandler && !_editMode ? rowActionLabel(row) : null\"\n [attr.aria-keyshortcuts]=\"hasRowClickHandler && !_editMode ? 'Enter Space' : null\"\n [ngClass]=\"getRowClass(row)\"\n [class.no-pointer]=\"!hasRowClickHandler || _editMode\"\n [class.edit-mode-row]=\"_editMode\"\n (click)=\"!_editMode && onRowClick(row, $event)\"\n (keydown.enter)=\"!_editMode && onRowClick(row, null)\"\n (keydown.space)=\"!_editMode && onRowClick(row, null); $event.preventDefault()\"\n ></tr>\n <ng-container [matColumnDef]=\"_GROUP_ROW_COLUMN\">\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[_ROW_GROUP_KEY])\" *ngIf=\"isNormalGroupRow(row)\">\u203A</span>\n <span *ngIf=\"isNormalGroupRow(row)\" class=\"group-key-label\">{{ row[_ROW_GROUP_KEY] }}</span>\n <span *ngIf=\"isGroupRowLoading(row)\" class=\"lazy-skeleton group-lazy-key-skeleton\"></span>\n <button *ngIf=\"isGroupRowError(row)\" type=\"button\" class=\"lazy-error-btn\" (click)=\"retryGroupByColumn($event)\" [title]=\"i18n.lazyErrorTitle\">\n <span class=\"lazy-error-icon\">\u26A0</span>\n <span class=\"lazy-error-label\">{{ i18n.lazyRetry }}</span>\n </button>\n <span *ngIf=\"isGroupRowTooMany(row)\" class=\"group-too-many-label\">{{ i18n.groupTooMany(row[_ROW_GROUP_COUNT]) }}</span>\n <span class=\"group-count-badge\">{{ row[_ROW_GROUP_COUNT] }}</span>\n <div class=\"group-pagination\" *ngIf=\"!isGroupCollapsed(row[_ROW_GROUP_KEY]) && row[_ROW_GROUP_PAGE_COUNT] > 1\" (click)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"group-page-btn\" [disabled]=\"row[_ROW_GROUP_PAGE] === 0\" (click)=\"groupPageBack(row[_ROW_GROUP_KEY])\">\u2039</button>\n <span class=\"group-page-info\">{{ row[_ROW_GROUP_PAGE] + 1 }} / {{ row[_ROW_GROUP_PAGE_COUNT] }}</span>\n <button type=\"button\" class=\"group-page-btn\" [disabled]=\"row[_ROW_GROUP_PAGE] >= row[_ROW_GROUP_PAGE_COUNT] - 1\" (click)=\"groupPageForward(row[_ROW_GROUP_KEY], row[_ROW_GROUP_COUNT])\">\u203A</button>\n </div>\n </div>\n </td>\n </ng-container>\n <tr mat-row *matRowDef=\"let row; columns: [_GROUP_ROW_COLUMN]; when: isGroupHeader\"\n class=\"group-header-row\"\n [class.group-header-loading]=\"isGroupRowLoading(row)\"\n [class.group-header-error]=\"isGroupRowError(row)\"\n [class.group-header-too-many]=\"isGroupRowTooMany(row)\"\n [attr.data-group-key]=\"row[_ROW_GROUP_KEY]\"\n [attr.tabindex]=\"isNormalGroupRow(row) ? 0 : null\"\n [attr.aria-expanded]=\"isNormalGroupRow(row) ? !isGroupCollapsed(row[_ROW_GROUP_KEY]) : null\"\n (click)=\"onGroupHeaderClick(row)\"\n (keydown.enter)=\"onGroupHeaderClick(row)\"\n (keydown.space)=\"onGroupHeaderClick(row); $event.preventDefault()\"></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\" [class.is-loading]=\"effectiveIsLoading\" *ngIf=\"showPagination\">\n <mat-paginator\n [length]=\"totalRows\"\n [pageSize]=\"pageSize\"\n [pageSizeOptions]=\"pageSizeOptions\"\n [showFirstLastButtons]=\"resolvedPaginationShowFirstLastButtons\"\n ></mat-paginator>\n <div class=\"paginator-range-loader\" \n [class.paginator-range-loader--no-first-last]=\"!resolvedPaginationShowFirstLastButtons\"\n *ngIf=\"effectiveIsLoading\">\n <div class=\"spinner\"></div>\n </div>\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 {{ i18n.groupRemove }}\n </button>\n <div class=\"group-bar-separator\"></div>\n <div class=\"group-sort-controls\">\n <span class=\"group-sort-label\">{{ i18n.groupSortLabel }}</span>\n <button type=\"button\" class=\"group-sort-btn\" [class.active]=\"groupSortOrder === 'asc'\" (click)=\"onGroupSortToggle('asc')\" [title]=\"i18n.groupSortAsc\">\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]=\"i18n.groupSortDesc\">\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\">{{ i18n.groupRowsPerGroup }}</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 ? i18n.groupShowAll : groupPageSize }}</mat-select-trigger>\n <mat-option *ngFor=\"let opt of pageSizeOptionsGroupBy\" [value]=\"opt\">{{ opt }}</mat-option>\n <mat-option [value]=\"0\">{{ i18n.groupShowAll }}</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-header-height: 56px;--jqt-header-cell-padding-y: 0px;--jqt-cell-bg: #ffffff;--jqt-footer-bg: #f5f5f5;--jqt-footer-text-color: #374151;--jqt-footer-icon-color: #374151;--jqt-footer-height: var(--jqt-header-height);--jqt-toolbar-control-height: 34px;--jqt-slice-panel-min-width: 155px;--jqt-slice-panel-max-width: 190px;--jqt-slice-header-height: 34px;--jqt-slice-header-margin-bottom: 8px;--jqt-slice-body-padding: 4px 10px 10px;--jqt-slice-section-title-font-size: 14px;--jqt-slice-item-font-size: 13px;--jqt-slice-item-min-height: 30px;--jqt-slice-item-padding: 5px 10px 5px 16px}:host.visual-fullscreen{display:block;width:100vw;height:100vh;background:#fff}.jquery-table-wrapper{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:inherit;padding:2px 2px 0;box-sizing:border-box}.visual-copy-feedback{display:block;position:absolute;top:8px;left:50%;z-index:20;transform:translate(-50%);padding:7px 12px;border:1px solid #c7dfd1;border-radius:5px;background:#f4fbf7f5;color:#24613b;font-size:12px;pointer-events:none}.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(var(--jqt-slice-panel-min-width),var(--jqt-slice-panel-max-width)) minmax(0,1fr);gap:12px;align-items:stretch;flex:1 1 0;min-height:0;height:auto}.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-x:auto;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;display:flex;align-items:center;gap:6px}.table-toolbar-right{flex:0 0 auto;display:flex;align-items:center;gap:6px}.table-action-btn{width:34px;height:34px;display:inline-flex;align-items:center;justify-content:center;border:0;border-radius:6px;background:transparent;color:inherit;cursor:pointer}.table-action-btn:hover{background:#00000014}.search-input-wrapper{position:relative;display:inline-flex;align-items:center;flex:1 1 auto;min-width:0;height:var(--jqt-toolbar-control-height);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-count-loader{flex-shrink:0;display:flex;align-items:center;justify-content:center;min-width:20px;height:20px}.spinner{width:14px;height:14px;border:2px solid #d1d5db;border-top-color:#374151;border-radius:50%;animation:spin .8s linear infinite}.search-count-loader .spinner{width:16px;height:16px;border:2.5px solid #e5e7eb;border-top-color:#a0a5ad}@keyframes spin{to{transform:rotate(360deg)}}.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)}.slice-expand-btn{flex-shrink:0;width:var(--jqt-toolbar-control-height);height:var(--jqt-toolbar-control-height);border:1px solid var(--jqt-border-color);border-radius:8px;background:var(--jqt-surface-color);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;color:var(--jqt-text-color);padding:0;line-height:1}.slice-expand-btn:hover{border-color:var(--jqt-text-muted)}.slice-expand-mat-icon{font-size:18px;width:18px;height:18px;line-height:1}.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}.export-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}.export-btn:hover{border-color:var(--jqt-text-muted)}.export-btn-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0}.view-menu-export-divider{margin:4px 0!important}.view-menu-export-item{display:flex;align-items:center;gap:6px;width:100%;padding:0 16px;height:40px;border:none;background:transparent;cursor:pointer;font-size:13px;font-weight:500;color:var(--jqt-text-color);text-align:left;box-sizing:border-box}.view-menu-export-item:hover{background:#0000000a}.view-menu-export-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0;color:var(--jqt-text-muted)}.edit-confirm-btn{position:absolute;top:10px;right:10px;z-index:200;display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:none;border-radius:50%;background:#16a34a;cursor:pointer;box-shadow:0 2px 8px #00000040;transition:background .15s}.edit-confirm-btn mat-icon{color:#fff;font-size:22px;width:22px;height:22px}.edit-confirm-btn:hover{background:#15803d}.preferences-message{display:flex;align-items:center;gap:8px;margin:0 0 8px;padding:8px 12px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:6px;font-size:12px;color:#15803d}.preferences-message .preferences-message-icon{font-size:16px;width:16px;height:16px}:host ::ng-deep .edit-mode-header{background:#3b82f60a!important;cursor:grab!important;-webkit-user-select:none;user-select:none;overflow:visible!important}.col-resize-handle{position:absolute;top:0;right:0;width:6px;height:100%;cursor:col-resize!important;background:#3b82f640;z-index:20}.col-resize-handle:hover{background:#3b82f6b3}.col-resize-handle:active{background:#3b82f6e6}:host ::ng-deep th.mat-mdc-header-cell{position:relative}:host ::ng-deep th.mat-mdc-header-cell.drag-enabled{overflow:visible!important}:host ::ng-deep .edit-mode-row{pointer-events:none;-webkit-user-select:none;user-select:none;opacity:.8}.cell-edit-mode{pointer-events:none;-webkit-user-select:none;user-select:none;color:var(--jqt-text-muted)}.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-error td,:host ::ng-deep .group-header-error: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);height:var(--jqt-header-height);min-height:var(--jqt-header-height)}:host ::ng-deep .mat-mdc-header-cell{background:var(--jqt-header-bg);color:var(--jqt-header-text-color);height:var(--jqt-header-height);min-height:var(--jqt-header-height);padding-top:var(--jqt-header-cell-padding-y);padding-bottom:var(--jqt-header-cell-padding-y)}: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:not(.no-pointer):focus-visible{outline:2px solid var(--jqt-primary-color);outline-offset:-2px}: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:0;background:transparent;border-radius:4px;font:inherit;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;min-height:var(--jqt-footer-height, var(--actual-header-height, 56px));position:relative;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;min-width:0;background:transparent;color:var(--jqt-footer-text-color)}:host ::ng-deep .bottom-bar .mat-mdc-paginator-outer-container{display:flex;width:100%}:host ::ng-deep .bottom-bar .mat-mdc-paginator-container{display:flex;align-items:center;flex-wrap:nowrap;justify-content:flex-end;width:100%;min-height:0;height:var(--jqt-footer-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{flex-shrink:0;font-size:12px;color:var(--jqt-footer-text-color);white-space:nowrap}:host ::ng-deep .bottom-bar .mat-mdc-paginator-page-size,:host ::ng-deep .bottom-bar .mat-mdc-paginator-range-actions{display:flex;flex-shrink:0;align-items:center;white-space:nowrap}:host ::ng-deep .bottom-bar .mat-mdc-paginator-range-label{margin:0 12px}: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)}:host ::ng-deep .bottom-bar.is-loading .mat-mdc-paginator-range-label{visibility:hidden}.paginator-range-loader{position:absolute;right:0;top:50%;transform:translateY(-50%);margin-right:145px;display:flex;align-items:center;justify-content:center;width:auto;padding:0 16px;height:24px}.paginator-range-loader--no-first-last{margin-right:80px}.paginator-range-loader .spinner{width:16px;height:16px;border:2.5px solid #e5e7eb;border-top-color:#a0a5ad}.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:wrap;min-height:var(--actual-header-height, 56px);height:auto;padding:8px 12px;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;flex:1 1 280px;min-width:0;flex-wrap:wrap;gap:8px}.group-sort-controls{display:flex;align-items:center;min-width:0;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;flex:0 1 auto;min-width:0;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;max-width:100%;overflow:hidden;text-overflow:ellipsis;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.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { 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: MatDividerModule }, { 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: "ngmodule", type: MatIconModule }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "component", type: i8.MatSelect, selector: "mat-select", inputs: ["disabled", "disableRipple", "tabIndex", "panelWidth", "hideSingleSelectionIndicator"], exportAs: ["matSelect"] }, { kind: "directive", type: i8.MatSelectTrigger, selector: "mat-select-trigger" }, { kind: "component", type: i9.MatOption, selector: "mat-option", exportAs: ["matOption"] }, { kind: "component", type: SlicePanelComponent, selector: "slice-panel", inputs: ["sliceConfigs", "columns", "data", "lazyStatus", "showToggle", "showCounts", "collapsedByDefault", "alwaysShow"], outputs: ["filterChange", "activeKeysChange", "dynamicSliceKeysChange", "collapsedChange"] }, { kind: "component", type: OrganizerButtonWrapperComponent, selector: "table-organizer-button", inputs: ["config", "state", "hideMenuValues"], outputs: ["viewChange", "sliceStateChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1984
2247
|
}
|
|
1985
2248
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TableComponent, decorators: [{
|
|
1986
2249
|
type: Component,
|
|
1987
|
-
args: [{ standalone: true, selector: 'jquery-table', imports: [CommonModule, FormsModule, MatTableModule, MatButtonModule, MatDividerModule, MatPaginatorModule, MatSortModule, MatMenuModule, MatIconModule, MatSelectModule, SlicePanelComponent, OrganizerButtonWrapperComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [{ provide: MatPaginatorIntl, useFactory: getFrenchPaginatorIntl }], template: "<section class=\"jquery-table-wrapper\">\n <div class=\"visual-copy-feedback\" *ngIf=\"copyFeedbackMessage\" role=\"status\">{{ copyFeedbackMessage }}</div>\n <header class=\"table-header\" *ngIf=\"showTitle\">\n <h3>{{ title }}</h3>\n </header>\n\n <div\n class=\"table-layout\"\n [class.no-slice]=\"!_showSlicePanel || _slicePanelCollapsed\"\n >\n <slice-panel\n [style.display]=\"!_showSlicePanel || _slicePanelCollapsed ? 'none' : null\"\n [sliceConfigs]=\"_sliceConfigs\"\n [columns]=\"_sliceColumns\"\n [data]=\"_sliceData\"\n [lazyData]=\"_lazyColumnData\"\n [lazyStatus]=\"_lazyColumnStatus\"\n [showToggle]=\"_sliceShowToggle\"\n [collapsedByDefault]=\"_slicePanelCollapsed\"\n (filterChange)=\"onSliceFilterChange($event)\"\n (dynamicSliceKeysChange)=\"onDynamicSliceKeysChange($event)\"\n (collapsedChange)=\"onSlicePanelCollapsedChange($event)\"\n ></slice-panel>\n\n <div class=\"table-shell\">\n <div class=\"table-toolbar\" *ngIf=\"showToolbar\">\n <div class=\"table-toolbar-left\">\n <button\n *ngIf=\"showSliceExpandBtn\"\n type=\"button\"\n class=\"slice-expand-btn\"\n [title]=\"i18n.expandFilterTitle\"\n (click)=\"expandSlicePanel()\"\n ><mat-icon class=\"slice-expand-mat-icon\">filter_list</mat-icon></button>\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]=\"i18n.searchPlaceholder\"\n autocomplete=\"off\"\n />\n <button *ngIf=\"searchQuery\" type=\"button\" class=\"search-clear-btn\" (click)=\"searchQuery = ''; onSearchChange()\">\u00D7</button>\n <span class=\"search-count\" *ngIf=\"!effectiveIsLoading\">{{ filteredRowCount }}</span>\n <div class=\"search-count-loader\" *ngIf=\"effectiveIsLoading\">\n <div class=\"spinner\"></div>\n </div>\n </div>\n </div>\n <div *ngIf=\"showOrganizerButton\" class=\"table-toolbar-right\">\n <table-organizer-button\n [config]=\"organizerConfig\"\n [state]=\"organizerState\"\n (viewChange)=\"onOrganizerViewChange($event)\">\n </table-organizer-button>\n </div>\n </div>\n\n <div class=\"table-body-wrapper\">\n\n <!-- Banni\u00E8re message pr\u00E9f\u00E9rences -->\n <div *ngIf=\"_preferencesMessage\" class=\"preferences-message\">\n <mat-icon class=\"preferences-message-icon\">check_circle</mat-icon>\n <span>{{ _preferencesMessage }}</span>\n </div>\n\n <!-- Bouton de confirmation du mode \u00E9dition -->\n <button\n *ngIf=\"_editMode\"\n type=\"button\"\n class=\"edit-confirm-btn\"\n [title]=\"i18n.preferencesConfirmEdit\"\n (click)=\"confirmEditMode()\"\n >\n <mat-icon>check_circle</mat-icon>\n </button>\n <div class=\"table-body-scroll\" #tableBodyScroll>\n <table mat-table matSort [dataSource]=\"tableDataSource\" class=\"jquery-table\"\n [style.min-width]=\"tableMinWidth\"\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]=\"(!_editMode && column.sortable !== false) ? column.key : ''\"\n [disabled]=\"_editMode || column.sortable === false\"\n [style.width]=\"getColumnWidth(column.key)\"\n [class.drag-enabled]=\"isColumnDragDropEnabled || _editMode\"\n [class.drop-target]=\"isHeaderDropTarget(column.key)\"\n [class.edit-mode-header]=\"_editMode\"\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 || _editMode\"\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 <!-- Poign\u00E9e de resize (visible uniquement en mode \u00E9dition) -->\n <span\n *ngIf=\"_editMode\"\n class=\"col-resize-handle\"\n draggable=\"false\"\n (mousedown)=\"onResizeStart(column.key, $event)\"\n ></span>\n </th>\n <td mat-cell *matCellDef=\"let row\"\n [style.width]=\"getColumnWidth(column.key)\"\n [class.edit-mode-cell]=\"_editMode\">\n <ng-container *ngIf=\"!_editMode; else editModeCellTpl\">\n <ng-container *ngIf=\"getCellTemplate(column.key) as customTpl; else defaultCellTpl\">\n <ng-container *ngTemplateOutlet=\"customTpl; context: { $implicit: row[_ROW_RAW_KEY], index: row[_ROW_INDEX_KEY] }\"></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]=\"i18n.lazyErrorTitle\">\n <span class=\"lazy-error-icon\">\u26A0</span>\n <span class=\"lazy-error-label\">{{ i18n.lazyRetry }}</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]) ? i18n.cellEmpty : row[column.key] }}</span>\n </ng-container>\n </ng-template>\n </ng-container>\n <ng-template #editModeCellTpl>\n <span class=\"cell-text cell-edit-mode\" [title]=\"asTitle(row[column.key])\">{{ row[column.key] }}</span>\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 && !_editMode ? 0 : null\"\n [ngClass]=\"getRowClass(row)\"\n [class.no-pointer]=\"!hasRowClickHandler || _editMode\"\n [class.edit-mode-row]=\"_editMode\"\n (click)=\"!_editMode && onRowClick(row, $event)\"\n (keydown.enter)=\"!_editMode && onRowClick(row, null)\"\n (keydown.space)=\"!_editMode && onRowClick(row, null)\"\n ></tr>\n <ng-container [matColumnDef]=\"_GROUP_ROW_COLUMN\">\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[_ROW_GROUP_KEY])\" *ngIf=\"isNormalGroupRow(row)\">\u203A</span>\n <span *ngIf=\"isNormalGroupRow(row)\" class=\"group-key-label\">{{ row[_ROW_GROUP_KEY] }}</span>\n <span *ngIf=\"isGroupRowLoading(row)\" class=\"lazy-skeleton group-lazy-key-skeleton\"></span>\n <span *ngIf=\"isGroupRowTooMany(row)\" class=\"group-too-many-label\">{{ i18n.groupTooMany(row[_ROW_GROUP_COUNT]) }}</span>\n <span class=\"group-count-badge\">{{ row[_ROW_GROUP_COUNT] }}</span>\n <div class=\"group-pagination\" *ngIf=\"!isGroupCollapsed(row[_ROW_GROUP_KEY]) && row[_ROW_GROUP_PAGE_COUNT] > 1\" (click)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"group-page-btn\" [disabled]=\"row[_ROW_GROUP_PAGE] === 0\" (click)=\"groupPageBack(row[_ROW_GROUP_KEY])\">\u2039</button>\n <span class=\"group-page-info\">{{ row[_ROW_GROUP_PAGE] + 1 }} / {{ row[_ROW_GROUP_PAGE_COUNT] }}</span>\n <button type=\"button\" class=\"group-page-btn\" [disabled]=\"row[_ROW_GROUP_PAGE] >= row[_ROW_GROUP_PAGE_COUNT] - 1\" (click)=\"groupPageForward(row[_ROW_GROUP_KEY], row[_ROW_GROUP_COUNT])\">\u203A</button>\n </div>\n </div>\n </td>\n </ng-container>\n <tr mat-row *matRowDef=\"let row; columns: [_GROUP_ROW_COLUMN]; when: isGroupHeader\"\n class=\"group-header-row\"\n [class.group-header-loading]=\"isGroupRowLoading(row)\"\n [class.group-header-too-many]=\"isGroupRowTooMany(row)\"\n [attr.data-group-key]=\"row[_ROW_GROUP_KEY]\"\n (click)=\"onGroupHeaderClick(row)\"></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\" [class.is-loading]=\"effectiveIsLoading\" *ngIf=\"showPagination\">\n <mat-paginator\n [length]=\"totalRows\"\n [pageSize]=\"pageSize\"\n [pageSizeOptions]=\"pageSizeOptions\"\n [showFirstLastButtons]=\"resolvedPaginationShowFirstLastButtons\"\n ></mat-paginator>\n <div class=\"paginator-range-loader\" \n [class.paginator-range-loader--no-first-last]=\"!resolvedPaginationShowFirstLastButtons\"\n *ngIf=\"effectiveIsLoading\">\n <div class=\"spinner\"></div>\n </div>\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 {{ i18n.groupRemove }}\n </button>\n <div class=\"group-bar-separator\"></div>\n <div class=\"group-sort-controls\">\n <span class=\"group-sort-label\">{{ i18n.groupSortLabel }}</span>\n <button type=\"button\" class=\"group-sort-btn\" [class.active]=\"groupSortOrder === 'asc'\" (click)=\"onGroupSortToggle('asc')\" [title]=\"i18n.groupSortAsc\">\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]=\"i18n.groupSortDesc\">\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\">{{ i18n.groupRowsPerGroup }}</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 ? i18n.groupShowAll : groupPageSize }}</mat-select-trigger>\n <mat-option *ngFor=\"let opt of pageSizeOptionsGroupBy\" [value]=\"opt\">{{ opt }}</mat-option>\n <mat-option [value]=\"0\">{{ i18n.groupShowAll }}</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}:host.visual-fullscreen{display:block;width:100vw;height:100vh;background:#fff}.jquery-table-wrapper{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:inherit;padding:2px 2px 0;box-sizing:border-box}.visual-copy-feedback{position:absolute;top:8px;left:50%;z-index:20;transform:translate(-50%);padding:7px 12px;border:1px solid #c7dfd1;border-radius:5px;background:#f4fbf7f5;color:#24613b;font-size:12px;pointer-events:none}.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-x:auto;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;display:flex;align-items:center;gap:6px}.table-toolbar-right{flex:0 0 auto;display:flex;align-items:center;gap:6px}.table-action-btn{width:34px;height:34px;display:inline-flex;align-items:center;justify-content:center;border:0;border-radius:6px;background:transparent;color:inherit;cursor:pointer}.table-action-btn:hover{background:#00000014}.search-input-wrapper{position:relative;display:inline-flex;align-items:center;flex:1 1 auto;min-width:0;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-count-loader{flex-shrink:0;display:flex;align-items:center;justify-content:center;min-width:20px;height:20px}.spinner{width:14px;height:14px;border:2px solid #d1d5db;border-top-color:#374151;border-radius:50%;animation:spin .8s linear infinite}.search-count-loader .spinner{width:16px;height:16px;border:2.5px solid #e5e7eb;border-top-color:#a0a5ad}@keyframes spin{to{transform:rotate(360deg)}}.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)}.slice-expand-btn{flex-shrink:0;width:34px;height:34px;border:1px solid var(--jqt-border-color);border-radius:8px;background:var(--jqt-surface-color);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;color:var(--jqt-text-color);padding:0;line-height:1}.slice-expand-btn:hover{border-color:var(--jqt-text-muted)}.slice-expand-mat-icon{font-size:18px;width:18px;height:18px;line-height:1}.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}.export-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}.export-btn:hover{border-color:var(--jqt-text-muted)}.export-btn-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0}.view-menu-export-divider{margin:4px 0!important}.view-menu-export-item{display:flex;align-items:center;gap:6px;width:100%;padding:0 16px;height:40px;border:none;background:transparent;cursor:pointer;font-size:13px;font-weight:500;color:var(--jqt-text-color);text-align:left;box-sizing:border-box}.view-menu-export-item:hover{background:#0000000a}.view-menu-export-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0;color:var(--jqt-text-muted)}.edit-confirm-btn{position:absolute;top:10px;right:10px;z-index:200;display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:none;border-radius:50%;background:#16a34a;cursor:pointer;box-shadow:0 2px 8px #00000040;transition:background .15s}.edit-confirm-btn mat-icon{color:#fff;font-size:22px;width:22px;height:22px}.edit-confirm-btn:hover{background:#15803d}.preferences-message{display:flex;align-items:center;gap:8px;margin:0 0 8px;padding:8px 12px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:6px;font-size:12px;color:#15803d}.preferences-message .preferences-message-icon{font-size:16px;width:16px;height:16px}:host ::ng-deep .edit-mode-header{background:#3b82f60a!important;cursor:grab!important;-webkit-user-select:none;user-select:none;overflow:visible!important}.col-resize-handle{position:absolute;top:0;right:0;width:6px;height:100%;cursor:col-resize!important;background:#3b82f640;z-index:20}.col-resize-handle:hover{background:#3b82f6b3}.col-resize-handle:active{background:#3b82f6e6}:host ::ng-deep th.mat-mdc-header-cell{position:relative}:host ::ng-deep th.mat-mdc-header-cell.drag-enabled{overflow:visible!important}:host ::ng-deep .edit-mode-row{pointer-events:none;-webkit-user-select:none;user-select:none;opacity:.8}.cell-edit-mode{pointer-events:none;-webkit-user-select:none;user-select:none;color:var(--jqt-text-muted)}.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;position:relative;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)}:host ::ng-deep .bottom-bar.is-loading .mat-mdc-paginator-range-label{visibility:hidden}.paginator-range-loader{position:absolute;right:0;top:50%;transform:translateY(-50%);margin-right:145px;display:flex;align-items:center;justify-content:center;width:auto;padding:0 16px;height:24px}.paginator-range-loader--no-first-last{margin-right:80px}.paginator-range-loader .spinner{width:16px;height:16px;border:2.5px solid #e5e7eb;border-top-color:#a0a5ad}.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:wrap;min-height:var(--actual-header-height, 56px);height:auto;padding:8px 12px;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;flex:1 1 280px;min-width:0;flex-wrap:wrap;gap:8px}.group-sort-controls{display:flex;align-items:center;min-width:0;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;flex:0 1 auto;min-width:0;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;max-width:100%;overflow:hidden;text-overflow:ellipsis;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"] }]
|
|
2250
|
+
args: [{ standalone: true, selector: 'jquery-table', imports: [CommonModule, FormsModule, MatTableModule, MatButtonModule, MatDividerModule, MatPaginatorModule, MatSortModule, MatMenuModule, MatIconModule, MatSelectModule, SlicePanelComponent, OrganizerButtonWrapperComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [{ provide: MatPaginatorIntl, useFactory: getFrenchPaginatorIntl }], template: "<section class=\"jquery-table-wrapper\">\n <output class=\"visual-copy-feedback\" *ngIf=\"copyFeedbackMessage\" aria-live=\"polite\">{{ copyFeedbackMessage }}</output>\n <header class=\"table-header\" *ngIf=\"showTitle\">\n <h3>{{ title }}</h3>\n </header>\n\n <div\n class=\"table-layout\"\n [class.no-slice]=\"!_showSlicePanel || _slicePanelCollapsed\"\n >\n <slice-panel\n [style.display]=\"!_showSlicePanel || _slicePanelCollapsed ? 'none' : null\"\n [sliceConfigs]=\"_sliceConfigs\"\n [columns]=\"_sliceColumns\"\n [data]=\"_sliceData\"\n [lazyStatus]=\"_lazyColumnStatus\"\n [showToggle]=\"_sliceShowToggle\"\n [collapsedByDefault]=\"_slicePanelCollapsed\"\n (filterChange)=\"onSliceFilterChange($event)\"\n (dynamicSliceKeysChange)=\"onDynamicSliceKeysChange($event)\"\n (collapsedChange)=\"onSlicePanelCollapsedChange($event)\"\n ></slice-panel>\n\n <div class=\"table-shell\" #tableShell>\n <div class=\"table-toolbar\" *ngIf=\"showToolbar\">\n <div class=\"table-toolbar-left\">\n <button\n *ngIf=\"showSliceExpandBtn\"\n type=\"button\"\n class=\"slice-expand-btn\"\n [title]=\"i18n.expandFilterTitle\"\n (click)=\"expandSlicePanel()\"\n ><mat-icon class=\"slice-expand-mat-icon\">filter_list</mat-icon></button>\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]=\"i18n.searchPlaceholder\"\n [attr.aria-label]=\"i18n.searchPlaceholder\"\n autocomplete=\"off\"\n />\n <button *ngIf=\"searchQuery\" type=\"button\" class=\"search-clear-btn\" (click)=\"searchQuery = ''; onSearchChange()\">\u00D7</button>\n <span class=\"search-count\" *ngIf=\"!effectiveIsLoading\">{{ filteredRowCount }}</span>\n <div class=\"search-count-loader\" *ngIf=\"effectiveIsLoading\">\n <div class=\"spinner\"></div>\n </div>\n </div>\n </div>\n <div *ngIf=\"showOrganizerButton\" class=\"table-toolbar-right\">\n <table-organizer-button\n [config]=\"organizerConfig\"\n [state]=\"organizerState\"\n [hideMenuValues]=\"organizerHideMenuValues\"\n (viewChange)=\"onOrganizerViewChange($event)\">\n </table-organizer-button>\n </div>\n </div>\n\n <div class=\"table-body-wrapper\">\n\n <!-- Banni\u00E8re message pr\u00E9f\u00E9rences -->\n <div *ngIf=\"_preferencesMessage\" class=\"preferences-message\">\n <mat-icon class=\"preferences-message-icon\">check_circle</mat-icon>\n <span>{{ _preferencesMessage }}</span>\n </div>\n\n <!-- Bouton de confirmation du mode \u00E9dition -->\n <button\n *ngIf=\"_editMode\"\n type=\"button\"\n class=\"edit-confirm-btn\"\n [title]=\"i18n.preferencesConfirmEdit\"\n (click)=\"confirmEditMode()\"\n >\n <mat-icon>check_circle</mat-icon>\n </button>\n <div class=\"table-body-scroll\" #tableBodyScroll>\n <table mat-table matSort [dataSource]=\"tableDataSource\" class=\"jquery-table\"\n [style.min-width]=\"tableMinWidth\"\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]=\"(!_editMode && column.sortable !== false) ? column.key : ''\"\n [disabled]=\"_editMode || column.sortable === false\"\n [style.width]=\"getColumnWidth(column.key)\"\n [class.drag-enabled]=\"isColumnDragDropEnabled || _editMode\"\n [class.drop-target]=\"isHeaderDropTarget(column.key)\"\n [class.edit-mode-header]=\"_editMode\"\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 <button\n type=\"button\"\n *ngIf=\"isColumnDragDropEnabled || _editMode\"\n class=\"drag-handle\"\n draggable=\"true\"\n tabindex=\"0\"\n [attr.aria-label]=\"'R\u00E9ordonner la colonne ' + column.header\"\n (dragstart)=\"onHeaderDragStart(column.key, $event)\"\n (dragend)=\"onHeaderDragEnd()\"\n (keydown)=\"onHeaderKeyDown(column.key, $event)\"\n (click)=\"$event.stopPropagation()\"\n >\n <mat-icon>drag_indicator</mat-icon>\n </button>\n </div>\n <!-- Poign\u00E9e de resize (visible uniquement en mode \u00E9dition) -->\n <span\n *ngIf=\"_editMode\"\n class=\"col-resize-handle\"\n draggable=\"false\"\n (mousedown)=\"onResizeStart(column.key, $event)\"\n ></span>\n </th>\n <td mat-cell *matCellDef=\"let row\"\n [style.width]=\"getColumnWidth(column.key)\"\n [class.edit-mode-cell]=\"_editMode\">\n <ng-container *ngIf=\"!_editMode; else editModeCellTpl\">\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)=\"$event.stopPropagation(); retryLazyColumns()\" [title]=\"i18n.lazyErrorTitle\">\n <span class=\"lazy-error-icon\">\u26A0</span>\n <span class=\"lazy-error-label\">{{ i18n.lazyRetry }}</span>\n </button>\n <ng-container *ngSwitchDefault>\n <ng-container *ngIf=\"getCellTemplate(column.key) as customTpl; else defaultCellTpl\">\n <ng-container *ngTemplateOutlet=\"customTpl; context: { $implicit: row[_ROW_RAW_KEY], index: row[_ROW_INDEX_KEY], value: row[column.key] }\"></ng-container>\n </ng-container>\n <ng-template #defaultCellTpl>\n <span class=\"cell-text\" [class.cell-empty]=\"isCellEmpty(row[column.key])\" [title]=\"asTitle(row[column.key])\">{{ isCellEmpty(row[column.key]) ? i18n.cellEmpty : row[column.key] }}</span>\n </ng-template>\n </ng-container>\n </ng-container>\n </ng-container>\n <ng-template #editModeCellTpl>\n <span class=\"cell-text cell-edit-mode\" [title]=\"asTitle(row[column.key])\">{{ row[column.key] }}</span>\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 && !_editMode ? 0 : null\"\n [attr.aria-label]=\"hasRowClickHandler && !_editMode ? rowActionLabel(row) : null\"\n [attr.aria-keyshortcuts]=\"hasRowClickHandler && !_editMode ? 'Enter Space' : null\"\n [ngClass]=\"getRowClass(row)\"\n [class.no-pointer]=\"!hasRowClickHandler || _editMode\"\n [class.edit-mode-row]=\"_editMode\"\n (click)=\"!_editMode && onRowClick(row, $event)\"\n (keydown.enter)=\"!_editMode && onRowClick(row, null)\"\n (keydown.space)=\"!_editMode && onRowClick(row, null); $event.preventDefault()\"\n ></tr>\n <ng-container [matColumnDef]=\"_GROUP_ROW_COLUMN\">\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[_ROW_GROUP_KEY])\" *ngIf=\"isNormalGroupRow(row)\">\u203A</span>\n <span *ngIf=\"isNormalGroupRow(row)\" class=\"group-key-label\">{{ row[_ROW_GROUP_KEY] }}</span>\n <span *ngIf=\"isGroupRowLoading(row)\" class=\"lazy-skeleton group-lazy-key-skeleton\"></span>\n <button *ngIf=\"isGroupRowError(row)\" type=\"button\" class=\"lazy-error-btn\" (click)=\"retryGroupByColumn($event)\" [title]=\"i18n.lazyErrorTitle\">\n <span class=\"lazy-error-icon\">\u26A0</span>\n <span class=\"lazy-error-label\">{{ i18n.lazyRetry }}</span>\n </button>\n <span *ngIf=\"isGroupRowTooMany(row)\" class=\"group-too-many-label\">{{ i18n.groupTooMany(row[_ROW_GROUP_COUNT]) }}</span>\n <span class=\"group-count-badge\">{{ row[_ROW_GROUP_COUNT] }}</span>\n <div class=\"group-pagination\" *ngIf=\"!isGroupCollapsed(row[_ROW_GROUP_KEY]) && row[_ROW_GROUP_PAGE_COUNT] > 1\" (click)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"group-page-btn\" [disabled]=\"row[_ROW_GROUP_PAGE] === 0\" (click)=\"groupPageBack(row[_ROW_GROUP_KEY])\">\u2039</button>\n <span class=\"group-page-info\">{{ row[_ROW_GROUP_PAGE] + 1 }} / {{ row[_ROW_GROUP_PAGE_COUNT] }}</span>\n <button type=\"button\" class=\"group-page-btn\" [disabled]=\"row[_ROW_GROUP_PAGE] >= row[_ROW_GROUP_PAGE_COUNT] - 1\" (click)=\"groupPageForward(row[_ROW_GROUP_KEY], row[_ROW_GROUP_COUNT])\">\u203A</button>\n </div>\n </div>\n </td>\n </ng-container>\n <tr mat-row *matRowDef=\"let row; columns: [_GROUP_ROW_COLUMN]; when: isGroupHeader\"\n class=\"group-header-row\"\n [class.group-header-loading]=\"isGroupRowLoading(row)\"\n [class.group-header-error]=\"isGroupRowError(row)\"\n [class.group-header-too-many]=\"isGroupRowTooMany(row)\"\n [attr.data-group-key]=\"row[_ROW_GROUP_KEY]\"\n [attr.tabindex]=\"isNormalGroupRow(row) ? 0 : null\"\n [attr.aria-expanded]=\"isNormalGroupRow(row) ? !isGroupCollapsed(row[_ROW_GROUP_KEY]) : null\"\n (click)=\"onGroupHeaderClick(row)\"\n (keydown.enter)=\"onGroupHeaderClick(row)\"\n (keydown.space)=\"onGroupHeaderClick(row); $event.preventDefault()\"></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\" [class.is-loading]=\"effectiveIsLoading\" *ngIf=\"showPagination\">\n <mat-paginator\n [length]=\"totalRows\"\n [pageSize]=\"pageSize\"\n [pageSizeOptions]=\"pageSizeOptions\"\n [showFirstLastButtons]=\"resolvedPaginationShowFirstLastButtons\"\n ></mat-paginator>\n <div class=\"paginator-range-loader\" \n [class.paginator-range-loader--no-first-last]=\"!resolvedPaginationShowFirstLastButtons\"\n *ngIf=\"effectiveIsLoading\">\n <div class=\"spinner\"></div>\n </div>\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 {{ i18n.groupRemove }}\n </button>\n <div class=\"group-bar-separator\"></div>\n <div class=\"group-sort-controls\">\n <span class=\"group-sort-label\">{{ i18n.groupSortLabel }}</span>\n <button type=\"button\" class=\"group-sort-btn\" [class.active]=\"groupSortOrder === 'asc'\" (click)=\"onGroupSortToggle('asc')\" [title]=\"i18n.groupSortAsc\">\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]=\"i18n.groupSortDesc\">\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\">{{ i18n.groupRowsPerGroup }}</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 ? i18n.groupShowAll : groupPageSize }}</mat-select-trigger>\n <mat-option *ngFor=\"let opt of pageSizeOptionsGroupBy\" [value]=\"opt\">{{ opt }}</mat-option>\n <mat-option [value]=\"0\">{{ i18n.groupShowAll }}</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-header-height: 56px;--jqt-header-cell-padding-y: 0px;--jqt-cell-bg: #ffffff;--jqt-footer-bg: #f5f5f5;--jqt-footer-text-color: #374151;--jqt-footer-icon-color: #374151;--jqt-footer-height: var(--jqt-header-height);--jqt-toolbar-control-height: 34px;--jqt-slice-panel-min-width: 155px;--jqt-slice-panel-max-width: 190px;--jqt-slice-header-height: 34px;--jqt-slice-header-margin-bottom: 8px;--jqt-slice-body-padding: 4px 10px 10px;--jqt-slice-section-title-font-size: 14px;--jqt-slice-item-font-size: 13px;--jqt-slice-item-min-height: 30px;--jqt-slice-item-padding: 5px 10px 5px 16px}:host.visual-fullscreen{display:block;width:100vw;height:100vh;background:#fff}.jquery-table-wrapper{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:inherit;padding:2px 2px 0;box-sizing:border-box}.visual-copy-feedback{display:block;position:absolute;top:8px;left:50%;z-index:20;transform:translate(-50%);padding:7px 12px;border:1px solid #c7dfd1;border-radius:5px;background:#f4fbf7f5;color:#24613b;font-size:12px;pointer-events:none}.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(var(--jqt-slice-panel-min-width),var(--jqt-slice-panel-max-width)) minmax(0,1fr);gap:12px;align-items:stretch;flex:1 1 0;min-height:0;height:auto}.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-x:auto;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;display:flex;align-items:center;gap:6px}.table-toolbar-right{flex:0 0 auto;display:flex;align-items:center;gap:6px}.table-action-btn{width:34px;height:34px;display:inline-flex;align-items:center;justify-content:center;border:0;border-radius:6px;background:transparent;color:inherit;cursor:pointer}.table-action-btn:hover{background:#00000014}.search-input-wrapper{position:relative;display:inline-flex;align-items:center;flex:1 1 auto;min-width:0;height:var(--jqt-toolbar-control-height);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-count-loader{flex-shrink:0;display:flex;align-items:center;justify-content:center;min-width:20px;height:20px}.spinner{width:14px;height:14px;border:2px solid #d1d5db;border-top-color:#374151;border-radius:50%;animation:spin .8s linear infinite}.search-count-loader .spinner{width:16px;height:16px;border:2.5px solid #e5e7eb;border-top-color:#a0a5ad}@keyframes spin{to{transform:rotate(360deg)}}.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)}.slice-expand-btn{flex-shrink:0;width:var(--jqt-toolbar-control-height);height:var(--jqt-toolbar-control-height);border:1px solid var(--jqt-border-color);border-radius:8px;background:var(--jqt-surface-color);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;color:var(--jqt-text-color);padding:0;line-height:1}.slice-expand-btn:hover{border-color:var(--jqt-text-muted)}.slice-expand-mat-icon{font-size:18px;width:18px;height:18px;line-height:1}.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}.export-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}.export-btn:hover{border-color:var(--jqt-text-muted)}.export-btn-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0}.view-menu-export-divider{margin:4px 0!important}.view-menu-export-item{display:flex;align-items:center;gap:6px;width:100%;padding:0 16px;height:40px;border:none;background:transparent;cursor:pointer;font-size:13px;font-weight:500;color:var(--jqt-text-color);text-align:left;box-sizing:border-box}.view-menu-export-item:hover{background:#0000000a}.view-menu-export-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0;color:var(--jqt-text-muted)}.edit-confirm-btn{position:absolute;top:10px;right:10px;z-index:200;display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:none;border-radius:50%;background:#16a34a;cursor:pointer;box-shadow:0 2px 8px #00000040;transition:background .15s}.edit-confirm-btn mat-icon{color:#fff;font-size:22px;width:22px;height:22px}.edit-confirm-btn:hover{background:#15803d}.preferences-message{display:flex;align-items:center;gap:8px;margin:0 0 8px;padding:8px 12px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:6px;font-size:12px;color:#15803d}.preferences-message .preferences-message-icon{font-size:16px;width:16px;height:16px}:host ::ng-deep .edit-mode-header{background:#3b82f60a!important;cursor:grab!important;-webkit-user-select:none;user-select:none;overflow:visible!important}.col-resize-handle{position:absolute;top:0;right:0;width:6px;height:100%;cursor:col-resize!important;background:#3b82f640;z-index:20}.col-resize-handle:hover{background:#3b82f6b3}.col-resize-handle:active{background:#3b82f6e6}:host ::ng-deep th.mat-mdc-header-cell{position:relative}:host ::ng-deep th.mat-mdc-header-cell.drag-enabled{overflow:visible!important}:host ::ng-deep .edit-mode-row{pointer-events:none;-webkit-user-select:none;user-select:none;opacity:.8}.cell-edit-mode{pointer-events:none;-webkit-user-select:none;user-select:none;color:var(--jqt-text-muted)}.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-error td,:host ::ng-deep .group-header-error: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);height:var(--jqt-header-height);min-height:var(--jqt-header-height)}:host ::ng-deep .mat-mdc-header-cell{background:var(--jqt-header-bg);color:var(--jqt-header-text-color);height:var(--jqt-header-height);min-height:var(--jqt-header-height);padding-top:var(--jqt-header-cell-padding-y);padding-bottom:var(--jqt-header-cell-padding-y)}: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:not(.no-pointer):focus-visible{outline:2px solid var(--jqt-primary-color);outline-offset:-2px}: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:0;background:transparent;border-radius:4px;font:inherit;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;min-height:var(--jqt-footer-height, var(--actual-header-height, 56px));position:relative;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;min-width:0;background:transparent;color:var(--jqt-footer-text-color)}:host ::ng-deep .bottom-bar .mat-mdc-paginator-outer-container{display:flex;width:100%}:host ::ng-deep .bottom-bar .mat-mdc-paginator-container{display:flex;align-items:center;flex-wrap:nowrap;justify-content:flex-end;width:100%;min-height:0;height:var(--jqt-footer-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{flex-shrink:0;font-size:12px;color:var(--jqt-footer-text-color);white-space:nowrap}:host ::ng-deep .bottom-bar .mat-mdc-paginator-page-size,:host ::ng-deep .bottom-bar .mat-mdc-paginator-range-actions{display:flex;flex-shrink:0;align-items:center;white-space:nowrap}:host ::ng-deep .bottom-bar .mat-mdc-paginator-range-label{margin:0 12px}: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)}:host ::ng-deep .bottom-bar.is-loading .mat-mdc-paginator-range-label{visibility:hidden}.paginator-range-loader{position:absolute;right:0;top:50%;transform:translateY(-50%);margin-right:145px;display:flex;align-items:center;justify-content:center;width:auto;padding:0 16px;height:24px}.paginator-range-loader--no-first-last{margin-right:80px}.paginator-range-loader .spinner{width:16px;height:16px;border:2.5px solid #e5e7eb;border-top-color:#a0a5ad}.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:wrap;min-height:var(--actual-header-height, 56px);height:auto;padding:8px 12px;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;flex:1 1 280px;min-width:0;flex-wrap:wrap;gap:8px}.group-sort-controls{display:flex;align-items:center;min-width:0;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;flex:0 1 auto;min-width:0;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;max-width:100%;overflow:hidden;text-overflow:ellipsis;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"] }]
|
|
1988
2251
|
}], propDecorators: { _isFullscreen: [{
|
|
1989
2252
|
type: HostBinding,
|
|
1990
2253
|
args: ['class.visual-fullscreen']
|
|
@@ -2004,6 +2267,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
|
|
|
2004
2267
|
type: Input
|
|
2005
2268
|
}], isLoading: [{
|
|
2006
2269
|
type: Input
|
|
2270
|
+
}], lazyError: [{
|
|
2271
|
+
type: Input
|
|
2007
2272
|
}], copyFeedback: [{
|
|
2008
2273
|
type: Input
|
|
2009
2274
|
}], clearSearchInput: [{
|
|
@@ -2030,6 +2295,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
|
|
|
2030
2295
|
type: Output
|
|
2031
2296
|
}], visualCopied: [{
|
|
2032
2297
|
type: Output
|
|
2298
|
+
}], lazyColumnsRequested: [{
|
|
2299
|
+
type: Output
|
|
2033
2300
|
}], paginator: [{
|
|
2034
2301
|
type: ViewChild,
|
|
2035
2302
|
args: [MatPaginator]
|
|
@@ -2039,6 +2306,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
|
|
|
2039
2306
|
}], tableBodyScrollRef: [{
|
|
2040
2307
|
type: ViewChild,
|
|
2041
2308
|
args: ['tableBodyScroll']
|
|
2309
|
+
}], tableShellRef: [{
|
|
2310
|
+
type: ViewChild,
|
|
2311
|
+
args: ['tableShell']
|
|
2042
2312
|
}], slicePanelRef: [{
|
|
2043
2313
|
type: ViewChild,
|
|
2044
2314
|
args: [SlicePanelComponent]
|