@oneteme/jquery-table 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/lib/component/organizer-button/organizer-button.component.mjs +49 -0
- package/esm2022/lib/component/table.component.mjs +186 -115
- package/esm2022/lib/jqt-i18n.token.mjs +2 -2
- package/esm2022/lib/jquery-table.model.mjs +1 -1
- package/esm2022/public-api.mjs +4 -3
- package/fesm2022/oneteme-jquery-table.mjs +266 -1113
- package/fesm2022/oneteme-jquery-table.mjs.map +1 -1
- package/lib/component/organizer-button/organizer-button.component.d.ts +12 -0
- package/lib/component/table.component.d.ts +10 -8
- package/lib/jquery-table.model.d.ts +2 -2
- package/package.json +3 -2
- package/public-api.d.ts +3 -2
- package/esm2022/lib/component/group-by.manager.mjs +0 -99
- package/esm2022/lib/component/lazy-column-manager.mjs +0 -99
- package/esm2022/lib/component/slice-panel/slice-panel.component.mjs +0 -477
- package/esm2022/lib/component/slice-panel/slice-panel.model.mjs +0 -2
- package/esm2022/lib/component/view/view.facade.mjs +0 -304
- package/lib/component/group-by.manager.d.ts +0 -44
- package/lib/component/lazy-column-manager.d.ts +0 -26
- package/lib/component/slice-panel/slice-panel.component.d.ts +0 -81
- package/lib/component/slice-panel/slice-panel.model.d.ts +0 -69
- package/lib/component/view/view.facade.d.ts +0 -95
|
@@ -1,28 +1,27 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, Directive, Input, EventEmitter,
|
|
2
|
+
import { InjectionToken, Directive, Input, EventEmitter, Component, ChangeDetectionStrategy, Output, inject, ChangeDetectorRef, ElementRef, NgZone, ViewChild, ContentChildren } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
4
|
import { CommonModule } from '@angular/common';
|
|
5
|
-
import * as i2
|
|
5
|
+
import * as i2 from '@angular/forms';
|
|
6
6
|
import { FormsModule } from '@angular/forms';
|
|
7
7
|
import { MatButtonModule } from '@angular/material/button';
|
|
8
|
-
import * as i4 from '@angular/material/divider';
|
|
9
8
|
import { MatDividerModule } from '@angular/material/divider';
|
|
10
|
-
import * as
|
|
9
|
+
import * as i6 from '@angular/material/icon';
|
|
11
10
|
import { MatIconModule } from '@angular/material/icon';
|
|
12
|
-
import * as i7 from '@angular/material/menu';
|
|
13
11
|
import { MatMenuModule } from '@angular/material/menu';
|
|
14
|
-
import * as
|
|
12
|
+
import * as i4 from '@angular/material/paginator';
|
|
15
13
|
import { MatPaginatorIntl, MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
|
|
16
|
-
import * as
|
|
14
|
+
import * as i8 from '@angular/material/select';
|
|
17
15
|
import { MatSelectModule } from '@angular/material/select';
|
|
18
|
-
import * as
|
|
16
|
+
import * as i5 from '@angular/material/sort';
|
|
19
17
|
import { MatSort, MatSortModule } from '@angular/material/sort';
|
|
20
18
|
import * as i3 from '@angular/material/table';
|
|
21
19
|
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
|
|
22
|
-
import { Subject,
|
|
23
|
-
import '@oneteme/jquery-
|
|
24
|
-
|
|
25
|
-
import * as
|
|
20
|
+
import { Subject, takeUntil } from 'rxjs';
|
|
21
|
+
import { OrganizerButtonComponent, OrganizerFacade, GroupByManager, LazyFieldManager, SlicePanelComponent } from '@oneteme/jquery-organizer';
|
|
22
|
+
export { OrganizerButtonComponent, SlicePanelComponent } from '@oneteme/jquery-organizer';
|
|
23
|
+
import * as i7 from '@angular/material/form-field';
|
|
24
|
+
import * as i9 from '@angular/material/core';
|
|
26
25
|
|
|
27
26
|
/**
|
|
28
27
|
* Raccourci pour déclarer une colonne de base.
|
|
@@ -65,7 +64,7 @@ const JQT_I18N_DEFAULTS = {
|
|
|
65
64
|
filterEmptyState: 'Aucune donnée',
|
|
66
65
|
exportButtonLabel: 'Exporter',
|
|
67
66
|
preferencesMenuLabel: 'Préférences',
|
|
68
|
-
preferencesEdit: '
|
|
67
|
+
preferencesEdit: 'Éditer',
|
|
69
68
|
preferencesSave: 'Sauvegarder',
|
|
70
69
|
preferencesClear: 'Réinitialiser',
|
|
71
70
|
preferencesSavedMessage: 'Configuration enregistrée. Vous pouvez la réinitialiser à tout moment via Préférences → Réinitialiser.',
|
|
@@ -111,476 +110,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
|
|
|
111
110
|
args: ['jqtCellDef']
|
|
112
111
|
}] } });
|
|
113
112
|
|
|
114
|
-
const EMPTY_CATEGORY_KEY = '__empty__';
|
|
115
|
-
const EMPTY_CATEGORY_LABEL = 'Vide';
|
|
116
|
-
class SlicePanelComponent {
|
|
117
|
-
constructor() {
|
|
118
|
-
this.sliceConfigs = [];
|
|
119
|
-
this.columns = [];
|
|
120
|
-
this.data = [];
|
|
121
|
-
this.lazyData = new Map();
|
|
122
|
-
this.lazyStatus = new Map();
|
|
123
|
-
this.showToggle = true;
|
|
124
|
-
this.showCounts = true;
|
|
125
|
-
this.collapsedByDefault = false;
|
|
126
|
-
this.alwaysShow = false;
|
|
127
|
-
this.filterChange = new EventEmitter();
|
|
128
|
-
this.activeKeysChange = new EventEmitter();
|
|
129
|
-
this.dynamicSliceKeysChange = new EventEmitter();
|
|
130
|
-
this.collapsedChange = new EventEmitter();
|
|
131
|
-
this._cdr = inject(ChangeDetectorRef);
|
|
132
|
-
this._i18nRaw = inject(JQT_I18N, { optional: true });
|
|
133
|
-
this.i18n = { ...JQT_I18N_DEFAULTS, ...(this._i18nRaw ?? {}) };
|
|
134
|
-
this.isCollapsed = false;
|
|
135
|
-
this.activeKeysBySlice = new Map();
|
|
136
|
-
this._expandedSlices = new Set();
|
|
137
|
-
this._dynamicSlices = [];
|
|
138
|
-
// ── Cache
|
|
139
|
-
this._cachedSlices = [];
|
|
140
|
-
this._countCache = new Map();
|
|
141
|
-
this.trackBySliceIndex = (index, _) => index;
|
|
142
|
-
this.trackByCategoryKey = (_, cat) => cat.key;
|
|
143
|
-
}
|
|
144
|
-
// ── Lifecycle
|
|
145
|
-
ngOnInit() {
|
|
146
|
-
this.isCollapsed = this.collapsedByDefault;
|
|
147
|
-
}
|
|
148
|
-
ngOnChanges(changes) {
|
|
149
|
-
if (changes['sliceConfigs'] || changes['data'] || changes['columns'] || changes['lazyData'] || changes['lazyStatus']) {
|
|
150
|
-
this._refreshDynamicSlices();
|
|
151
|
-
this._rebuildCache();
|
|
152
|
-
const shouldReemitFilter = !!(changes['data'] || changes['columns'] || changes['lazyData'] || changes['lazyStatus']);
|
|
153
|
-
const hasActiveFilters = [...this.activeKeysBySlice.values()].some(s => s.size > 0);
|
|
154
|
-
if (hasActiveFilters && shouldReemitFilter) {
|
|
155
|
-
this._emitFilter();
|
|
156
|
-
}
|
|
157
|
-
this._cdr.markForCheck();
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
// ── Computed
|
|
161
|
-
get showPanel() {
|
|
162
|
-
const hasCfg = (this.sliceConfigs || []).length > 0 || this._dynamicSlices.length > 0;
|
|
163
|
-
if (this.alwaysShow)
|
|
164
|
-
return hasCfg;
|
|
165
|
-
return hasCfg && (this.data?.length ?? 0) > 0;
|
|
166
|
-
}
|
|
167
|
-
get slices() {
|
|
168
|
-
return this._cachedSlices;
|
|
169
|
-
}
|
|
170
|
-
get staticSliceCount() {
|
|
171
|
-
return (this.sliceConfigs || []).length;
|
|
172
|
-
}
|
|
173
|
-
get staticSlicesForMenu() {
|
|
174
|
-
return (this.sliceConfigs || [])
|
|
175
|
-
.filter((s) => !!s.columnKey)
|
|
176
|
-
.map((s) => ({ key: s.columnKey, title: s.title || s.columnKey }));
|
|
177
|
-
}
|
|
178
|
-
get activeDynamicSliceColumns() {
|
|
179
|
-
return this._dynamicSlices
|
|
180
|
-
.map((d) => (this.columns || []).find((c) => c.key === d.key))
|
|
181
|
-
.filter((c) => !!c);
|
|
182
|
-
}
|
|
183
|
-
get availableColumnsForDynamicSlice() {
|
|
184
|
-
const dynamicKeys = new Set(this._dynamicSlices.map((d) => d.key));
|
|
185
|
-
const staticKeys = new Set((this.sliceConfigs || []).map((s) => s.columnKey).filter((k) => !!k));
|
|
186
|
-
const existingKeys = new Set([...dynamicKeys, ...staticKeys]);
|
|
187
|
-
const seen = new Set();
|
|
188
|
-
return (this.columns || []).filter((c) => {
|
|
189
|
-
if (existingKeys.has(c.key) || seen.has(c.key))
|
|
190
|
-
return false;
|
|
191
|
-
seen.add(c.key);
|
|
192
|
-
return true;
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
get activeSliceByLabel() {
|
|
196
|
-
const staticActiveCount = (this.sliceConfigs || []).length;
|
|
197
|
-
const total = staticActiveCount + this._dynamicSlices.length;
|
|
198
|
-
if (total === 0)
|
|
199
|
-
return 'Aucun';
|
|
200
|
-
if (total === 1) {
|
|
201
|
-
const first = (this.sliceConfigs || [])[0];
|
|
202
|
-
if (first)
|
|
203
|
-
return first.title || first.columnKey || '1';
|
|
204
|
-
return this.activeDynamicSliceColumns[0]?.header || '1';
|
|
205
|
-
}
|
|
206
|
-
return `${total} actifs`;
|
|
207
|
-
}
|
|
208
|
-
// ── UI state
|
|
209
|
-
isSliceCollapsed(sliceIndex) {
|
|
210
|
-
if (this._cachedSlices.length === 1) {
|
|
211
|
-
return this._expandedSlices.has(-1 - sliceIndex);
|
|
212
|
-
}
|
|
213
|
-
return !this._expandedSlices.has(sliceIndex);
|
|
214
|
-
}
|
|
215
|
-
toggleSliceCollapse(sliceIndex) {
|
|
216
|
-
if (this.slices.length === 1) {
|
|
217
|
-
const closedKey = -1 - sliceIndex;
|
|
218
|
-
if (this._expandedSlices.has(closedKey)) {
|
|
219
|
-
this._expandedSlices.delete(closedKey);
|
|
220
|
-
}
|
|
221
|
-
else {
|
|
222
|
-
this._expandedSlices.add(closedKey);
|
|
223
|
-
this._scrollSliceTitleIntoView(sliceIndex);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
if (this._expandedSlices.has(sliceIndex)) {
|
|
228
|
-
this._expandedSlices.delete(sliceIndex);
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
this._expandedSlices.clear();
|
|
232
|
-
this._expandedSlices.add(sliceIndex);
|
|
233
|
-
this._scrollSliceTitleIntoView(sliceIndex);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
this._cdr.markForCheck();
|
|
237
|
-
}
|
|
238
|
-
togglePanel() {
|
|
239
|
-
this.isCollapsed = !this.isCollapsed;
|
|
240
|
-
this.collapsedChange.emit(this.isCollapsed);
|
|
241
|
-
this._cdr.markForCheck();
|
|
242
|
-
}
|
|
243
|
-
isSliceDynamic(sliceIndex) {
|
|
244
|
-
return sliceIndex >= this.staticSliceCount;
|
|
245
|
-
}
|
|
246
|
-
isSliceLoading(sliceIndex) {
|
|
247
|
-
const staticSlice = (this.sliceConfigs || [])[sliceIndex];
|
|
248
|
-
if (staticSlice?.columnKey) {
|
|
249
|
-
const colDef = (this.columns || []).find(c => c.key === staticSlice.columnKey);
|
|
250
|
-
if (colDef?.lazy) {
|
|
251
|
-
const status = this.lazyStatus.get(staticSlice.columnKey) ?? 'idle';
|
|
252
|
-
return status === 'loading' || status === 'idle';
|
|
253
|
-
}
|
|
254
|
-
return false;
|
|
255
|
-
}
|
|
256
|
-
const key = this._getDynamicSliceColumnKey(sliceIndex);
|
|
257
|
-
if (!key)
|
|
258
|
-
return false;
|
|
259
|
-
const colDef = (this.columns || []).find(c => c.key === key);
|
|
260
|
-
if (!colDef?.lazy)
|
|
261
|
-
return false;
|
|
262
|
-
const status = this.lazyStatus.get(key) ?? 'idle';
|
|
263
|
-
return status === 'loading' || status === 'idle';
|
|
264
|
-
}
|
|
265
|
-
isStaticSliceVisible(_columnKey) {
|
|
266
|
-
return true;
|
|
267
|
-
}
|
|
268
|
-
toggleStaticSlice(_columnKey) {
|
|
269
|
-
// Délégué à table.component via _staticSliceHiddenKeys — ne rien faire ici.
|
|
270
|
-
}
|
|
271
|
-
// ── Selection
|
|
272
|
-
getSliceIcon(slice) {
|
|
273
|
-
if (slice.icon)
|
|
274
|
-
return slice.icon;
|
|
275
|
-
if (slice.columnKey) {
|
|
276
|
-
return (this.columns || []).find(c => c.key === slice.columnKey)?.icon;
|
|
277
|
-
}
|
|
278
|
-
return undefined;
|
|
279
|
-
}
|
|
280
|
-
isSliceCategoryActive(sliceIndex, categoryKey) {
|
|
281
|
-
return this.activeKeysBySlice.get(sliceIndex)?.has(categoryKey) ?? false;
|
|
282
|
-
}
|
|
283
|
-
isSliceAllActive(sliceIndex) {
|
|
284
|
-
return !this.activeKeysBySlice.has(sliceIndex) || this.activeKeysBySlice.get(sliceIndex).size === 0;
|
|
285
|
-
}
|
|
286
|
-
sliceCategoryCount(sliceIndex, categoryKey) {
|
|
287
|
-
const cacheKey = `${sliceIndex}:${categoryKey}`;
|
|
288
|
-
if (this._countCache.has(cacheKey)) {
|
|
289
|
-
return this._countCache.get(cacheKey);
|
|
290
|
-
}
|
|
291
|
-
const slice = this._cachedSlices[sliceIndex];
|
|
292
|
-
if (!slice)
|
|
293
|
-
return 0;
|
|
294
|
-
const category = (slice.categories || []).find((c) => c.key === categoryKey);
|
|
295
|
-
const count = category ? (this.data || []).filter((row) => category.filter(row)).length : 0;
|
|
296
|
-
this._countCache.set(cacheKey, count);
|
|
297
|
-
return count;
|
|
298
|
-
}
|
|
299
|
-
isSliceEmpty(sliceIndex) {
|
|
300
|
-
const slice = this._cachedSlices[sliceIndex];
|
|
301
|
-
if (!slice)
|
|
302
|
-
return true;
|
|
303
|
-
return (slice.categories || []).every(c => this.sliceCategoryCount(sliceIndex, c.key) === 0);
|
|
304
|
-
}
|
|
305
|
-
onSliceCategorySelected(sliceIndex, categoryKey) {
|
|
306
|
-
const slice = this.slices[sliceIndex];
|
|
307
|
-
if (!slice)
|
|
308
|
-
return;
|
|
309
|
-
if (!this.activeKeysBySlice.has(sliceIndex)) {
|
|
310
|
-
this.activeKeysBySlice.set(sliceIndex, new Set());
|
|
311
|
-
}
|
|
312
|
-
const activeKeys = this.activeKeysBySlice.get(sliceIndex);
|
|
313
|
-
if (slice.multiSelect === false) {
|
|
314
|
-
if (activeKeys.has(categoryKey)) {
|
|
315
|
-
activeKeys.clear();
|
|
316
|
-
}
|
|
317
|
-
else {
|
|
318
|
-
activeKeys.clear();
|
|
319
|
-
activeKeys.add(categoryKey);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
else {
|
|
323
|
-
if (activeKeys.has(categoryKey)) {
|
|
324
|
-
activeKeys.delete(categoryKey);
|
|
325
|
-
}
|
|
326
|
-
else {
|
|
327
|
-
activeKeys.add(categoryKey);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
this._emitFilter();
|
|
331
|
-
this._cdr.markForCheck();
|
|
332
|
-
}
|
|
333
|
-
onSliceAllSelected(sliceIndex) {
|
|
334
|
-
if (this.activeKeysBySlice.has(sliceIndex)) {
|
|
335
|
-
this.activeKeysBySlice.get(sliceIndex).clear();
|
|
336
|
-
}
|
|
337
|
-
this._emitFilter();
|
|
338
|
-
this._cdr.markForCheck();
|
|
339
|
-
}
|
|
340
|
-
/** Retourne l'état des filtres actifs sous forme sérialisable (sliceIndex → tableau de clés).
|
|
341
|
-
* Inclut les slices sans sélection (tableau vide) pour distinguer "aucune tranche sélectionnée"
|
|
342
|
-
* (= filtre ouvert, rien coché) de "slice absente". */
|
|
343
|
-
getActiveFilters() {
|
|
344
|
-
const result = {};
|
|
345
|
-
this._cachedSlices.forEach((_, idx) => {
|
|
346
|
-
const keys = this.activeKeysBySlice.get(idx);
|
|
347
|
-
result[idx] = keys ? [...keys] : [];
|
|
348
|
-
});
|
|
349
|
-
return result;
|
|
350
|
-
}
|
|
351
|
-
/** Restaure les filtres actifs depuis un snapshot sérialisé et réémet le filtre. */
|
|
352
|
-
restoreFilters(filters) {
|
|
353
|
-
this.activeKeysBySlice = new Map(Object.entries(filters).map(([idx, keys]) => [Number(idx), new Set(keys)]));
|
|
354
|
-
this._emitFilter();
|
|
355
|
-
this._cdr.markForCheck();
|
|
356
|
-
}
|
|
357
|
-
// ── Dynamic slice management
|
|
358
|
-
addDynamicSlice(column) {
|
|
359
|
-
const distinctValues = this._computeDistinctValues(column.key);
|
|
360
|
-
const newSlice = {
|
|
361
|
-
title: column.header,
|
|
362
|
-
columnKey: column.key,
|
|
363
|
-
categories: distinctValues.map((v) => this._buildCategoryFromValue(column.key, v)),
|
|
364
|
-
};
|
|
365
|
-
this._dynamicSlices = [...this._dynamicSlices, { key: column.key, slice: newSlice }];
|
|
366
|
-
this._rebuildCache();
|
|
367
|
-
this.dynamicSliceKeysChange.emit(this._dynamicSlices.map(d => d.key));
|
|
368
|
-
this._emitFilter();
|
|
369
|
-
this._cdr.markForCheck();
|
|
370
|
-
}
|
|
371
|
-
removeDynamicSlice(sliceIndex) {
|
|
372
|
-
const dynamicIndex = sliceIndex - this.staticSliceCount;
|
|
373
|
-
if (dynamicIndex < 0)
|
|
374
|
-
return;
|
|
375
|
-
const newActiveKeys = new Map();
|
|
376
|
-
this.activeKeysBySlice.forEach((keys, idx) => {
|
|
377
|
-
if (idx < sliceIndex)
|
|
378
|
-
newActiveKeys.set(idx, keys);
|
|
379
|
-
else if (idx > sliceIndex)
|
|
380
|
-
newActiveKeys.set(idx - 1, keys);
|
|
381
|
-
});
|
|
382
|
-
this.activeKeysBySlice = newActiveKeys;
|
|
383
|
-
this._dynamicSlices = this._dynamicSlices.filter((_, i) => i !== dynamicIndex);
|
|
384
|
-
this._rebuildCache();
|
|
385
|
-
this.dynamicSliceKeysChange.emit(this._dynamicSlices.map(d => d.key));
|
|
386
|
-
this._emitFilter();
|
|
387
|
-
this._cdr.markForCheck();
|
|
388
|
-
}
|
|
389
|
-
removeDynamicSliceByKey(key) {
|
|
390
|
-
const dynamicIndex = this._dynamicSlices.findIndex((d) => d.key === key);
|
|
391
|
-
if (dynamicIndex < 0)
|
|
392
|
-
return;
|
|
393
|
-
const sliceIndex = this.staticSliceCount + dynamicIndex;
|
|
394
|
-
this.removeDynamicSlice(sliceIndex);
|
|
395
|
-
}
|
|
396
|
-
// ── Private helpers
|
|
397
|
-
_emitFilter() {
|
|
398
|
-
const slices = this.slices;
|
|
399
|
-
const activeKeysBySlice = this.activeKeysBySlice;
|
|
400
|
-
this.activeKeysChange.emit(slices.map((_, i) => {
|
|
401
|
-
const keys = activeKeysBySlice.get(i);
|
|
402
|
-
return keys ? [...keys] : [];
|
|
403
|
-
}));
|
|
404
|
-
const pred = (row) => slices.every((slice, sliceIndex) => {
|
|
405
|
-
const activeKeys = activeKeysBySlice.get(sliceIndex);
|
|
406
|
-
if (!activeKeys || activeKeys.size === 0)
|
|
407
|
-
return true;
|
|
408
|
-
return [...activeKeys].some((key) => {
|
|
409
|
-
const category = (slice.categories || []).find((c) => c.key === key);
|
|
410
|
-
if (!category)
|
|
411
|
-
return false;
|
|
412
|
-
// Si pas de filter défini (usage chart), on ne filtre pas en mémoire
|
|
413
|
-
if (!category?.filter)
|
|
414
|
-
return true;
|
|
415
|
-
return category.filter(row);
|
|
416
|
-
});
|
|
417
|
-
});
|
|
418
|
-
this.filterChange.emit(pred);
|
|
419
|
-
}
|
|
420
|
-
_getDynamicSliceColumnKey(sliceIndex) {
|
|
421
|
-
const dynamicIndex = sliceIndex - this.staticSliceCount;
|
|
422
|
-
return this._dynamicSlices[dynamicIndex]?.key ?? null;
|
|
423
|
-
}
|
|
424
|
-
_getSliceIdentity(slice, fallbackIndex) {
|
|
425
|
-
if (slice.columnKey) {
|
|
426
|
-
return `column:${slice.columnKey}`;
|
|
427
|
-
}
|
|
428
|
-
if (slice.title) {
|
|
429
|
-
return `title:${slice.title}`;
|
|
430
|
-
}
|
|
431
|
-
const categoryKeys = (slice.categories || []).map((category) => category.key).join('|');
|
|
432
|
-
return `index:${fallbackIndex}|categories:${categoryKeys}`;
|
|
433
|
-
}
|
|
434
|
-
_materializeSlice(slice) {
|
|
435
|
-
if (!slice.columnKey || (slice.categories && slice.categories.length > 0)) {
|
|
436
|
-
return slice;
|
|
437
|
-
}
|
|
438
|
-
const col = (this.columns || []).find(c => c.key === slice.columnKey);
|
|
439
|
-
const isLazy = !!col?.lazy;
|
|
440
|
-
if (isLazy && this.lazyStatus.get(slice.columnKey) !== 'loaded') {
|
|
441
|
-
return { ...slice, categories: [] };
|
|
442
|
-
}
|
|
443
|
-
if (slice.bucket) {
|
|
444
|
-
const distinctValues = [...new Set((this.data || []).map(slice.bucket))].sort();
|
|
445
|
-
return {
|
|
446
|
-
...slice,
|
|
447
|
-
categories: distinctValues.map((v) => ({
|
|
448
|
-
key: v,
|
|
449
|
-
label: v,
|
|
450
|
-
filter: (row) => slice.bucket(row) === v,
|
|
451
|
-
})),
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
const distinctValues = this._computeDistinctValues(slice.columnKey);
|
|
455
|
-
return {
|
|
456
|
-
...slice,
|
|
457
|
-
categories: distinctValues.map((v) => this._buildCategoryFromValue(slice.columnKey, v)),
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
_buildCategoryFromValue(columnKey, v) {
|
|
461
|
-
if (v === EMPTY_CATEGORY_KEY) {
|
|
462
|
-
return {
|
|
463
|
-
key: EMPTY_CATEGORY_KEY,
|
|
464
|
-
label: EMPTY_CATEGORY_LABEL,
|
|
465
|
-
filter: (row) => {
|
|
466
|
-
const val = this._getLazyAwareValue(columnKey, row);
|
|
467
|
-
return val == null || val === '';
|
|
468
|
-
},
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
return {
|
|
472
|
-
key: v,
|
|
473
|
-
label: v,
|
|
474
|
-
filter: (row) => String(this._getLazyAwareValue(columnKey, row) ?? '') === v,
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
_computeDistinctValues(columnKey) {
|
|
478
|
-
const rawValues = (this.data || []).map((row) => {
|
|
479
|
-
const val = this._getLazyAwareValue(columnKey, row);
|
|
480
|
-
return val != null && val !== '' ? String(val) : null;
|
|
481
|
-
});
|
|
482
|
-
const distinct = [...new Set(rawValues.filter((v) => v !== null))].sort();
|
|
483
|
-
if (rawValues.some(v => v === null)) {
|
|
484
|
-
distinct.push(EMPTY_CATEGORY_KEY);
|
|
485
|
-
}
|
|
486
|
-
return distinct;
|
|
487
|
-
}
|
|
488
|
-
_getLazyAwareValue(columnKey, row) {
|
|
489
|
-
const colDef = (this.columns || []).find(c => c.key === columnKey);
|
|
490
|
-
if (colDef?.lazy) {
|
|
491
|
-
return this.lazyData.get(columnKey)?.get(row) ?? null;
|
|
492
|
-
}
|
|
493
|
-
if (colDef?.value) {
|
|
494
|
-
return colDef.value(row, 0);
|
|
495
|
-
}
|
|
496
|
-
return row[columnKey];
|
|
497
|
-
}
|
|
498
|
-
_rebuildCache() {
|
|
499
|
-
const previousSlices = this._cachedSlices;
|
|
500
|
-
const previousActiveKeys = this.activeKeysBySlice;
|
|
501
|
-
const allSlices = [...(this.sliceConfigs || []), ...this._dynamicSlices.map((d) => d.slice)];
|
|
502
|
-
this._cachedSlices = allSlices.map((slice) => this._materializeSlice(slice));
|
|
503
|
-
const previousKeysByIdentity = new Map();
|
|
504
|
-
previousSlices.forEach((slice, index) => {
|
|
505
|
-
const activeKeys = previousActiveKeys.get(index);
|
|
506
|
-
if (!activeKeys || activeKeys.size === 0)
|
|
507
|
-
return;
|
|
508
|
-
previousKeysByIdentity.set(this._getSliceIdentity(slice, index), new Set(activeKeys));
|
|
509
|
-
});
|
|
510
|
-
const nextActiveKeys = new Map();
|
|
511
|
-
this._cachedSlices.forEach((slice, index) => {
|
|
512
|
-
const previousKeys = previousKeysByIdentity.get(this._getSliceIdentity(slice, index));
|
|
513
|
-
if (!previousKeys || previousKeys.size === 0)
|
|
514
|
-
return;
|
|
515
|
-
const availableKeys = new Set((slice.categories || []).map((category) => category.key));
|
|
516
|
-
const matchingKeys = [...previousKeys].filter((key) => availableKeys.has(key));
|
|
517
|
-
if (matchingKeys.length > 0) {
|
|
518
|
-
nextActiveKeys.set(index, new Set(matchingKeys));
|
|
519
|
-
}
|
|
520
|
-
});
|
|
521
|
-
this.activeKeysBySlice = nextActiveKeys;
|
|
522
|
-
this._countCache.clear();
|
|
523
|
-
}
|
|
524
|
-
_refreshDynamicSlices() {
|
|
525
|
-
this._dynamicSlices = this._dynamicSlices.map(({ key, slice }) => {
|
|
526
|
-
const distinctValues = this._computeDistinctValues(key);
|
|
527
|
-
return {
|
|
528
|
-
key,
|
|
529
|
-
slice: {
|
|
530
|
-
...slice,
|
|
531
|
-
categories: distinctValues.map((v) => this._buildCategoryFromValue(key, v)),
|
|
532
|
-
},
|
|
533
|
-
};
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
_scrollSliceTitleIntoView(sliceIndex) {
|
|
537
|
-
setTimeout(() => {
|
|
538
|
-
const container = this.slicePanelBodyRef?.nativeElement;
|
|
539
|
-
if (!container)
|
|
540
|
-
return;
|
|
541
|
-
const row = container.querySelector(`[data-slice-index="${sliceIndex}"]`);
|
|
542
|
-
if (!row)
|
|
543
|
-
return;
|
|
544
|
-
row.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
545
|
-
}, 0);
|
|
546
|
-
}
|
|
547
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SlicePanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
548
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: SlicePanelComponent, isStandalone: true, selector: "slice-panel", inputs: { sliceConfigs: "sliceConfigs", columns: "columns", data: "data", lazyData: "lazyData", lazyStatus: "lazyStatus", showToggle: "showToggle", showCounts: "showCounts", collapsedByDefault: "collapsedByDefault", alwaysShow: "alwaysShow" }, outputs: { filterChange: "filterChange", activeKeysChange: "activeKeysChange", dynamicSliceKeysChange: "dynamicSliceKeysChange", collapsedChange: "collapsedChange" }, viewQueries: [{ propertyName: "slicePanelBodyRef", first: true, predicate: ["slicePanelBody"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"slice-panel-wrapper\" [class.collapsed]=\"isCollapsed\">\n <aside class=\"slice-panel\" [class.collapsed]=\"isCollapsed\">\n <div class=\"slice-panel-header\">\n <span *ngIf=\"!isCollapsed\" class=\"slice-panel-title\">\n {{ i18n.filterPanelTitle(slices.length) }}\n </span>\n <button\n *ngIf=\"showToggle\"\n type=\"button\"\n class=\"slice-toggle\"\n [attr.aria-label]=\"isCollapsed ? i18n.expandFilterTitle : i18n.collapseFilterTitle\"\n (click)=\"togglePanel()\"\n >\n <mat-icon class=\"slice-toggle-mat-icon\">{{ isCollapsed ? 'filter_list' : 'filter_list_off' }}</mat-icon>\n </button>\n </div>\n\n <div class=\"slice-panel-body\" *ngIf=\"!isCollapsed\" #slicePanelBody>\n <ng-container *ngFor=\"let slice of slices; let si = index; trackBy: trackBySliceIndex\">\n <div class=\"slice-title-row\" [attr.data-slice-index]=\"si\" (click)=\"toggleSliceCollapse(si)\">\n <span class=\"slice-title-chevron\" [class.expanded]=\"!isSliceCollapsed(si)\">\u203A</span>\n <mat-icon *ngIf=\"getSliceIcon(slice)\" class=\"slice-title-icon\">{{ getSliceIcon(slice) }}</mat-icon>\n <p class=\"slice-title\">{{ slice.title || i18n.filterBlockDefaultTitle }}</p>\n <button\n *ngIf=\"isSliceDynamic(si)\"\n type=\"button\"\n class=\"slice-remove-btn\"\n (click)=\"$event.stopPropagation(); removeDynamicSlice(si)\"\n >\u00D7</button>\n </div>\n\n <div class=\"slice-section-items\" *ngIf=\"!isSliceCollapsed(si)\">\n <ng-container *ngIf=\"isSliceLoading(si); else sliceContent\">\n <div class=\"slice-lazy-loading\">\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n </div>\n </ng-container>\n <ng-template #sliceContent>\n <ng-container *ngFor=\"let category of slice.categories; trackBy: trackByCategoryKey\">\n <button\n *ngIf=\"sliceCategoryCount(si, category.key) > 0\"\n class=\"slice-item\"\n type=\"button\"\n [class.active]=\"isSliceCategoryActive(si, category.key)\"\n [attr.data-category-key]=\"category.key\"\n [title]=\"category.label\"\n (click)=\"onSliceCategorySelected(si, category.key)\"\n >\n <span>{{ category.label }}</span>\n <span *ngIf=\"showCounts\">{{ sliceCategoryCount(si, category.key) }}</span>\n </button>\n </ng-container>\n <div *ngIf=\"isSliceEmpty(si)\" class=\"slice-empty-state\">{{ i18n.filterEmptyState }}</div>\n </ng-template>\n </div>\n\n <hr *ngIf=\"si < slices.length - 1\" class=\"slice-divider\" />\n </ng-container>\n </div>\n </aside>\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;box-sizing:border-box}.slice-panel-wrapper{height:100%;max-height:100%;overflow-y:auto;box-sizing:border-box}.slice-panel{margin-top:0;border:1px solid var(--jqt-border-color);border-radius:10px;background:var(--jqt-surface-color);display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden;box-sizing:border-box}.slice-panel-header{flex-shrink:0;display:flex;justify-content:flex-end;align-items:center;height:34px;padding:0 8px;margin-bottom:8px}.slice-panel-header:has(.slice-panel-title){justify-content:space-between;padding-left:12px}.slice-panel-body{flex:1 1 0;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:4px;padding:4px 10px 10px;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-panel-body::-webkit-scrollbar{width:2px}.slice-panel-body::-webkit-scrollbar-thumb{background:var(--jqt-primary-color);border-radius:4px}.slice-panel-title{margin:0;padding-top:2px;font-size:11px;font-weight:700;line-height:1;text-transform:uppercase;letter-spacing:.08em;color:var(--jqt-text-color);-webkit-user-select:none;user-select:none;align-self:center;display:inline-flex;align-items:center;gap:5px}.slice-panel-title .slice-panel-title-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0}.slice-section-items{display:flex;flex-direction:column;gap:2px;flex:0 0 auto;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-section-items::-webkit-scrollbar{width:2px}.slice-section-items .slice-empty-state{padding:8px 10px;font-size:12px;color:var(--jqt-text-muted, #9ca3af);font-style:italic;text-align:center}.slice-section-items::-webkit-scrollbar-thumb{background:var(--jqt-border-color);border-radius:4px}.slice-toggle{border:0;background:var(--jqt-surface-color);color:var(--jqt-text-color);border-radius:6px;width:22px;height:22px;padding:0;font-size:14px;text-align:center;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;line-height:1}.slice-toggle-mat-icon{font-size:16px;width:16px;height:16px;line-height:1}.slice-panel.collapsed{border-color:transparent;background:transparent;overflow:visible}.slice-panel.collapsed .slice-panel-header{height:34px;padding:0;justify-content:center;align-items:center}.slice-panel.collapsed .slice-toggle{width:34px;height:34px;border:1px solid var(--jqt-border-color);background:var(--jqt-surface-color);border-radius:8px}.slice-panel.collapsed .slice-toggle:hover{border-color:var(--jqt-text-muted)}.slice-panel.collapsed .slice-toggle .slice-toggle-mat-icon{font-size:18px;width:18px;height:18px}.slice-title-row{display:flex;align-items:center;justify-content:space-between;gap:6px;margin:4px 0 6px;cursor:pointer;-webkit-user-select:none;user-select:none}.slice-title-row:hover .slice-title{color:var(--jqt-text-color)}.slice-title-row:hover .slice-title-chevron{color:var(--jqt-text-secondary)}.slice-title-row:first-of-type .slice-title{margin-top:0}.slice-title-chevron{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;font-size:16px;line-height:1;color:var(--jqt-text-muted);transition:transform .15s ease;transform:rotate(0);position:relative;top:-1px}.slice-title-chevron.expanded{transform:rotate(90deg)}.slice-title{flex:1 1 auto;margin:0;font-size:14px;font-weight:600;color:var(--jqt-title-color)}.slice-title-icon{flex-shrink:0;font-size:14px!important;width:14px!important;height:14px!important;color:var(--jqt-title-color)}.slice-remove-btn{background:none;border:none;cursor:pointer;color:var(--jqt-border-color);font-size:12px;padding:0 2px;line-height:1;flex-shrink:0;opacity:.5;transition:opacity .15s,color .15s}.slice-remove-btn:hover{color:var(--jqt-text-secondary);opacity:1}.slice-title-row:hover .slice-remove-btn{opacity:.8}.slice-item{display:flex;justify-content:space-between;align-items:center;text-align:left;width:100%;padding:5px 10px 5px 16px;background:transparent;border:0;cursor:pointer;border-radius:8px;color:var(--jqt-text-color);box-sizing:border-box;min-width:0;gap:8px}.slice-item.active{background:var(--jqt-row-hover-bg);font-weight:600}.slice-item.slice-multi{padding-left:6px}.slice-item span:first-child{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.slice-item span:last-child{flex:0 0 auto;min-width:1.2em;text-align:right}.slice-check-box{flex:0 0 16px;width:16px;height:16px;border:1.5px solid var(--jqt-text-muted);border-radius:4px;background:var(--jqt-surface-color);display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;transition:background .15s,border-color .15s}.slice-check-box.checked{background:var(--jqt-text-secondary);border-color:var(--jqt-text-secondary)}.slice-check-box.checked:after{content:\"\";display:block;width:9px;height:5px;border-left:2px solid #fff;border-bottom:2px solid #fff;transform:rotate(-45deg) translateY(-1px)}.slice-divider{border:none;border-top:1px solid var(--jqt-border-color);margin:6px 0}.lazy-skeleton{display:block;height:12px;width:70%;border-radius:4px;background:linear-gradient(90deg,var(--jqt-border-color) 25%,var(--jqt-surface-alt-color) 50%,var(--jqt-border-color) 75%);background-size:200% 100%;animation:lazy-shimmer 1.4s ease-in-out infinite}.slice-lazy-loading{display:flex;flex-direction:column;gap:8px;padding:8px 10px}.slice-lazy-loading .lazy-skeleton{width:80%}.slice-lazy-loading .lazy-skeleton:nth-child(2){width:60%}.slice-lazy-loading .lazy-skeleton:nth-child(3){width:45%}@keyframes lazy-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
549
|
-
}
|
|
550
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SlicePanelComponent, decorators: [{
|
|
551
|
-
type: Component,
|
|
552
|
-
args: [{ standalone: true, selector: 'slice-panel', imports: [CommonModule, MatIconModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"slice-panel-wrapper\" [class.collapsed]=\"isCollapsed\">\n <aside class=\"slice-panel\" [class.collapsed]=\"isCollapsed\">\n <div class=\"slice-panel-header\">\n <span *ngIf=\"!isCollapsed\" class=\"slice-panel-title\">\n {{ i18n.filterPanelTitle(slices.length) }}\n </span>\n <button\n *ngIf=\"showToggle\"\n type=\"button\"\n class=\"slice-toggle\"\n [attr.aria-label]=\"isCollapsed ? i18n.expandFilterTitle : i18n.collapseFilterTitle\"\n (click)=\"togglePanel()\"\n >\n <mat-icon class=\"slice-toggle-mat-icon\">{{ isCollapsed ? 'filter_list' : 'filter_list_off' }}</mat-icon>\n </button>\n </div>\n\n <div class=\"slice-panel-body\" *ngIf=\"!isCollapsed\" #slicePanelBody>\n <ng-container *ngFor=\"let slice of slices; let si = index; trackBy: trackBySliceIndex\">\n <div class=\"slice-title-row\" [attr.data-slice-index]=\"si\" (click)=\"toggleSliceCollapse(si)\">\n <span class=\"slice-title-chevron\" [class.expanded]=\"!isSliceCollapsed(si)\">\u203A</span>\n <mat-icon *ngIf=\"getSliceIcon(slice)\" class=\"slice-title-icon\">{{ getSliceIcon(slice) }}</mat-icon>\n <p class=\"slice-title\">{{ slice.title || i18n.filterBlockDefaultTitle }}</p>\n <button\n *ngIf=\"isSliceDynamic(si)\"\n type=\"button\"\n class=\"slice-remove-btn\"\n (click)=\"$event.stopPropagation(); removeDynamicSlice(si)\"\n >\u00D7</button>\n </div>\n\n <div class=\"slice-section-items\" *ngIf=\"!isSliceCollapsed(si)\">\n <ng-container *ngIf=\"isSliceLoading(si); else sliceContent\">\n <div class=\"slice-lazy-loading\">\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n </div>\n </ng-container>\n <ng-template #sliceContent>\n <ng-container *ngFor=\"let category of slice.categories; trackBy: trackByCategoryKey\">\n <button\n *ngIf=\"sliceCategoryCount(si, category.key) > 0\"\n class=\"slice-item\"\n type=\"button\"\n [class.active]=\"isSliceCategoryActive(si, category.key)\"\n [attr.data-category-key]=\"category.key\"\n [title]=\"category.label\"\n (click)=\"onSliceCategorySelected(si, category.key)\"\n >\n <span>{{ category.label }}</span>\n <span *ngIf=\"showCounts\">{{ sliceCategoryCount(si, category.key) }}</span>\n </button>\n </ng-container>\n <div *ngIf=\"isSliceEmpty(si)\" class=\"slice-empty-state\">{{ i18n.filterEmptyState }}</div>\n </ng-template>\n </div>\n\n <hr *ngIf=\"si < slices.length - 1\" class=\"slice-divider\" />\n </ng-container>\n </div>\n </aside>\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;box-sizing:border-box}.slice-panel-wrapper{height:100%;max-height:100%;overflow-y:auto;box-sizing:border-box}.slice-panel{margin-top:0;border:1px solid var(--jqt-border-color);border-radius:10px;background:var(--jqt-surface-color);display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden;box-sizing:border-box}.slice-panel-header{flex-shrink:0;display:flex;justify-content:flex-end;align-items:center;height:34px;padding:0 8px;margin-bottom:8px}.slice-panel-header:has(.slice-panel-title){justify-content:space-between;padding-left:12px}.slice-panel-body{flex:1 1 0;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:4px;padding:4px 10px 10px;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-panel-body::-webkit-scrollbar{width:2px}.slice-panel-body::-webkit-scrollbar-thumb{background:var(--jqt-primary-color);border-radius:4px}.slice-panel-title{margin:0;padding-top:2px;font-size:11px;font-weight:700;line-height:1;text-transform:uppercase;letter-spacing:.08em;color:var(--jqt-text-color);-webkit-user-select:none;user-select:none;align-self:center;display:inline-flex;align-items:center;gap:5px}.slice-panel-title .slice-panel-title-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0}.slice-section-items{display:flex;flex-direction:column;gap:2px;flex:0 0 auto;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-section-items::-webkit-scrollbar{width:2px}.slice-section-items .slice-empty-state{padding:8px 10px;font-size:12px;color:var(--jqt-text-muted, #9ca3af);font-style:italic;text-align:center}.slice-section-items::-webkit-scrollbar-thumb{background:var(--jqt-border-color);border-radius:4px}.slice-toggle{border:0;background:var(--jqt-surface-color);color:var(--jqt-text-color);border-radius:6px;width:22px;height:22px;padding:0;font-size:14px;text-align:center;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;line-height:1}.slice-toggle-mat-icon{font-size:16px;width:16px;height:16px;line-height:1}.slice-panel.collapsed{border-color:transparent;background:transparent;overflow:visible}.slice-panel.collapsed .slice-panel-header{height:34px;padding:0;justify-content:center;align-items:center}.slice-panel.collapsed .slice-toggle{width:34px;height:34px;border:1px solid var(--jqt-border-color);background:var(--jqt-surface-color);border-radius:8px}.slice-panel.collapsed .slice-toggle:hover{border-color:var(--jqt-text-muted)}.slice-panel.collapsed .slice-toggle .slice-toggle-mat-icon{font-size:18px;width:18px;height:18px}.slice-title-row{display:flex;align-items:center;justify-content:space-between;gap:6px;margin:4px 0 6px;cursor:pointer;-webkit-user-select:none;user-select:none}.slice-title-row:hover .slice-title{color:var(--jqt-text-color)}.slice-title-row:hover .slice-title-chevron{color:var(--jqt-text-secondary)}.slice-title-row:first-of-type .slice-title{margin-top:0}.slice-title-chevron{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;font-size:16px;line-height:1;color:var(--jqt-text-muted);transition:transform .15s ease;transform:rotate(0);position:relative;top:-1px}.slice-title-chevron.expanded{transform:rotate(90deg)}.slice-title{flex:1 1 auto;margin:0;font-size:14px;font-weight:600;color:var(--jqt-title-color)}.slice-title-icon{flex-shrink:0;font-size:14px!important;width:14px!important;height:14px!important;color:var(--jqt-title-color)}.slice-remove-btn{background:none;border:none;cursor:pointer;color:var(--jqt-border-color);font-size:12px;padding:0 2px;line-height:1;flex-shrink:0;opacity:.5;transition:opacity .15s,color .15s}.slice-remove-btn:hover{color:var(--jqt-text-secondary);opacity:1}.slice-title-row:hover .slice-remove-btn{opacity:.8}.slice-item{display:flex;justify-content:space-between;align-items:center;text-align:left;width:100%;padding:5px 10px 5px 16px;background:transparent;border:0;cursor:pointer;border-radius:8px;color:var(--jqt-text-color);box-sizing:border-box;min-width:0;gap:8px}.slice-item.active{background:var(--jqt-row-hover-bg);font-weight:600}.slice-item.slice-multi{padding-left:6px}.slice-item span:first-child{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.slice-item span:last-child{flex:0 0 auto;min-width:1.2em;text-align:right}.slice-check-box{flex:0 0 16px;width:16px;height:16px;border:1.5px solid var(--jqt-text-muted);border-radius:4px;background:var(--jqt-surface-color);display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;transition:background .15s,border-color .15s}.slice-check-box.checked{background:var(--jqt-text-secondary);border-color:var(--jqt-text-secondary)}.slice-check-box.checked:after{content:\"\";display:block;width:9px;height:5px;border-left:2px solid #fff;border-bottom:2px solid #fff;transform:rotate(-45deg) translateY(-1px)}.slice-divider{border:none;border-top:1px solid var(--jqt-border-color);margin:6px 0}.lazy-skeleton{display:block;height:12px;width:70%;border-radius:4px;background:linear-gradient(90deg,var(--jqt-border-color) 25%,var(--jqt-surface-alt-color) 50%,var(--jqt-border-color) 75%);background-size:200% 100%;animation:lazy-shimmer 1.4s ease-in-out infinite}.slice-lazy-loading{display:flex;flex-direction:column;gap:8px;padding:8px 10px}.slice-lazy-loading .lazy-skeleton{width:80%}.slice-lazy-loading .lazy-skeleton:nth-child(2){width:60%}.slice-lazy-loading .lazy-skeleton:nth-child(3){width:45%}@keyframes lazy-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}\n"] }]
|
|
553
|
-
}], propDecorators: { sliceConfigs: [{
|
|
554
|
-
type: Input
|
|
555
|
-
}], columns: [{
|
|
556
|
-
type: Input
|
|
557
|
-
}], data: [{
|
|
558
|
-
type: Input
|
|
559
|
-
}], lazyData: [{
|
|
560
|
-
type: Input
|
|
561
|
-
}], lazyStatus: [{
|
|
562
|
-
type: Input
|
|
563
|
-
}], showToggle: [{
|
|
564
|
-
type: Input
|
|
565
|
-
}], showCounts: [{
|
|
566
|
-
type: Input
|
|
567
|
-
}], collapsedByDefault: [{
|
|
568
|
-
type: Input
|
|
569
|
-
}], alwaysShow: [{
|
|
570
|
-
type: Input
|
|
571
|
-
}], filterChange: [{
|
|
572
|
-
type: Output
|
|
573
|
-
}], activeKeysChange: [{
|
|
574
|
-
type: Output
|
|
575
|
-
}], dynamicSliceKeysChange: [{
|
|
576
|
-
type: Output
|
|
577
|
-
}], collapsedChange: [{
|
|
578
|
-
type: Output
|
|
579
|
-
}], slicePanelBodyRef: [{
|
|
580
|
-
type: ViewChild,
|
|
581
|
-
args: ['slicePanelBody']
|
|
582
|
-
}] } });
|
|
583
|
-
|
|
584
113
|
/** Traductions françaises du paginator Material. */
|
|
585
114
|
function getFrenchPaginatorIntl() {
|
|
586
115
|
const intl = new MatPaginatorIntl();
|
|
@@ -639,435 +168,6 @@ function humanizeKey(key) {
|
|
|
639
168
|
.replace(/^./, (value) => value.toUpperCase());
|
|
640
169
|
}
|
|
641
170
|
|
|
642
|
-
// ── Facade
|
|
643
|
-
/**
|
|
644
|
-
* Centralise l'état et les actions du panneau View (Champs / Group by / Slice by).
|
|
645
|
-
* Pas de dépendance Angular/DOM : peut être extraite vers jquery-core à terme.
|
|
646
|
-
*
|
|
647
|
-
* Usage :
|
|
648
|
-
* this._view = new ViewFacade();
|
|
649
|
-
* this._view.events$.subscribe(e => { ... });
|
|
650
|
-
* this._view.update({ config, columns, sliceConfigs });
|
|
651
|
-
*/
|
|
652
|
-
class ViewFacade {
|
|
653
|
-
constructor() {
|
|
654
|
-
// ── State
|
|
655
|
-
this.fields = {
|
|
656
|
-
activeColumns: [],
|
|
657
|
-
userCustomized: false,
|
|
658
|
-
};
|
|
659
|
-
this.groupBy = {
|
|
660
|
-
activeKey: null,
|
|
661
|
-
userCustomized: false,
|
|
662
|
-
};
|
|
663
|
-
this.sliceBy = {
|
|
664
|
-
activeDynamicColumns: [],
|
|
665
|
-
availableForDynamic: [],
|
|
666
|
-
allDynamicColumns: [],
|
|
667
|
-
activeLabel: 'Aucun',
|
|
668
|
-
};
|
|
669
|
-
this._panelConfig = { columns: [], sliceConfigs: [] };
|
|
670
|
-
this._hiddenStaticKeys = new Set();
|
|
671
|
-
// ── Events
|
|
672
|
-
this._events$ = new Subject();
|
|
673
|
-
this.events$ = this._events$.asObservable();
|
|
674
|
-
}
|
|
675
|
-
// ── Computed flags
|
|
676
|
-
get enabled() {
|
|
677
|
-
return this._panelConfig.config?.enabled === true;
|
|
678
|
-
}
|
|
679
|
-
get showFields() {
|
|
680
|
-
return this.enabled;
|
|
681
|
-
}
|
|
682
|
-
get showGroupBySection() {
|
|
683
|
-
return this.enabled && this.groupByColumns.length > 0;
|
|
684
|
-
}
|
|
685
|
-
get showSliceBySection() {
|
|
686
|
-
return this.enabled && ((this._panelConfig.sliceConfigs?.length ?? 0) > 0 ||
|
|
687
|
-
this.sliceBy.allDynamicColumns.length > 0);
|
|
688
|
-
}
|
|
689
|
-
get allowColumnRemoval() {
|
|
690
|
-
return this._panelConfig.config?.enableColumnRemoval !== false;
|
|
691
|
-
}
|
|
692
|
-
get isColumnDragDropEnabled() {
|
|
693
|
-
return this._panelConfig.config?.enableColumnDragDrop === true;
|
|
694
|
-
}
|
|
695
|
-
get menuBaseColumns() {
|
|
696
|
-
return this._uniqueByKey((this._panelConfig.columns || []).filter(c => !c.optional));
|
|
697
|
-
}
|
|
698
|
-
get menuOptionalColumns() {
|
|
699
|
-
return this._uniqueByKey((this._panelConfig.columns || []).filter(c => c.optional));
|
|
700
|
-
}
|
|
701
|
-
get totalColumnCount() {
|
|
702
|
-
return new Set((this._panelConfig.columns || []).map(c => c.key)).size;
|
|
703
|
-
}
|
|
704
|
-
get groupByColumns() {
|
|
705
|
-
const seen = new Set();
|
|
706
|
-
return (this._panelConfig.columns || []).filter(c => {
|
|
707
|
-
if (c.groupable === false || seen.has(c.key))
|
|
708
|
-
return false;
|
|
709
|
-
if (!c.header && c.groupable !== true)
|
|
710
|
-
return false;
|
|
711
|
-
seen.add(c.key);
|
|
712
|
-
return true;
|
|
713
|
-
});
|
|
714
|
-
}
|
|
715
|
-
get activeGroupByLabel() {
|
|
716
|
-
if (!this.groupBy.activeKey)
|
|
717
|
-
return this._panelConfig.noneLabel ?? 'Aucun';
|
|
718
|
-
const col = this.groupByColumns.find(c => c.key === this.groupBy.activeKey);
|
|
719
|
-
return col?.header || this.groupBy.activeKey;
|
|
720
|
-
}
|
|
721
|
-
get staticSlicesForMenu() {
|
|
722
|
-
return (this._panelConfig.sliceConfigs ?? [])
|
|
723
|
-
.filter(s => !!s.columnKey)
|
|
724
|
-
.map(s => {
|
|
725
|
-
const colIcon = (this._panelConfig.columns ?? []).find(c => c.key === s.columnKey)?.icon;
|
|
726
|
-
return { key: s.columnKey, title: s.title || humanizeKey(s.columnKey), icon: s.icon ?? colIcon };
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
|
-
colLabel(col) {
|
|
730
|
-
return col.header || humanizeKey(col.key);
|
|
731
|
-
}
|
|
732
|
-
// ── Mise à jour de la config (appelée depuis refreshViewModel)
|
|
733
|
-
update(panelConfig) {
|
|
734
|
-
this._panelConfig = panelConfig;
|
|
735
|
-
this._refreshDefaultColumns();
|
|
736
|
-
this._refreshDynamicSliceMeta();
|
|
737
|
-
if (!this.groupBy.userCustomized) {
|
|
738
|
-
this.groupBy.activeKey = panelConfig.defaultGroupBy ?? null;
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
// ── Actions Fields
|
|
742
|
-
setActiveColumns(columns) {
|
|
743
|
-
this.fields.activeColumns = columns;
|
|
744
|
-
this.fields.userCustomized = true;
|
|
745
|
-
}
|
|
746
|
-
setUserCustomized(value) {
|
|
747
|
-
this.fields.userCustomized = value;
|
|
748
|
-
}
|
|
749
|
-
/** Remet la vue dans son état initial : colonnes par défaut, groupBy par défaut, pas de dynamic slices. */
|
|
750
|
-
resetToDefaults() {
|
|
751
|
-
this.fields.userCustomized = false;
|
|
752
|
-
this.groupBy.userCustomized = false;
|
|
753
|
-
this._refreshDefaultColumns();
|
|
754
|
-
this.groupBy.activeKey = this._panelConfig.defaultGroupBy ?? null;
|
|
755
|
-
this.sliceBy.activeDynamicColumns = [];
|
|
756
|
-
this._refreshDynamicSliceMeta();
|
|
757
|
-
}
|
|
758
|
-
addColumn(column) {
|
|
759
|
-
const configCols = this._panelConfig.columns ?? [];
|
|
760
|
-
const configIndex = configCols.findIndex(c => c.key === column.key);
|
|
761
|
-
if (configIndex === -1) {
|
|
762
|
-
this.fields.activeColumns = [...this.fields.activeColumns, column];
|
|
763
|
-
}
|
|
764
|
-
else {
|
|
765
|
-
const insertBefore = this.fields.activeColumns.findIndex(c => {
|
|
766
|
-
const ci = configCols.findIndex(cc => cc.key === c.key);
|
|
767
|
-
return ci !== -1 && ci > configIndex;
|
|
768
|
-
});
|
|
769
|
-
if (insertBefore === -1) {
|
|
770
|
-
this.fields.activeColumns = [...this.fields.activeColumns, column];
|
|
771
|
-
}
|
|
772
|
-
else {
|
|
773
|
-
const copy = [...this.fields.activeColumns];
|
|
774
|
-
copy.splice(insertBefore, 0, column);
|
|
775
|
-
this.fields.activeColumns = copy;
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
this.fields.userCustomized = true;
|
|
779
|
-
this._events$.next({ type: 'fieldsChanged', fieldIds: this.fields.activeColumns.map(c => c.key) });
|
|
780
|
-
}
|
|
781
|
-
removeColumn(columnKey) {
|
|
782
|
-
const col = this.fields.activeColumns.find(c => c.key === columnKey);
|
|
783
|
-
if (!col)
|
|
784
|
-
return false;
|
|
785
|
-
if (!col.optional && !this.allowColumnRemoval)
|
|
786
|
-
return false;
|
|
787
|
-
if (this.fields.activeColumns.length <= 1)
|
|
788
|
-
return false;
|
|
789
|
-
this.fields.activeColumns = this.fields.activeColumns.filter(c => c.key !== columnKey);
|
|
790
|
-
this.fields.userCustomized = true;
|
|
791
|
-
this._events$.next({ type: 'fieldsChanged', fieldIds: this.fields.activeColumns.map(c => c.key) });
|
|
792
|
-
return true;
|
|
793
|
-
}
|
|
794
|
-
toggleColumnVisibility(column, visible) {
|
|
795
|
-
const isVisible = this.isColumnVisible(column.key);
|
|
796
|
-
if (visible && !isVisible) {
|
|
797
|
-
this.addColumn(column);
|
|
798
|
-
}
|
|
799
|
-
else if (!visible && isVisible) {
|
|
800
|
-
this.removeColumn(column.key);
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
reorderColumns(fromKey, toKey) {
|
|
804
|
-
const prev = this.fields.activeColumns.findIndex(c => c.key === fromKey);
|
|
805
|
-
const curr = this.fields.activeColumns.findIndex(c => c.key === toKey);
|
|
806
|
-
if (prev === -1 || curr === -1 || prev === curr)
|
|
807
|
-
return false;
|
|
808
|
-
const reordered = [...this.fields.activeColumns];
|
|
809
|
-
const [moved] = reordered.splice(prev, 1);
|
|
810
|
-
reordered.splice(curr, 0, moved);
|
|
811
|
-
this.fields.activeColumns = reordered;
|
|
812
|
-
this.fields.userCustomized = true;
|
|
813
|
-
this._events$.next({ type: 'fieldsChanged', fieldIds: this.fields.activeColumns.map(c => c.key) });
|
|
814
|
-
return true;
|
|
815
|
-
}
|
|
816
|
-
isColumnVisible(key) {
|
|
817
|
-
return this.fields.activeColumns.some(c => c.key === key);
|
|
818
|
-
}
|
|
819
|
-
// ── Actions Group by
|
|
820
|
-
setGroupBy(key) {
|
|
821
|
-
this.groupBy.activeKey = key;
|
|
822
|
-
this.groupBy.userCustomized = true;
|
|
823
|
-
this._events$.next({ type: 'groupByChanged', key });
|
|
824
|
-
}
|
|
825
|
-
// ── Actions Slice by (dynamique)
|
|
826
|
-
/** Appelée quand le SlicePanelComponent émet dynamicSliceKeysChange */
|
|
827
|
-
onDynamicSliceKeysChange(keys) {
|
|
828
|
-
const allCols = this._panelConfig.columns ?? [];
|
|
829
|
-
const staticKeys = new Set((this._panelConfig.sliceConfigs ?? []).map(s => s.columnKey).filter((k) => !!k));
|
|
830
|
-
const dynamicKeySet = new Set(keys);
|
|
831
|
-
const existingKeys = new Set([...staticKeys, ...dynamicKeySet]);
|
|
832
|
-
this.sliceBy.activeDynamicColumns = keys
|
|
833
|
-
.map(k => allCols.find(c => c.key === k))
|
|
834
|
-
.filter((c) => !!c);
|
|
835
|
-
const seen = new Set();
|
|
836
|
-
this.sliceBy.availableForDynamic = allCols.filter(c => {
|
|
837
|
-
if (c.sliceable === false || existingKeys.has(c.key) || seen.has(c.key))
|
|
838
|
-
return false;
|
|
839
|
-
seen.add(c.key);
|
|
840
|
-
return true;
|
|
841
|
-
});
|
|
842
|
-
this._computeSliceLabel();
|
|
843
|
-
this._events$.next({ type: 'dynamicSlicesChanged', keys });
|
|
844
|
-
}
|
|
845
|
-
isDynamicSliceActive(key) {
|
|
846
|
-
return this.sliceBy.activeDynamicColumns.some(c => c.key === key);
|
|
847
|
-
}
|
|
848
|
-
// ── Contrat core
|
|
849
|
-
/** Retourne un snapshot ViewState (contrat core) de l'état courant. */
|
|
850
|
-
toViewState() {
|
|
851
|
-
return {
|
|
852
|
-
selectedFieldIds: this.fields.activeColumns.map(c => c.key),
|
|
853
|
-
groupByKey: this.groupBy.activeKey,
|
|
854
|
-
dynamicSliceKeys: this.sliceBy.activeDynamicColumns.map(c => c.key),
|
|
855
|
-
};
|
|
856
|
-
}
|
|
857
|
-
/** Convertit une TableColumnProvider en ViewField (contrat core). */
|
|
858
|
-
static toViewField(col) {
|
|
859
|
-
return {
|
|
860
|
-
id: col.key,
|
|
861
|
-
label: col.header,
|
|
862
|
-
icon: col.icon,
|
|
863
|
-
groupable: col.groupable,
|
|
864
|
-
sliceable: col.sliceable,
|
|
865
|
-
optional: col.optional,
|
|
866
|
-
};
|
|
867
|
-
}
|
|
868
|
-
// ── Destructor
|
|
869
|
-
destroy() {
|
|
870
|
-
this._events$.complete();
|
|
871
|
-
}
|
|
872
|
-
// ── Helpers privés
|
|
873
|
-
_refreshDefaultColumns() {
|
|
874
|
-
const defaultCols = (this._panelConfig.columns || []).filter(c => !c.optional);
|
|
875
|
-
if (!this.fields.userCustomized || this.fields.activeColumns.length === 0) {
|
|
876
|
-
this.fields.activeColumns = [...defaultCols];
|
|
877
|
-
}
|
|
878
|
-
else {
|
|
879
|
-
const allowedKeys = new Set((this._panelConfig.columns || []).map(c => c.key));
|
|
880
|
-
this.fields.activeColumns = this.fields.activeColumns.filter(c => allowedKeys.has(c.key));
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
_refreshDynamicSliceMeta() {
|
|
884
|
-
const allCols = this._panelConfig.columns ?? [];
|
|
885
|
-
const staticKeys = new Set((this._panelConfig.sliceConfigs ?? []).map(s => s.columnKey).filter((k) => !!k));
|
|
886
|
-
const activeDynKeys = new Set(this.sliceBy.activeDynamicColumns.map(c => c.key));
|
|
887
|
-
const seen1 = new Set();
|
|
888
|
-
this.sliceBy.availableForDynamic = allCols.filter(c => {
|
|
889
|
-
if (c.sliceable === false || staticKeys.has(c.key) || activeDynKeys.has(c.key) || seen1.has(c.key))
|
|
890
|
-
return false;
|
|
891
|
-
if (!c.header && c.sliceable !== true)
|
|
892
|
-
return false;
|
|
893
|
-
seen1.add(c.key);
|
|
894
|
-
return true;
|
|
895
|
-
});
|
|
896
|
-
const seen2 = new Set();
|
|
897
|
-
this.sliceBy.allDynamicColumns = allCols.filter(c => {
|
|
898
|
-
if (c.sliceable === false || staticKeys.has(c.key) || seen2.has(c.key))
|
|
899
|
-
return false;
|
|
900
|
-
if (!c.header && c.sliceable !== true)
|
|
901
|
-
return false;
|
|
902
|
-
seen2.add(c.key);
|
|
903
|
-
return true;
|
|
904
|
-
});
|
|
905
|
-
this._computeSliceLabel();
|
|
906
|
-
}
|
|
907
|
-
/** Notifie la facade des slices statiques actuellement cachées (géré par table.component).
|
|
908
|
-
* Recalcule le label slice afin qu'il reflète uniquement les slices visibles. */
|
|
909
|
-
updateHiddenStaticKeys(keys) {
|
|
910
|
-
this._hiddenStaticKeys = keys;
|
|
911
|
-
this._computeSliceLabel();
|
|
912
|
-
}
|
|
913
|
-
_computeSliceLabel() {
|
|
914
|
-
const visibleStatics = (this._panelConfig.sliceConfigs ?? []).filter(s => !s.columnKey || !this._hiddenStaticKeys.has(s.columnKey));
|
|
915
|
-
const visibleStaticCount = visibleStatics.length;
|
|
916
|
-
const total = visibleStaticCount + this.sliceBy.activeDynamicColumns.length;
|
|
917
|
-
if (total === 0) {
|
|
918
|
-
this.sliceBy.activeLabel = this._panelConfig.noneLabel ?? 'Aucun';
|
|
919
|
-
return;
|
|
920
|
-
}
|
|
921
|
-
if (total === 1) {
|
|
922
|
-
if (visibleStaticCount === 1) {
|
|
923
|
-
const first = visibleStatics[0];
|
|
924
|
-
this.sliceBy.activeLabel = first?.title || first?.columnKey || '1';
|
|
925
|
-
return;
|
|
926
|
-
}
|
|
927
|
-
this.sliceBy.activeLabel = this.sliceBy.activeDynamicColumns[0]?.header || '1';
|
|
928
|
-
return;
|
|
929
|
-
}
|
|
930
|
-
this.sliceBy.activeLabel = `${total} actifs`;
|
|
931
|
-
}
|
|
932
|
-
_uniqueByKey(cols) {
|
|
933
|
-
const seen = new Set();
|
|
934
|
-
return cols.filter(c => {
|
|
935
|
-
if (seen.has(c.key))
|
|
936
|
-
return false;
|
|
937
|
-
seen.add(c.key);
|
|
938
|
-
return true;
|
|
939
|
-
});
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
/**
|
|
944
|
-
* Constantes sentinel utilisées pour les identifiants de colonnes et les valeurs
|
|
945
|
-
* projetées dans le datasource interne de la table.
|
|
946
|
-
*
|
|
947
|
-
* Centraliser ici évite les typos silencieuses et facilite les tests unitaires.
|
|
948
|
-
*/
|
|
949
|
-
/** Identifiant de la colonne virtuelle rendue sur toute la largeur pour les en-têtes de groupe. */
|
|
950
|
-
const GROUP_ROW_COLUMN = '__group_row';
|
|
951
|
-
/** Marqueur posé sur les objets de ligne pour signaler une ligne d'en-tête de groupe. */
|
|
952
|
-
const GROUP_HEADER_MARKER = '__groupHeader';
|
|
953
|
-
/** Clé du groupe spéciale « chargement lazy en cours ». */
|
|
954
|
-
const GROUP_KEY_LAZY_LOADING = '__lazy_loading__';
|
|
955
|
-
/** Clé du groupe spéciale « trop de groupes ». */
|
|
956
|
-
const GROUP_KEY_TOO_MANY = '__too_many_groups__';
|
|
957
|
-
/** Propriété injectée dans chaque ligne projetée : référence vers la ligne brute d'origine. */
|
|
958
|
-
const ROW_RAW_KEY = '__raw';
|
|
959
|
-
/** Propriété injectée dans chaque ligne projetée : index global de la ligne dans le jeu de données filtré. */
|
|
960
|
-
const ROW_INDEX_KEY = '__index';
|
|
961
|
-
/** Propriété injectée dans chaque ligne projetée : clé du groupe auquel appartient la ligne d'en-tête. */
|
|
962
|
-
const ROW_GROUP_KEY = '__groupKey';
|
|
963
|
-
/** Propriété injectée dans chaque ligne projetée : nombre de lignes dans le groupe. */
|
|
964
|
-
const ROW_GROUP_COUNT = '__groupCount';
|
|
965
|
-
/** Propriété injectée dans chaque ligne projetée : page courante du groupe. */
|
|
966
|
-
const ROW_GROUP_PAGE = '__groupPage';
|
|
967
|
-
/** Propriété injectée dans chaque ligne projetée : nombre de pages du groupe. */
|
|
968
|
-
const ROW_GROUP_PAGE_COUNT = '__groupPageCount';
|
|
969
|
-
/** Valeur sentinelle posée dans une cellule lazy en cours de chargement. */
|
|
970
|
-
const LAZY_LOADING_VALUE = '__lazy_loading__';
|
|
971
|
-
/** Valeur sentinelle posée dans une cellule lazy en erreur. */
|
|
972
|
-
const LAZY_ERROR_VALUE = '__lazy_error__';
|
|
973
|
-
|
|
974
|
-
/**
|
|
975
|
-
* Gère le cycle de vie des colonnes à chargement différé (lazy) :
|
|
976
|
-
* statuts, données, annulation des requêtes en cours.
|
|
977
|
-
*
|
|
978
|
-
* Pas de dépendance Angular/DOM — peut être instancié manuellement dans le composant.
|
|
979
|
-
*/
|
|
980
|
-
class LazyColumnManager {
|
|
981
|
-
constructor() {
|
|
982
|
-
this._status = new Map();
|
|
983
|
-
this._data = new Map();
|
|
984
|
-
this._cancels = new Map();
|
|
985
|
-
}
|
|
986
|
-
get status() {
|
|
987
|
-
return this._status;
|
|
988
|
-
}
|
|
989
|
-
get data() {
|
|
990
|
-
return this._data;
|
|
991
|
-
}
|
|
992
|
-
getStatus(key) {
|
|
993
|
-
return this._status.get(key) ?? 'idle';
|
|
994
|
-
}
|
|
995
|
-
getRenderType(rowValue) {
|
|
996
|
-
if (rowValue === LAZY_LOADING_VALUE)
|
|
997
|
-
return 'loading';
|
|
998
|
-
if (rowValue === LAZY_ERROR_VALUE)
|
|
999
|
-
return 'error';
|
|
1000
|
-
return 'value';
|
|
1001
|
-
}
|
|
1002
|
-
/** Annule tous les fetches en cours et réinitialise l'état (appeler lors d'un changement de données). */
|
|
1003
|
-
cancelAll() {
|
|
1004
|
-
this._cancels.forEach(s => { s.next(); s.complete(); });
|
|
1005
|
-
this._cancels.clear();
|
|
1006
|
-
this._status = new Map();
|
|
1007
|
-
this._data = new Map();
|
|
1008
|
-
}
|
|
1009
|
-
/** Réinitialise une colonne et relance son fetch. */
|
|
1010
|
-
retry(column, resolvedData, callbacks) {
|
|
1011
|
-
const k = column.key;
|
|
1012
|
-
const prev = this._cancels.get(k);
|
|
1013
|
-
if (prev) {
|
|
1014
|
-
prev.next();
|
|
1015
|
-
prev.complete();
|
|
1016
|
-
}
|
|
1017
|
-
this._cancels.delete(k);
|
|
1018
|
-
const newStatus = new Map(this._status);
|
|
1019
|
-
newStatus.delete(k);
|
|
1020
|
-
this._status = newStatus;
|
|
1021
|
-
const newData = new Map(this._data);
|
|
1022
|
-
newData.delete(k);
|
|
1023
|
-
this._data = newData;
|
|
1024
|
-
this.fetch(column, resolvedData, callbacks);
|
|
1025
|
-
}
|
|
1026
|
-
/** Lance le chargement différé d'une colonne. No-op si déjà en cours. */
|
|
1027
|
-
fetch(column, resolvedData, callbacks) {
|
|
1028
|
-
if (!column.lazy)
|
|
1029
|
-
return;
|
|
1030
|
-
const k = column.key;
|
|
1031
|
-
if (this._status.get(k) === 'loading')
|
|
1032
|
-
return;
|
|
1033
|
-
const prev = this._cancels.get(k);
|
|
1034
|
-
if (prev) {
|
|
1035
|
-
prev.next();
|
|
1036
|
-
prev.complete();
|
|
1037
|
-
}
|
|
1038
|
-
const cancel$ = new Subject();
|
|
1039
|
-
this._cancels.set(k, cancel$);
|
|
1040
|
-
const newStatus = new Map(this._status);
|
|
1041
|
-
newStatus.set(k, 'loading');
|
|
1042
|
-
this._status = newStatus;
|
|
1043
|
-
callbacks.onRefresh();
|
|
1044
|
-
column.lazy.fetchFn().pipe(take(1), takeUntil(cancel$)).subscribe({
|
|
1045
|
-
next: (values) => {
|
|
1046
|
-
const rowMap = new Map();
|
|
1047
|
-
values.forEach((value, i) => {
|
|
1048
|
-
if (i < resolvedData.length)
|
|
1049
|
-
rowMap.set(resolvedData[i], value);
|
|
1050
|
-
});
|
|
1051
|
-
const newData = new Map(this._data);
|
|
1052
|
-
newData.set(k, rowMap);
|
|
1053
|
-
this._data = newData;
|
|
1054
|
-
const newStatus2 = new Map(this._status);
|
|
1055
|
-
newStatus2.set(k, 'loaded');
|
|
1056
|
-
this._status = newStatus2;
|
|
1057
|
-
callbacks.onRefresh();
|
|
1058
|
-
callbacks.onMarkForCheck();
|
|
1059
|
-
},
|
|
1060
|
-
error: () => {
|
|
1061
|
-
const newStatusErr = new Map(this._status);
|
|
1062
|
-
newStatusErr.set(k, 'error');
|
|
1063
|
-
this._status = newStatusErr;
|
|
1064
|
-
callbacks.onRefresh();
|
|
1065
|
-
callbacks.onMarkForCheck();
|
|
1066
|
-
},
|
|
1067
|
-
});
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
171
|
const STORAGE_PREFIX = 'jqt_prefs_';
|
|
1072
172
|
/**
|
|
1073
173
|
* Gère la persistance de la configuration d'un tableau dans le localStorage.
|
|
@@ -1106,105 +206,6 @@ class TablePreferencesManager {
|
|
|
1106
206
|
}
|
|
1107
207
|
}
|
|
1108
208
|
|
|
1109
|
-
/**
|
|
1110
|
-
* GroupByManager
|
|
1111
|
-
*
|
|
1112
|
-
* Encapsule tout l'état et la logique liés au mode Group By :
|
|
1113
|
-
* - état collapsed / expanded des groupes
|
|
1114
|
-
* - pagination interne par groupe
|
|
1115
|
-
* - tri des groupes (asc / desc)
|
|
1116
|
-
* - scroll vers un groupe
|
|
1117
|
-
*
|
|
1118
|
-
* Le composant TableComponent l'instancie et lui passe deux callbacks :
|
|
1119
|
-
* - `onNeedProjection` : demande une reprojection légère de la page courante
|
|
1120
|
-
* - `onNeedRender` : demande un rebuild complet (tri changé etc.)
|
|
1121
|
-
*/
|
|
1122
|
-
class GroupByManager {
|
|
1123
|
-
constructor(onNeedProjection, onNeedRender) {
|
|
1124
|
-
this.onNeedProjection = onNeedProjection;
|
|
1125
|
-
this.onNeedRender = onNeedRender;
|
|
1126
|
-
/** Groupes actuellement ouverts (mode default-collapsed) ou fermés (mode default-expanded). */
|
|
1127
|
-
this._collapsedGroups = new Set();
|
|
1128
|
-
/** Page courante par clé de groupe. */
|
|
1129
|
-
this._groupPages = new Map();
|
|
1130
|
-
/** Taille de page pour la pagination interne des groupes. */
|
|
1131
|
-
this.groupPageSize = 5;
|
|
1132
|
-
/** Ordre de tri des groupes ('asc' | 'desc'). */
|
|
1133
|
-
this.groupSortOrder = 'asc';
|
|
1134
|
-
/**
|
|
1135
|
-
* Quand `true`, tous les groupes sont collapsés par défaut.
|
|
1136
|
-
* `_collapsedGroups` contient alors le seul groupe ouvert.
|
|
1137
|
-
*/
|
|
1138
|
-
this._defaultGroupCollapsed = false;
|
|
1139
|
-
}
|
|
1140
|
-
// ── API publique
|
|
1141
|
-
/** Initialise le mode par défaut selon qu'un groupBy vient d'être activé. */
|
|
1142
|
-
setDefaultCollapsed(collapsed) {
|
|
1143
|
-
this._defaultGroupCollapsed = collapsed;
|
|
1144
|
-
}
|
|
1145
|
-
/** Réinitialise l'état (à appeler quand le groupBy change de colonne). */
|
|
1146
|
-
reset() {
|
|
1147
|
-
this._collapsedGroups.clear();
|
|
1148
|
-
this._groupPages.clear();
|
|
1149
|
-
}
|
|
1150
|
-
isGroupCollapsed(groupKey) {
|
|
1151
|
-
return this._defaultGroupCollapsed
|
|
1152
|
-
? !this._collapsedGroups.has(groupKey)
|
|
1153
|
-
: this._collapsedGroups.has(groupKey);
|
|
1154
|
-
}
|
|
1155
|
-
toggleGroupCollapse(groupKey, scrollTo) {
|
|
1156
|
-
if (this._collapsedGroups.has(groupKey)) {
|
|
1157
|
-
this._collapsedGroups.delete(groupKey);
|
|
1158
|
-
}
|
|
1159
|
-
else {
|
|
1160
|
-
this._collapsedGroups.clear();
|
|
1161
|
-
this._collapsedGroups.add(groupKey);
|
|
1162
|
-
scrollTo(groupKey);
|
|
1163
|
-
}
|
|
1164
|
-
this.onNeedProjection();
|
|
1165
|
-
}
|
|
1166
|
-
/** Retourne le groupe actuellement ouvert (seulement en mode default-collapsed avec exactement 1 ouvert). */
|
|
1167
|
-
getSingleOpenGroup() {
|
|
1168
|
-
return this._defaultGroupCollapsed && this._collapsedGroups.size === 1
|
|
1169
|
-
? [...this._collapsedGroups][0]
|
|
1170
|
-
: null;
|
|
1171
|
-
}
|
|
1172
|
-
// ── Pagination par groupe
|
|
1173
|
-
getPage(groupKey) {
|
|
1174
|
-
return this._groupPages.get(groupKey) ?? 0;
|
|
1175
|
-
}
|
|
1176
|
-
groupPageBack(groupKey) {
|
|
1177
|
-
const current = this._groupPages.get(groupKey) ?? 0;
|
|
1178
|
-
if (current > 0) {
|
|
1179
|
-
this._groupPages.set(groupKey, current - 1);
|
|
1180
|
-
this.onNeedProjection();
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
groupPageForward(groupKey, totalCount) {
|
|
1184
|
-
const maxPage = Math.ceil(totalCount / this.groupPageSize) - 1;
|
|
1185
|
-
const current = this._groupPages.get(groupKey) ?? 0;
|
|
1186
|
-
if (current < maxPage) {
|
|
1187
|
-
this._groupPages.set(groupKey, current + 1);
|
|
1188
|
-
this.onNeedProjection();
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
onGroupPageSizeChange(size) {
|
|
1192
|
-
this.groupPageSize = size;
|
|
1193
|
-
this._groupPages.clear();
|
|
1194
|
-
this.onNeedRender();
|
|
1195
|
-
}
|
|
1196
|
-
onGroupSortToggle(dir, scrollTo) {
|
|
1197
|
-
if (this.groupSortOrder === dir)
|
|
1198
|
-
return;
|
|
1199
|
-
this.groupSortOrder = dir;
|
|
1200
|
-
this._groupPages.clear();
|
|
1201
|
-
this.onNeedRender();
|
|
1202
|
-
const openGroup = this.getSingleOpenGroup();
|
|
1203
|
-
if (openGroup)
|
|
1204
|
-
scrollTo(openGroup);
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
209
|
/**
|
|
1209
210
|
* ExportManager
|
|
1210
211
|
*
|
|
@@ -1250,6 +251,83 @@ class ExportManager {
|
|
|
1250
251
|
}
|
|
1251
252
|
}
|
|
1252
253
|
|
|
254
|
+
/**
|
|
255
|
+
* Constantes sentinel utilisées pour les identifiants de colonnes et les valeurs
|
|
256
|
+
* projetées dans le datasource interne de la table.
|
|
257
|
+
*
|
|
258
|
+
* Centraliser ici évite les typos silencieuses et facilite les tests unitaires.
|
|
259
|
+
*/
|
|
260
|
+
/** Identifiant de la colonne virtuelle rendue sur toute la largeur pour les en-têtes de groupe. */
|
|
261
|
+
const GROUP_ROW_COLUMN = '__group_row';
|
|
262
|
+
/** Marqueur posé sur les objets de ligne pour signaler une ligne d'en-tête de groupe. */
|
|
263
|
+
const GROUP_HEADER_MARKER = '__groupHeader';
|
|
264
|
+
/** Clé du groupe spéciale « chargement lazy en cours ». */
|
|
265
|
+
const GROUP_KEY_LAZY_LOADING = '__lazy_loading__';
|
|
266
|
+
/** Clé du groupe spéciale « trop de groupes ». */
|
|
267
|
+
const GROUP_KEY_TOO_MANY = '__too_many_groups__';
|
|
268
|
+
/** Propriété injectée dans chaque ligne projetée : référence vers la ligne brute d'origine. */
|
|
269
|
+
const ROW_RAW_KEY = '__raw';
|
|
270
|
+
/** Propriété injectée dans chaque ligne projetée : index global de la ligne dans le jeu de données filtré. */
|
|
271
|
+
const ROW_INDEX_KEY = '__index';
|
|
272
|
+
/** Propriété injectée dans chaque ligne projetée : clé du groupe auquel appartient la ligne d'en-tête. */
|
|
273
|
+
const ROW_GROUP_KEY = '__groupKey';
|
|
274
|
+
/** Propriété injectée dans chaque ligne projetée : nombre de lignes dans le groupe. */
|
|
275
|
+
const ROW_GROUP_COUNT = '__groupCount';
|
|
276
|
+
/** Propriété injectée dans chaque ligne projetée : page courante du groupe. */
|
|
277
|
+
const ROW_GROUP_PAGE = '__groupPage';
|
|
278
|
+
/** Propriété injectée dans chaque ligne projetée : nombre de pages du groupe. */
|
|
279
|
+
const ROW_GROUP_PAGE_COUNT = '__groupPageCount';
|
|
280
|
+
/** Valeur sentinelle posée dans une cellule lazy en cours de chargement. */
|
|
281
|
+
const LAZY_LOADING_VALUE = '__lazy_loading__';
|
|
282
|
+
/** Valeur sentinelle posée dans une cellule lazy en erreur. */
|
|
283
|
+
const LAZY_ERROR_VALUE = '__lazy_error__';
|
|
284
|
+
|
|
285
|
+
class OrganizerButtonWrapperComponent {
|
|
286
|
+
constructor() {
|
|
287
|
+
this.hideMenuValues = false;
|
|
288
|
+
this.viewChange = new EventEmitter();
|
|
289
|
+
this.sliceStateChange = new EventEmitter();
|
|
290
|
+
}
|
|
291
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: OrganizerButtonWrapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
292
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: OrganizerButtonWrapperComponent, isStandalone: true, selector: "table-organizer-button", inputs: { config: "config", state: "state", hideMenuValues: "hideMenuValues" }, outputs: { viewChange: "viewChange", sliceStateChange: "sliceStateChange" }, ngImport: i0, template: `
|
|
293
|
+
<organizer-button
|
|
294
|
+
[config]="config"
|
|
295
|
+
[state]="state"
|
|
296
|
+
[hideMenuValues]="hideMenuValues"
|
|
297
|
+
(viewChange)="viewChange.emit($event)"
|
|
298
|
+
(sliceStateChange)="sliceStateChange.emit($event)">
|
|
299
|
+
</organizer-button>
|
|
300
|
+
`, isInline: true, dependencies: [{ kind: "component", type: OrganizerButtonComponent, selector: "organizer-button", inputs: ["config", "state", "hideMenuValues"], outputs: ["viewChange", "sliceStateChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
301
|
+
}
|
|
302
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: OrganizerButtonWrapperComponent, decorators: [{
|
|
303
|
+
type: Component,
|
|
304
|
+
args: [{
|
|
305
|
+
standalone: true,
|
|
306
|
+
selector: 'table-organizer-button',
|
|
307
|
+
imports: [OrganizerButtonComponent],
|
|
308
|
+
template: `
|
|
309
|
+
<organizer-button
|
|
310
|
+
[config]="config"
|
|
311
|
+
[state]="state"
|
|
312
|
+
[hideMenuValues]="hideMenuValues"
|
|
313
|
+
(viewChange)="viewChange.emit($event)"
|
|
314
|
+
(sliceStateChange)="sliceStateChange.emit($event)">
|
|
315
|
+
</organizer-button>
|
|
316
|
+
`,
|
|
317
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
318
|
+
}]
|
|
319
|
+
}], propDecorators: { config: [{
|
|
320
|
+
type: Input
|
|
321
|
+
}], state: [{
|
|
322
|
+
type: Input
|
|
323
|
+
}], hideMenuValues: [{
|
|
324
|
+
type: Input
|
|
325
|
+
}], viewChange: [{
|
|
326
|
+
type: Output
|
|
327
|
+
}], sliceStateChange: [{
|
|
328
|
+
type: Output
|
|
329
|
+
}] } });
|
|
330
|
+
|
|
1253
331
|
class TableComponent {
|
|
1254
332
|
constructor() {
|
|
1255
333
|
this.isLoading = false;
|
|
@@ -1282,12 +360,12 @@ class TableComponent {
|
|
|
1282
360
|
this._ROW_GROUP_PAGE = ROW_GROUP_PAGE;
|
|
1283
361
|
this._ROW_GROUP_PAGE_COUNT = ROW_GROUP_PAGE_COUNT;
|
|
1284
362
|
/** Facade View : état et actions centralisés (Champs / Group by / Slice by) */
|
|
1285
|
-
this.
|
|
363
|
+
this._organizer = new OrganizerFacade();
|
|
1286
364
|
/**
|
|
1287
|
-
* Snapshot stable synchronisé depuis
|
|
365
|
+
* Snapshot stable synchronisé depuis _organizer.fields.activeFields dans refreshViewModel().
|
|
1288
366
|
* Propriété (non getter) pour qu'Angular puisse la suivre sans NG0100.
|
|
1289
367
|
*/
|
|
1290
|
-
this.
|
|
368
|
+
this.activeFields = [];
|
|
1291
369
|
this.activeSliceFilter = () => true;
|
|
1292
370
|
// Propriétés stables pour les bindings du template (contre erreur NG0100)
|
|
1293
371
|
this._sliceConfigs = [];
|
|
@@ -1309,7 +387,7 @@ class TableComponent {
|
|
|
1309
387
|
/** Gestionnaire du mode Group By (état collapsed, pagination, tri des groupes). */
|
|
1310
388
|
this._groupBy = new GroupByManager(() => { this._projectCurrentPage(); this._cdr.markForCheck(); }, () => this.refreshViewModel());
|
|
1311
389
|
/** Gestionnaire du cycle de vie des colonnes lazy (fetch, annulation, statuts, données). */
|
|
1312
|
-
this._lazy = new
|
|
390
|
+
this._lazy = new LazyFieldManager();
|
|
1313
391
|
this._cdr = inject(ChangeDetectorRef);
|
|
1314
392
|
this._el = inject((ElementRef));
|
|
1315
393
|
this._ngZone = inject(NgZone);
|
|
@@ -1318,7 +396,7 @@ class TableComponent {
|
|
|
1318
396
|
this.i18n = { ...JQT_I18N_DEFAULTS, ...(this._i18nRaw ?? {}) };
|
|
1319
397
|
this.isGroupHeader = (_, row) => row[GROUP_HEADER_MARKER] === true;
|
|
1320
398
|
/** Gestionnaire export CSV. */
|
|
1321
|
-
this._export = new ExportManager(() => this._allFilteredRows, () => this.
|
|
399
|
+
this._export = new ExportManager(() => this._allFilteredRows, () => this.activeFields, () => this.resolvedConfig.export, (col, row, idx) => this.resolveCellValue(col, row, idx));
|
|
1322
400
|
// ── Lifecycle
|
|
1323
401
|
this.pageSize = 5;
|
|
1324
402
|
this.pageSizeOptions = [5, 10, 20];
|
|
@@ -1356,6 +434,7 @@ class TableComponent {
|
|
|
1356
434
|
this._configHiddenKeys = new Set();
|
|
1357
435
|
/** Clés des colonnes visibles du dernier émission columnsChange (pour éviter les émissions dupliquement). */
|
|
1358
436
|
this._prevColumnKeys = [];
|
|
437
|
+
this._organizerConfigCache = null;
|
|
1359
438
|
this.trackRowFn = (_index, row) => row[GROUP_HEADER_MARKER] ? `__group_${row[ROW_GROUP_KEY]}` : row[ROW_INDEX_KEY] ?? _index;
|
|
1360
439
|
this._preferencesMessageTimer = null;
|
|
1361
440
|
}
|
|
@@ -1375,12 +454,12 @@ class TableComponent {
|
|
|
1375
454
|
}
|
|
1376
455
|
get paginator() { return this._paginator; }
|
|
1377
456
|
// Délégués vers _view (conservés pour compatibilité template sans refactoring HTML)
|
|
1378
|
-
get _staticSlicesForMenu() { return this.
|
|
1379
|
-
get _activeDynamicSliceColumns() { return this.
|
|
1380
|
-
get _availableColumnsForDynamicSlice() { return this.
|
|
1381
|
-
get _allDynamicSliceColumns() { return this.
|
|
1382
|
-
get _activeSliceByLabel() { return this.
|
|
1383
|
-
get activeGroupByKey() { return this.
|
|
457
|
+
get _staticSlicesForMenu() { return this._organizer.staticSlicesForMenu; }
|
|
458
|
+
get _activeDynamicSliceColumns() { return this._organizer.sliceBy.activeDynamicFields; }
|
|
459
|
+
get _availableColumnsForDynamicSlice() { return this._organizer.sliceBy.availableForDynamic; }
|
|
460
|
+
get _allDynamicSliceColumns() { return this._organizer.sliceBy.allDynamicFields; }
|
|
461
|
+
get _activeSliceByLabel() { return this._organizer.sliceBy.activeLabel; }
|
|
462
|
+
get activeGroupByKey() { return this._organizer.groupBy.activeKey; }
|
|
1384
463
|
/** Délégués vers GroupByManager pour la compatibilité des templates. */
|
|
1385
464
|
get groupPageSize() { return this._groupBy.groupPageSize; }
|
|
1386
465
|
set groupPageSize(v) { this._groupBy.groupPageSize = v; }
|
|
@@ -1413,9 +492,10 @@ class TableComponent {
|
|
|
1413
492
|
if (changes['config']) {
|
|
1414
493
|
this.applyConfigSearchReset();
|
|
1415
494
|
this.applyInitialSearchQuery();
|
|
495
|
+
this._invalidateOrganizerConfig(); // les colonnes ont pu changer
|
|
1416
496
|
}
|
|
1417
497
|
if (dataChanged) {
|
|
1418
|
-
this.
|
|
498
|
+
this.activeFields.filter(c => c.lazy).forEach(c => this.triggerLazyFetch(c));
|
|
1419
499
|
}
|
|
1420
500
|
}
|
|
1421
501
|
ngAfterContentInit() {
|
|
@@ -1441,25 +521,24 @@ class TableComponent {
|
|
|
1441
521
|
const filters = this._pendingSliceFilters ? { ...this._pendingSliceFilters } : null;
|
|
1442
522
|
this._pendingDynamicSliceKeys = null;
|
|
1443
523
|
this._pendingSliceFilters = null;
|
|
1444
|
-
// Tout est différé dans le même setTimeout pour :
|
|
1445
|
-
// 1. Éviter NG0100 (modifications après vérification dans ngAfterViewInit)
|
|
1446
|
-
// 2. Garantir que les dynamic slices sont ajoutées AVANT la restauration des filtres
|
|
1447
|
-
// (les indices dans sliceFilters dépendent de l'ordre final de _cachedSlices)
|
|
1448
524
|
setTimeout(() => {
|
|
525
|
+
if (this._resolvedData.length === 0) {
|
|
526
|
+
console.log('[SliceRestore] ngAfterViewInit: données absentes, restauration différée à _recomputeShowSlicePanel', { keys, filters });
|
|
527
|
+
if (filters)
|
|
528
|
+
this._pendingSliceFilters = filters;
|
|
529
|
+
if (keys.length > 0)
|
|
530
|
+
this._pendingDynamicSliceKeys = [...keys];
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
console.log('[SliceRestore] ngAfterViewInit: données présentes, application immédiate', { keys, filters });
|
|
1449
534
|
keys.forEach(key => {
|
|
1450
535
|
const col = allCols.find(c => c.key === key);
|
|
1451
536
|
if (col)
|
|
1452
537
|
this.addDynamicSlice(col);
|
|
1453
538
|
});
|
|
1454
|
-
if (filters) {
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
}
|
|
1458
|
-
else {
|
|
1459
|
-
// slicePanelRef toujours null (données pas encore arrivées) :
|
|
1460
|
-
// remettre en attente pour que _recomputeShowSlicePanel les applique
|
|
1461
|
-
this._pendingSliceFilters = filters;
|
|
1462
|
-
}
|
|
539
|
+
if (filters && this.slicePanelRef) {
|
|
540
|
+
console.log('[SliceRestore] restoreFilters depuis ngAfterViewInit', filters);
|
|
541
|
+
this.slicePanelRef.restoreFilters(filters);
|
|
1463
542
|
}
|
|
1464
543
|
this._cdr.markForCheck();
|
|
1465
544
|
});
|
|
@@ -1475,7 +554,7 @@ class TableComponent {
|
|
|
1475
554
|
this._resizeState = null;
|
|
1476
555
|
this._destroy$.next();
|
|
1477
556
|
this._destroy$.complete();
|
|
1478
|
-
this.
|
|
557
|
+
this._organizer.destroy();
|
|
1479
558
|
}
|
|
1480
559
|
_measureHeaderHeight() {
|
|
1481
560
|
const measure = () => {
|
|
@@ -1492,7 +571,73 @@ class TableComponent {
|
|
|
1492
571
|
};
|
|
1493
572
|
this._ngZone.runOutsideAngular(() => requestAnimationFrame(() => measure()));
|
|
1494
573
|
}
|
|
1495
|
-
|
|
574
|
+
_invalidateOrganizerConfig() { this._organizerConfigCache = null; }
|
|
575
|
+
get organizerConfig() {
|
|
576
|
+
if (this._organizerConfigCache)
|
|
577
|
+
return this._organizerConfigCache;
|
|
578
|
+
const allCols = [...this._organizer.menuBaseFields, ...this._organizer.menuOptionalFields];
|
|
579
|
+
this._organizerConfigCache = {
|
|
580
|
+
fields: this._organizer.showFields ? allCols.map(c => ({
|
|
581
|
+
id: c.key,
|
|
582
|
+
label: this.colLabel(c),
|
|
583
|
+
icon: c.icon,
|
|
584
|
+
state: 'ready',
|
|
585
|
+
})) : undefined,
|
|
586
|
+
groups: this._organizer.showGroupBySection ? this._organizer.groupByFields.map(c => ({
|
|
587
|
+
id: c.key,
|
|
588
|
+
label: this.colLabel(c),
|
|
589
|
+
icon: c.icon,
|
|
590
|
+
})) : undefined,
|
|
591
|
+
slices: this._organizer.showSliceBySection ? this._sliceByMenuItems.map(item => ({
|
|
592
|
+
id: item.key,
|
|
593
|
+
label: item.title,
|
|
594
|
+
icon: item.icon,
|
|
595
|
+
})) : undefined,
|
|
596
|
+
showExport: this.showExportButton,
|
|
597
|
+
onExport: () => this.onExport(),
|
|
598
|
+
showPreferences: this.showPreferencesMenu,
|
|
599
|
+
hasSavedPreferences: this.hasSavedPreferences,
|
|
600
|
+
onPreferencesEdit: () => this.enterEditMode(),
|
|
601
|
+
onPreferencesSave: () => this.savePreferences(),
|
|
602
|
+
onPreferencesClear: () => this.clearPreferences(),
|
|
603
|
+
};
|
|
604
|
+
return this._organizerConfigCache;
|
|
605
|
+
}
|
|
606
|
+
get organizerState() {
|
|
607
|
+
return {
|
|
608
|
+
visibleFields: this.visibleColumns.map(c => c.key),
|
|
609
|
+
selectedGroupBy: this.activeGroupByKey ?? undefined,
|
|
610
|
+
selectedSlices: this._sliceByMenuItems
|
|
611
|
+
.filter(i => this.isSliceMenuItemActive(i))
|
|
612
|
+
.map(i => i.key),
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
onOrganizerViewChange(event) {
|
|
616
|
+
if (event.type === 'fieldToggled') {
|
|
617
|
+
const allCols = [...this._organizer.menuBaseFields, ...this._organizer.menuOptionalFields];
|
|
618
|
+
const newVisible = new Set(event.state.visibleFields ?? []);
|
|
619
|
+
for (const col of allCols) {
|
|
620
|
+
const isNowVisible = newVisible.has(col.key);
|
|
621
|
+
if (this.isColumnVisible(col.key) !== isNowVisible) {
|
|
622
|
+
this.onColumnVisibilityChange(col, isNowVisible);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
else if (event.type === 'groupBySelected') {
|
|
627
|
+
const newKey = event.state.selectedGroupBy ?? null;
|
|
628
|
+
this.onGroupByChange(newKey === this.activeGroupByKey ? null : newKey);
|
|
629
|
+
}
|
|
630
|
+
else if (event.type === 'sliceSelected') {
|
|
631
|
+
const prevSlices = new Set(this.organizerState.selectedSlices ?? []);
|
|
632
|
+
const clickedId = (event.state.selectedSlices ?? []).find(id => !prevSlices.has(id));
|
|
633
|
+
if (clickedId) {
|
|
634
|
+
const item = this._sliceByMenuItems.find(i => i.key === clickedId);
|
|
635
|
+
if (item)
|
|
636
|
+
this.onSliceMenuItemClick(item);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
this._cdr.markForCheck();
|
|
640
|
+
}
|
|
1496
641
|
get title() {
|
|
1497
642
|
return this.resolvedConfig.title ?? '';
|
|
1498
643
|
}
|
|
@@ -1500,10 +645,11 @@ class TableComponent {
|
|
|
1500
645
|
return !!this.resolvedConfig.title;
|
|
1501
646
|
}
|
|
1502
647
|
get visibleColumns() {
|
|
1503
|
-
return this.
|
|
648
|
+
return this.activeFields;
|
|
1504
649
|
}
|
|
1505
650
|
get showSearchBar() { return this.resolvedConfig.search?.enabled === true; }
|
|
1506
|
-
get showViewButton() { return this.
|
|
651
|
+
get showViewButton() { return this._organizer.enabled || this.showExportButton || this.showPreferencesMenu; }
|
|
652
|
+
get showOrganizerButton() { return this._organizer.enabled || this.showExportButton || this.showPreferencesMenu; }
|
|
1507
653
|
get showSliceExpandBtn() { return this._showSlicePanel && this._slicePanelCollapsed && this._sliceShowToggle; }
|
|
1508
654
|
get showExportButton() {
|
|
1509
655
|
return this.resolvedConfig.export?.enabled === true;
|
|
@@ -1521,21 +667,20 @@ class TableComponent {
|
|
|
1521
667
|
expandSlicePanel() {
|
|
1522
668
|
this.slicePanelRef?.togglePanel();
|
|
1523
669
|
}
|
|
1524
|
-
get showFields() { return this.
|
|
1525
|
-
get showGroupBySection() { return this.
|
|
670
|
+
get showFields() { return this._organizer.showFields; }
|
|
671
|
+
get showGroupBySection() { return this._organizer.showGroupBySection; }
|
|
1526
672
|
get filteredRowCount() {
|
|
1527
673
|
return this.activeGroupByKey ? this._totalFilteredCount : this._allFilteredRows.length;
|
|
1528
674
|
}
|
|
1529
|
-
get activeGroupByLabel() { return this.
|
|
1530
|
-
get totalColumnCount() { return this.
|
|
1531
|
-
get activeSliceByLabel() { return this.
|
|
1532
|
-
get activeDynamicSliceColumns() { return this.
|
|
675
|
+
get activeGroupByLabel() { return this._organizer.activeGroupByLabel; }
|
|
676
|
+
get totalColumnCount() { return this._organizer.totalFieldCount; }
|
|
677
|
+
get activeSliceByLabel() { return this._organizer.sliceBy.activeLabel; }
|
|
678
|
+
get activeDynamicSliceColumns() { return this._organizer.sliceBy.activeDynamicFields; }
|
|
1533
679
|
removeDynamicSliceByKey(key) {
|
|
1534
680
|
this.slicePanelRef?.removeDynamicSliceByKey(key);
|
|
1535
681
|
}
|
|
1536
|
-
get groupByColumns() { return this.
|
|
1537
|
-
colLabel(col) { return this.
|
|
1538
|
-
// ── Actions utilisateur
|
|
682
|
+
get groupByColumns() { return this._organizer.groupByFields; }
|
|
683
|
+
colLabel(col) { return this._organizer.fieldLabel(col); }
|
|
1539
684
|
onSearchChange() {
|
|
1540
685
|
this._preservePageIndex = 0;
|
|
1541
686
|
this.refreshViewModel();
|
|
@@ -1543,7 +688,7 @@ class TableComponent {
|
|
|
1543
688
|
this.searchChange.emit(this.searchQuery);
|
|
1544
689
|
}
|
|
1545
690
|
onGroupByChange(key) {
|
|
1546
|
-
this.
|
|
691
|
+
this._organizer.setGroupBy(key);
|
|
1547
692
|
this._groupBy.setDefaultCollapsed(key !== null);
|
|
1548
693
|
this._groupBy.reset();
|
|
1549
694
|
this._groupBy.groupPageSize = this.resolveDefaultGroupPageSize();
|
|
@@ -1551,7 +696,6 @@ class TableComponent {
|
|
|
1551
696
|
this.paginator?.firstPage();
|
|
1552
697
|
this.groupByChange.emit(key);
|
|
1553
698
|
if (key) {
|
|
1554
|
-
this.viewMenuTrigger?.closeMenu();
|
|
1555
699
|
const colDef = (this.resolvedConfig.columns || []).find(c => c.key === key);
|
|
1556
700
|
if (colDef?.lazy && this._lazyColumnStatus.get(key) !== 'loaded') {
|
|
1557
701
|
this.triggerLazyFetch(colDef);
|
|
@@ -1571,7 +715,6 @@ class TableComponent {
|
|
|
1571
715
|
this._ngZone.runOutsideAngular(() => setTimeout(() => this.scrollToGroupHeader(key), 0));
|
|
1572
716
|
});
|
|
1573
717
|
}
|
|
1574
|
-
// ── Lazy loading
|
|
1575
718
|
getLazyColumnStatus(key) {
|
|
1576
719
|
return this._lazy.getStatus(key);
|
|
1577
720
|
}
|
|
@@ -1590,7 +733,6 @@ class TableComponent {
|
|
|
1590
733
|
onMarkForCheck: () => this._cdr.markForCheck(),
|
|
1591
734
|
};
|
|
1592
735
|
}
|
|
1593
|
-
// ── Groupement
|
|
1594
736
|
toggleGroupCollapse(groupKey) {
|
|
1595
737
|
this._groupBy.toggleGroupCollapse(groupKey, (key) => {
|
|
1596
738
|
this._ngZone.runOutsideAngular(() => setTimeout(() => this.scrollToGroupHeader(key), 0));
|
|
@@ -1619,24 +761,22 @@ class TableComponent {
|
|
|
1619
761
|
groupPageForward(groupKey, totalCount) {
|
|
1620
762
|
this._groupBy.groupPageForward(groupKey, totalCount);
|
|
1621
763
|
}
|
|
1622
|
-
// ── Colonnes
|
|
1623
764
|
get hasAddableColumns() {
|
|
1624
|
-
return this.
|
|
765
|
+
return this._organizer.menuBaseFields.length > 0 || this._organizer.menuOptionalFields.length > 0;
|
|
1625
766
|
}
|
|
1626
767
|
get menuBaseColumns() {
|
|
1627
|
-
return this.
|
|
768
|
+
return this._organizer.menuBaseFields;
|
|
1628
769
|
}
|
|
1629
770
|
get menuOptionalColumns() {
|
|
1630
|
-
return this.
|
|
771
|
+
return this._organizer.menuOptionalFields;
|
|
1631
772
|
}
|
|
1632
773
|
get availableColumnsToAdd() {
|
|
1633
|
-
const activeKeys = new Set(this.
|
|
774
|
+
const activeKeys = new Set(this.activeFields.map((column) => column.key));
|
|
1634
775
|
return (this.resolvedConfig.columns || []).filter((c) => c.optional && !activeKeys.has(c.key));
|
|
1635
776
|
}
|
|
1636
777
|
get allowColumnRemoval() {
|
|
1637
|
-
return this.
|
|
778
|
+
return this._organizer.allowFieldRemoval;
|
|
1638
779
|
}
|
|
1639
|
-
// ── Slices
|
|
1640
780
|
get hasSliceConfig() {
|
|
1641
781
|
return (this.resolvedConfig.slices?.length ?? 0) > 0;
|
|
1642
782
|
}
|
|
@@ -1648,28 +788,43 @@ class TableComponent {
|
|
|
1648
788
|
this._cdr.markForCheck();
|
|
1649
789
|
}
|
|
1650
790
|
_recomputeShowSlicePanel() {
|
|
1651
|
-
const hasCfg = this._sliceConfigs.length > 0 || this.
|
|
791
|
+
const hasCfg = this._sliceConfigs.length > 0 || this._organizer.sliceBy.activeDynamicFields.length > 0;
|
|
1652
792
|
const wasVisible = this._showSlicePanel;
|
|
1653
793
|
this._showSlicePanel = hasCfg && this._resolvedData.length > 0;
|
|
1654
|
-
//
|
|
1655
|
-
//
|
|
1656
|
-
|
|
794
|
+
// Les données viennent d'arriver : appliquer les dynamic slices ET les filtres en attente.
|
|
795
|
+
// Les dynamic slices doivent être ajoutées AVANT restoreFilters car les indices de
|
|
796
|
+
// sliceFilters dépendent de l'ordre final de _cachedSlices.
|
|
797
|
+
if (!wasVisible && this._showSlicePanel && (this._pendingSliceFilters || this._pendingDynamicSliceKeys)) {
|
|
1657
798
|
const filters = this._pendingSliceFilters;
|
|
799
|
+
const dynamicKeys = this._pendingDynamicSliceKeys;
|
|
1658
800
|
this._pendingSliceFilters = null;
|
|
801
|
+
this._pendingDynamicSliceKeys = null;
|
|
802
|
+
const allCols = this.resolvedConfig.columns ?? [];
|
|
803
|
+
console.log('[SliceRestore] _recomputeShowSlicePanel: données arrivées', { dynamicKeys, filters });
|
|
1659
804
|
setTimeout(() => {
|
|
1660
|
-
|
|
805
|
+
if (dynamicKeys) {
|
|
806
|
+
dynamicKeys.forEach(key => {
|
|
807
|
+
const col = allCols.find(c => c.key === key);
|
|
808
|
+
if (col)
|
|
809
|
+
this.addDynamicSlice(col);
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
if (filters && this.slicePanelRef) {
|
|
813
|
+
console.log('[SliceRestore] restoreFilters depuis _recomputeShowSlicePanel', filters);
|
|
814
|
+
this.slicePanelRef.restoreFilters(filters);
|
|
815
|
+
}
|
|
1661
816
|
this._cdr.markForCheck();
|
|
1662
817
|
});
|
|
1663
818
|
}
|
|
1664
819
|
}
|
|
1665
820
|
get showAddSliceButton() {
|
|
1666
|
-
return this.
|
|
821
|
+
return this._organizer.showSliceBySection;
|
|
1667
822
|
}
|
|
1668
823
|
get staticSlicesForMenu() {
|
|
1669
|
-
return this.
|
|
824
|
+
return this._organizer.staticSlicesForMenu;
|
|
1670
825
|
}
|
|
1671
826
|
get availableColumnsForDynamicSlice() {
|
|
1672
|
-
return this.
|
|
827
|
+
return this._organizer.sliceBy.availableForDynamic;
|
|
1673
828
|
}
|
|
1674
829
|
isStaticSliceVisible(columnKey) {
|
|
1675
830
|
return !this._staticSliceHiddenKeys.has(columnKey);
|
|
@@ -1681,7 +836,7 @@ class TableComponent {
|
|
|
1681
836
|
else
|
|
1682
837
|
next.add(columnKey);
|
|
1683
838
|
this._staticSliceHiddenKeys = next;
|
|
1684
|
-
this.
|
|
839
|
+
this._organizer.updateHiddenStaticKeys(this._staticSliceHiddenKeys);
|
|
1685
840
|
this._sliceConfigs = this._computeSliceConfigs();
|
|
1686
841
|
// Si une slice vient d'être cachée, réinitialise le filtre actif pour éviter
|
|
1687
842
|
// un filtre orphelin (la slice n'est plus visible mais son filtre resterait actif)
|
|
@@ -1702,7 +857,7 @@ class TableComponent {
|
|
|
1702
857
|
return this._allDynamicSliceColumns;
|
|
1703
858
|
}
|
|
1704
859
|
isDynamicSliceActive(key) {
|
|
1705
|
-
return this.
|
|
860
|
+
return this._organizer.isDynamicSliceActive(key);
|
|
1706
861
|
}
|
|
1707
862
|
toggleDynamicSlice(col) {
|
|
1708
863
|
if (this.isDynamicSliceActive(col.key)) {
|
|
@@ -1719,7 +874,7 @@ class TableComponent {
|
|
|
1719
874
|
this.paginator?.firstPage();
|
|
1720
875
|
}
|
|
1721
876
|
onDynamicSliceKeysChange(keys) {
|
|
1722
|
-
this.
|
|
877
|
+
this._organizer.onDynamicSliceKeysChange(keys);
|
|
1723
878
|
this._recomputeShowSlicePanel();
|
|
1724
879
|
this._cdr.markForCheck();
|
|
1725
880
|
}
|
|
@@ -1742,10 +897,10 @@ class TableComponent {
|
|
|
1742
897
|
return this.resolvedConfig.pagination?.enabled !== false;
|
|
1743
898
|
}
|
|
1744
899
|
get resolvedPaginationShowFirstLastButtons() {
|
|
1745
|
-
return this.resolvedConfig.pagination?.showFirstLastButtons
|
|
900
|
+
return this.resolvedConfig.pagination?.showFirstLastButtons !== false;
|
|
1746
901
|
}
|
|
1747
902
|
get isColumnDragDropEnabled() {
|
|
1748
|
-
return this.
|
|
903
|
+
return this._organizer.isFieldDragDropEnabled;
|
|
1749
904
|
}
|
|
1750
905
|
get totalRows() {
|
|
1751
906
|
return this.activeGroupByKey
|
|
@@ -1754,7 +909,7 @@ class TableComponent {
|
|
|
1754
909
|
}
|
|
1755
910
|
// ── Gestion des colonnes
|
|
1756
911
|
onAddColumn(column) {
|
|
1757
|
-
this.
|
|
912
|
+
this._organizer.addField(column);
|
|
1758
913
|
this._preservePageIndex = this.paginator?.pageIndex ?? null;
|
|
1759
914
|
this.refreshViewModel();
|
|
1760
915
|
if (column.lazy) {
|
|
@@ -1764,13 +919,13 @@ class TableComponent {
|
|
|
1764
919
|
this.columnAdded.emit(column);
|
|
1765
920
|
}
|
|
1766
921
|
onColumnVisibilityChange(column, checked) {
|
|
1767
|
-
const isVisible = this.
|
|
922
|
+
const isVisible = this._organizer.isFieldVisible(column.key);
|
|
1768
923
|
if (checked && !isVisible) {
|
|
1769
924
|
this.onAddColumn(column);
|
|
1770
925
|
return;
|
|
1771
926
|
}
|
|
1772
927
|
if (!checked && isVisible) {
|
|
1773
|
-
const removed = this.
|
|
928
|
+
const removed = this._organizer.removeField(column.key);
|
|
1774
929
|
if (!removed)
|
|
1775
930
|
return;
|
|
1776
931
|
this._preservePageIndex = this.paginator?.pageIndex ?? null;
|
|
@@ -1779,14 +934,14 @@ class TableComponent {
|
|
|
1779
934
|
}
|
|
1780
935
|
}
|
|
1781
936
|
isColumnVisible(columnKey) {
|
|
1782
|
-
return this.
|
|
937
|
+
return this.activeFields.some(c => c.key === columnKey);
|
|
1783
938
|
}
|
|
1784
939
|
onRemoveColumn(columnKey, event) {
|
|
1785
940
|
event?.stopPropagation();
|
|
1786
|
-
const column = this.
|
|
941
|
+
const column = this.activeFields.find(item => item.key === columnKey);
|
|
1787
942
|
if (!column)
|
|
1788
943
|
return;
|
|
1789
|
-
const removed = this.
|
|
944
|
+
const removed = this._organizer.removeField(columnKey);
|
|
1790
945
|
if (!removed)
|
|
1791
946
|
return;
|
|
1792
947
|
this.refreshViewModel();
|
|
@@ -1810,11 +965,11 @@ class TableComponent {
|
|
|
1810
965
|
* Seules les valeurs explicitement en `px` sont sommables ; les pourcentages ou autres unités sont ignorés. */
|
|
1811
966
|
get tableMinWidth() {
|
|
1812
967
|
const hasResized = Object.keys(this._columnWidths).length > 0;
|
|
1813
|
-
const hasSomeExplicitWidth = hasResized || this.
|
|
968
|
+
const hasSomeExplicitWidth = hasResized || this.activeFields.some(c => c.width);
|
|
1814
969
|
if (!hasSomeExplicitWidth)
|
|
1815
970
|
return null;
|
|
1816
971
|
let sum = 0;
|
|
1817
|
-
for (const col of this.
|
|
972
|
+
for (const col of this.activeFields) {
|
|
1818
973
|
if (this._columnWidths[col.key] != null) {
|
|
1819
974
|
sum += this._columnWidths[col.key];
|
|
1820
975
|
}
|
|
@@ -1873,7 +1028,7 @@ class TableComponent {
|
|
|
1873
1028
|
return;
|
|
1874
1029
|
}
|
|
1875
1030
|
event.preventDefault();
|
|
1876
|
-
const moved = this.
|
|
1031
|
+
const moved = this._organizer.reorderFields(this.draggedColumnKey, targetColumnKey);
|
|
1877
1032
|
if (!moved) {
|
|
1878
1033
|
this.onHeaderDragEnd();
|
|
1879
1034
|
return;
|
|
@@ -1955,12 +1110,12 @@ class TableComponent {
|
|
|
1955
1110
|
savePreferences() {
|
|
1956
1111
|
if (!this._preferencesManager)
|
|
1957
1112
|
return;
|
|
1958
|
-
const dynamicSliceKeys = this.
|
|
1113
|
+
const dynamicSliceKeys = this._organizer.sliceBy.activeDynamicFields.map(c => c.key);
|
|
1959
1114
|
const config = {
|
|
1960
1115
|
search: this.searchQuery || undefined,
|
|
1961
1116
|
groupBy: this.activeGroupByKey,
|
|
1962
|
-
columnOrder: this.
|
|
1963
|
-
visibleColumns: this.
|
|
1117
|
+
columnOrder: this.activeFields.map(c => c.key),
|
|
1118
|
+
visibleColumns: this.activeFields.map(c => c.key),
|
|
1964
1119
|
columnWidths: Object.keys(this._columnWidths).length ? { ...this._columnWidths } : undefined,
|
|
1965
1120
|
slicePanelCollapsed: this._slicePanelCollapsed,
|
|
1966
1121
|
hiddenSliceKeys: [...this._staticSliceHiddenKeys],
|
|
@@ -1977,7 +1132,7 @@ class TableComponent {
|
|
|
1977
1132
|
this._preferencesManager.clear();
|
|
1978
1133
|
this._hasSavedConfig = false;
|
|
1979
1134
|
// Capturer les dynamic slices actives AVANT le reset (resetToDefaults() les vide)
|
|
1980
|
-
const dynamicSlicesToRemove = this.
|
|
1135
|
+
const dynamicSlicesToRemove = this._organizer.sliceBy.activeDynamicFields.slice();
|
|
1981
1136
|
// Reset complet de l'état vers les valeurs par défaut de la config
|
|
1982
1137
|
this.searchQuery = '';
|
|
1983
1138
|
this._columnWidths = {};
|
|
@@ -1992,9 +1147,9 @@ class TableComponent {
|
|
|
1992
1147
|
this._groupBy.setDefaultCollapsed(false);
|
|
1993
1148
|
// Slices statiques : revenir aux seules celles cachées par la config (pas par l'utilisateur)
|
|
1994
1149
|
this._staticSliceHiddenKeys = new Set(this._configHiddenKeys);
|
|
1995
|
-
this.
|
|
1150
|
+
this._organizer.updateHiddenStaticKeys(this._staticSliceHiddenKeys);
|
|
1996
1151
|
// Colonnes et groupBy : reset via la facade (retire les optionnelles, remet l'ordre config)
|
|
1997
|
-
this.
|
|
1152
|
+
this._organizer.resetToDefaults();
|
|
1998
1153
|
this._defaultGroupByApplied = false;
|
|
1999
1154
|
// Dynamic slices : les retirer du slice panel avec la liste capturée avant le reset
|
|
2000
1155
|
dynamicSlicesToRemove.forEach(col => {
|
|
@@ -2005,8 +1160,8 @@ class TableComponent {
|
|
|
2005
1160
|
if (this.slicePanelRef)
|
|
2006
1161
|
this.slicePanelRef.restoreFilters({});
|
|
2007
1162
|
// Rafraîchit toute la vue
|
|
2008
|
-
this.
|
|
2009
|
-
this.renderedColumns = this.
|
|
1163
|
+
this.activeFields = this._organizer.fields.activeFields;
|
|
1164
|
+
this.renderedColumns = this.activeFields.map(c => c.key);
|
|
2010
1165
|
this._pendingDynamicSliceKeys = null;
|
|
2011
1166
|
this._pendingSliceFilters = null;
|
|
2012
1167
|
this.refreshViewModel();
|
|
@@ -2034,7 +1189,7 @@ class TableComponent {
|
|
|
2034
1189
|
this._columnWidths = { ...saved.columnWidths };
|
|
2035
1190
|
// Group by
|
|
2036
1191
|
if (saved.groupBy !== undefined) {
|
|
2037
|
-
this.
|
|
1192
|
+
this._organizer.setGroupBy(saved.groupBy);
|
|
2038
1193
|
if (saved.groupBy) {
|
|
2039
1194
|
this._groupBy.setDefaultCollapsed(true);
|
|
2040
1195
|
this._groupBy.reset();
|
|
@@ -2046,9 +1201,9 @@ class TableComponent {
|
|
|
2046
1201
|
// Slices statiques cachées — on utilise la valeur exacte sauvegardée (pas les defaults de config)
|
|
2047
1202
|
if (saved.hiddenSliceKeys !== undefined) {
|
|
2048
1203
|
this._staticSliceHiddenKeys = new Set(saved.hiddenSliceKeys);
|
|
2049
|
-
this.
|
|
1204
|
+
this._organizer.updateHiddenStaticKeys(this._staticSliceHiddenKeys);
|
|
2050
1205
|
}
|
|
2051
|
-
// Colonnes visibles + ordre — reconstruit
|
|
1206
|
+
// Colonnes visibles + ordre — reconstruit activeFields en une seule passe.
|
|
2052
1207
|
// On part de saved.columnOrder (ordre sauvegardé) si disponible, sinon saved.visibleColumns.
|
|
2053
1208
|
// Cela gère à la fois : colonnes optionnelles ajoutées, colonnes retirées, et réordonnancement.
|
|
2054
1209
|
const targetKeys = saved.columnOrder ?? saved.visibleColumns;
|
|
@@ -2061,7 +1216,7 @@ class TableComponent {
|
|
|
2061
1216
|
// Colonnes actives actuelllement non présentes dans targetKeys (non-optional passées sous silence)
|
|
2062
1217
|
// → on les exclut : l'utilisateur avait explicitement choisi ce sous-ensemble
|
|
2063
1218
|
if (ordered.length > 0) {
|
|
2064
|
-
this.
|
|
1219
|
+
this._organizer.setActiveFields(ordered);
|
|
2065
1220
|
}
|
|
2066
1221
|
}
|
|
2067
1222
|
// Dynamic slices — différé : slicePanelRef pas encore disponible au moment de ngOnChanges
|
|
@@ -2077,9 +1232,9 @@ class TableComponent {
|
|
|
2077
1232
|
}
|
|
2078
1233
|
}
|
|
2079
1234
|
canRemoveColumn(column) {
|
|
2080
|
-
return (this.
|
|
1235
|
+
return (this._organizer.allowFieldRemoval &&
|
|
2081
1236
|
column.removable !== false &&
|
|
2082
|
-
this.
|
|
1237
|
+
this.activeFields.length > 1);
|
|
2083
1238
|
}
|
|
2084
1239
|
isCellEmpty(value) {
|
|
2085
1240
|
return value === '' || value == null;
|
|
@@ -2104,7 +1259,7 @@ class TableComponent {
|
|
|
2104
1259
|
? this.resolvedConfig.preferences.tableId
|
|
2105
1260
|
: null;
|
|
2106
1261
|
const currentId = prefsId ?? null;
|
|
2107
|
-
// Préférences à appliquer après
|
|
1262
|
+
// Préférences à appliquer après _organizer.update() (quand activeFields est déjà peuplé)
|
|
2108
1263
|
let pendingPrefs = null;
|
|
2109
1264
|
if (currentId && this._preferencesManager?.key !== ('jqt_prefs_' + currentId)) {
|
|
2110
1265
|
this._preferencesManager = new TablePreferencesManager(currentId);
|
|
@@ -2116,21 +1271,21 @@ class TableComponent {
|
|
|
2116
1271
|
this._hasSavedConfig = false;
|
|
2117
1272
|
}
|
|
2118
1273
|
// Délègue à la facade : mise à jour de l'état View (colonnes, groupBy meta, sliceBy meta)
|
|
2119
|
-
this.
|
|
1274
|
+
this._organizer.update({
|
|
2120
1275
|
config: this.resolvedConfig.view,
|
|
2121
|
-
|
|
1276
|
+
fields: this.resolvedConfig.columns ?? [],
|
|
2122
1277
|
sliceConfigs: this.resolvedConfig.slices ?? [],
|
|
2123
1278
|
defaultGroupBy: this.resolvedConfig.defaultGroupBy,
|
|
2124
1279
|
});
|
|
2125
|
-
if (!pendingPrefs && !this._defaultGroupByApplied && !this.
|
|
1280
|
+
if (!pendingPrefs && !this._defaultGroupByApplied && !this._organizer.groupBy.userCustomized && this._organizer.groupBy.activeKey != null) {
|
|
2126
1281
|
this._defaultGroupByApplied = true;
|
|
2127
1282
|
this._groupBy.setDefaultCollapsed(true);
|
|
2128
1283
|
this._groupBy.reset();
|
|
2129
1284
|
this._groupBy.groupPageSize = this.resolveDefaultGroupPageSize();
|
|
2130
1285
|
}
|
|
2131
1286
|
// Synchronise la propriété stable (évite NG0100 — la facade est source de vérité)
|
|
2132
|
-
this.
|
|
2133
|
-
this.renderedColumns = this.
|
|
1287
|
+
this.activeFields = this._organizer.fields.activeFields;
|
|
1288
|
+
this.renderedColumns = this.activeFields.map(c => c.key);
|
|
2134
1289
|
this.pageSize = this.paginator?.pageSize || this.resolvePageSize();
|
|
2135
1290
|
this.pageSizeOptions = this.resolvePageSizeOptions();
|
|
2136
1291
|
this.pageSizeOptionsGroupBy = this.resolvePageSizeOptionsGroupBy();
|
|
@@ -2148,10 +1303,10 @@ class TableComponent {
|
|
|
2148
1303
|
// (pendingPrefs les gèrera lui-même via _applyPreferences → hiddenSliceKeys)
|
|
2149
1304
|
if (!pendingPrefs) {
|
|
2150
1305
|
this._staticSliceHiddenKeys = new Set(newConfigHidden);
|
|
2151
|
-
this.
|
|
1306
|
+
this._organizer.updateHiddenStaticKeys(this._staticSliceHiddenKeys);
|
|
2152
1307
|
}
|
|
2153
1308
|
}
|
|
2154
|
-
// Applique les préférences ICI, APRÈS
|
|
1309
|
+
// Applique les préférences ICI, APRÈS _organizer.update() ET après _configHiddenKeys mis à jour,
|
|
2155
1310
|
// mais avant _computeSliceConfigs() pour que hiddenSliceKeys soit déjà restauré.
|
|
2156
1311
|
if (pendingPrefs) {
|
|
2157
1312
|
this._applyPreferences(pendingPrefs);
|
|
@@ -2209,6 +1364,7 @@ class TableComponent {
|
|
|
2209
1364
|
}
|
|
2210
1365
|
}
|
|
2211
1366
|
this._sliceByMenuItems = items;
|
|
1367
|
+
this._invalidateOrganizerConfig(); // les slices ont changé
|
|
2212
1368
|
}
|
|
2213
1369
|
get sliceByMenuItems() { return this._sliceByMenuItems; }
|
|
2214
1370
|
onSliceMenuItemClick(item) {
|
|
@@ -2269,7 +1425,7 @@ class TableComponent {
|
|
|
2269
1425
|
pageRows.forEach((row, i) => {
|
|
2270
1426
|
const idx = start + i;
|
|
2271
1427
|
const p = { [ROW_RAW_KEY]: row, [ROW_INDEX_KEY]: idx };
|
|
2272
|
-
this.
|
|
1428
|
+
this.activeFields.forEach((col) => {
|
|
2273
1429
|
p[col.key] = this.resolveCellValue(col, row, idx);
|
|
2274
1430
|
});
|
|
2275
1431
|
projected.push(p);
|
|
@@ -2382,7 +1538,7 @@ class TableComponent {
|
|
|
2382
1538
|
const result = [];
|
|
2383
1539
|
rows.forEach((row, index) => {
|
|
2384
1540
|
const projected = { [ROW_RAW_KEY]: row, [ROW_INDEX_KEY]: index };
|
|
2385
|
-
this.
|
|
1541
|
+
this.activeFields.forEach((col) => {
|
|
2386
1542
|
projected[col.key] = this.resolveCellValue(col, row, index);
|
|
2387
1543
|
});
|
|
2388
1544
|
result.push(projected);
|
|
@@ -2490,7 +1646,7 @@ class TableComponent {
|
|
|
2490
1646
|
groupRows.slice(start, end).forEach((row, i) => {
|
|
2491
1647
|
const idx = start + i;
|
|
2492
1648
|
const projected = { [ROW_RAW_KEY]: row, [ROW_INDEX_KEY]: idx };
|
|
2493
|
-
this.
|
|
1649
|
+
this.activeFields.forEach((col) => {
|
|
2494
1650
|
projected[col.key] = this.resolveCellValue(col, row, idx);
|
|
2495
1651
|
});
|
|
2496
1652
|
result.push(projected);
|
|
@@ -2644,11 +1800,11 @@ class TableComponent {
|
|
|
2644
1800
|
}
|
|
2645
1801
|
}
|
|
2646
1802
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2647
|
-
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", clearSearchInput: "clearSearchInput" }, outputs: { addRequested: "addRequested", columnAdded: "columnAdded", columnRemoved: "columnRemoved", categorySelected: "categorySelected", rowSelected: "rowSelected", sortChange: "sortChange", pageChange: "pageChange", searchChange: "searchChange", groupByChange: "groupByChange", columnsChange: "columnsChange" }, providers: [{ provide: MatPaginatorIntl, useFactory: getFrenchPaginatorIntl }], queries: [{ propertyName: "_cellDefs", predicate: JqtCellDefDirective }], viewQueries: [{ propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }, { propertyName: "tableBodyScrollRef", first: true, predicate: ["tableBodyScroll"], descendants: true }, { propertyName: "slicePanelRef", first: true, predicate: SlicePanelComponent, descendants: true }, { propertyName: "viewMenuTrigger", first: true, predicate: ["viewMenuTrigger"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<section class=\"jquery-table-wrapper\">\n <header class=\"table-header\" *ngIf=\"showTitle\">\n <h3>{{ title }}</h3>\n </header>\n\n <div\n class=\"table-layout\"\n [class.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=\"showViewButton\" class=\"table-toolbar-right\">\n <button *ngIf=\"showViewButton\" type=\"button\" class=\"view-btn\" [matMenuTriggerFor]=\"viewMenu\" #viewMenuTrigger=\"matMenuTrigger\" [attr.aria-label]=\"'View'\">\n <mat-icon class=\"view-btn-icon\">settings</mat-icon>\n </button>\n\n <mat-menu #viewMenu=\"matMenu\" [panelClass]=\"'view-menu-panel'\">\n <button *ngIf=\"showFields\" mat-menu-item [matMenuTriggerFor]=\"fieldsSubMenu\" class=\"view-menu-sub-trigger\">\n <span class=\"view-menu-sub-label\">{{ i18n.viewMenuFields }}</span>\n <span class=\"view-menu-sub-value\">{{ visibleColumns.length }}/{{ totalColumnCount }}</span>\n </button>\n <button *ngIf=\"showGroupBySection\" mat-menu-item [matMenuTriggerFor]=\"groupBySubMenu\" class=\"view-menu-sub-trigger\">\n <span class=\"view-menu-sub-label\">{{ i18n.viewMenuGroupBy }}</span>\n <span class=\"view-menu-sub-value\">{{ activeGroupByLabel }}</span>\n </button>\n <button *ngIf=\"showAddSliceButton\" mat-menu-item [matMenuTriggerFor]=\"sliceBySubMenu\" class=\"view-menu-sub-trigger\">\n <span class=\"view-menu-sub-label\">{{ i18n.viewMenuSliceBy }}</span>\n <span class=\"view-menu-sub-value\">{{ activeSliceByLabel }}</span>\n </button>\n <mat-divider *ngIf=\"showExportButton\" class=\"view-menu-export-divider\"></mat-divider>\n <button *ngIf=\"showExportButton\" mat-menu-item type=\"button\" class=\"view-menu-export-item\"\n (click)=\"onExport(); viewMenuTrigger.closeMenu()\">\n <mat-icon class=\"view-menu-export-icon\">download</mat-icon>\n <span class=\"view-menu-sub-label\">{{ i18n.exportButtonLabel }}</span>\n </button>\n <mat-divider *ngIf=\"showPreferencesMenu\" class=\"view-menu-export-divider\"></mat-divider>\n <button *ngIf=\"showPreferencesMenu\" mat-menu-item [matMenuTriggerFor]=\"preferencesSubMenu\" class=\"view-menu-sub-trigger\">\n <span class=\"view-menu-sub-label\">{{ i18n.preferencesMenuLabel }}</span>\n </button>\n </mat-menu>\n\n <mat-menu #preferencesSubMenu=\"matMenu\" [panelClass]=\"'view-submenu-panel'\">\n <div class=\"view-submenu\" (mousedown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"view-menu-item\" (mousedown)=\"enterEditMode(); viewMenuTrigger.closeMenu()\">\n <span class=\"view-menu-check\" aria-hidden=\"true\"></span>\n <mat-icon class=\"view-menu-col-icon\">edit</mat-icon>\n {{ i18n.preferencesEdit }}\n </button>\n <button type=\"button\" class=\"view-menu-item\" (mousedown)=\"savePreferences(); viewMenuTrigger.closeMenu()\">\n <span class=\"view-menu-check\" aria-hidden=\"true\"></span>\n <mat-icon class=\"view-menu-col-icon\">save</mat-icon>\n {{ i18n.preferencesSave }}\n </button>\n <button type=\"button\" class=\"view-menu-item\" [disabled]=\"!hasSavedPreferences\" (mousedown)=\"clearPreferences(); viewMenuTrigger.closeMenu()\">\n <span class=\"view-menu-check\" aria-hidden=\"true\"></span>\n <mat-icon class=\"view-menu-col-icon\">delete_outline</mat-icon>\n {{ i18n.preferencesClear }}\n </button>\n </div>\n </mat-menu>\n\n <mat-menu #fieldsSubMenu=\"matMenu\" [panelClass]=\"'view-submenu-panel'\">\n <div class=\"view-submenu\" (mousedown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"view-menu-item\" *ngFor=\"let column of menuBaseColumns\"\n [disabled]=\"!column.optional && !allowColumnRemoval\"\n (mousedown)=\"onColumnVisibilityChange(column, !isColumnVisible(column.key))\">\n <span class=\"view-menu-check\">{{ isColumnVisible(column.key) ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"column.icon\" class=\"view-menu-col-icon\" fontSet=\"material-icons-outlined\">{{ column.icon }}</mat-icon>\n {{ colLabel(column) }}\n </button>\n <ng-container *ngIf=\"menuOptionalColumns.length > 0\">\n <button type=\"button\" class=\"view-menu-item\" *ngFor=\"let column of menuOptionalColumns\"\n [disabled]=\"(isColumnVisible(column.key) && visibleColumns.length === 1) || getLazyColumnStatus(column.key) === 'loading'\"\n (mousedown)=\"onColumnVisibilityChange(column, !isColumnVisible(column.key))\">\n <span class=\"view-menu-check\">{{ isColumnVisible(column.key) ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"column.icon\" class=\"view-menu-col-icon\" fontSet=\"material-icons-outlined\">{{ column.icon }}</mat-icon>\n {{ colLabel(column) }}\n <span *ngIf=\"column.lazy && getLazyColumnStatus(column.key) === 'loading'\" class=\"lazy-menu-spinner\">\u21BB</span>\n <span *ngIf=\"column.lazy && getLazyColumnStatus(column.key) === 'error'\" class=\"lazy-menu-error\" [title]=\"i18n.lazyErrorMenuTitle\">\u26A0</span>\n </button>\n </ng-container>\n </div>\n </mat-menu>\n\n <mat-menu #groupBySubMenu=\"matMenu\" [panelClass]=\"'view-submenu-panel'\">\n <div class=\"view-submenu\" (mousedown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"view-menu-item\" *ngFor=\"let col of groupByColumns\"\n [class.active]=\"activeGroupByKey === col.key\"\n (mousedown)=\"onGroupByChange(activeGroupByKey === col.key ? null : col.key)\">\n <span class=\"view-menu-check\">{{ activeGroupByKey === col.key ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"col.icon\" class=\"view-menu-col-icon\" fontSet=\"material-icons-outlined\">{{ col.icon }}</mat-icon>\n {{ colLabel(col) }}\n </button>\n </div>\n </mat-menu>\n\n <mat-menu #sliceBySubMenu=\"matMenu\" [panelClass]=\"'view-submenu-panel'\">\n <div class=\"view-submenu\" (mousedown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"view-menu-item\" *ngFor=\"let item of sliceByMenuItems\"\n [class.active]=\"isSliceMenuItemActive(item)\"\n (mousedown)=\"onSliceMenuItemClick(item)\">\n <span class=\"view-menu-check\">{{ isSliceMenuItemActive(item) ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"item.icon\" class=\"view-menu-col-icon\" fontSet=\"material-icons-outlined\">{{ item.icon }}</mat-icon>\n {{ item.title }}\n </button>\n </div>\n </mat-menu>\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\" *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\" *ngIf=\"!isDefaultGroupByActive\" (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\" *ngIf=\"!isDefaultGroupByActive\"></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}.jquery-table-wrapper{display:flex;flex-direction:column;width:100%;height:100%;min-height:inherit;padding:2px 2px 0;box-sizing:border-box}.table-header{display:flex;align-items:center;justify-content:flex-start;gap:8px;margin-bottom:12px}.table-header h3{margin:0;font-size:20px;font-weight:600;color:var(--jqt-title-color)}.table-layout{display:grid;grid-template-columns:minmax(155px,190px) minmax(0,1fr);gap:12px;align-items:stretch;min-height:inherit;height:100%}.table-layout.no-slice{grid-template-columns:minmax(0,1fr)}.table-layout.slice-collapsed{grid-template-columns:40px minmax(0,1fr)}.table-shell{display:flex;flex-direction:column;flex:1 1 0;min-height:0;min-width:0;width:100%;max-width:100%}.table-body-wrapper{position:relative;flex:1 1 0;min-height:0;display:flex;flex-direction:column}.table-body-scroll{flex:1 1 0;overflow-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}.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 .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:nowrap;height:var(--actual-header-height, 56px);padding:0 16px;gap:16px;width:100%;box-sizing:border-box}.group-bar-separator{width:1px;height:20px;background:#9ca3af;flex-shrink:0;margin:0 4px}.group-bottom-bar-left{display:flex;align-items:center;gap:8px;flex-shrink:0}.group-sort-controls{display:flex;align-items:center;gap:2px}.group-sort-label{font-size:12px;color:var(--jqt-footer-text-color);white-space:nowrap;margin-right:2px}.group-sort-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--jqt-footer-icon-color);cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease}.group-sort-btn mat-icon{font-size:16px;width:16px;height:16px;line-height:16px}.group-sort-btn:hover{background:#0000000a}.group-sort-btn.active{color:var(--jqt-primary-color, #1976d2);border-color:var(--jqt-primary-color, #1976d2);background:#1976d214}:host ::ng-deep .group-bottom-bar .mat-mdc-paginator-page-size{display:flex;align-items:center;gap:8px}:host ::ng-deep .group-bottom-bar .mat-mdc-paginator-page-size-label{font-size:12px;color:var(--jqt-footer-text-color);white-space:nowrap}:host ::ng-deep .group-bottom-bar .mat-mdc-paginator-page-size-select{width:72px;font-size:12px}:host ::ng-deep .group-bottom-bar .mat-mdc-text-field-wrapper{padding:0 8px}:host ::ng-deep .group-bottom-bar .mdc-text-field--outlined{height:28px}:host ::ng-deep .group-bottom-bar .mat-mdc-form-field-infix{padding-top:0;padding-bottom:0;min-height:0;height:28px;display:flex;align-items:center}:host ::ng-deep .group-bottom-bar .mat-mdc-select-value-text{font-size:12px;color:var(--jqt-text-color)}:host ::ng-deep .group-bottom-bar .mdc-notched-outline__leading,:host ::ng-deep .group-bottom-bar .mdc-notched-outline__notch,:host ::ng-deep .group-bottom-bar .mdc-notched-outline__trailing{border-color:var(--jqt-border-color)}:host ::ng-deep .group-bottom-bar .mat-mdc-select-arrow{color:var(--jqt-footer-text-color)}.group-reset-btn{display:inline-flex;align-items:center;gap:6px;height:30px;padding:0 10px;border:1px solid var(--jqt-border-color);border-radius:8px;background:var(--jqt-surface-color);color:var(--jqt-text-secondary);font-size:12px;font-weight:500;font-family:inherit;cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease;white-space:nowrap;flex-shrink:0}.group-reset-btn:hover{background:var(--jqt-danger-hover-bg);color:var(--jqt-danger-color);border-color:var(--jqt-danger-border)}.group-reset-icon{width:14px;height:14px;flex-shrink:0}::ng-deep .mat-mdc-paginator-select-page-size .mat-pseudo-checkbox{display:none!important}::ng-deep .group-page-size-panel{min-width:90px!important}::ng-deep .group-page-size-panel .mat-mdc-option{justify-content:center;min-height:40px}::ng-deep .group-page-size-panel .mdc-list-item__primary-text{text-align:center;width:100%}@media (max-width: 960px){.table-layout{grid-template-columns:1fr}}::ng-deep .view-menu-panel .mat-mdc-menu-panel{min-width:220px!important;overflow:visible!important}::ng-deep .view-menu-panel .mat-mdc-menu-content{padding:4px 0!important}::ng-deep .view-submenu-panel .mat-mdc-menu-panel{min-width:200px!important}::ng-deep .view-submenu-panel .mat-mdc-menu-content{padding:0!important}::ng-deep .view-menu-panel .view-menu-sub-trigger.mat-mdc-menu-item{min-height:42px;padding:0 14px;display:flex;align-items:center;box-sizing:border-box}::ng-deep .view-menu-panel .view-menu-sub-trigger .mdc-list-item__primary-text{display:flex;align-items:center;width:100%;gap:0;overflow:hidden}::ng-deep .view-menu-sub-label{font-size:13px;font-weight:500;color:var(--jqt-text-color);flex:0 0 auto;white-space:nowrap;padding-right:8px}::ng-deep .view-menu-sub-value{font-size:12px;color:var(--jqt-text-secondary);flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:right}::ng-deep .view-submenu{padding:4px 6px;min-width:180px;box-sizing:border-box}::ng-deep .view-submenu .view-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;color:var(--jqt-text-color);cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}::ng-deep .view-submenu .view-menu-item:hover{background:var(--jqt-row-hover-bg)}::ng-deep .view-submenu .view-menu-item.active{font-weight:600}::ng-deep .view-submenu .view-menu-item:disabled{opacity:.4;cursor:default}::ng-deep .view-menu-col-icon{flex-shrink:0;font-size:14px!important;width:14px!important;height:14px!important;color:var(--jqt-text-muted)}::ng-deep .view-menu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:var(--jqt-text-color)}::ng-deep .view-menu-separator{border:none;border-top:1px solid var(--jqt-border-color);margin:4px 0}.lazy-skeleton{display:block;height:12px;width:70%;border-radius:4px;background:linear-gradient(90deg,var(--jqt-border-color) 25%,var(--jqt-surface-alt-color) 50%,var(--jqt-border-color) 75%);background-size:200% 100%;animation:lazy-shimmer 1.4s ease-in-out infinite}.group-lazy-key-skeleton{flex:1 1 auto;min-width:80px;height:13px;border-radius:4px}@keyframes lazy-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}.lazy-error-btn{display:inline-flex;align-items:center;gap:4px;background:none;border:1px solid #fca5a5;border-radius:4px;padding:2px 6px;cursor:pointer;color:#ef4444;font-size:12px;opacity:.85;transition:opacity .15s}.lazy-error-btn:hover{opacity:1;background:#fef2f2}.lazy-error-icon{font-size:12px}.lazy-error-label{font-size:11px;font-weight:500}::ng-deep .lazy-menu-spinner{display:inline-block;margin-left:auto;font-size:13px;color:var(--jqt-text-secondary);animation:lazy-spin 1s linear infinite}::ng-deep .lazy-menu-error{display:inline-block;margin-left:auto;font-size:13px;color:#ef4444}@keyframes lazy-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i3.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i3.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i3.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i3.MatColumnDef, selector: "[matColumnDef]", inputs: ["sticky", "matColumnDef"] }, { kind: "directive", type: i3.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i3.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i3.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i3.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i3.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i3.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "directive", type: i3.MatNoDataRow, selector: "ng-template[matNoDataRow]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i4.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i5.MatPaginator, selector: "mat-paginator", inputs: ["disabled"], exportAs: ["matPaginator"] }, { kind: "ngmodule", type: MatSortModule }, { kind: "directive", type: i6.MatSort, selector: "[matSort]", inputs: ["matSortDisabled", "matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i6.MatSortHeader, selector: "[mat-sort-header]", inputs: ["disabled", "mat-sort-header", "arrowPosition", "start", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i7.MatMenu, selector: "mat-menu", exportAs: ["matMenu"] }, { kind: "component", type: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["disabled", "disableRipple", "role"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i9.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "component", type: i10.MatSelect, selector: "mat-select", inputs: ["disabled", "disableRipple", "tabIndex", "panelWidth", "hideSingleSelectionIndicator"], exportAs: ["matSelect"] }, { kind: "directive", type: i10.MatSelectTrigger, selector: "mat-select-trigger" }, { kind: "component", type: i11.MatOption, selector: "mat-option", exportAs: ["matOption"] }, { kind: "component", type: SlicePanelComponent, selector: "slice-panel", inputs: ["sliceConfigs", "columns", "data", "lazyData", "lazyStatus", "showToggle", "showCounts", "collapsedByDefault", "alwaysShow"], outputs: ["filterChange", "activeKeysChange", "dynamicSliceKeysChange", "collapsedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1803
|
+
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", clearSearchInput: "clearSearchInput" }, outputs: { addRequested: "addRequested", columnAdded: "columnAdded", columnRemoved: "columnRemoved", categorySelected: "categorySelected", rowSelected: "rowSelected", sortChange: "sortChange", pageChange: "pageChange", searchChange: "searchChange", groupByChange: "groupByChange", columnsChange: "columnsChange" }, 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 <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}.jquery-table-wrapper{display:flex;flex-direction:column;width:100%;height:100%;min-height:inherit;padding:2px 2px 0;box-sizing:border-box}.table-header{display:flex;align-items:center;justify-content:flex-start;gap:8px;margin-bottom:12px}.table-header h3{margin:0;font-size:20px;font-weight:600;color:var(--jqt-title-color)}.table-layout{display:grid;grid-template-columns:minmax(155px,190px) minmax(0,1fr);gap:12px;align-items:stretch;min-height:inherit;height:100%}.table-layout.no-slice{grid-template-columns:minmax(0,1fr)}.table-layout.slice-collapsed{grid-template-columns:40px minmax(0,1fr)}.table-shell{display:flex;flex-direction:column;flex:1 1 0;min-height:0;min-width:0;width:100%;max-width:100%}.table-body-wrapper{position:relative;flex:1 1 0;min-height:0;display:flex;flex-direction:column}.table-body-scroll{flex:1 1 0;overflow-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}.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:nowrap;height:var(--actual-header-height, 56px);padding:0 16px;gap:16px;width:100%;box-sizing:border-box}.group-bar-separator{width:1px;height:20px;background:#9ca3af;flex-shrink:0;margin:0 4px}.group-bottom-bar-left{display:flex;align-items:center;gap:8px;flex-shrink:0}.group-sort-controls{display:flex;align-items:center;gap:2px}.group-sort-label{font-size:12px;color:var(--jqt-footer-text-color);white-space:nowrap;margin-right:2px}.group-sort-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--jqt-footer-icon-color);cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease}.group-sort-btn mat-icon{font-size:16px;width:16px;height:16px;line-height:16px}.group-sort-btn:hover{background:#0000000a}.group-sort-btn.active{color:var(--jqt-primary-color, #1976d2);border-color:var(--jqt-primary-color, #1976d2);background:#1976d214}:host ::ng-deep .group-bottom-bar .mat-mdc-paginator-page-size{display:flex;align-items:center;gap:8px}:host ::ng-deep .group-bottom-bar .mat-mdc-paginator-page-size-label{font-size:12px;color:var(--jqt-footer-text-color);white-space:nowrap}:host ::ng-deep .group-bottom-bar .mat-mdc-paginator-page-size-select{width:72px;font-size:12px}:host ::ng-deep .group-bottom-bar .mat-mdc-text-field-wrapper{padding:0 8px}:host ::ng-deep .group-bottom-bar .mdc-text-field--outlined{height:28px}:host ::ng-deep .group-bottom-bar .mat-mdc-form-field-infix{padding-top:0;padding-bottom:0;min-height:0;height:28px;display:flex;align-items:center}:host ::ng-deep .group-bottom-bar .mat-mdc-select-value-text{font-size:12px;color:var(--jqt-text-color)}:host ::ng-deep .group-bottom-bar .mdc-notched-outline__leading,:host ::ng-deep .group-bottom-bar .mdc-notched-outline__notch,:host ::ng-deep .group-bottom-bar .mdc-notched-outline__trailing{border-color:var(--jqt-border-color)}:host ::ng-deep .group-bottom-bar .mat-mdc-select-arrow{color:var(--jqt-footer-text-color)}.group-reset-btn{display:inline-flex;align-items:center;gap:6px;height:30px;padding:0 10px;border:1px solid var(--jqt-border-color);border-radius:8px;background:var(--jqt-surface-color);color:var(--jqt-text-secondary);font-size:12px;font-weight:500;font-family:inherit;cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease;white-space:nowrap;flex-shrink:0}.group-reset-btn:hover{background:var(--jqt-danger-hover-bg);color:var(--jqt-danger-color);border-color:var(--jqt-danger-border)}.group-reset-icon{width:14px;height:14px;flex-shrink:0}::ng-deep .mat-mdc-paginator-select-page-size .mat-pseudo-checkbox{display:none!important}::ng-deep .group-page-size-panel{min-width:90px!important}::ng-deep .group-page-size-panel .mat-mdc-option{justify-content:center;min-height:40px}::ng-deep .group-page-size-panel .mdc-list-item__primary-text{text-align:center;width:100%}@media (max-width: 960px){.table-layout{grid-template-columns:1fr}}::ng-deep .view-menu-panel .mat-mdc-menu-panel{min-width:220px!important;overflow:visible!important}::ng-deep .view-menu-panel .mat-mdc-menu-content{padding:4px 0!important}::ng-deep .view-submenu-panel .mat-mdc-menu-panel{min-width:200px!important}::ng-deep .view-submenu-panel .mat-mdc-menu-content{padding:0!important}::ng-deep .view-menu-panel .view-menu-sub-trigger.mat-mdc-menu-item{min-height:42px;padding:0 14px;display:flex;align-items:center;box-sizing:border-box}::ng-deep .view-menu-panel .view-menu-sub-trigger .mdc-list-item__primary-text{display:flex;align-items:center;width:100%;gap:0;overflow:hidden}::ng-deep .view-menu-sub-label{font-size:13px;font-weight:500;color:var(--jqt-text-color);flex:0 0 auto;white-space:nowrap;padding-right:8px}::ng-deep .view-menu-sub-value{font-size:12px;color:var(--jqt-text-secondary);flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:right}::ng-deep .view-submenu{padding:4px 6px;min-width:180px;box-sizing:border-box}::ng-deep .view-submenu .view-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;color:var(--jqt-text-color);cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}::ng-deep .view-submenu .view-menu-item:hover{background:var(--jqt-row-hover-bg)}::ng-deep .view-submenu .view-menu-item.active{font-weight:600}::ng-deep .view-submenu .view-menu-item:disabled{opacity:.4;cursor:default}::ng-deep .view-menu-col-icon{flex-shrink:0;font-size:14px!important;width:14px!important;height:14px!important;color:var(--jqt-text-muted)}::ng-deep .view-menu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:var(--jqt-text-color)}::ng-deep .view-menu-separator{border:none;border-top:1px solid var(--jqt-border-color);margin:4px 0}.lazy-skeleton{display:block;height:12px;width:70%;border-radius:4px;background:linear-gradient(90deg,var(--jqt-border-color) 25%,var(--jqt-surface-alt-color) 50%,var(--jqt-border-color) 75%);background-size:200% 100%;animation:lazy-shimmer 1.4s ease-in-out infinite}.group-lazy-key-skeleton{flex:1 1 auto;min-width:80px;height:13px;border-radius:4px}@keyframes lazy-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}.lazy-error-btn{display:inline-flex;align-items:center;gap:4px;background:none;border:1px solid #fca5a5;border-radius:4px;padding:2px 6px;cursor:pointer;color:#ef4444;font-size:12px;opacity:.85;transition:opacity .15s}.lazy-error-btn:hover{opacity:1;background:#fef2f2}.lazy-error-icon{font-size:12px}.lazy-error-label{font-size:11px;font-weight:500}::ng-deep .lazy-menu-spinner{display:inline-block;margin-left:auto;font-size:13px;color:var(--jqt-text-secondary);animation:lazy-spin 1s linear infinite}::ng-deep .lazy-menu-error{display:inline-block;margin-left:auto;font-size:13px;color:#ef4444}@keyframes lazy-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.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 }); }
|
|
2648
1804
|
}
|
|
2649
1805
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TableComponent, decorators: [{
|
|
2650
1806
|
type: Component,
|
|
2651
|
-
args: [{ standalone: true, selector: 'jquery-table', imports: [CommonModule, FormsModule, MatTableModule, MatButtonModule, MatDividerModule, MatPaginatorModule, MatSortModule, MatMenuModule, MatIconModule, MatSelectModule, SlicePanelComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [{ provide: MatPaginatorIntl, useFactory: getFrenchPaginatorIntl }], template: "<section class=\"jquery-table-wrapper\">\n <header class=\"table-header\" *ngIf=\"showTitle\">\n <h3>{{ title }}</h3>\n </header>\n\n <div\n class=\"table-layout\"\n [class.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=\"showViewButton\" class=\"table-toolbar-right\">\n <button *ngIf=\"showViewButton\" type=\"button\" class=\"view-btn\" [matMenuTriggerFor]=\"viewMenu\" #viewMenuTrigger=\"matMenuTrigger\" [attr.aria-label]=\"'View'\">\n <mat-icon class=\"view-btn-icon\">settings</mat-icon>\n </button>\n\n <mat-menu #viewMenu=\"matMenu\" [panelClass]=\"'view-menu-panel'\">\n <button *ngIf=\"showFields\" mat-menu-item [matMenuTriggerFor]=\"fieldsSubMenu\" class=\"view-menu-sub-trigger\">\n <span class=\"view-menu-sub-label\">{{ i18n.viewMenuFields }}</span>\n <span class=\"view-menu-sub-value\">{{ visibleColumns.length }}/{{ totalColumnCount }}</span>\n </button>\n <button *ngIf=\"showGroupBySection\" mat-menu-item [matMenuTriggerFor]=\"groupBySubMenu\" class=\"view-menu-sub-trigger\">\n <span class=\"view-menu-sub-label\">{{ i18n.viewMenuGroupBy }}</span>\n <span class=\"view-menu-sub-value\">{{ activeGroupByLabel }}</span>\n </button>\n <button *ngIf=\"showAddSliceButton\" mat-menu-item [matMenuTriggerFor]=\"sliceBySubMenu\" class=\"view-menu-sub-trigger\">\n <span class=\"view-menu-sub-label\">{{ i18n.viewMenuSliceBy }}</span>\n <span class=\"view-menu-sub-value\">{{ activeSliceByLabel }}</span>\n </button>\n <mat-divider *ngIf=\"showExportButton\" class=\"view-menu-export-divider\"></mat-divider>\n <button *ngIf=\"showExportButton\" mat-menu-item type=\"button\" class=\"view-menu-export-item\"\n (click)=\"onExport(); viewMenuTrigger.closeMenu()\">\n <mat-icon class=\"view-menu-export-icon\">download</mat-icon>\n <span class=\"view-menu-sub-label\">{{ i18n.exportButtonLabel }}</span>\n </button>\n <mat-divider *ngIf=\"showPreferencesMenu\" class=\"view-menu-export-divider\"></mat-divider>\n <button *ngIf=\"showPreferencesMenu\" mat-menu-item [matMenuTriggerFor]=\"preferencesSubMenu\" class=\"view-menu-sub-trigger\">\n <span class=\"view-menu-sub-label\">{{ i18n.preferencesMenuLabel }}</span>\n </button>\n </mat-menu>\n\n <mat-menu #preferencesSubMenu=\"matMenu\" [panelClass]=\"'view-submenu-panel'\">\n <div class=\"view-submenu\" (mousedown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"view-menu-item\" (mousedown)=\"enterEditMode(); viewMenuTrigger.closeMenu()\">\n <span class=\"view-menu-check\" aria-hidden=\"true\"></span>\n <mat-icon class=\"view-menu-col-icon\">edit</mat-icon>\n {{ i18n.preferencesEdit }}\n </button>\n <button type=\"button\" class=\"view-menu-item\" (mousedown)=\"savePreferences(); viewMenuTrigger.closeMenu()\">\n <span class=\"view-menu-check\" aria-hidden=\"true\"></span>\n <mat-icon class=\"view-menu-col-icon\">save</mat-icon>\n {{ i18n.preferencesSave }}\n </button>\n <button type=\"button\" class=\"view-menu-item\" [disabled]=\"!hasSavedPreferences\" (mousedown)=\"clearPreferences(); viewMenuTrigger.closeMenu()\">\n <span class=\"view-menu-check\" aria-hidden=\"true\"></span>\n <mat-icon class=\"view-menu-col-icon\">delete_outline</mat-icon>\n {{ i18n.preferencesClear }}\n </button>\n </div>\n </mat-menu>\n\n <mat-menu #fieldsSubMenu=\"matMenu\" [panelClass]=\"'view-submenu-panel'\">\n <div class=\"view-submenu\" (mousedown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"view-menu-item\" *ngFor=\"let column of menuBaseColumns\"\n [disabled]=\"!column.optional && !allowColumnRemoval\"\n (mousedown)=\"onColumnVisibilityChange(column, !isColumnVisible(column.key))\">\n <span class=\"view-menu-check\">{{ isColumnVisible(column.key) ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"column.icon\" class=\"view-menu-col-icon\" fontSet=\"material-icons-outlined\">{{ column.icon }}</mat-icon>\n {{ colLabel(column) }}\n </button>\n <ng-container *ngIf=\"menuOptionalColumns.length > 0\">\n <button type=\"button\" class=\"view-menu-item\" *ngFor=\"let column of menuOptionalColumns\"\n [disabled]=\"(isColumnVisible(column.key) && visibleColumns.length === 1) || getLazyColumnStatus(column.key) === 'loading'\"\n (mousedown)=\"onColumnVisibilityChange(column, !isColumnVisible(column.key))\">\n <span class=\"view-menu-check\">{{ isColumnVisible(column.key) ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"column.icon\" class=\"view-menu-col-icon\" fontSet=\"material-icons-outlined\">{{ column.icon }}</mat-icon>\n {{ colLabel(column) }}\n <span *ngIf=\"column.lazy && getLazyColumnStatus(column.key) === 'loading'\" class=\"lazy-menu-spinner\">\u21BB</span>\n <span *ngIf=\"column.lazy && getLazyColumnStatus(column.key) === 'error'\" class=\"lazy-menu-error\" [title]=\"i18n.lazyErrorMenuTitle\">\u26A0</span>\n </button>\n </ng-container>\n </div>\n </mat-menu>\n\n <mat-menu #groupBySubMenu=\"matMenu\" [panelClass]=\"'view-submenu-panel'\">\n <div class=\"view-submenu\" (mousedown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"view-menu-item\" *ngFor=\"let col of groupByColumns\"\n [class.active]=\"activeGroupByKey === col.key\"\n (mousedown)=\"onGroupByChange(activeGroupByKey === col.key ? null : col.key)\">\n <span class=\"view-menu-check\">{{ activeGroupByKey === col.key ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"col.icon\" class=\"view-menu-col-icon\" fontSet=\"material-icons-outlined\">{{ col.icon }}</mat-icon>\n {{ colLabel(col) }}\n </button>\n </div>\n </mat-menu>\n\n <mat-menu #sliceBySubMenu=\"matMenu\" [panelClass]=\"'view-submenu-panel'\">\n <div class=\"view-submenu\" (mousedown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"view-menu-item\" *ngFor=\"let item of sliceByMenuItems\"\n [class.active]=\"isSliceMenuItemActive(item)\"\n (mousedown)=\"onSliceMenuItemClick(item)\">\n <span class=\"view-menu-check\">{{ isSliceMenuItemActive(item) ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"item.icon\" class=\"view-menu-col-icon\" fontSet=\"material-icons-outlined\">{{ item.icon }}</mat-icon>\n {{ item.title }}\n </button>\n </div>\n </mat-menu>\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\" *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\" *ngIf=\"!isDefaultGroupByActive\" (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\" *ngIf=\"!isDefaultGroupByActive\"></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}.jquery-table-wrapper{display:flex;flex-direction:column;width:100%;height:100%;min-height:inherit;padding:2px 2px 0;box-sizing:border-box}.table-header{display:flex;align-items:center;justify-content:flex-start;gap:8px;margin-bottom:12px}.table-header h3{margin:0;font-size:20px;font-weight:600;color:var(--jqt-title-color)}.table-layout{display:grid;grid-template-columns:minmax(155px,190px) minmax(0,1fr);gap:12px;align-items:stretch;min-height:inherit;height:100%}.table-layout.no-slice{grid-template-columns:minmax(0,1fr)}.table-layout.slice-collapsed{grid-template-columns:40px minmax(0,1fr)}.table-shell{display:flex;flex-direction:column;flex:1 1 0;min-height:0;min-width:0;width:100%;max-width:100%}.table-body-wrapper{position:relative;flex:1 1 0;min-height:0;display:flex;flex-direction:column}.table-body-scroll{flex:1 1 0;overflow-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}.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 .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:nowrap;height:var(--actual-header-height, 56px);padding:0 16px;gap:16px;width:100%;box-sizing:border-box}.group-bar-separator{width:1px;height:20px;background:#9ca3af;flex-shrink:0;margin:0 4px}.group-bottom-bar-left{display:flex;align-items:center;gap:8px;flex-shrink:0}.group-sort-controls{display:flex;align-items:center;gap:2px}.group-sort-label{font-size:12px;color:var(--jqt-footer-text-color);white-space:nowrap;margin-right:2px}.group-sort-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--jqt-footer-icon-color);cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease}.group-sort-btn mat-icon{font-size:16px;width:16px;height:16px;line-height:16px}.group-sort-btn:hover{background:#0000000a}.group-sort-btn.active{color:var(--jqt-primary-color, #1976d2);border-color:var(--jqt-primary-color, #1976d2);background:#1976d214}:host ::ng-deep .group-bottom-bar .mat-mdc-paginator-page-size{display:flex;align-items:center;gap:8px}:host ::ng-deep .group-bottom-bar .mat-mdc-paginator-page-size-label{font-size:12px;color:var(--jqt-footer-text-color);white-space:nowrap}:host ::ng-deep .group-bottom-bar .mat-mdc-paginator-page-size-select{width:72px;font-size:12px}:host ::ng-deep .group-bottom-bar .mat-mdc-text-field-wrapper{padding:0 8px}:host ::ng-deep .group-bottom-bar .mdc-text-field--outlined{height:28px}:host ::ng-deep .group-bottom-bar .mat-mdc-form-field-infix{padding-top:0;padding-bottom:0;min-height:0;height:28px;display:flex;align-items:center}:host ::ng-deep .group-bottom-bar .mat-mdc-select-value-text{font-size:12px;color:var(--jqt-text-color)}:host ::ng-deep .group-bottom-bar .mdc-notched-outline__leading,:host ::ng-deep .group-bottom-bar .mdc-notched-outline__notch,:host ::ng-deep .group-bottom-bar .mdc-notched-outline__trailing{border-color:var(--jqt-border-color)}:host ::ng-deep .group-bottom-bar .mat-mdc-select-arrow{color:var(--jqt-footer-text-color)}.group-reset-btn{display:inline-flex;align-items:center;gap:6px;height:30px;padding:0 10px;border:1px solid var(--jqt-border-color);border-radius:8px;background:var(--jqt-surface-color);color:var(--jqt-text-secondary);font-size:12px;font-weight:500;font-family:inherit;cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease;white-space:nowrap;flex-shrink:0}.group-reset-btn:hover{background:var(--jqt-danger-hover-bg);color:var(--jqt-danger-color);border-color:var(--jqt-danger-border)}.group-reset-icon{width:14px;height:14px;flex-shrink:0}::ng-deep .mat-mdc-paginator-select-page-size .mat-pseudo-checkbox{display:none!important}::ng-deep .group-page-size-panel{min-width:90px!important}::ng-deep .group-page-size-panel .mat-mdc-option{justify-content:center;min-height:40px}::ng-deep .group-page-size-panel .mdc-list-item__primary-text{text-align:center;width:100%}@media (max-width: 960px){.table-layout{grid-template-columns:1fr}}::ng-deep .view-menu-panel .mat-mdc-menu-panel{min-width:220px!important;overflow:visible!important}::ng-deep .view-menu-panel .mat-mdc-menu-content{padding:4px 0!important}::ng-deep .view-submenu-panel .mat-mdc-menu-panel{min-width:200px!important}::ng-deep .view-submenu-panel .mat-mdc-menu-content{padding:0!important}::ng-deep .view-menu-panel .view-menu-sub-trigger.mat-mdc-menu-item{min-height:42px;padding:0 14px;display:flex;align-items:center;box-sizing:border-box}::ng-deep .view-menu-panel .view-menu-sub-trigger .mdc-list-item__primary-text{display:flex;align-items:center;width:100%;gap:0;overflow:hidden}::ng-deep .view-menu-sub-label{font-size:13px;font-weight:500;color:var(--jqt-text-color);flex:0 0 auto;white-space:nowrap;padding-right:8px}::ng-deep .view-menu-sub-value{font-size:12px;color:var(--jqt-text-secondary);flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:right}::ng-deep .view-submenu{padding:4px 6px;min-width:180px;box-sizing:border-box}::ng-deep .view-submenu .view-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;color:var(--jqt-text-color);cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}::ng-deep .view-submenu .view-menu-item:hover{background:var(--jqt-row-hover-bg)}::ng-deep .view-submenu .view-menu-item.active{font-weight:600}::ng-deep .view-submenu .view-menu-item:disabled{opacity:.4;cursor:default}::ng-deep .view-menu-col-icon{flex-shrink:0;font-size:14px!important;width:14px!important;height:14px!important;color:var(--jqt-text-muted)}::ng-deep .view-menu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:var(--jqt-text-color)}::ng-deep .view-menu-separator{border:none;border-top:1px solid var(--jqt-border-color);margin:4px 0}.lazy-skeleton{display:block;height:12px;width:70%;border-radius:4px;background:linear-gradient(90deg,var(--jqt-border-color) 25%,var(--jqt-surface-alt-color) 50%,var(--jqt-border-color) 75%);background-size:200% 100%;animation:lazy-shimmer 1.4s ease-in-out infinite}.group-lazy-key-skeleton{flex:1 1 auto;min-width:80px;height:13px;border-radius:4px}@keyframes lazy-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}.lazy-error-btn{display:inline-flex;align-items:center;gap:4px;background:none;border:1px solid #fca5a5;border-radius:4px;padding:2px 6px;cursor:pointer;color:#ef4444;font-size:12px;opacity:.85;transition:opacity .15s}.lazy-error-btn:hover{opacity:1;background:#fef2f2}.lazy-error-icon{font-size:12px}.lazy-error-label{font-size:11px;font-weight:500}::ng-deep .lazy-menu-spinner{display:inline-block;margin-left:auto;font-size:13px;color:var(--jqt-text-secondary);animation:lazy-spin 1s linear infinite}::ng-deep .lazy-menu-error{display:inline-block;margin-left:auto;font-size:13px;color:#ef4444}@keyframes lazy-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
|
|
1807
|
+
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 <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}.jquery-table-wrapper{display:flex;flex-direction:column;width:100%;height:100%;min-height:inherit;padding:2px 2px 0;box-sizing:border-box}.table-header{display:flex;align-items:center;justify-content:flex-start;gap:8px;margin-bottom:12px}.table-header h3{margin:0;font-size:20px;font-weight:600;color:var(--jqt-title-color)}.table-layout{display:grid;grid-template-columns:minmax(155px,190px) minmax(0,1fr);gap:12px;align-items:stretch;min-height:inherit;height:100%}.table-layout.no-slice{grid-template-columns:minmax(0,1fr)}.table-layout.slice-collapsed{grid-template-columns:40px minmax(0,1fr)}.table-shell{display:flex;flex-direction:column;flex:1 1 0;min-height:0;min-width:0;width:100%;max-width:100%}.table-body-wrapper{position:relative;flex:1 1 0;min-height:0;display:flex;flex-direction:column}.table-body-scroll{flex:1 1 0;overflow-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}.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:nowrap;height:var(--actual-header-height, 56px);padding:0 16px;gap:16px;width:100%;box-sizing:border-box}.group-bar-separator{width:1px;height:20px;background:#9ca3af;flex-shrink:0;margin:0 4px}.group-bottom-bar-left{display:flex;align-items:center;gap:8px;flex-shrink:0}.group-sort-controls{display:flex;align-items:center;gap:2px}.group-sort-label{font-size:12px;color:var(--jqt-footer-text-color);white-space:nowrap;margin-right:2px}.group-sort-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--jqt-footer-icon-color);cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease}.group-sort-btn mat-icon{font-size:16px;width:16px;height:16px;line-height:16px}.group-sort-btn:hover{background:#0000000a}.group-sort-btn.active{color:var(--jqt-primary-color, #1976d2);border-color:var(--jqt-primary-color, #1976d2);background:#1976d214}:host ::ng-deep .group-bottom-bar .mat-mdc-paginator-page-size{display:flex;align-items:center;gap:8px}:host ::ng-deep .group-bottom-bar .mat-mdc-paginator-page-size-label{font-size:12px;color:var(--jqt-footer-text-color);white-space:nowrap}:host ::ng-deep .group-bottom-bar .mat-mdc-paginator-page-size-select{width:72px;font-size:12px}:host ::ng-deep .group-bottom-bar .mat-mdc-text-field-wrapper{padding:0 8px}:host ::ng-deep .group-bottom-bar .mdc-text-field--outlined{height:28px}:host ::ng-deep .group-bottom-bar .mat-mdc-form-field-infix{padding-top:0;padding-bottom:0;min-height:0;height:28px;display:flex;align-items:center}:host ::ng-deep .group-bottom-bar .mat-mdc-select-value-text{font-size:12px;color:var(--jqt-text-color)}:host ::ng-deep .group-bottom-bar .mdc-notched-outline__leading,:host ::ng-deep .group-bottom-bar .mdc-notched-outline__notch,:host ::ng-deep .group-bottom-bar .mdc-notched-outline__trailing{border-color:var(--jqt-border-color)}:host ::ng-deep .group-bottom-bar .mat-mdc-select-arrow{color:var(--jqt-footer-text-color)}.group-reset-btn{display:inline-flex;align-items:center;gap:6px;height:30px;padding:0 10px;border:1px solid var(--jqt-border-color);border-radius:8px;background:var(--jqt-surface-color);color:var(--jqt-text-secondary);font-size:12px;font-weight:500;font-family:inherit;cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease;white-space:nowrap;flex-shrink:0}.group-reset-btn:hover{background:var(--jqt-danger-hover-bg);color:var(--jqt-danger-color);border-color:var(--jqt-danger-border)}.group-reset-icon{width:14px;height:14px;flex-shrink:0}::ng-deep .mat-mdc-paginator-select-page-size .mat-pseudo-checkbox{display:none!important}::ng-deep .group-page-size-panel{min-width:90px!important}::ng-deep .group-page-size-panel .mat-mdc-option{justify-content:center;min-height:40px}::ng-deep .group-page-size-panel .mdc-list-item__primary-text{text-align:center;width:100%}@media (max-width: 960px){.table-layout{grid-template-columns:1fr}}::ng-deep .view-menu-panel .mat-mdc-menu-panel{min-width:220px!important;overflow:visible!important}::ng-deep .view-menu-panel .mat-mdc-menu-content{padding:4px 0!important}::ng-deep .view-submenu-panel .mat-mdc-menu-panel{min-width:200px!important}::ng-deep .view-submenu-panel .mat-mdc-menu-content{padding:0!important}::ng-deep .view-menu-panel .view-menu-sub-trigger.mat-mdc-menu-item{min-height:42px;padding:0 14px;display:flex;align-items:center;box-sizing:border-box}::ng-deep .view-menu-panel .view-menu-sub-trigger .mdc-list-item__primary-text{display:flex;align-items:center;width:100%;gap:0;overflow:hidden}::ng-deep .view-menu-sub-label{font-size:13px;font-weight:500;color:var(--jqt-text-color);flex:0 0 auto;white-space:nowrap;padding-right:8px}::ng-deep .view-menu-sub-value{font-size:12px;color:var(--jqt-text-secondary);flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:right}::ng-deep .view-submenu{padding:4px 6px;min-width:180px;box-sizing:border-box}::ng-deep .view-submenu .view-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;color:var(--jqt-text-color);cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}::ng-deep .view-submenu .view-menu-item:hover{background:var(--jqt-row-hover-bg)}::ng-deep .view-submenu .view-menu-item.active{font-weight:600}::ng-deep .view-submenu .view-menu-item:disabled{opacity:.4;cursor:default}::ng-deep .view-menu-col-icon{flex-shrink:0;font-size:14px!important;width:14px!important;height:14px!important;color:var(--jqt-text-muted)}::ng-deep .view-menu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:var(--jqt-text-color)}::ng-deep .view-menu-separator{border:none;border-top:1px solid var(--jqt-border-color);margin:4px 0}.lazy-skeleton{display:block;height:12px;width:70%;border-radius:4px;background:linear-gradient(90deg,var(--jqt-border-color) 25%,var(--jqt-surface-alt-color) 50%,var(--jqt-border-color) 75%);background-size:200% 100%;animation:lazy-shimmer 1.4s ease-in-out infinite}.group-lazy-key-skeleton{flex:1 1 auto;min-width:80px;height:13px;border-radius:4px}@keyframes lazy-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}.lazy-error-btn{display:inline-flex;align-items:center;gap:4px;background:none;border:1px solid #fca5a5;border-radius:4px;padding:2px 6px;cursor:pointer;color:#ef4444;font-size:12px;opacity:.85;transition:opacity .15s}.lazy-error-btn:hover{opacity:1;background:#fef2f2}.lazy-error-icon{font-size:12px}.lazy-error-label{font-size:11px;font-weight:500}::ng-deep .lazy-menu-spinner{display:inline-block;margin-left:auto;font-size:13px;color:var(--jqt-text-secondary);animation:lazy-spin 1s linear infinite}::ng-deep .lazy-menu-error{display:inline-block;margin-left:auto;font-size:13px;color:#ef4444}@keyframes lazy-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
|
|
2652
1808
|
}], propDecorators: { config: [{
|
|
2653
1809
|
type: Input
|
|
2654
1810
|
}], dataSource: [{
|
|
@@ -2699,9 +1855,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
|
|
|
2699
1855
|
}], slicePanelRef: [{
|
|
2700
1856
|
type: ViewChild,
|
|
2701
1857
|
args: [SlicePanelComponent]
|
|
2702
|
-
}], viewMenuTrigger: [{
|
|
2703
|
-
type: ViewChild,
|
|
2704
|
-
args: ['viewMenuTrigger']
|
|
2705
1858
|
}], _cellDefs: [{
|
|
2706
1859
|
type: ContentChildren,
|
|
2707
1860
|
args: [JqtCellDefDirective]
|
|
@@ -2715,5 +1868,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
|
|
|
2715
1868
|
* Generated bundle index. Do not edit.
|
|
2716
1869
|
*/
|
|
2717
1870
|
|
|
2718
|
-
export { JQT_I18N, JQT_I18N_DEFAULTS, JqtCellDefDirective,
|
|
1871
|
+
export { JQT_I18N, JQT_I18N_DEFAULTS, JqtCellDefDirective, OrganizerButtonWrapperComponent, TableComponent, col };
|
|
2719
1872
|
//# sourceMappingURL=oneteme-jquery-table.mjs.map
|