@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.
@@ -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, LazyFieldManager, SlicePanelComponent } from '@oneteme/jquery-organizer';
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`) et `index` (index global).
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
- localStorage.removeItem(this.key);
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
- return localStorage.getItem(this.key) !== null;
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 filename = (cfg.filename || 'export') + '.csv';
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.endsWith('.csv') ? filename : filename + '.csv';
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.replace(/"/g, '""') + '"';
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
- /** Gestionnaire du cycle de vie des colonnes lazy (fetch, annulation, statuts, données). */
436
- this._lazy = new LazyFieldManager();
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 = { ...JQT_I18N_DEFAULTS, ...(this._i18nRaw ?? {}) };
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.resolveCellValue(col, row, idx));
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) => row[GROUP_HEADER_MARKER] ? `__group_${row[ROW_GROUP_KEY]}` : row[ROW_INDEX_KEY] ?? _index;
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
- return row[ROW_GROUP_KEY] !== GROUP_KEY_LAZY_LOADING && row[ROW_GROUP_KEY] !== GROUP_KEY_TOO_MANY;
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['config'] || changes['data'] || changes['dataSource']);
536
- if (dataChanged) {
537
- this._lazy.cancelAll();
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.activeFields.filter(c => c.lazy).forEach(c => this.triggerLazyFetch(c));
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
- setTimeout(() => {
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._ngZone.runOutsideAngular(() => requestAnimationFrame(() => measure()));
700
+ this._scheduleHeaderMeasure(measure);
621
701
  };
622
- this._ngZone.runOutsideAngular(() => requestAnimationFrame(() => measure()));
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.refreshViewModel();
900
+ this._scheduleRender();
777
901
  this.paginator?.firstPage();
778
- this.searchChange.emit(this.searchQuery);
779
902
  }
780
- onGroupByChange(key) {
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
- this.groupByChange.emit(key);
788
- if (key) {
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
- }, 0));
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(() => setTimeout(() => this.scrollToGroupHeader(key), 0));
928
+ this._ngZone.runOutsideAngular(() => this._scheduleDeferred(() => this.scrollToGroupHeader(key)));
806
929
  });
807
930
  }
808
931
  getLazyColumnStatus(key) {
809
- return this._lazy.getStatus(key);
932
+ return this._lazyColumnStatus.get(key) ?? 'idle';
810
933
  }
811
934
  getLazyRenderType(rowValue) {
812
- return this._lazy.getRenderType(rowValue);
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
- retryLazyColumn(column) {
815
- this._lazy.retry(column, this._resolvedData, this._lazyCallbacks());
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
- triggerLazyFetch(column) {
818
- this._lazy.fetch(column, this._resolvedData, this._lazyCallbacks());
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
- _lazyCallbacks() {
821
- return {
822
- onRefresh: () => this.refreshViewModel(),
823
- onMarkForCheck: () => this._cdr.markForCheck(),
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(() => setTimeout(() => this.scrollToGroupHeader(key), 0));
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
- console.log('[SliceRestore] _recomputeShowSlicePanel: données arrivées', { dynamicKeys, filters });
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
- this._sliceConfigs = this._computeSliceConfigs();
933
- // Si une slice vient d'être cachée, réinitialise le filtre actif pour éviter
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._organizer.setGroupBy(saved.groupBy);
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._organizer.setGroupBy(groupKey);
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.resolvedConfig.view,
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._sliceConfigs = this._computeSliceConfigs();
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
- this.searchQuery = initialQuery ?? '';
1660
- this.onSearchChange();
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
- return [{ [GROUP_HEADER_MARKER]: true, [ROW_GROUP_KEY]: GROUP_KEY_LAZY_LOADING, [ROW_GROUP_COUNT]: rows.length, [ROW_GROUP_PAGE]: 0, [ROW_GROUP_PAGE_COUNT]: 1 }];
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
- let fastCompareFn = null;
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, i) => {
1827
- const idx = start + i;
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
- _buildSortComparator() {
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
- const { active, direction } = sort;
1843
- if (!active || !direction)
2091
+ return this._buildCachedSortComparator(rows, sort);
2092
+ }
2093
+ _buildCachedSortComparator(rows, sort) {
2094
+ if (!sort.active || !sort.direction)
1844
2095
  return null;
1845
- return (a, b) => {
1846
- const va = this.getLazyAwareValue(active, a);
1847
- const vb = this.getLazyAwareValue(active, b);
1848
- let cmp = 0;
1849
- if (typeof va === 'number' && typeof vb === 'number') {
1850
- cmp = va < vb ? -1 : va > vb ? 1 : 0;
1851
- }
1852
- else {
1853
- const sa = va == null ? '' : String(va).toLowerCase();
1854
- const sb = vb == null ? '' : String(vb).toLowerCase();
1855
- cmp = sa < sb ? -1 : sa > sb ? 1 : 0;
1856
- }
1857
- return direction === 'asc' ? cmp : -cmp;
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
- const status = this._lazyColumnStatus.get(col.key) ?? 'idle';
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 this._lazyColumnData.get(columnKey)?.get(row) ?? null;
2138
+ if (colDef?.lazy && this._lazyColumnStatus.get(columnKey) !== 'loaded') {
2139
+ return null;
1883
2140
  }
1884
2141
  if (colDef?.sortValue) {
1885
- return normalizeCellValue(colDef.sortValue(row, 0));
2142
+ return this._toSortInput(colDef.sortValue(row, 0));
1886
2143
  }
1887
2144
  if (colDef?.value) {
1888
- return normalizeCellValue(colDef.value(row, 0));
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) ?? 'idle';
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 === 'loaded') {
1900
- const rowMap = this._lazyColumnData.get(col.key);
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]