@esfaenza/es-table 20.3.22 → 20.3.24
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/fesm2022/esfaenza-es-table.mjs +793 -549
- package/fesm2022/esfaenza-es-table.mjs.map +1 -1
- package/index.d.ts +135 -81
- package/package.json +1 -1
|
@@ -6135,6 +6135,525 @@ function collapseChildren(rows, ownKey, parentKey, parentK) {
|
|
|
6135
6135
|
}
|
|
6136
6136
|
}
|
|
6137
6137
|
|
|
6138
|
+
// Motore di coercizione valori — puro e stateless (come row-grouping.ts / hierarchy.ts).
|
|
6139
|
+
// Converte una stringa (digitata o incollata) nel tipo della colonna, segnalando la
|
|
6140
|
+
// compatibilità. Estratto da EsTable2Component per essere testabile in isolamento.
|
|
6141
|
+
/**
|
|
6142
|
+
* Converte `raw` nel tipo di `col`. `ok:false` = incompatibile (es. testo in una
|
|
6143
|
+
* colonna numerica) → il chiamante avvisa e non scrive. Una stringa vuota è sempre
|
|
6144
|
+
* valida e azzera il valore (`null`).
|
|
6145
|
+
*/
|
|
6146
|
+
function coerceValue(raw, col) {
|
|
6147
|
+
const type = col.type;
|
|
6148
|
+
const v = raw == null ? '' : String(raw).trim();
|
|
6149
|
+
switch (type) {
|
|
6150
|
+
case 'int':
|
|
6151
|
+
case 'number':
|
|
6152
|
+
case 'float':
|
|
6153
|
+
case 'currency': {
|
|
6154
|
+
if (v === '')
|
|
6155
|
+
return { ok: true, value: null };
|
|
6156
|
+
const n = toNumber(v);
|
|
6157
|
+
if (n == null || isNaN(n))
|
|
6158
|
+
return { ok: false, value: null };
|
|
6159
|
+
return { ok: true, value: (type === 'int' || type === 'number') ? Math.round(n) : n };
|
|
6160
|
+
}
|
|
6161
|
+
case 'boolean': {
|
|
6162
|
+
if (v === '')
|
|
6163
|
+
return { ok: true, value: null };
|
|
6164
|
+
if (/^(true|1|✓|s[iì]|si|yes|y)$/i.test(v))
|
|
6165
|
+
return { ok: true, value: true };
|
|
6166
|
+
if (/^(false|0|—|no|n)$/i.test(v))
|
|
6167
|
+
return { ok: true, value: false };
|
|
6168
|
+
return { ok: false, value: null };
|
|
6169
|
+
}
|
|
6170
|
+
case 'enum':
|
|
6171
|
+
case 'autocomplete': {
|
|
6172
|
+
if (!col.source || col.source.length === 0)
|
|
6173
|
+
return { ok: true, value: v };
|
|
6174
|
+
const byId = col.source.find(o => String(o.id) === v);
|
|
6175
|
+
if (byId)
|
|
6176
|
+
return { ok: true, value: byId.id };
|
|
6177
|
+
const byDesc = col.source.find(o => o.description === v);
|
|
6178
|
+
if (byDesc)
|
|
6179
|
+
return { ok: true, value: byDesc.id };
|
|
6180
|
+
return { ok: false, value: null };
|
|
6181
|
+
}
|
|
6182
|
+
case 'date':
|
|
6183
|
+
case 'datetime':
|
|
6184
|
+
case 'time': {
|
|
6185
|
+
if (v === '')
|
|
6186
|
+
return { ok: true, value: null };
|
|
6187
|
+
const d = parseDateLoose(v);
|
|
6188
|
+
return d ? { ok: true, value: d } : { ok: false, value: null };
|
|
6189
|
+
}
|
|
6190
|
+
default:
|
|
6191
|
+
return { ok: true, value: raw };
|
|
6192
|
+
}
|
|
6193
|
+
}
|
|
6194
|
+
/** Parsing numerico tollerante (separatori di migliaia + virgola/punto decimale). `NaN` = non numerico */
|
|
6195
|
+
function toNumber(v) {
|
|
6196
|
+
let s = v.replace(/\s/g, '').replace(/[€$%]/g, '');
|
|
6197
|
+
if (s === '')
|
|
6198
|
+
return NaN;
|
|
6199
|
+
const hasComma = s.includes(','), hasDot = s.includes('.');
|
|
6200
|
+
if (hasComma && hasDot) {
|
|
6201
|
+
// l'ultimo separatore è quello decimale
|
|
6202
|
+
if (s.lastIndexOf(',') > s.lastIndexOf('.'))
|
|
6203
|
+
s = s.replace(/\./g, '').replace(',', '.');
|
|
6204
|
+
else
|
|
6205
|
+
s = s.replace(/,/g, '');
|
|
6206
|
+
}
|
|
6207
|
+
else if (hasComma) {
|
|
6208
|
+
s = s.replace(',', '.');
|
|
6209
|
+
}
|
|
6210
|
+
return /^-?\d*\.?\d+$/.test(s) ? parseFloat(s) : NaN;
|
|
6211
|
+
}
|
|
6212
|
+
/** Parsing date tollerante: `dd/MM/yyyy [HH:mm[:ss]]` o formati nativi. `null` = non valida */
|
|
6213
|
+
function parseDateLoose(v) {
|
|
6214
|
+
const m = v.match(/^(\d{1,2})[\/\-.](\d{1,2})[\/\-.](\d{2,4})(?:[ T](\d{1,2}):(\d{2})(?::(\d{2}))?)?$/);
|
|
6215
|
+
if (m) {
|
|
6216
|
+
const y = m[3].length === 2 ? 2000 + +m[3] : +m[3];
|
|
6217
|
+
const d = new Date(y, +m[2] - 1, +m[1], +(m[4] || 0), +(m[5] || 0), +(m[6] || 0));
|
|
6218
|
+
return isNaN(d.getTime()) ? null : d;
|
|
6219
|
+
}
|
|
6220
|
+
const d = new Date(v);
|
|
6221
|
+
return isNaN(d.getTime()) ? null : d;
|
|
6222
|
+
}
|
|
6223
|
+
|
|
6224
|
+
// Geometria degli header-group multi-livello — pura e stateless.
|
|
6225
|
+
// Deriva le righe di intestazione-gruppo, i confini dei gruppi-colonna e la
|
|
6226
|
+
// contiguità dall'insieme (colonne visibili, mappa th, id-gruppo). Estratta da
|
|
6227
|
+
// EsTable2Component per poterla testare in isolamento (bottom-alignment, spacer,
|
|
6228
|
+
// contiguità sono geometria sottile che merita specifiche dedicate).
|
|
6229
|
+
/**
|
|
6230
|
+
* Catena di antenati di una colonna via `thParent`. Default: padre-immediato → root.
|
|
6231
|
+
* `rootFirst=true` la restituisce root → padre-immediato. Guardia a 32 contro cicli.
|
|
6232
|
+
*/
|
|
6233
|
+
function ancestorChain(id, thById, rootFirst = false) {
|
|
6234
|
+
const chain = [];
|
|
6235
|
+
let p = thById.get(id)?.thParent ?? null;
|
|
6236
|
+
let guard = 0;
|
|
6237
|
+
while (p && guard++ < 32) {
|
|
6238
|
+
chain.push(p);
|
|
6239
|
+
p = thById.get(p)?.thParent ?? null;
|
|
6240
|
+
}
|
|
6241
|
+
return rootFirst ? chain.reverse() : chain;
|
|
6242
|
+
}
|
|
6243
|
+
/**
|
|
6244
|
+
* Righe di header-group multi-livello, dall'alto (root) verso il basso (padre
|
|
6245
|
+
* immediato). Ogni gruppo si allinea in BASSO: il padre immediato di una colonna
|
|
6246
|
+
* sta sempre nella riga direttamente sopra la colonna; gli antenati più alti
|
|
6247
|
+
* salgono. Le colonne senza gruppo (o con catena più corta) hanno celle-spacer
|
|
6248
|
+
* vuote in alto.
|
|
6249
|
+
*/
|
|
6250
|
+
function computeHeaderGroupRows(leaves, thById, parentIds) {
|
|
6251
|
+
if (parentIds.size === 0 || leaves.length === 0)
|
|
6252
|
+
return [];
|
|
6253
|
+
const chains = leaves.map(c => ancestorChain(c.id, thById));
|
|
6254
|
+
const maxDepth = chains.reduce((m, ch) => Math.max(m, ch.length), 0);
|
|
6255
|
+
if (maxDepth === 0)
|
|
6256
|
+
return [];
|
|
6257
|
+
const rows = [];
|
|
6258
|
+
for (let r = 0; r < maxDepth; r++) {
|
|
6259
|
+
const row = [];
|
|
6260
|
+
let i = 0;
|
|
6261
|
+
while (i < leaves.length) {
|
|
6262
|
+
// allineamento in basso: alla riga r (0=alto) corrisponde l'antenato a
|
|
6263
|
+
// distanza (maxDepth-1-r) dalla colonna (0 = padre immediato)
|
|
6264
|
+
const anc = chains[i][maxDepth - 1 - r];
|
|
6265
|
+
if (anc == null) {
|
|
6266
|
+
row.push({ id: `_hg_spacer_${r}_${i}`, span: 1, isGroup: false, label: '', template: null });
|
|
6267
|
+
i++;
|
|
6268
|
+
}
|
|
6269
|
+
else {
|
|
6270
|
+
let span = 0;
|
|
6271
|
+
while (i < leaves.length && chains[i][maxDepth - 1 - r] === anc) {
|
|
6272
|
+
span++;
|
|
6273
|
+
i++;
|
|
6274
|
+
}
|
|
6275
|
+
const gth = thById.get(anc);
|
|
6276
|
+
row.push({ id: anc, span, isGroup: true, label: gth?.thStaticContent ?? anc, template: gth?.Template ?? null });
|
|
6277
|
+
}
|
|
6278
|
+
}
|
|
6279
|
+
rows.push(row);
|
|
6280
|
+
}
|
|
6281
|
+
return rows;
|
|
6282
|
+
}
|
|
6283
|
+
/**
|
|
6284
|
+
* Indici (in `cols`) delle colonne che aprono un nuovo gruppo-colonna, cioè dove
|
|
6285
|
+
* il gruppo padre immediato cambia rispetto alla colonna precedente. Serve a
|
|
6286
|
+
* disegnare i confini dei gruppi anche nel corpo della tabella.
|
|
6287
|
+
*/
|
|
6288
|
+
function computeColumnGroupBoundaries(cols, thById, hasParents) {
|
|
6289
|
+
const set = new Set();
|
|
6290
|
+
if (!hasParents)
|
|
6291
|
+
return set;
|
|
6292
|
+
let prev = undefined;
|
|
6293
|
+
for (let i = 0; i < cols.length; i++) {
|
|
6294
|
+
const parent = thById.get(cols[i].id)?.thParent ?? null;
|
|
6295
|
+
if (i > 0 && parent !== prev)
|
|
6296
|
+
set.add(i);
|
|
6297
|
+
prev = parent;
|
|
6298
|
+
}
|
|
6299
|
+
return set;
|
|
6300
|
+
}
|
|
6301
|
+
/**
|
|
6302
|
+
* Riordina `orderIds` così che i membri di uno stesso gruppo restino contigui.
|
|
6303
|
+
* Chiave di ordinamento = [prima-apparizione di ogni antenato (root→foglia), poi
|
|
6304
|
+
* indice proprio]. Due colonne dello stesso gruppo non possono più essere
|
|
6305
|
+
* interlacciate con colonne esterne.
|
|
6306
|
+
*/
|
|
6307
|
+
function enforceGroupContiguity(orderIds, thById, hasParents) {
|
|
6308
|
+
if (!hasParents)
|
|
6309
|
+
return orderIds;
|
|
6310
|
+
const chainOf = (id) => ancestorChain(id, thById, true);
|
|
6311
|
+
const firstIdx = new Map();
|
|
6312
|
+
orderIds.forEach((id, i) => {
|
|
6313
|
+
for (const g of chainOf(id))
|
|
6314
|
+
if (!firstIdx.has(g))
|
|
6315
|
+
firstIdx.set(g, i);
|
|
6316
|
+
});
|
|
6317
|
+
const keyOf = (id, i) => [...chainOf(id).map(g => firstIdx.get(g)), i];
|
|
6318
|
+
return orderIds
|
|
6319
|
+
.map((id, i) => ({ id, key: keyOf(id, i), i }))
|
|
6320
|
+
.sort((a, b) => {
|
|
6321
|
+
const n = Math.max(a.key.length, b.key.length);
|
|
6322
|
+
for (let k = 0; k < n; k++) {
|
|
6323
|
+
const av = a.key[k] ?? -1, bv = b.key[k] ?? -1;
|
|
6324
|
+
if (av !== bv)
|
|
6325
|
+
return av - bv;
|
|
6326
|
+
}
|
|
6327
|
+
return a.i - b.i;
|
|
6328
|
+
})
|
|
6329
|
+
.map(x => x.id);
|
|
6330
|
+
}
|
|
6331
|
+
|
|
6332
|
+
// Modello di colonna dell'es-table2 — tipo puro, condiviso da componente e motori.
|
|
6333
|
+
// Estratto qui per rompere la dipendenza circolare col motore `core/columns.ts`.
|
|
6334
|
+
/**
|
|
6335
|
+
* Factory di `Est2Column`: applica i default costanti (visible/orderable/alignment/…)
|
|
6336
|
+
* e sovrascrive col `partial`. I costruttori di colonne (direttive, multi, dinamica,
|
|
6337
|
+
* report) passano solo i campi che differiscono, evitando derive di campo. I default
|
|
6338
|
+
* `false`/`null` sono equivalenti in comportamento agli `undefined` che i literal
|
|
6339
|
+
* omettevano (`pinned` falsy, `pinnedWidth || 120`, `headerBg` falsy).
|
|
6340
|
+
*/
|
|
6341
|
+
function makeColumn(partial) {
|
|
6342
|
+
return {
|
|
6343
|
+
visible: true, orderable: false, alignment: 'left',
|
|
6344
|
+
property: null, type: null, source: null, format: null,
|
|
6345
|
+
cssClass: '', headerClass: '', wrap: false, isGroup: false, aggSpecs: [],
|
|
6346
|
+
headerText: null, headerBg: null, pinned: false, pinnedWidth: null,
|
|
6347
|
+
...partial
|
|
6348
|
+
};
|
|
6349
|
+
}
|
|
6350
|
+
|
|
6351
|
+
// Costruttori di colonne (direttive / multi / dinamica / report) — puri e stateless.
|
|
6352
|
+
// Producono solo dati (`Est2Column[]` e, per Multi, le mappe header-group aggiornate);
|
|
6353
|
+
// il componente resta responsabile di applicarli allo stato a signal. Estratti da
|
|
6354
|
+
// EsTable2Component per isolare ~230 righe della logica più densa e branch-heavy.
|
|
6355
|
+
/** Colonne da direttive `*th`/`*td` dirette (esclude gruppi e Multi). */
|
|
6356
|
+
function mapDirectiveColumns(ths, tdByCol, parentIds, defAlignment) {
|
|
6357
|
+
const cols = [];
|
|
6358
|
+
for (const th of ths) {
|
|
6359
|
+
if (!th.thIf)
|
|
6360
|
+
continue;
|
|
6361
|
+
if (th.thMulti)
|
|
6362
|
+
continue; // srotolata separatamente
|
|
6363
|
+
if (parentIds.has(th.th))
|
|
6364
|
+
continue; // è un header di gruppo, non una colonna-dato
|
|
6365
|
+
const td = tdByCol.get(th.th);
|
|
6366
|
+
cols.push(makeColumn({
|
|
6367
|
+
id: th.th,
|
|
6368
|
+
header: th,
|
|
6369
|
+
cell: td,
|
|
6370
|
+
visible: th.thVisible,
|
|
6371
|
+
orderable: th.thOrderable,
|
|
6372
|
+
alignment: (td?.tdAlignment || th.thAlignment)?.toLowerCase() || defAlignment,
|
|
6373
|
+
property: th.thProperty,
|
|
6374
|
+
type: th.thType ?? null,
|
|
6375
|
+
source: th.thSource,
|
|
6376
|
+
format: td?.tdFormat || th.thFormat || null,
|
|
6377
|
+
cssClass: td?.tdClass || '', // `*td Class` → solo `<td>`
|
|
6378
|
+
headerClass: th.thClass || '', // `*th Class` → solo `<th>`
|
|
6379
|
+
wrap: th.thWrap,
|
|
6380
|
+
isGroup: th.thGroup,
|
|
6381
|
+
aggSpecs: parseAggregationSpecs(th.th, th.thAggregation),
|
|
6382
|
+
headerText: th.thStaticContent,
|
|
6383
|
+
headerBg: th.thBackgroundColor,
|
|
6384
|
+
routePath: th.thRoutePath,
|
|
6385
|
+
routeParams: th.thRouteParameterProperties,
|
|
6386
|
+
rendererName: th.thRendererName,
|
|
6387
|
+
pinned: !!th.thPinned,
|
|
6388
|
+
pinnedWidth: th.thPinnedWidth ?? null
|
|
6389
|
+
}));
|
|
6390
|
+
}
|
|
6391
|
+
return cols;
|
|
6392
|
+
}
|
|
6393
|
+
/** Colonne da una `EsTableColumnsDefinition[]` (modalità dinamica). */
|
|
6394
|
+
function mapDynamicColumns(defs, tds) {
|
|
6395
|
+
const cols = [];
|
|
6396
|
+
for (let i = 0; i < defs.length; i++) {
|
|
6397
|
+
const d = defs[i];
|
|
6398
|
+
if (!d.Visible)
|
|
6399
|
+
continue;
|
|
6400
|
+
const renderer = tds.find(t => (d.RendererName && t.td === d.RendererName) || t.tdForProperty === d.PropertyName);
|
|
6401
|
+
cols.push(makeColumn({
|
|
6402
|
+
id: d.PropertyName,
|
|
6403
|
+
cell: renderer,
|
|
6404
|
+
orderable: d.Orderable !== false,
|
|
6405
|
+
property: d.PropertyName,
|
|
6406
|
+
type: d.PropertyType ?? null,
|
|
6407
|
+
source: d.PropertySource ?? null,
|
|
6408
|
+
format: d.Format ?? null,
|
|
6409
|
+
cssClass: d.Clss || '', headerClass: d.Clss || '', // dinamica: un'unica classe colonna (no *th/*td separati)
|
|
6410
|
+
wrap: !!d.HeaderWraps,
|
|
6411
|
+
headerText: d.Description,
|
|
6412
|
+
routePath: d.RoutePath,
|
|
6413
|
+
routeParams: d.RouteParameterProperties,
|
|
6414
|
+
rendererName: d.RendererName,
|
|
6415
|
+
colorIndex: i // forecolors/backcolors allineati all'ordine delle colonne
|
|
6416
|
+
}));
|
|
6417
|
+
}
|
|
6418
|
+
return cols;
|
|
6419
|
+
}
|
|
6420
|
+
/** Colonne da `report_columns` (modalità report): il valore è preso per indice. */
|
|
6421
|
+
function mapReportColumns(reportColumns) {
|
|
6422
|
+
const cols = [];
|
|
6423
|
+
for (let i = 0; i < reportColumns.length; i++) {
|
|
6424
|
+
const c = reportColumns[i];
|
|
6425
|
+
cols.push(makeColumn({
|
|
6426
|
+
id: c.id,
|
|
6427
|
+
alignment: c.alignment?.toLowerCase() || 'left',
|
|
6428
|
+
format: c.format ?? null,
|
|
6429
|
+
headerText: c.description,
|
|
6430
|
+
headerBg: c.color || null,
|
|
6431
|
+
propAccessor: String(i),
|
|
6432
|
+
colorIndex: i
|
|
6433
|
+
}));
|
|
6434
|
+
}
|
|
6435
|
+
return cols;
|
|
6436
|
+
}
|
|
6437
|
+
/**
|
|
6438
|
+
* Srotola le colonne `*th Multi:true` sui dati: ogni entry unica `(header_group,
|
|
6439
|
+
* header)` della proprietà-array diventa una colonna sintetica, e i livelli di
|
|
6440
|
+
* `header_group` (separati da ">") alimentano gli header-group. Puro: restituisce
|
|
6441
|
+
* le colonne e le mappe th/parent aggiornate (il componente le applica allo stato).
|
|
6442
|
+
*/
|
|
6443
|
+
function buildMultiColumns(multiDefs, baseThById, baseParentIds, items) {
|
|
6444
|
+
const cols = [];
|
|
6445
|
+
const thById = new Map(baseThById);
|
|
6446
|
+
const parentIds = new Set(baseParentIds);
|
|
6447
|
+
const seen = new Set();
|
|
6448
|
+
for (const def of multiDefs) {
|
|
6449
|
+
const prop = def.prop;
|
|
6450
|
+
for (const item of (items || [])) {
|
|
6451
|
+
const arr = item?.[prop];
|
|
6452
|
+
if (!Array.isArray(arr))
|
|
6453
|
+
continue;
|
|
6454
|
+
for (let ii = 0; ii < arr.length; ii++) {
|
|
6455
|
+
const mv = arr[ii] || {};
|
|
6456
|
+
const header = mv.header ?? '';
|
|
6457
|
+
const group = mv.header_group ?? null;
|
|
6458
|
+
const key = `${group}__${header}`;
|
|
6459
|
+
if (seen.has(key))
|
|
6460
|
+
continue;
|
|
6461
|
+
seen.add(key);
|
|
6462
|
+
const hier = group ? String(group).split('>').map((s) => s.trim()).filter(Boolean) : [];
|
|
6463
|
+
const immediateParent = hier.length ? hier[hier.length - 1] : null;
|
|
6464
|
+
const colId = `${prop}_${ii}`;
|
|
6465
|
+
// th sintetico foglia: fornisce la catena thParent + il template header
|
|
6466
|
+
const leafTh = new EsThDirective(def.th.Template);
|
|
6467
|
+
leafTh.th = colId;
|
|
6468
|
+
leafTh.thStaticContent = header;
|
|
6469
|
+
leafTh.thParent = immediateParent;
|
|
6470
|
+
leafTh.thOrderable = false;
|
|
6471
|
+
thById.set(colId, leafTh);
|
|
6472
|
+
// th sintetici dei gruppi padre (una volta ciascuno)
|
|
6473
|
+
for (let h = 0; h < hier.length; h++) {
|
|
6474
|
+
const pid = hier[h];
|
|
6475
|
+
parentIds.add(pid);
|
|
6476
|
+
if (!thById.has(pid)) {
|
|
6477
|
+
const pth = new EsThDirective(null);
|
|
6478
|
+
pth.th = pid;
|
|
6479
|
+
pth.thStaticContent = pid;
|
|
6480
|
+
pth.thParent = h > 0 ? hier[h - 1] : null;
|
|
6481
|
+
thById.set(pid, pth);
|
|
6482
|
+
}
|
|
6483
|
+
}
|
|
6484
|
+
cols.push(makeColumn({
|
|
6485
|
+
id: colId,
|
|
6486
|
+
header: leafTh,
|
|
6487
|
+
cell: def.td,
|
|
6488
|
+
alignment: 'right',
|
|
6489
|
+
cssClass: def.td?.tdClass || '', // `*td Class` → solo `<td>`
|
|
6490
|
+
headerClass: def.th.thClass || '', // `*th Class` → solo `<th>`
|
|
6491
|
+
headerText: header,
|
|
6492
|
+
multiProp: prop, multiIndex: ii
|
|
6493
|
+
}));
|
|
6494
|
+
}
|
|
6495
|
+
}
|
|
6496
|
+
}
|
|
6497
|
+
return { columns: cols, thById, parentIds };
|
|
6498
|
+
}
|
|
6499
|
+
|
|
6500
|
+
// Controller di esportazione (E4) — signal-native ma costruito con una interfaccia
|
|
6501
|
+
// host STRETTA (una manciata di accessor/callback), non con il back-ref `this.est`
|
|
6502
|
+
// dell'es-table1. Possiede solo `inProgress` + il lazy `ExportService`. La logica è
|
|
6503
|
+
// identica a prima (byte-compatibile con `ExportHandler.tryExport`): estratta dal
|
|
6504
|
+
// componente per isolare l'export come feature a sé, con la superficie di scrittura
|
|
6505
|
+
// più piccola (legge soltanto).
|
|
6506
|
+
class ExportController {
|
|
6507
|
+
constructor(host) {
|
|
6508
|
+
this.host = host;
|
|
6509
|
+
/** Evita esportazioni concorrenti (parità con l'`ExportHandler` originale) */
|
|
6510
|
+
this.inProgress = false;
|
|
6511
|
+
/** `ExportService` risolto pigramente e in modo difensivo (vedi `resolveService`) */
|
|
6512
|
+
this.svc = undefined;
|
|
6513
|
+
}
|
|
6514
|
+
/**
|
|
6515
|
+
* È `providedIn:'root'` ma la sua costruzione dipende da
|
|
6516
|
+
* `LocalizationService`/`DateService`/`UtilityService`; se quella catena non è
|
|
6517
|
+
* presente (test isolati, o consumer che non importa `@esfaenza/extensions`),
|
|
6518
|
+
* `injector.get` lancia → catturo e faccio fallback al percorso self-contained.
|
|
6519
|
+
*/
|
|
6520
|
+
resolveService() {
|
|
6521
|
+
if (this.svc === undefined) {
|
|
6522
|
+
try {
|
|
6523
|
+
this.svc = this.host.injector.get(ExportService, null);
|
|
6524
|
+
}
|
|
6525
|
+
catch {
|
|
6526
|
+
this.svc = null;
|
|
6527
|
+
}
|
|
6528
|
+
}
|
|
6529
|
+
return this.svc;
|
|
6530
|
+
}
|
|
6531
|
+
run(format = 'CSV') {
|
|
6532
|
+
const fn = this.host.exportFunction();
|
|
6533
|
+
// Parità con l'originale (`ExportHandler.tryExport`): se il consumer fornisce
|
|
6534
|
+
// una ExportFunction gliela lascio gestire. La callback ha la STESSA firma
|
|
6535
|
+
// dell'es-table1 — `(data, type, cancel?)` — così le funzioni di export dei
|
|
6536
|
+
// componenti legacy funzionano invariate: `cancel === true` annulla l'export
|
|
6537
|
+
// senza scaricare nulla. È l'`ExportService` che, ricreando ogni riga come
|
|
6538
|
+
// `new Type()`, legge i decoratori `@Export` — NON le colonne `*th`.
|
|
6539
|
+
if (fn) {
|
|
6540
|
+
if (this.inProgress) {
|
|
6541
|
+
this.host.flashNotice("Un'esportazione è già in corso, attendere.");
|
|
6542
|
+
return;
|
|
6543
|
+
}
|
|
6544
|
+
this.inProgress = true;
|
|
6545
|
+
try {
|
|
6546
|
+
fn((data, type, cancel = false) => {
|
|
6547
|
+
this.inProgress = false;
|
|
6548
|
+
if (cancel)
|
|
6549
|
+
return;
|
|
6550
|
+
this.viaService(data, type, format);
|
|
6551
|
+
}, format);
|
|
6552
|
+
}
|
|
6553
|
+
catch (e) {
|
|
6554
|
+
this.inProgress = false; // ExportFunction lanciata in modo sincrono: non blocco il guard
|
|
6555
|
+
throw e;
|
|
6556
|
+
}
|
|
6557
|
+
return;
|
|
6558
|
+
}
|
|
6559
|
+
// Nessuna ExportFunction: percorso self-contained sulle colonne `*th` correnti.
|
|
6560
|
+
const matrix = this.buildMatrix();
|
|
6561
|
+
if (matrix.length <= 1) {
|
|
6562
|
+
this.host.flashNotice('Nessun dato da esportare.');
|
|
6563
|
+
return;
|
|
6564
|
+
}
|
|
6565
|
+
if (format === 'XLSX')
|
|
6566
|
+
this.xlsx(matrix);
|
|
6567
|
+
else
|
|
6568
|
+
this.csv(matrix);
|
|
6569
|
+
}
|
|
6570
|
+
/** Delego all'`ExportService` originale (decorator-driven, byte-compatibile con es-table1). */
|
|
6571
|
+
viaService(data, type, format) {
|
|
6572
|
+
if (!data?.length) {
|
|
6573
|
+
this.host.flashNotice('Nessun dato da esportare.');
|
|
6574
|
+
return;
|
|
6575
|
+
}
|
|
6576
|
+
const svc = this.resolveService();
|
|
6577
|
+
if (!svc) {
|
|
6578
|
+
this.selfContained(data, format);
|
|
6579
|
+
return;
|
|
6580
|
+
} // niente servizio → fallback `*th`
|
|
6581
|
+
const columnsFilter = this.host.exportOnlyVisible() ? this.exportColumns().map(c => c.id) : undefined;
|
|
6582
|
+
const drcd = this.host.dynamicDefs();
|
|
6583
|
+
const genericHeaders = drcd && drcd.length > 0
|
|
6584
|
+
? drcd.map(t => ({ label: t.Description, key: t.PropertyName, propKey: t.PropertyName, order: t.ColumnOrder, type: 'string' }))
|
|
6585
|
+
: undefined;
|
|
6586
|
+
svc.export(data, format, this.host.exportBaseName(), type, columnsFilter, genericHeaders);
|
|
6587
|
+
}
|
|
6588
|
+
/** Percorso di fallback self-contained (dati forniti dalla callback, senza ExportService). */
|
|
6589
|
+
selfContained(data, format) {
|
|
6590
|
+
if (!data?.length) {
|
|
6591
|
+
this.host.flashNotice('Nessun dato da esportare.');
|
|
6592
|
+
return;
|
|
6593
|
+
}
|
|
6594
|
+
const matrix = this.matrixFrom(data.filter(r => !r._group));
|
|
6595
|
+
if (format === 'XLSX')
|
|
6596
|
+
this.xlsx(matrix);
|
|
6597
|
+
else
|
|
6598
|
+
this.csv(matrix);
|
|
6599
|
+
}
|
|
6600
|
+
/** Colonne da esportare (tutte o solo visibili) e con dato testuale */
|
|
6601
|
+
exportColumns() {
|
|
6602
|
+
const cols = this.host.exportOnlyVisible() ? this.host.visibleColumns() : this.host.columns();
|
|
6603
|
+
return cols.filter(c => !c.isGroup);
|
|
6604
|
+
}
|
|
6605
|
+
buildMatrix() {
|
|
6606
|
+
return this.matrixFrom(this.host.boundSource().filter(r => !r._group));
|
|
6607
|
+
}
|
|
6608
|
+
/** Matrice [header, ...righe] con i valori visualizzati */
|
|
6609
|
+
matrixFrom(rows) {
|
|
6610
|
+
const cols = this.exportColumns();
|
|
6611
|
+
const texts = this.host.renderedHeaderTexts();
|
|
6612
|
+
const header = cols.map(c => texts.get(c.id) || this.host.columnLabel(c));
|
|
6613
|
+
const body = rows.map(item => cols.map(c => this.host.cellDisplayText(item, c)));
|
|
6614
|
+
return [header, ...body];
|
|
6615
|
+
}
|
|
6616
|
+
csv(matrix) {
|
|
6617
|
+
const esc = (v) => {
|
|
6618
|
+
const s = v == null ? '' : String(v);
|
|
6619
|
+
return /[",\n;]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
|
|
6620
|
+
};
|
|
6621
|
+
const csv = matrix.map(row => row.map(esc).join(';')).join('\r\n');
|
|
6622
|
+
// BOM per la corretta apertura in Excel con caratteri accentati
|
|
6623
|
+
this.downloadBlob('' + csv, this.fileName('csv'), 'text/csv;charset=utf-8;');
|
|
6624
|
+
}
|
|
6625
|
+
async xlsx(matrix) {
|
|
6626
|
+
try {
|
|
6627
|
+
const XLSX = await import('xlsx');
|
|
6628
|
+
const ws = XLSX.utils.aoa_to_sheet(matrix);
|
|
6629
|
+
const wb = XLSX.utils.book_new();
|
|
6630
|
+
XLSX.utils.book_append_sheet(wb, ws, 'Export');
|
|
6631
|
+
XLSX.writeFile(wb, this.fileName('xlsx'));
|
|
6632
|
+
}
|
|
6633
|
+
catch {
|
|
6634
|
+
this.host.flashNotice('Export XLSX non disponibile (SheetJS assente): esporto in CSV.');
|
|
6635
|
+
this.csv(matrix);
|
|
6636
|
+
}
|
|
6637
|
+
}
|
|
6638
|
+
fileName(ext) {
|
|
6639
|
+
const name = this.host.exportBaseName() || 'Export';
|
|
6640
|
+
return /\.(csv|xlsx)$/i.test(name) ? name.replace(/\.(csv|xlsx)$/i, '.' + ext) : `${name}.${ext}`;
|
|
6641
|
+
}
|
|
6642
|
+
downloadBlob(content, filename, mime) {
|
|
6643
|
+
if (typeof document === 'undefined')
|
|
6644
|
+
return;
|
|
6645
|
+
const blob = new Blob([content], { type: mime });
|
|
6646
|
+
const url = URL.createObjectURL(blob);
|
|
6647
|
+
const a = document.createElement('a');
|
|
6648
|
+
a.href = url;
|
|
6649
|
+
a.download = filename;
|
|
6650
|
+
document.body.appendChild(a);
|
|
6651
|
+
a.click();
|
|
6652
|
+
document.body.removeChild(a);
|
|
6653
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
6654
|
+
}
|
|
6655
|
+
}
|
|
6656
|
+
|
|
6138
6657
|
/**
|
|
6139
6658
|
* Paginatore dell'es-table2.
|
|
6140
6659
|
*
|
|
@@ -6302,6 +6821,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
6302
6821
|
}], propDecorators: { page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }], pages: [{ type: i0.Input, args: [{ isSignal: true, alias: "pages", required: false }] }], total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: false }] }], itemsPerPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemsPerPage", required: false }] }], countLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "countLabel", required: false }] }], showCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCount", required: false }] }], showButtons: [{ type: i0.Input, args: [{ isSignal: true, alias: "showButtons", required: false }] }], showPagingOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPagingOptions", required: false }] }], allowAll: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowAll", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], itemsPerPageChange: [{ type: i0.Output, args: ["itemsPerPageChange"] }] } });
|
|
6303
6822
|
|
|
6304
6823
|
// Angular
|
|
6824
|
+
// `Est2HeaderGroupCell` è definita nel motore puro `core/header-groups.ts` e
|
|
6825
|
+
// re-esportata sopra (retro-compatibile per chi la importa da questo modulo).
|
|
6305
6826
|
/**
|
|
6306
6827
|
* es-table2 — riscrittura 2026 dell'es-table.
|
|
6307
6828
|
*
|
|
@@ -6335,15 +6856,15 @@ class EsTable2Component {
|
|
|
6335
6856
|
}
|
|
6336
6857
|
}
|
|
6337
6858
|
get denseClass() { return this.HighCellDensity(); }
|
|
6859
|
+
/**
|
|
6860
|
+
* Larghezza FISSA (px) di una colonna pinnata. Una colonna sticky è per contratto a
|
|
6861
|
+
* larghezza fissa: la usiamo sia per il calcolo degli offset che per vincolare la
|
|
6862
|
+
* cella (min/max-width) così il contenuto lungo viene troncato invece di sbordare
|
|
6863
|
+
* sulla colonna successiva. Default = `PINNED_DEFAULT_W` (coerente con gli offset).
|
|
6864
|
+
*/
|
|
6865
|
+
pinnedWidthPx(col) { return col.pinnedWidth || this.PINNED_DEFAULT_W; }
|
|
6338
6866
|
/** Offset `left` (px) di una colonna sticky all'indice `ci` in visibleColumns */
|
|
6339
|
-
pinnedLeftPx(ci) {
|
|
6340
|
-
const cols = this.visibleColumns();
|
|
6341
|
-
let left = this.Selection() ? this.SEL_STICKY_W : 0;
|
|
6342
|
-
for (let i = 0; i < ci && i < cols.length; i++)
|
|
6343
|
-
if (cols[i].pinned)
|
|
6344
|
-
left += (cols[i].pinnedWidth || 120);
|
|
6345
|
-
return left;
|
|
6346
|
-
}
|
|
6867
|
+
pinnedLeftPx(ci) { return this.pinnedOffsets()[ci] ?? 0; }
|
|
6347
6868
|
constructor(ngControl, cdr, hostEl, prefService, injector, defaults, debugMode, exportGlobalAcl) {
|
|
6348
6869
|
this.ngControl = ngControl;
|
|
6349
6870
|
this.cdr = cdr;
|
|
@@ -6364,6 +6885,7 @@ class EsTable2Component {
|
|
|
6364
6885
|
this.SelectionDisabled = input(false, ...(ngDevMode ? [{ debugName: "SelectionDisabled" }] : []));
|
|
6365
6886
|
this._SelectAll = input(null, ...(ngDevMode ? [{ debugName: "_SelectAll", alias: 'SelectAll' }] : [{ alias: 'SelectAll' }]));
|
|
6366
6887
|
this.SelectAll = linkedSignal(() => this.io(this._SelectAll(), null, false), ...(ngDevMode ? [{ debugName: "SelectAll" }] : []));
|
|
6888
|
+
/** @deprecated NO-OP di parità v1 (cache selezione cross-pagina): dichiarato per il drop-in, non usato */
|
|
6367
6889
|
this._UseSelectionCache = input(null, ...(ngDevMode ? [{ debugName: "_UseSelectionCache", alias: 'UseSelectionCache' }] : [{ alias: 'UseSelectionCache' }]));
|
|
6368
6890
|
this.UseSelectionCache = linkedSignal(() => this.io(this._UseSelectionCache(), this.defaults?.UseSelectionCache, true), ...(ngDevMode ? [{ debugName: "UseSelectionCache" }] : []));
|
|
6369
6891
|
this._ShiftClick = input(null, ...(ngDevMode ? [{ debugName: "_ShiftClick", alias: 'ShiftClick' }] : [{ alias: 'ShiftClick' }]));
|
|
@@ -6405,15 +6927,21 @@ class EsTable2Component {
|
|
|
6405
6927
|
this.ContainerClass = linkedSignal(() => this.io(this._ContainerClass(), this.defaults?.ContainerClass, ''), ...(ngDevMode ? [{ debugName: "ContainerClass" }] : []));
|
|
6406
6928
|
this.EsTableHandledSearch = input(false, ...(ngDevMode ? [{ debugName: "EsTableHandledSearch" }] : []));
|
|
6407
6929
|
this.SearchThrottle = input(50, ...(ngDevMode ? [{ debugName: "SearchThrottle" }] : []));
|
|
6408
|
-
|
|
6930
|
+
/** Abilita il ridimensionamento via handle sul bordo destro dell'header delle colonne PINNATE
|
|
6931
|
+
* (la nuova larghezza è persistita nelle preferenze). Il resize delle colonne non pinnate
|
|
6932
|
+
* non è ancora portato. */
|
|
6409
6933
|
this._ColumnsResizable = input(null, ...(ngDevMode ? [{ debugName: "_ColumnsResizable", alias: 'ColumnsResizable' }] : [{ alias: 'ColumnsResizable' }]));
|
|
6410
6934
|
this.ColumnsResizable = linkedSignal(() => this.io(this._ColumnsResizable(), this.defaults?.ColumnsResizable, false), ...(ngDevMode ? [{ debugName: "ColumnsResizable" }] : []));
|
|
6411
6935
|
this._ColumnsPinnable = input(null, ...(ngDevMode ? [{ debugName: "_ColumnsPinnable", alias: 'ColumnsPinnable' }] : [{ alias: 'ColumnsPinnable' }]));
|
|
6412
6936
|
this.ColumnsPinnable = linkedSignal(() => this.io(this._ColumnsPinnable(), this.defaults?.ColumnsPinnable, true), ...(ngDevMode ? [{ debugName: "ColumnsPinnable" }] : []));
|
|
6937
|
+
// Parità v1: se non valorizzati esplicitamente, `HiddenColumns`/`ColumnsOrdering`
|
|
6938
|
+
// (e la relativa voce di menu per gestire le colonne) sono ATTIVI di default sulle
|
|
6939
|
+
// tabelle a celle custom (direttive `*th`/`*td`). Un valore esplicito (anche `false`)
|
|
6940
|
+
// vince sempre. Vedi `es_table.component.ts` righe 625-626.
|
|
6413
6941
|
this._HiddenColumns = input(null, ...(ngDevMode ? [{ debugName: "_HiddenColumns", alias: 'HiddenColumns' }] : [{ alias: 'HiddenColumns' }]));
|
|
6414
|
-
this.HiddenColumns = linkedSignal(() => this.io(this._HiddenColumns(), null,
|
|
6942
|
+
this.HiddenColumns = linkedSignal(() => this.io(this._HiddenColumns(), null, this.directivesBased()), ...(ngDevMode ? [{ debugName: "HiddenColumns" }] : []));
|
|
6415
6943
|
this._ColumnsOrdering = input(null, ...(ngDevMode ? [{ debugName: "_ColumnsOrdering", alias: 'ColumnsOrdering' }] : [{ alias: 'ColumnsOrdering' }]));
|
|
6416
|
-
this.ColumnsOrdering = linkedSignal(() => this.io(this._ColumnsOrdering(), null,
|
|
6944
|
+
this.ColumnsOrdering = linkedSignal(() => this.io(this._ColumnsOrdering(), null, this.directivesBased()), ...(ngDevMode ? [{ debugName: "ColumnsOrdering" }] : []));
|
|
6417
6945
|
this._Export = input(null, ...(ngDevMode ? [{ debugName: "_Export", alias: 'Export' }] : [{ alias: 'Export' }]));
|
|
6418
6946
|
this.Export = linkedSignal(() => this.io(this._Export(), this.defaults?.Export, false), ...(ngDevMode ? [{ debugName: "Export" }] : []));
|
|
6419
6947
|
this.XLSXExport = input(true, ...(ngDevMode ? [{ debugName: "XLSXExport" }] : []));
|
|
@@ -6439,13 +6967,26 @@ class EsTable2Component {
|
|
|
6439
6967
|
this.SavePreferences = linkedSignal(() => this.io(this._SavePreferences(), this.defaults?.SavePreferences, false), ...(ngDevMode ? [{ debugName: "SavePreferences" }] : []));
|
|
6440
6968
|
/** Nome della tabella: chiave per la persistenza delle preferenze colonne (localStorage) */
|
|
6441
6969
|
this.Name = input('', ...(ngDevMode ? [{ debugName: "Name" }] : []));
|
|
6970
|
+
/** @deprecated NO-OP di parità v1 (stile paginazione gruppi): dichiarato per il drop-in, non risolto/usato */
|
|
6442
6971
|
this._RowGroupingPagingStyle = input(null, ...(ngDevMode ? [{ debugName: "_RowGroupingPagingStyle", alias: 'RowGroupingPagingStyle' }] : [{ alias: 'RowGroupingPagingStyle' }]));
|
|
6443
6972
|
this._ShowItemGroupsColumns = input(null, ...(ngDevMode ? [{ debugName: "_ShowItemGroupsColumns", alias: 'ShowItemGroupsColumns' }] : [{ alias: 'ShowItemGroupsColumns' }]));
|
|
6444
6973
|
this.Editable = input(false, ...(ngDevMode ? [{ debugName: "Editable" }] : []));
|
|
6445
6974
|
this.RangeSelection = input(false, ...(ngDevMode ? [{ debugName: "RangeSelection" }] : []));
|
|
6446
6975
|
this.ItemSourceProperty = input('', ...(ngDevMode ? [{ debugName: "ItemSourceProperty" }] : []));
|
|
6976
|
+
// --- Input di sola PARITÀ v1 (dichiarati per il drop-in, NON implementati) ----
|
|
6977
|
+
// Esistono solo perché un template consumer che li bind (`[HasHeaderGroup]`, …)
|
|
6978
|
+
// compili invariato passando da <es-table> a <es-table2>. Sono NO-OP volutamente:
|
|
6979
|
+
// · HasHeaderGroup/HasSecondaryHeaderGroup — header-group manuali via `#headerGroup`
|
|
6980
|
+
// (superati dall'approccio automatico `Parent:`/`Multi`).
|
|
6981
|
+
// · SearchView — drill-down gruppi server-side (intenzionalmente non portato).
|
|
6982
|
+
// · UseSelectionCache (175), RowGroupingPagingStyle (260) — vedi le rispettive
|
|
6983
|
+
// dichiarazioni. Rimuoverli romperebbe il drop-in.
|
|
6984
|
+
// (ColumnsResizable (224) NON è più un no-op: ridimensiona le colonne pinnate.)
|
|
6985
|
+
/** @deprecated NO-OP di parità v1 (header-group manuali, superati da `Parent:`/`Multi`) */
|
|
6447
6986
|
this.HasHeaderGroup = input(false, ...(ngDevMode ? [{ debugName: "HasHeaderGroup" }] : []));
|
|
6987
|
+
/** @deprecated NO-OP di parità v1 (seconda riga header-group manuale) */
|
|
6448
6988
|
this.HasSecondaryHeaderGroup = input(false, ...(ngDevMode ? [{ debugName: "HasSecondaryHeaderGroup" }] : []));
|
|
6989
|
+
/** @deprecated NO-OP di parità v1 (drill-down gruppi server-side, non portato) */
|
|
6449
6990
|
this.SearchView = input(null, ...(ngDevMode ? [{ debugName: "SearchView" }] : []));
|
|
6450
6991
|
/** Abilita il "patacchino" di auto-aggiornamento (toggle + intervallo) in alto a destra */
|
|
6451
6992
|
this._AutoUpdate = input(null, ...(ngDevMode ? [{ debugName: "_AutoUpdate", alias: 'AutoUpdate' }] : [{ alias: 'AutoUpdate' }]));
|
|
@@ -6526,87 +7067,33 @@ class EsTable2Component {
|
|
|
6526
7067
|
this.hasPinned = computed(() => this.pinnedCount() > 0, ...(ngDevMode ? [{ debugName: "hasPinned" }] : []));
|
|
6527
7068
|
/** Larghezza (px) della colonna di selezione quando sticky */
|
|
6528
7069
|
this.SEL_STICKY_W = 44;
|
|
7070
|
+
/** Larghezza (px) di default di una colonna pinnata senza `PinnedWidth` esplicito */
|
|
7071
|
+
this.PINNED_DEFAULT_W = 120;
|
|
7072
|
+
/**
|
|
7073
|
+
* Offset `left` (px) cumulativi delle colonne sticky, memoizzati: ricalcolati solo
|
|
7074
|
+
* quando cambiano `visibleColumns`/`Selection`. Prima `pinnedLeftPx` era O(n) per
|
|
7075
|
+
* chiamata e veniva invocato per OGNI cella pinnata del corpo → O(P²·R) per CD.
|
|
7076
|
+
*/
|
|
7077
|
+
this.pinnedOffsets = computed(() => {
|
|
7078
|
+
const cols = this.visibleColumns();
|
|
7079
|
+
let left = this.Selection() ? this.SEL_STICKY_W : 0;
|
|
7080
|
+
return cols.map(c => { const at = left; if (c.pinned)
|
|
7081
|
+
left += this.pinnedWidthPx(c); return at; });
|
|
7082
|
+
}, ...(ngDevMode ? [{ debugName: "pinnedOffsets" }] : []));
|
|
6529
7083
|
/** true se la tabella renderizza colonne strutturate (direttive/dinamica/report), non template semplici */
|
|
6530
7084
|
this.usesColumns = computed(() => this.columns().length > 0, ...(ngDevMode ? [{ debugName: "usesColumns" }] : []));
|
|
6531
7085
|
/**
|
|
6532
|
-
* Righe di header-group multi-livello
|
|
6533
|
-
*
|
|
6534
|
-
*
|
|
6535
|
-
* Ogni gruppo si allinea in BASSO: il padre immediato di una colonna sta sempre
|
|
6536
|
-
* nella riga direttamente sopra la colonna; gli antenati più alti salgono. Le
|
|
6537
|
-
* colonne senza gruppo (o con catena più corta) hanno celle-spacer vuote in alto.
|
|
7086
|
+
* Righe di header-group multi-livello (bottom-aligned). Delega al motore puro
|
|
7087
|
+
* `core/header-groups.ts`; ricalcolate automaticamente su riordino/visibilità.
|
|
6538
7088
|
*/
|
|
6539
|
-
this.headerGroupRows = computed(() => {
|
|
6540
|
-
const parentIds = this.hgParentIds();
|
|
6541
|
-
if (parentIds.size === 0)
|
|
6542
|
-
return [];
|
|
6543
|
-
const thById = this.hgThById();
|
|
6544
|
-
const leaves = this.visibleColumns();
|
|
6545
|
-
if (leaves.length === 0)
|
|
6546
|
-
return [];
|
|
6547
|
-
// catena antenati (padre-immediato → root) per un id, via thParent
|
|
6548
|
-
const chainOf = (id) => {
|
|
6549
|
-
const chain = [];
|
|
6550
|
-
let p = thById.get(id)?.thParent ?? null;
|
|
6551
|
-
let guard = 0;
|
|
6552
|
-
while (p && guard++ < 32) {
|
|
6553
|
-
chain.push(p);
|
|
6554
|
-
p = thById.get(p)?.thParent ?? null;
|
|
6555
|
-
}
|
|
6556
|
-
return chain;
|
|
6557
|
-
};
|
|
6558
|
-
const chains = leaves.map(c => chainOf(c.id));
|
|
6559
|
-
const maxDepth = chains.reduce((m, ch) => Math.max(m, ch.length), 0);
|
|
6560
|
-
if (maxDepth === 0)
|
|
6561
|
-
return [];
|
|
6562
|
-
const rows = [];
|
|
6563
|
-
for (let r = 0; r < maxDepth; r++) {
|
|
6564
|
-
const row = [];
|
|
6565
|
-
let i = 0;
|
|
6566
|
-
while (i < leaves.length) {
|
|
6567
|
-
// allineamento in basso: alla riga r (0=alto) corrisponde l'antenato a
|
|
6568
|
-
// distanza (maxDepth-1-r) dalla colonna (0 = padre immediato)
|
|
6569
|
-
const anc = chains[i][maxDepth - 1 - r];
|
|
6570
|
-
if (anc == null) {
|
|
6571
|
-
row.push({ id: `_hg_spacer_${r}_${i}`, span: 1, isGroup: false, label: '', template: null });
|
|
6572
|
-
i++;
|
|
6573
|
-
}
|
|
6574
|
-
else {
|
|
6575
|
-
let span = 0;
|
|
6576
|
-
while (i < leaves.length && chains[i][maxDepth - 1 - r] === anc) {
|
|
6577
|
-
span++;
|
|
6578
|
-
i++;
|
|
6579
|
-
}
|
|
6580
|
-
const gth = thById.get(anc);
|
|
6581
|
-
row.push({ id: anc, span, isGroup: true, label: gth?.thStaticContent ?? anc, template: gth?.Template ?? null });
|
|
6582
|
-
}
|
|
6583
|
-
}
|
|
6584
|
-
rows.push(row);
|
|
6585
|
-
}
|
|
6586
|
-
return rows;
|
|
6587
|
-
}, ...(ngDevMode ? [{ debugName: "headerGroupRows" }] : []));
|
|
7089
|
+
this.headerGroupRows = computed(() => computeHeaderGroupRows(this.visibleColumns(), this.hgThById(), this.hgParentIds()), ...(ngDevMode ? [{ debugName: "headerGroupRows" }] : []));
|
|
6588
7090
|
/** true se ci sono header-group da renderizzare */
|
|
6589
7091
|
this.hasHeaderGroups = computed(() => this.headerGroupRows().length > 0, ...(ngDevMode ? [{ debugName: "hasHeaderGroups" }] : []));
|
|
6590
7092
|
/**
|
|
6591
|
-
* Indici (in visibleColumns) delle colonne che aprono un nuovo gruppo-colonna
|
|
6592
|
-
*
|
|
6593
|
-
* Serve a disegnare i confini dei gruppi anche nel corpo della tabella.
|
|
7093
|
+
* Indici (in visibleColumns) delle colonne che aprono un nuovo gruppo-colonna.
|
|
7094
|
+
* Delega al motore puro; disegna i confini dei gruppi anche nel corpo tabella.
|
|
6594
7095
|
*/
|
|
6595
|
-
this.columnGroupBoundaries = computed(() => {
|
|
6596
|
-
const set = new Set();
|
|
6597
|
-
if (this.hgParentIds().size === 0)
|
|
6598
|
-
return set;
|
|
6599
|
-
const cols = this.visibleColumns();
|
|
6600
|
-
const thById = this.hgThById();
|
|
6601
|
-
let prev = undefined;
|
|
6602
|
-
for (let i = 0; i < cols.length; i++) {
|
|
6603
|
-
const parent = thById.get(cols[i].id)?.thParent ?? null;
|
|
6604
|
-
if (i > 0 && parent !== prev)
|
|
6605
|
-
set.add(i);
|
|
6606
|
-
prev = parent;
|
|
6607
|
-
}
|
|
6608
|
-
return set;
|
|
6609
|
-
}, ...(ngDevMode ? [{ debugName: "columnGroupBoundaries" }] : []));
|
|
7096
|
+
this.columnGroupBoundaries = computed(() => computeColumnGroupBoundaries(this.visibleColumns(), this.hgThById(), this.hgParentIds().size > 0), ...(ngDevMode ? [{ debugName: "columnGroupBoundaries" }] : []));
|
|
6610
7097
|
/** Numero di elementi totali (per il pager) */
|
|
6611
7098
|
this.totalCount = computed(() => {
|
|
6612
7099
|
const v = this.view();
|
|
@@ -6628,7 +7115,6 @@ class EsTable2Component {
|
|
|
6628
7115
|
return Math.max(1, Math.ceil(this.totalCount() / ipp));
|
|
6629
7116
|
}, ...(ngDevMode ? [{ debugName: "totalPages" }] : []));
|
|
6630
7117
|
this.effectiveAllValue = 999999;
|
|
6631
|
-
this.inited = false;
|
|
6632
7118
|
// ===========================================================================
|
|
6633
7119
|
// CVA plumbing
|
|
6634
7120
|
// ===========================================================================
|
|
@@ -6689,7 +7175,7 @@ class EsTable2Component {
|
|
|
6689
7175
|
// ===========================================================================
|
|
6690
7176
|
// Fase 6 — Colonne (visibilità/ordine), preferenze, export
|
|
6691
7177
|
// ===========================================================================
|
|
6692
|
-
/** Preferenze colonne in memoria (ordine + nascoste). Persistite se `SavePreferences`. */
|
|
7178
|
+
/** Preferenze colonne in memoria (ordine + nascoste + pin + larghezze). Persistite se `SavePreferences`. */
|
|
6693
7179
|
this.columnPrefs = null;
|
|
6694
7180
|
/** true una volta caricate le preferenze dal server (evita retrieve multipli) */
|
|
6695
7181
|
this.prefsLoaded = false;
|
|
@@ -6697,29 +7183,43 @@ class EsTable2Component {
|
|
|
6697
7183
|
this.columnsDialogOpen = signal(false, ...(ngDevMode ? [{ debugName: "columnsDialogOpen" }] : []));
|
|
6698
7184
|
/** Lista di lavoro della dialog (copia, per poter annullare) */
|
|
6699
7185
|
this.dialogCols = signal([], ...(ngDevMode ? [{ debugName: "dialogCols" }] : []));
|
|
7186
|
+
// --- Riordino via drag-drop (gated da ColumnsOrdering) ---------------------
|
|
7187
|
+
/** Indice della riga trascinata (o null) */
|
|
7188
|
+
this.dragIndex = signal(null, ...(ngDevMode ? [{ debugName: "dragIndex" }] : []));
|
|
7189
|
+
/** Indice della riga sotto il cursore durante il drag (per l'indicatore) */
|
|
7190
|
+
this.dragOverIndex = signal(null, ...(ngDevMode ? [{ debugName: "dragOverIndex" }] : []));
|
|
6700
7191
|
// --- Export (CSV self-contained / XLSX via SheetJS opzionale) --------------
|
|
6701
7192
|
this.exportMenuOpen = signal(false, ...(ngDevMode ? [{ debugName: "exportMenuOpen" }] : []));
|
|
6702
7193
|
this.cornerMenuOpen = signal(false, ...(ngDevMode ? [{ debugName: "cornerMenuOpen" }] : []));
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
* dipende da `LocalizationService`/`DateService`/`UtilityService`; se quella catena
|
|
6709
|
-
* non è presente (test isolati, o consumer che non importa `@esfaenza/extensions`),
|
|
6710
|
-
* `injector.get` lancia → catturo e faccio fallback al percorso self-contained.
|
|
6711
|
-
*/
|
|
6712
|
-
this.exportService = undefined;
|
|
6713
|
-
this.trackByIndex = (i) => i;
|
|
7194
|
+
// --- Ridimensionamento colonne pinnate (gated da [ColumnsResizable]) --------
|
|
7195
|
+
/** Larghezza minima (px) di una colonna pinnata durante il resize */
|
|
7196
|
+
this.PINNED_MIN_W = 48;
|
|
7197
|
+
/** Stato del resize in corso (colonna + coordinate iniziali) */
|
|
7198
|
+
this.resizing = null;
|
|
6714
7199
|
this.trackRow = (i, item) => item?._hash ?? item?.id ?? item;
|
|
6715
7200
|
this.trackCol = (_, col) => col.id;
|
|
6716
7201
|
if (ngControl)
|
|
6717
7202
|
ngControl.valueAccessor = this;
|
|
7203
|
+
// Controller di export (E4): costruito con una interfaccia host STRETTA (accessor
|
|
7204
|
+
// lazy), non col back-ref completo. `injector` è già assegnato (parameter property).
|
|
7205
|
+
this.exportCtrl = new ExportController({
|
|
7206
|
+
injector: this.injector,
|
|
7207
|
+
visibleColumns: () => this.visibleColumns(),
|
|
7208
|
+
columns: () => this.columns(),
|
|
7209
|
+
boundSource: () => this.boundSource(),
|
|
7210
|
+
exportOnlyVisible: () => this.ExportOnlyVisibleColumns(),
|
|
7211
|
+
exportBaseName: () => this.ExportFileName(),
|
|
7212
|
+
dynamicDefs: () => this.DynamicRowColumnsDefinition(),
|
|
7213
|
+
exportFunction: () => this.ExportFunction,
|
|
7214
|
+
cellDisplayText: (item, col) => this.cellDisplayText(item, col),
|
|
7215
|
+
columnLabel: (col) => this.columnLabel(col),
|
|
7216
|
+
renderedHeaderTexts: () => this.renderedHeaderTexts(),
|
|
7217
|
+
flashNotice: (msg) => this.flashNotice(msg)
|
|
7218
|
+
});
|
|
6718
7219
|
}
|
|
6719
7220
|
ngOnInit() {
|
|
6720
7221
|
// Tutti gli input risolti sono ora `computed()`/`linkedSignal()` reattivi
|
|
6721
7222
|
// (vedi dichiarazioni): niente più risoluzione one-shot qui.
|
|
6722
|
-
this.inited = true;
|
|
6723
7223
|
// Click destro: alla apertura del menu seleziono l'elemento (se non già selezionato)
|
|
6724
7224
|
const menu = this.ContextMenu || this.tableEmptyMenu;
|
|
6725
7225
|
if (menu)
|
|
@@ -6901,171 +7401,29 @@ class EsTable2Component {
|
|
|
6901
7401
|
this.hgThById.set(new Map(thById));
|
|
6902
7402
|
this.hgParentIds.set(new Set(parentIds));
|
|
6903
7403
|
const def = this.DefaultAlignment().toLowerCase();
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
if (!th.thIf)
|
|
6907
|
-
continue;
|
|
6908
|
-
if (th.thMulti)
|
|
6909
|
-
continue; // srotolata separatamente
|
|
6910
|
-
if (parentIds.has(th.th))
|
|
6911
|
-
continue; // è un header di gruppo, non una colonna-dato
|
|
6912
|
-
const td = tdByCol.get(th.th);
|
|
6913
|
-
cols.push({
|
|
6914
|
-
id: th.th,
|
|
6915
|
-
header: th,
|
|
6916
|
-
cell: td,
|
|
6917
|
-
visible: th.thVisible,
|
|
6918
|
-
orderable: th.thOrderable,
|
|
6919
|
-
alignment: (td?.tdAlignment || th.thAlignment)?.toLowerCase() || def,
|
|
6920
|
-
property: th.thProperty,
|
|
6921
|
-
type: th.thType ?? null,
|
|
6922
|
-
source: th.thSource,
|
|
6923
|
-
format: td?.tdFormat || th.thFormat || null,
|
|
6924
|
-
cssClass: td?.tdClass || th.thClass || '',
|
|
6925
|
-
wrap: th.thWrap,
|
|
6926
|
-
isGroup: th.thGroup,
|
|
6927
|
-
aggSpecs: parseAggregationSpecs(th.th, th.thAggregation),
|
|
6928
|
-
headerText: th.thStaticContent,
|
|
6929
|
-
headerBg: th.thBackgroundColor,
|
|
6930
|
-
routePath: th.thRoutePath,
|
|
6931
|
-
routeParams: th.thRouteParameterProperties,
|
|
6932
|
-
rendererName: th.thRendererName,
|
|
6933
|
-
pinned: !!th.thPinned,
|
|
6934
|
-
pinnedWidth: th.thPinnedWidth ?? null
|
|
6935
|
-
});
|
|
6936
|
-
}
|
|
7404
|
+
// Mapping direttive → colonne delegato al motore puro `core/columns.ts`
|
|
7405
|
+
const cols = mapDirectiveColumns(ths, tdByCol, parentIds, def);
|
|
6937
7406
|
this.directiveColumns = cols;
|
|
6938
7407
|
this.setColumns(cols);
|
|
6939
7408
|
}
|
|
6940
7409
|
/**
|
|
6941
|
-
* Srotola le colonne `*th Multi:true` sui dati
|
|
6942
|
-
* `
|
|
6943
|
-
* e i livelli di `header_group` (separati da ">") alimentano gli header-group.
|
|
7410
|
+
* Srotola le colonne `*th Multi:true` sui dati. Delega al motore puro
|
|
7411
|
+
* `core/columns.ts`; qui applico allo stato le mappe th/parent aggiornate.
|
|
6944
7412
|
*/
|
|
6945
7413
|
buildMultiColumns(items) {
|
|
6946
|
-
const
|
|
6947
|
-
const thById = new Map(this.baseThById);
|
|
6948
|
-
const parentIds = new Set(this.baseParentIds);
|
|
6949
|
-
const seen = new Set();
|
|
6950
|
-
for (const def of this.multiDefs) {
|
|
6951
|
-
const prop = def.prop;
|
|
6952
|
-
for (const item of (items || [])) {
|
|
6953
|
-
const arr = item?.[prop];
|
|
6954
|
-
if (!Array.isArray(arr))
|
|
6955
|
-
continue;
|
|
6956
|
-
for (let ii = 0; ii < arr.length; ii++) {
|
|
6957
|
-
const mv = arr[ii] || {};
|
|
6958
|
-
const header = mv.header ?? '';
|
|
6959
|
-
const group = mv.header_group ?? null;
|
|
6960
|
-
const key = `${group}__${header}`;
|
|
6961
|
-
if (seen.has(key))
|
|
6962
|
-
continue;
|
|
6963
|
-
seen.add(key);
|
|
6964
|
-
const hier = group ? String(group).split('>').map((s) => s.trim()).filter(Boolean) : [];
|
|
6965
|
-
const immediateParent = hier.length ? hier[hier.length - 1] : null;
|
|
6966
|
-
const colId = `${prop}_${ii}`;
|
|
6967
|
-
// th sintetico foglia: fornisce la catena thParent + il template header
|
|
6968
|
-
const leafTh = new EsThDirective(def.th.Template);
|
|
6969
|
-
leafTh.th = colId;
|
|
6970
|
-
leafTh.thStaticContent = header;
|
|
6971
|
-
leafTh.thParent = immediateParent;
|
|
6972
|
-
leafTh.thOrderable = false;
|
|
6973
|
-
thById.set(colId, leafTh);
|
|
6974
|
-
// th sintetici dei gruppi padre (una volta ciascuno)
|
|
6975
|
-
for (let h = 0; h < hier.length; h++) {
|
|
6976
|
-
const pid = hier[h];
|
|
6977
|
-
parentIds.add(pid);
|
|
6978
|
-
if (!thById.has(pid)) {
|
|
6979
|
-
const pth = new EsThDirective(null);
|
|
6980
|
-
pth.th = pid;
|
|
6981
|
-
pth.thStaticContent = pid;
|
|
6982
|
-
pth.thParent = h > 0 ? hier[h - 1] : null;
|
|
6983
|
-
thById.set(pid, pth);
|
|
6984
|
-
}
|
|
6985
|
-
}
|
|
6986
|
-
cols.push({
|
|
6987
|
-
id: colId,
|
|
6988
|
-
header: leafTh,
|
|
6989
|
-
cell: def.td,
|
|
6990
|
-
visible: true,
|
|
6991
|
-
orderable: false,
|
|
6992
|
-
alignment: 'right',
|
|
6993
|
-
property: null,
|
|
6994
|
-
type: null, source: null, format: null,
|
|
6995
|
-
cssClass: def.td?.tdClass || def.th.thClass || '',
|
|
6996
|
-
wrap: false, isGroup: false, aggSpecs: [],
|
|
6997
|
-
headerText: header,
|
|
6998
|
-
headerBg: null,
|
|
6999
|
-
multiProp: prop, multiIndex: ii,
|
|
7000
|
-
pinned: false, pinnedWidth: null
|
|
7001
|
-
});
|
|
7002
|
-
}
|
|
7003
|
-
}
|
|
7004
|
-
}
|
|
7414
|
+
const { columns, thById, parentIds } = buildMultiColumns(this.multiDefs, this.baseThById, this.baseParentIds, items);
|
|
7005
7415
|
this.hgThById.set(thById);
|
|
7006
7416
|
this.hgParentIds.set(parentIds);
|
|
7007
7417
|
this.multiColumnsBuilt = true;
|
|
7008
|
-
return
|
|
7418
|
+
return columns;
|
|
7009
7419
|
}
|
|
7010
|
-
/**
|
|
7420
|
+
/** Colonne dinamiche — delega al motore puro `core/columns.ts` */
|
|
7011
7421
|
buildDynamicColumns(defs) {
|
|
7012
|
-
|
|
7013
|
-
const tds = this.tdDirectives?.toArray() ?? [];
|
|
7014
|
-
const cols = [];
|
|
7015
|
-
for (let i = 0; i < defs.length; i++) {
|
|
7016
|
-
const d = defs[i];
|
|
7017
|
-
if (!d.Visible)
|
|
7018
|
-
continue;
|
|
7019
|
-
const renderer = tds.find(t => (d.RendererName && t.td === d.RendererName) || t.tdForProperty === d.PropertyName);
|
|
7020
|
-
cols.push({
|
|
7021
|
-
id: d.PropertyName,
|
|
7022
|
-
cell: renderer,
|
|
7023
|
-
visible: true,
|
|
7024
|
-
orderable: d.Orderable !== false,
|
|
7025
|
-
alignment: 'left',
|
|
7026
|
-
property: d.PropertyName,
|
|
7027
|
-
type: d.PropertyType ?? null,
|
|
7028
|
-
source: d.PropertySource ?? null,
|
|
7029
|
-
format: d.Format ?? null,
|
|
7030
|
-
cssClass: d.Clss || '',
|
|
7031
|
-
wrap: !!d.HeaderWraps,
|
|
7032
|
-
isGroup: false,
|
|
7033
|
-
aggSpecs: [],
|
|
7034
|
-
headerText: d.Description,
|
|
7035
|
-
routePath: d.RoutePath,
|
|
7036
|
-
routeParams: d.RouteParameterProperties,
|
|
7037
|
-
rendererName: d.RendererName,
|
|
7038
|
-
// colori per-cella opzionali (forecolors/backcolors allineati all'ordine delle colonne)
|
|
7039
|
-
colorIndex: i
|
|
7040
|
-
});
|
|
7041
|
-
}
|
|
7042
|
-
this.setColumns(cols);
|
|
7422
|
+
this.setColumns(mapDynamicColumns(defs, this.tdDirectives?.toArray() ?? []));
|
|
7043
7423
|
}
|
|
7044
|
-
/**
|
|
7424
|
+
/** Colonne report — delega al motore puro `core/columns.ts` */
|
|
7045
7425
|
buildReportColumns(reportColumns) {
|
|
7046
|
-
|
|
7047
|
-
for (let i = 0; i < reportColumns.length; i++) {
|
|
7048
|
-
const c = reportColumns[i];
|
|
7049
|
-
cols.push({
|
|
7050
|
-
id: c.id,
|
|
7051
|
-
visible: true,
|
|
7052
|
-
orderable: false,
|
|
7053
|
-
alignment: c.alignment?.toLowerCase() || 'left',
|
|
7054
|
-
property: null,
|
|
7055
|
-
type: null,
|
|
7056
|
-
source: null,
|
|
7057
|
-
format: c.format ?? null,
|
|
7058
|
-
cssClass: '',
|
|
7059
|
-
wrap: false,
|
|
7060
|
-
isGroup: false,
|
|
7061
|
-
aggSpecs: [],
|
|
7062
|
-
headerText: c.description,
|
|
7063
|
-
headerBg: c.color || null,
|
|
7064
|
-
propAccessor: String(i),
|
|
7065
|
-
colorIndex: i
|
|
7066
|
-
});
|
|
7067
|
-
}
|
|
7068
|
-
this.setColumns(cols);
|
|
7426
|
+
this.setColumns(mapReportColumns(reportColumns));
|
|
7069
7427
|
}
|
|
7070
7428
|
/** Sceglie la sorgente delle colonne in base al modello: report → dinamica → direttive */
|
|
7071
7429
|
resolveColumns(items) {
|
|
@@ -7555,8 +7913,8 @@ class EsTable2Component {
|
|
|
7555
7913
|
const td = target?.closest?.('td[data-r]');
|
|
7556
7914
|
if (!td)
|
|
7557
7915
|
return null;
|
|
7558
|
-
const r = parseInt(td.getAttribute('data-r'), 10);
|
|
7559
|
-
const c = parseInt(td.getAttribute('data-c'), 10);
|
|
7916
|
+
const r = parseInt(td.getAttribute('data-r') ?? '', 10);
|
|
7917
|
+
const c = parseInt(td.getAttribute('data-c') ?? '', 10);
|
|
7560
7918
|
return isNaN(r) || isNaN(c) ? null : { r, c };
|
|
7561
7919
|
}
|
|
7562
7920
|
/** mousedown sulla griglia: apre un nuovo range sulla cella-ancora */
|
|
@@ -7790,11 +8148,20 @@ class EsTable2Component {
|
|
|
7790
8148
|
this.cdr.markForCheck();
|
|
7791
8149
|
}
|
|
7792
8150
|
// --- Valori: lettura/scrittura tipizzata + testo di copia ------------------
|
|
8151
|
+
/** Lettura bag-aware di una proprietà (gestisce gli oggetti "generic" con `properties`) */
|
|
8152
|
+
readProp(item, key) {
|
|
8153
|
+
return item?.properties ? item.properties[key] : item?.[key];
|
|
8154
|
+
}
|
|
8155
|
+
/** Scrittura bag-aware di una proprietà */
|
|
8156
|
+
writeProp(item, key, v) {
|
|
8157
|
+
if (item?.properties)
|
|
8158
|
+
item.properties[key] = v;
|
|
8159
|
+
else
|
|
8160
|
+
item[key] = v;
|
|
8161
|
+
}
|
|
7793
8162
|
/** Valore grezzo su cui operano editing/paste (gestisce gli oggetti `properties`) */
|
|
7794
8163
|
rawCellValue(item, col) {
|
|
7795
|
-
|
|
7796
|
-
return null;
|
|
7797
|
-
return item?.properties ? item.properties[col.property] : item?.[col.property];
|
|
8164
|
+
return col.property ? this.readProp(item, col.property) : null;
|
|
7798
8165
|
}
|
|
7799
8166
|
/** Scrive un valore GIÀ convertito e restituisce l'eventuale EsTableModelChange */
|
|
7800
8167
|
applyCoerced(item, col, niu) {
|
|
@@ -7803,94 +8170,12 @@ class EsTable2Component {
|
|
|
7803
8170
|
const old = this.rawCellValue(item, col);
|
|
7804
8171
|
if (old === niu)
|
|
7805
8172
|
return null;
|
|
7806
|
-
|
|
7807
|
-
item.properties[col.property] = niu;
|
|
7808
|
-
else
|
|
7809
|
-
item[col.property] = niu;
|
|
8173
|
+
this.writeProp(item, col.property, niu);
|
|
7810
8174
|
return new EsTableModelChange(item, String(col.headerText ?? col.id), col.property, old, niu);
|
|
7811
8175
|
}
|
|
7812
|
-
/**
|
|
7813
|
-
* Converte una stringa (digitata/incollata) nel tipo della colonna, segnalando
|
|
7814
|
-
* se è compatibile. `ok:false` = valore incompatibile col tipo (es. testo in una
|
|
7815
|
-
* colonna numerica) → chi chiama avvisa e non scrive. Una stringa vuota è sempre
|
|
7816
|
-
* valida e azzera il valore (null).
|
|
7817
|
-
*/
|
|
8176
|
+
/** Coercizione valore → delega al motore puro `core/coercion.ts` */
|
|
7818
8177
|
coerceValue(raw, col) {
|
|
7819
|
-
|
|
7820
|
-
const v = raw == null ? '' : String(raw).trim();
|
|
7821
|
-
switch (type) {
|
|
7822
|
-
case 'int':
|
|
7823
|
-
case 'number':
|
|
7824
|
-
case 'float':
|
|
7825
|
-
case 'currency': {
|
|
7826
|
-
if (v === '')
|
|
7827
|
-
return { ok: true, value: null };
|
|
7828
|
-
const n = this.toNumber(v);
|
|
7829
|
-
if (n == null || isNaN(n))
|
|
7830
|
-
return { ok: false, value: null };
|
|
7831
|
-
return { ok: true, value: (type === 'int' || type === 'number') ? Math.round(n) : n };
|
|
7832
|
-
}
|
|
7833
|
-
case 'boolean': {
|
|
7834
|
-
if (v === '')
|
|
7835
|
-
return { ok: true, value: null };
|
|
7836
|
-
if (/^(true|1|✓|s[iì]|si|yes|y)$/i.test(v))
|
|
7837
|
-
return { ok: true, value: true };
|
|
7838
|
-
if (/^(false|0|—|no|n)$/i.test(v))
|
|
7839
|
-
return { ok: true, value: false };
|
|
7840
|
-
return { ok: false, value: null };
|
|
7841
|
-
}
|
|
7842
|
-
case 'enum':
|
|
7843
|
-
case 'autocomplete': {
|
|
7844
|
-
if (!col.source || col.source.length === 0)
|
|
7845
|
-
return { ok: true, value: v };
|
|
7846
|
-
const byId = col.source.find(o => String(o.id) === v);
|
|
7847
|
-
if (byId)
|
|
7848
|
-
return { ok: true, value: byId.id };
|
|
7849
|
-
const byDesc = col.source.find(o => o.description === v);
|
|
7850
|
-
if (byDesc)
|
|
7851
|
-
return { ok: true, value: byDesc.id };
|
|
7852
|
-
return { ok: false, value: null };
|
|
7853
|
-
}
|
|
7854
|
-
case 'date':
|
|
7855
|
-
case 'datetime':
|
|
7856
|
-
case 'time': {
|
|
7857
|
-
if (v === '')
|
|
7858
|
-
return { ok: true, value: null };
|
|
7859
|
-
const d = this.parseDateLoose(v);
|
|
7860
|
-
return d ? { ok: true, value: d } : { ok: false, value: null };
|
|
7861
|
-
}
|
|
7862
|
-
default:
|
|
7863
|
-
return { ok: true, value: raw };
|
|
7864
|
-
}
|
|
7865
|
-
}
|
|
7866
|
-
/** Parsing numerico tollerante (separatori di migliaia + virgola/punto decimale). `NaN` = non numerico */
|
|
7867
|
-
toNumber(v) {
|
|
7868
|
-
let s = v.replace(/\s/g, '').replace(/[€$%]/g, '');
|
|
7869
|
-
if (s === '')
|
|
7870
|
-
return NaN;
|
|
7871
|
-
const hasComma = s.includes(','), hasDot = s.includes('.');
|
|
7872
|
-
if (hasComma && hasDot) {
|
|
7873
|
-
// l'ultimo separatore è quello decimale
|
|
7874
|
-
if (s.lastIndexOf(',') > s.lastIndexOf('.'))
|
|
7875
|
-
s = s.replace(/\./g, '').replace(',', '.');
|
|
7876
|
-
else
|
|
7877
|
-
s = s.replace(/,/g, '');
|
|
7878
|
-
}
|
|
7879
|
-
else if (hasComma) {
|
|
7880
|
-
s = s.replace(',', '.');
|
|
7881
|
-
}
|
|
7882
|
-
return /^-?\d*\.?\d+$/.test(s) ? parseFloat(s) : NaN;
|
|
7883
|
-
}
|
|
7884
|
-
/** Parsing date tollerante: `dd/MM/yyyy [HH:mm[:ss]]` o formati nativi. `null` = non valida */
|
|
7885
|
-
parseDateLoose(v) {
|
|
7886
|
-
const m = v.match(/^(\d{1,2})[\/\-.](\d{1,2})[\/\-.](\d{2,4})(?:[ T](\d{1,2}):(\d{2})(?::(\d{2}))?)?$/);
|
|
7887
|
-
if (m) {
|
|
7888
|
-
const y = m[3].length === 2 ? 2000 + +m[3] : +m[3];
|
|
7889
|
-
const d = new Date(y, +m[2] - 1, +m[1], +(m[4] || 0), +(m[5] || 0), +(m[6] || 0));
|
|
7890
|
-
return isNaN(d.getTime()) ? null : d;
|
|
7891
|
-
}
|
|
7892
|
-
const d = new Date(v);
|
|
7893
|
-
return isNaN(d.getTime()) ? null : d;
|
|
8178
|
+
return coerceValue(raw, col);
|
|
7894
8179
|
}
|
|
7895
8180
|
/** Testo visualizzato di una cella (usato dalla copia) */
|
|
7896
8181
|
cellDisplayText(item, col) {
|
|
@@ -8151,7 +8436,7 @@ class EsTable2Component {
|
|
|
8151
8436
|
}
|
|
8152
8437
|
removeItem(item) { this.onRemoval.emit(item); }
|
|
8153
8438
|
abortRemoval(item) { this.onAbortRemoval.emit(item); }
|
|
8154
|
-
/** Applica ordine + visibilità + pin delle preferenze a una lista di colonne appena costruita */
|
|
8439
|
+
/** Applica ordine + visibilità + pin + larghezze delle preferenze a una lista di colonne appena costruita */
|
|
8155
8440
|
setColumns(cols) {
|
|
8156
8441
|
const p = this.columnPrefs;
|
|
8157
8442
|
if (!p) {
|
|
@@ -8176,6 +8461,9 @@ class EsTable2Component {
|
|
|
8176
8461
|
// il pin salvato sovrascrive il default della direttiva (solo se presente nelle prefs)
|
|
8177
8462
|
if (p.pinned)
|
|
8178
8463
|
c.pinned = p.pinned.includes(c.id);
|
|
8464
|
+
// larghezza pinnata salvata (resize) → sovrascrive il PinnedWidth della direttiva
|
|
8465
|
+
if (p.widths && p.widths[c.id] != null)
|
|
8466
|
+
c.pinnedWidth = p.widths[c.id];
|
|
8179
8467
|
}
|
|
8180
8468
|
this.columns.set(ordered);
|
|
8181
8469
|
this.ensurePrefsLoaded();
|
|
@@ -8185,7 +8473,7 @@ class EsTable2Component {
|
|
|
8185
8473
|
* del controllo (ngModel/formControlName). In fallback usa `[Name]`.
|
|
8186
8474
|
*/
|
|
8187
8475
|
prefsKey() {
|
|
8188
|
-
const key = this.ngControl?.name || this.Name() || null;
|
|
8476
|
+
const key = String(this.ngControl?.name ?? '') || this.Name() || null;
|
|
8189
8477
|
if (!key && this.SavePreferences())
|
|
8190
8478
|
console.error("[es-table2] Non posso usare le preferenze senza un 'name' (ngModel) o un [Name] per la tabella");
|
|
8191
8479
|
return key;
|
|
@@ -8245,7 +8533,15 @@ class EsTable2Component {
|
|
|
8245
8533
|
// "perdono" le colonne aggiunte a una tabella con preferenze già memorizzate.
|
|
8246
8534
|
const knownToPrefs = new Set([...order, ...visible]);
|
|
8247
8535
|
const hidden = [...currentIds].filter(id => knownToPrefs.has(id) && !visible.includes(id));
|
|
8248
|
-
|
|
8536
|
+
// `widths` (resize colonne pinnate): estensione retro-compatibile, filtrata sulle colonne presenti
|
|
8537
|
+
let widths;
|
|
8538
|
+
if (prefs.widths && typeof prefs.widths === 'object') {
|
|
8539
|
+
widths = {};
|
|
8540
|
+
for (const id of Object.keys(prefs.widths))
|
|
8541
|
+
if (currentIds.has(id))
|
|
8542
|
+
widths[id] = prefs.widths[id];
|
|
8543
|
+
}
|
|
8544
|
+
this.columnPrefs = { order, hidden, pinned, widths };
|
|
8249
8545
|
this.setColumns(this.columns());
|
|
8250
8546
|
if (prefs.orders?.length) {
|
|
8251
8547
|
this.orders.set([...prefs.orders]);
|
|
@@ -8267,12 +8563,18 @@ class EsTable2Component {
|
|
|
8267
8563
|
// Includo anche le sotto-colonne Multi (id stabili `prop_index`): il
|
|
8268
8564
|
// caricamento è gated finché non esistono, quindi il guard non le rifiuta.
|
|
8269
8565
|
const cols = this.columns();
|
|
8566
|
+
// larghezze personalizzate delle colonne pinnate (resize) → { id: px }
|
|
8567
|
+
const widths = {};
|
|
8568
|
+
for (const c of cols)
|
|
8569
|
+
if (c.pinned && c.pinnedWidth)
|
|
8570
|
+
widths[c.id] = c.pinnedWidth;
|
|
8270
8571
|
const payload = {
|
|
8271
8572
|
columns: cols.filter(c => c.visible).map(c => c.id),
|
|
8272
8573
|
column_ordering: cols.map(c => c.id),
|
|
8273
8574
|
orders: this.orders(),
|
|
8274
|
-
//
|
|
8275
|
-
pinned: cols.filter(c => c.pinned).map(c => c.id)
|
|
8575
|
+
// estensioni es-table2 (retro-compatibili: l'originale ignora i campi extra)
|
|
8576
|
+
pinned: cols.filter(c => c.pinned).map(c => c.id),
|
|
8577
|
+
widths
|
|
8276
8578
|
};
|
|
8277
8579
|
// allineo anche i campi wire dell'AppSearch (parità col server-side)
|
|
8278
8580
|
if (this.viewMode() && this.view()) {
|
|
@@ -8325,13 +8627,11 @@ class EsTable2Component {
|
|
|
8325
8627
|
}
|
|
8326
8628
|
openColumnsDialog() {
|
|
8327
8629
|
// Leggo dal DOM i testi header renderizzati (leaf + gruppi) per etichette esatte.
|
|
8328
|
-
const headerTexts =
|
|
8630
|
+
const headerTexts = this.renderedHeaderTexts();
|
|
8329
8631
|
const groupLabels = new Map();
|
|
8330
8632
|
const host = this.hostEl?.nativeElement;
|
|
8331
|
-
if (host)
|
|
8332
|
-
host.querySelectorAll('thead th[data-
|
|
8333
|
-
host.querySelectorAll('thead th[data-groupid]').forEach((el) => groupLabels.set(el.getAttribute('data-groupid'), (el.textContent || '').trim()));
|
|
8334
|
-
}
|
|
8633
|
+
if (host)
|
|
8634
|
+
host.querySelectorAll('thead th[data-groupid]').forEach((el) => groupLabels.set(el.getAttribute('data-groupid') ?? '', (el.textContent || '').trim()));
|
|
8335
8635
|
// TUTTE le colonne (comprese le Multi), con etichetta gruppo-consapevole.
|
|
8336
8636
|
this.dialogCols.set(this.columns().map(c => ({
|
|
8337
8637
|
id: c.id,
|
|
@@ -8402,6 +8702,62 @@ class EsTable2Component {
|
|
|
8402
8702
|
: [...list.slice(0, bStart), ...list.slice(aStart, aEnd + 1), ...list.slice(bStart, bEnd + 1), ...list.slice(aEnd + 1)];
|
|
8403
8703
|
this.dialogCols.set(newList);
|
|
8404
8704
|
}
|
|
8705
|
+
dialogDragStart(i, ev) {
|
|
8706
|
+
this.dragIndex.set(i);
|
|
8707
|
+
if (ev.dataTransfer) {
|
|
8708
|
+
ev.dataTransfer.effectAllowed = 'move';
|
|
8709
|
+
try {
|
|
8710
|
+
ev.dataTransfer.setData('text/plain', String(i));
|
|
8711
|
+
}
|
|
8712
|
+
catch { /* alcuni browser */ }
|
|
8713
|
+
}
|
|
8714
|
+
this.cdr.markForCheck();
|
|
8715
|
+
}
|
|
8716
|
+
dialogDragOver(i, ev) {
|
|
8717
|
+
if (this.dragIndex() == null)
|
|
8718
|
+
return;
|
|
8719
|
+
ev.preventDefault(); // consente il drop
|
|
8720
|
+
if (ev.dataTransfer)
|
|
8721
|
+
ev.dataTransfer.dropEffect = 'move';
|
|
8722
|
+
if (this.dragOverIndex() !== i) {
|
|
8723
|
+
this.dragOverIndex.set(i);
|
|
8724
|
+
this.cdr.markForCheck();
|
|
8725
|
+
}
|
|
8726
|
+
}
|
|
8727
|
+
dialogDrop(i, ev) {
|
|
8728
|
+
ev.preventDefault();
|
|
8729
|
+
const from = this.dragIndex();
|
|
8730
|
+
if (from != null && from !== i)
|
|
8731
|
+
this.dialogReorder(from, i);
|
|
8732
|
+
this.dialogDragEnd();
|
|
8733
|
+
}
|
|
8734
|
+
dialogDragEnd() {
|
|
8735
|
+
this.dragIndex.set(null);
|
|
8736
|
+
this.dragOverIndex.set(null);
|
|
8737
|
+
this.cdr.markForCheck();
|
|
8738
|
+
}
|
|
8739
|
+
/**
|
|
8740
|
+
* Sposta la riga `from` alla posizione `to`, poi RIPARA la contiguità dei gruppi
|
|
8741
|
+
* in tempo reale (come `dialogMove`): un drag che porterebbe una colonna fuori dal
|
|
8742
|
+
* suo gruppo la fa "scattare" nella posizione contigua più vicina, così la dialog
|
|
8743
|
+
* non mostra mai gruppi spezzati durante l'editing.
|
|
8744
|
+
*/
|
|
8745
|
+
dialogReorder(from, to) {
|
|
8746
|
+
const list = [...this.dialogCols()];
|
|
8747
|
+
if (from < 0 || to < 0 || from >= list.length || to >= list.length)
|
|
8748
|
+
return;
|
|
8749
|
+
const [moved] = list.splice(from, 1);
|
|
8750
|
+
list.splice(to, 0, moved);
|
|
8751
|
+
if (this.hgParentIds().size > 0) {
|
|
8752
|
+
const orderIds = this.enforceGroupContiguity(list.map(c => c.id));
|
|
8753
|
+
const byId = new Map(list.map(c => [c.id, c]));
|
|
8754
|
+
this.dialogCols.set(orderIds.map(id => byId.get(id)));
|
|
8755
|
+
}
|
|
8756
|
+
else {
|
|
8757
|
+
this.dialogCols.set(list);
|
|
8758
|
+
}
|
|
8759
|
+
this.cdr.markForCheck();
|
|
8760
|
+
}
|
|
8405
8761
|
applyColumnsDialog() {
|
|
8406
8762
|
// Riparo l'ordine mantenendo CONTIGUI i membri di ogni gruppo: spostare una
|
|
8407
8763
|
// colonna fuori dal suo gruppo non deve produrre header-group spezzati.
|
|
@@ -8428,41 +8784,14 @@ class EsTable2Component {
|
|
|
8428
8784
|
* colonne esterne.
|
|
8429
8785
|
*/
|
|
8430
8786
|
/** Catena degli antenati-gruppo (root→padre immediato) di una colonna; `[]` se non raggruppata */
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
let p = thById.get(id)?.thParent ?? null;
|
|
8435
|
-
let guard = 0;
|
|
8436
|
-
while (p && guard++ < 32) {
|
|
8437
|
-
chain.push(p);
|
|
8438
|
-
p = thById.get(p)?.thParent ?? null;
|
|
8439
|
-
}
|
|
8440
|
-
return chain.reverse(); // root-first
|
|
8787
|
+
/** Catena antenati di una colonna — delega al motore `core/header-groups.ts` */
|
|
8788
|
+
ancestorChain(id, rootFirst = false) {
|
|
8789
|
+
return ancestorChain(id, this.hgThById(), rootFirst);
|
|
8441
8790
|
}
|
|
8791
|
+
columnChain(id) { return this.ancestorChain(id, true); }
|
|
8792
|
+
/** Riordino group-contiguo — delega al motore `core/header-groups.ts` */
|
|
8442
8793
|
enforceGroupContiguity(orderIds) {
|
|
8443
|
-
|
|
8444
|
-
return orderIds;
|
|
8445
|
-
const chainOf = (id) => this.columnChain(id);
|
|
8446
|
-
// prima apparizione di ogni id-gruppo nell'ordine utente
|
|
8447
|
-
const firstIdx = new Map();
|
|
8448
|
-
orderIds.forEach((id, i) => {
|
|
8449
|
-
for (const g of chainOf(id))
|
|
8450
|
-
if (!firstIdx.has(g))
|
|
8451
|
-
firstIdx.set(g, i);
|
|
8452
|
-
});
|
|
8453
|
-
const keyOf = (id, i) => [...chainOf(id).map(g => firstIdx.get(g)), i];
|
|
8454
|
-
return orderIds
|
|
8455
|
-
.map((id, i) => ({ id, key: keyOf(id, i), i }))
|
|
8456
|
-
.sort((a, b) => {
|
|
8457
|
-
const n = Math.max(a.key.length, b.key.length);
|
|
8458
|
-
for (let k = 0; k < n; k++) {
|
|
8459
|
-
const av = a.key[k] ?? -1, bv = b.key[k] ?? -1;
|
|
8460
|
-
if (av !== bv)
|
|
8461
|
-
return av - bv;
|
|
8462
|
-
}
|
|
8463
|
-
return a.i - b.i;
|
|
8464
|
-
})
|
|
8465
|
-
.map(x => x.id);
|
|
8794
|
+
return enforceGroupContiguity(orderIds, this.hgThById(), this.hgParentIds().size > 0);
|
|
8466
8795
|
}
|
|
8467
8796
|
resetColumnsDialog() {
|
|
8468
8797
|
this.columnPrefs = null;
|
|
@@ -8483,172 +8812,22 @@ class EsTable2Component {
|
|
|
8483
8812
|
this.cdr.markForCheck();
|
|
8484
8813
|
}
|
|
8485
8814
|
}
|
|
8486
|
-
/**
|
|
8487
|
-
exportColumns() {
|
|
8488
|
-
const cols = this.ExportOnlyVisibleColumns() ? this.visibleColumns() : this.columns();
|
|
8489
|
-
return cols.filter(c => !c.isGroup);
|
|
8490
|
-
}
|
|
8491
|
-
/** Righe-dato correnti (esclude righe-gruppo) */
|
|
8492
|
-
exportRows() {
|
|
8493
|
-
return this.boundSource().filter(r => !r._group);
|
|
8494
|
-
}
|
|
8495
|
-
/** Matrice [header, ...righe] con i valori visualizzati */
|
|
8496
|
-
buildExportMatrix() {
|
|
8497
|
-
const cols = this.exportColumns();
|
|
8498
|
-
const header = cols.map(c => this.headerLabel(c));
|
|
8499
|
-
const rows = this.exportRows().map(item => cols.map(c => this.cellDisplayText(item, c)));
|
|
8500
|
-
return [header, ...rows];
|
|
8501
|
-
}
|
|
8502
|
-
/**
|
|
8503
|
-
* Etichetta di intestazione per l'export self-contained: usa il testo header
|
|
8504
|
-
* RENDERIZZATO letto dal DOM (rispetta il casing del template `*th`, es. "ID"),
|
|
8505
|
-
* con fallback su `columnLabel` (headerText statico / id) se il DOM non è leggibile.
|
|
8506
|
-
*/
|
|
8507
|
-
headerLabel(col) {
|
|
8508
|
-
const el = this.hostEl?.nativeElement?.querySelector(`thead th[data-colid="${col.id}"]`);
|
|
8509
|
-
const txt = (el?.textContent || '').trim();
|
|
8510
|
-
return txt || this.columnLabel(col);
|
|
8511
|
-
}
|
|
8512
|
-
resolveExportService() {
|
|
8513
|
-
if (this.exportService === undefined) {
|
|
8514
|
-
try {
|
|
8515
|
-
this.exportService = this.injector.get(ExportService, null);
|
|
8516
|
-
}
|
|
8517
|
-
catch {
|
|
8518
|
-
this.exportService = null;
|
|
8519
|
-
}
|
|
8520
|
-
}
|
|
8521
|
-
return this.exportService;
|
|
8522
|
-
}
|
|
8815
|
+
/** Export delegato al controller `ExportController` (E4); chiude prima il menu chrome. */
|
|
8523
8816
|
export(format = 'CSV') {
|
|
8524
8817
|
this.exportMenuOpen.set(false);
|
|
8525
|
-
|
|
8526
|
-
// una ExportFunction gliela lascio gestire. La callback ha la STESSA firma
|
|
8527
|
-
// dell'es-table1 — `(data, type, cancel?)` — così le funzioni di export dei
|
|
8528
|
-
// componenti legacy funzionano invariate: `cancel === true` annulla l'export
|
|
8529
|
-
// (es. l'utente ha abortito il recupero dati lato server) senza scaricare nulla.
|
|
8530
|
-
//
|
|
8531
|
-
// Il flusso è IDENTICO a es-table1: la callback riceve `(data, Type)` dove
|
|
8532
|
-
// `Type` è la classe DTO decorata con `@Export(...)`. È l'`ExportService` che,
|
|
8533
|
-
// ricreando ogni riga come `new Type()`, legge quei decoratori per determinare
|
|
8534
|
-
// colonne/intestazioni/ordine/formato — NON le colonne `*th` della tabella.
|
|
8535
|
-
// La tabella contribuisce solo il filtro delle colonne visibili (via `tableid`
|
|
8536
|
-
// del decoratore) e, in modalità dinamica, gli header generici.
|
|
8537
|
-
if (this.ExportFunction) {
|
|
8538
|
-
if (this.exportInProgress) {
|
|
8539
|
-
this.flashNotice("Un'esportazione è già in corso, attendere.");
|
|
8540
|
-
return;
|
|
8541
|
-
}
|
|
8542
|
-
this.exportInProgress = true;
|
|
8543
|
-
try {
|
|
8544
|
-
this.ExportFunction((data, type, cancel = false) => {
|
|
8545
|
-
this.exportInProgress = false;
|
|
8546
|
-
if (cancel)
|
|
8547
|
-
return;
|
|
8548
|
-
this.exportViaService(data, type, format);
|
|
8549
|
-
}, format);
|
|
8550
|
-
}
|
|
8551
|
-
catch (e) {
|
|
8552
|
-
// Se la ExportFunction lancia in modo sincrono, non lascio il guard bloccato
|
|
8553
|
-
this.exportInProgress = false;
|
|
8554
|
-
throw e;
|
|
8555
|
-
}
|
|
8556
|
-
return;
|
|
8557
|
-
}
|
|
8558
|
-
// Nessuna ExportFunction: percorso self-contained (nessuna classe decorata a
|
|
8559
|
-
// disposizione) — esporto le colonne `*th` correnti con le intestazioni renderizzate.
|
|
8560
|
-
const matrix = this.buildExportMatrix();
|
|
8561
|
-
if (matrix.length <= 1) {
|
|
8562
|
-
this.flashNotice('Nessun dato da esportare.');
|
|
8563
|
-
return;
|
|
8564
|
-
}
|
|
8565
|
-
if (format === 'XLSX')
|
|
8566
|
-
this.exportXlsx(matrix);
|
|
8567
|
-
else
|
|
8568
|
-
this.exportCsv(matrix);
|
|
8818
|
+
this.exportCtrl.run(format);
|
|
8569
8819
|
}
|
|
8570
8820
|
/**
|
|
8571
|
-
*
|
|
8572
|
-
*
|
|
8573
|
-
*
|
|
8574
|
-
* Se il servizio non è disponibile (raro: è `providedIn:'root'` nel package), fallback
|
|
8575
|
-
* al percorso self-contained mappando le colonne `*th`.
|
|
8821
|
+
* Mappa id-colonna → testo header RENDERIZZATO letto dal DOM (rispetta il casing
|
|
8822
|
+
* del template `*th`, es. "ID"). Costruita in un'unica passata; usata dall'export
|
|
8823
|
+
* self-contained e dalla dialog colonne (prima due letture DOM separate).
|
|
8576
8824
|
*/
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
if (!svc) {
|
|
8584
|
-
// Nessun ExportService: non posso leggere i decoratori → mappo le colonne `*th`.
|
|
8585
|
-
this.exportData(data, format);
|
|
8586
|
-
return;
|
|
8587
|
-
}
|
|
8588
|
-
// `columnsFilter`: solo se richiesto esplicitamente. Sono gli id delle colonne
|
|
8589
|
-
// visibili; l'ExportService li confronta col `tableid` del decoratore `@Export`.
|
|
8590
|
-
const columnsFilter = this.ExportOnlyVisibleColumns() ? this.exportColumns().map(c => c.id) : undefined;
|
|
8591
|
-
// `genericHeaders`: in modalità dinamica gli header vengono dalle definizioni colonna.
|
|
8592
|
-
const drcd = this.DynamicRowColumnsDefinition();
|
|
8593
|
-
const genericHeaders = drcd && drcd.length > 0
|
|
8594
|
-
? drcd.map(t => ({ label: t.Description, key: t.PropertyName, propKey: t.PropertyName, order: t.ColumnOrder, type: 'string' }))
|
|
8595
|
-
: undefined;
|
|
8596
|
-
svc.export(data, format, this.ExportFileName(), type, columnsFilter, genericHeaders);
|
|
8597
|
-
}
|
|
8598
|
-
/** @deprecated Percorso di fallback self-contained (usato solo senza ExportService) */
|
|
8599
|
-
exportData(data, format) {
|
|
8600
|
-
if (!data?.length) {
|
|
8601
|
-
this.flashNotice('Nessun dato da esportare.');
|
|
8602
|
-
return;
|
|
8603
|
-
}
|
|
8604
|
-
const cols = this.exportColumns();
|
|
8605
|
-
const header = cols.map(c => this.headerLabel(c));
|
|
8606
|
-
const rows = data.filter(r => !r._group).map(item => cols.map(c => this.cellDisplayText(item, c)));
|
|
8607
|
-
const matrix = [header, ...rows];
|
|
8608
|
-
if (format === 'XLSX')
|
|
8609
|
-
this.exportXlsx(matrix);
|
|
8610
|
-
else
|
|
8611
|
-
this.exportCsv(matrix);
|
|
8612
|
-
}
|
|
8613
|
-
exportCsv(matrix) {
|
|
8614
|
-
const esc = (v) => {
|
|
8615
|
-
const s = v == null ? '' : String(v);
|
|
8616
|
-
return /[",\n;]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
|
|
8617
|
-
};
|
|
8618
|
-
const csv = matrix.map(row => row.map(esc).join(';')).join('\r\n');
|
|
8619
|
-
// BOM per la corretta apertura in Excel con caratteri accentati
|
|
8620
|
-
this.downloadBlob('' + csv, this.exportFileName('csv'), 'text/csv;charset=utf-8;');
|
|
8621
|
-
}
|
|
8622
|
-
async exportXlsx(matrix) {
|
|
8623
|
-
try {
|
|
8624
|
-
const XLSX = await import('xlsx');
|
|
8625
|
-
const ws = XLSX.utils.aoa_to_sheet(matrix);
|
|
8626
|
-
const wb = XLSX.utils.book_new();
|
|
8627
|
-
XLSX.utils.book_append_sheet(wb, ws, 'Export');
|
|
8628
|
-
XLSX.writeFile(wb, this.exportFileName('xlsx'));
|
|
8629
|
-
}
|
|
8630
|
-
catch {
|
|
8631
|
-
this.flashNotice('Export XLSX non disponibile (SheetJS assente): esporto in CSV.');
|
|
8632
|
-
this.exportCsv(matrix);
|
|
8633
|
-
}
|
|
8634
|
-
}
|
|
8635
|
-
/** Nome file rispettando l'estensione richiesta */
|
|
8636
|
-
exportFileName(ext) {
|
|
8637
|
-
const name = this.ExportFileName() || 'Export';
|
|
8638
|
-
return /\.(csv|xlsx)$/i.test(name) ? name.replace(/\.(csv|xlsx)$/i, '.' + ext) : `${name}.${ext}`;
|
|
8639
|
-
}
|
|
8640
|
-
downloadBlob(content, filename, mime) {
|
|
8641
|
-
if (typeof document === 'undefined')
|
|
8642
|
-
return;
|
|
8643
|
-
const blob = new Blob([content], { type: mime });
|
|
8644
|
-
const url = URL.createObjectURL(blob);
|
|
8645
|
-
const a = document.createElement('a');
|
|
8646
|
-
a.href = url;
|
|
8647
|
-
a.download = filename;
|
|
8648
|
-
document.body.appendChild(a);
|
|
8649
|
-
a.click();
|
|
8650
|
-
document.body.removeChild(a);
|
|
8651
|
-
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
8825
|
+
renderedHeaderTexts() {
|
|
8826
|
+
const m = new Map();
|
|
8827
|
+
const host = this.hostEl?.nativeElement;
|
|
8828
|
+
if (host)
|
|
8829
|
+
host.querySelectorAll('thead th[data-colid]').forEach((el) => m.set(el.getAttribute('data-colid') ?? '', (el.textContent || '').trim()));
|
|
8830
|
+
return m;
|
|
8652
8831
|
}
|
|
8653
8832
|
// ===========================================================================
|
|
8654
8833
|
// Corner menu / helpers template
|
|
@@ -8682,9 +8861,7 @@ class EsTable2Component {
|
|
|
8682
8861
|
*/
|
|
8683
8862
|
cellValue(item, col) {
|
|
8684
8863
|
const key = col.property || col.id;
|
|
8685
|
-
|
|
8686
|
-
return null;
|
|
8687
|
-
return item?.properties ? item.properties[key] : item?.[key];
|
|
8864
|
+
return key ? this.readProp(item, key) : null;
|
|
8688
8865
|
}
|
|
8689
8866
|
/** Entry Multi corrispondente a una cella (`item[multiProp][multiIndex]`) */
|
|
8690
8867
|
multiCell(item, col) {
|
|
@@ -8693,12 +8870,73 @@ class EsTable2Component {
|
|
|
8693
8870
|
const arr = item?.[col.multiProp];
|
|
8694
8871
|
return (Array.isArray(arr) ? arr[col.multiIndex] : null) ?? {};
|
|
8695
8872
|
}
|
|
8873
|
+
/**
|
|
8874
|
+
* Tooltip "smart" al passaggio del mouse: mostra il testo REALMENTE renderizzato
|
|
8875
|
+
* della cella (qualunque sia il `*td`) SOLO se è troncato. Legge `el.textContent`
|
|
8876
|
+
* anziché `cellDisplayText`, che conosce solo `property`/`id` e non il contenuto di
|
|
8877
|
+
* un template custom (era il bug del tooltip che mostrava sempre l'id colonna).
|
|
8878
|
+
*/
|
|
8879
|
+
onCellHover(ev) {
|
|
8880
|
+
const el = ev.currentTarget;
|
|
8881
|
+
if (!el)
|
|
8882
|
+
return;
|
|
8883
|
+
if (el.scrollWidth > el.clientWidth + 1) {
|
|
8884
|
+
const txt = (el.textContent || '').trim();
|
|
8885
|
+
if (txt)
|
|
8886
|
+
el.setAttribute('title', txt);
|
|
8887
|
+
else
|
|
8888
|
+
el.removeAttribute('title');
|
|
8889
|
+
}
|
|
8890
|
+
else {
|
|
8891
|
+
el.removeAttribute('title');
|
|
8892
|
+
}
|
|
8893
|
+
}
|
|
8894
|
+
/** mousedown sull'handle: avvia il ridimensionamento della colonna pinnata */
|
|
8895
|
+
startColumnResize(col, ev) {
|
|
8896
|
+
ev.preventDefault();
|
|
8897
|
+
ev.stopPropagation(); // non deve innescare l'ordinamento sull'header
|
|
8898
|
+
this.resizing = { col, startX: ev.clientX, startW: this.pinnedWidthPx(col) };
|
|
8899
|
+
document.body.style.userSelect = 'none';
|
|
8900
|
+
document.body.style.cursor = 'col-resize';
|
|
8901
|
+
}
|
|
8902
|
+
onResizeMove(ev) {
|
|
8903
|
+
if (!this.resizing)
|
|
8904
|
+
return;
|
|
8905
|
+
const w = Math.max(this.PINNED_MIN_W, Math.round(this.resizing.startW + (ev.clientX - this.resizing.startX)));
|
|
8906
|
+
this.resizing.col.pinnedWidth = w;
|
|
8907
|
+
this.columns.set([...this.columns()]); // forza il ricalcolo di pinnedOffsets/visibleColumns
|
|
8908
|
+
this.cdr.markForCheck();
|
|
8909
|
+
}
|
|
8910
|
+
onResizeEnd() {
|
|
8911
|
+
if (!this.resizing)
|
|
8912
|
+
return;
|
|
8913
|
+
this.resizing = null;
|
|
8914
|
+
document.body.style.userSelect = '';
|
|
8915
|
+
document.body.style.cursor = '';
|
|
8916
|
+
// persisto la nuova larghezza nelle preferenze (colonna → px)
|
|
8917
|
+
this.persistColumnWidths();
|
|
8918
|
+
}
|
|
8919
|
+
/** Aggiorna `columnPrefs.widths` dalle larghezze correnti delle colonne pinnate e salva */
|
|
8920
|
+
persistColumnWidths() {
|
|
8921
|
+
const cols = this.columns();
|
|
8922
|
+
const widths = {};
|
|
8923
|
+
for (const c of cols)
|
|
8924
|
+
if (c.pinned && c.pinnedWidth)
|
|
8925
|
+
widths[c.id] = c.pinnedWidth;
|
|
8926
|
+
this.columnPrefs = {
|
|
8927
|
+
order: cols.map(c => c.id),
|
|
8928
|
+
hidden: cols.filter(c => !c.visible).map(c => c.id),
|
|
8929
|
+
pinned: cols.filter(c => c.pinned).map(c => c.id),
|
|
8930
|
+
widths
|
|
8931
|
+
};
|
|
8932
|
+
this.saveColumnPrefs();
|
|
8933
|
+
}
|
|
8696
8934
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: EsTable2Component, deps: [{ token: i1.NgControl, optional: true, self: true }, { token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: i2$1.PreferencesService, optional: true }, { token: i0.Injector }, { token: EST2_DEFAULTS, optional: true }, { token: EST2_DEBUG, optional: true }, { token: EST2_EXPORT_GLOBAL_ACL, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
8697
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: EsTable2Component, isStandalone: false, selector: "es-table2", inputs: { ContextMenu: { classPropertyName: "ContextMenu", publicName: "ContextMenu", isSignal: false, isRequired: false, transformFunction: null }, _Selection: { classPropertyName: "_Selection", publicName: "Selection", isSignal: true, isRequired: false, transformFunction: null }, SingleSelection: { classPropertyName: "SingleSelection", publicName: "SingleSelection", isSignal: true, isRequired: false, transformFunction: null }, SelectionDisabled: { classPropertyName: "SelectionDisabled", publicName: "SelectionDisabled", isSignal: true, isRequired: false, transformFunction: null }, _SelectAll: { classPropertyName: "_SelectAll", publicName: "SelectAll", isSignal: true, isRequired: false, transformFunction: null }, _UseSelectionCache: { classPropertyName: "_UseSelectionCache", publicName: "UseSelectionCache", isSignal: true, isRequired: false, transformFunction: null }, _ShiftClick: { classPropertyName: "_ShiftClick", publicName: "ShiftClick", isSignal: true, isRequired: false, transformFunction: null }, _OrderByColumn: { classPropertyName: "_OrderByColumn", publicName: "OrderByColumn", isSignal: true, isRequired: false, transformFunction: null }, MultipleOrderingDirectives: { classPropertyName: "MultipleOrderingDirectives", publicName: "MultipleOrderingDirectives", isSignal: true, isRequired: false, transformFunction: null }, Removal: { classPropertyName: "Removal", publicName: "Removal", isSignal: true, isRequired: false, transformFunction: null }, RemovalCondition: { classPropertyName: "RemovalCondition", publicName: "RemovalCondition", isSignal: true, isRequired: false, transformFunction: null }, RowClassAssigner: { classPropertyName: "RowClassAssigner", publicName: "RowClassAssigner", isSignal: false, isRequired: false, transformFunction: null }, _HidePaging: { classPropertyName: "_HidePaging", publicName: "HidePaging", isSignal: true, isRequired: false, transformFunction: null }, _HidePagingCount: { classPropertyName: "_HidePagingCount", publicName: "HidePagingCount", isSignal: true, isRequired: false, transformFunction: null }, _HidePagingButtons: { classPropertyName: "_HidePagingButtons", publicName: "HidePagingButtons", isSignal: true, isRequired: false, transformFunction: null }, _AllSearch: { classPropertyName: "_AllSearch", publicName: "AllSearch", isSignal: true, isRequired: false, transformFunction: null }, _PagingStyle: { classPropertyName: "_PagingStyle", publicName: "PagingStyle", isSignal: true, isRequired: false, transformFunction: null }, _ArraymodeItemsPerPage: { classPropertyName: "_ArraymodeItemsPerPage", publicName: "ArraymodeItemsPerPage", isSignal: true, isRequired: false, transformFunction: null }, _UseArrayModePaging: { classPropertyName: "_UseArrayModePaging", publicName: "UseArrayModePaging", isSignal: true, isRequired: false, transformFunction: null }, CountLabel: { classPropertyName: "CountLabel", publicName: "CountLabel", isSignal: true, isRequired: false, transformFunction: null }, Height: { classPropertyName: "Height", publicName: "Height", isSignal: true, isRequired: false, transformFunction: null }, MaxHeight: { classPropertyName: "MaxHeight", publicName: "MaxHeight", isSignal: true, isRequired: false, transformFunction: null }, EmptySpaceBackgroundColor: { classPropertyName: "EmptySpaceBackgroundColor", publicName: "EmptySpaceBackgroundColor", isSignal: true, isRequired: false, transformFunction: null }, HighCellDensity: { classPropertyName: "HighCellDensity", publicName: "HighCellDensity", isSignal: true, isRequired: false, transformFunction: null }, HeaderHidden: { classPropertyName: "HeaderHidden", publicName: "HeaderHidden", isSignal: true, isRequired: false, transformFunction: null }, BodyHidden: { classPropertyName: "BodyHidden", publicName: "BodyHidden", isSignal: true, isRequired: false, transformFunction: null }, ShowLoadingOnBootstrap: { classPropertyName: "ShowLoadingOnBootstrap", publicName: "ShowLoadingOnBootstrap", isSignal: true, isRequired: false, transformFunction: null }, _DefaultAlignment: { classPropertyName: "_DefaultAlignment", publicName: "DefaultAlignment", isSignal: true, isRequired: false, transformFunction: null }, _TableClass: { classPropertyName: "_TableClass", publicName: "TableClass", isSignal: true, isRequired: false, transformFunction: null }, _ContainerClass: { classPropertyName: "_ContainerClass", publicName: "ContainerClass", isSignal: true, isRequired: false, transformFunction: null }, EsTableHandledSearch: { classPropertyName: "EsTableHandledSearch", publicName: "EsTableHandledSearch", isSignal: true, isRequired: false, transformFunction: null }, SearchThrottle: { classPropertyName: "SearchThrottle", publicName: "SearchThrottle", isSignal: true, isRequired: false, transformFunction: null }, _ColumnsResizable: { classPropertyName: "_ColumnsResizable", publicName: "ColumnsResizable", isSignal: true, isRequired: false, transformFunction: null }, _ColumnsPinnable: { classPropertyName: "_ColumnsPinnable", publicName: "ColumnsPinnable", isSignal: true, isRequired: false, transformFunction: null }, _HiddenColumns: { classPropertyName: "_HiddenColumns", publicName: "HiddenColumns", isSignal: true, isRequired: false, transformFunction: null }, _ColumnsOrdering: { classPropertyName: "_ColumnsOrdering", publicName: "ColumnsOrdering", isSignal: true, isRequired: false, transformFunction: null }, _Export: { classPropertyName: "_Export", publicName: "Export", isSignal: true, isRequired: false, transformFunction: null }, XLSXExport: { classPropertyName: "XLSXExport", publicName: "XLSXExport", isSignal: true, isRequired: false, transformFunction: null }, CSVExport: { classPropertyName: "CSVExport", publicName: "CSVExport", isSignal: true, isRequired: false, transformFunction: null }, ExportFileName: { classPropertyName: "ExportFileName", publicName: "ExportFileName", isSignal: true, isRequired: false, transformFunction: null }, ExportOnlyVisibleColumns: { classPropertyName: "ExportOnlyVisibleColumns", publicName: "ExportOnlyVisibleColumns", isSignal: true, isRequired: false, transformFunction: null }, ExportFunction: { classPropertyName: "ExportFunction", publicName: "ExportFunction", isSignal: false, isRequired: false, transformFunction: null }, CornerMenuOptions: { classPropertyName: "CornerMenuOptions", publicName: "CornerMenuOptions", isSignal: true, isRequired: false, transformFunction: null }, DynamicOperations: { classPropertyName: "DynamicOperations", publicName: "DynamicOperations", isSignal: true, isRequired: false, transformFunction: null }, _DynamicRowColumnsDefinition: { classPropertyName: "_DynamicRowColumnsDefinition", publicName: "DynamicRowColumnsDefinition", isSignal: true, isRequired: false, transformFunction: null }, Hierarchy: { classPropertyName: "Hierarchy", publicName: "Hierarchy", isSignal: true, isRequired: false, transformFunction: null }, _ParentKey: { classPropertyName: "_ParentKey", publicName: "ParentKey", isSignal: true, isRequired: false, transformFunction: null }, _OwnKey: { classPropertyName: "_OwnKey", publicName: "OwnKey", isSignal: true, isRequired: false, transformFunction: null }, _AutoSortHierarchy: { classPropertyName: "_AutoSortHierarchy", publicName: "AutoSortHierarchy", isSignal: true, isRequired: false, transformFunction: null }, StartsExpanded: { classPropertyName: "StartsExpanded", publicName: "StartsExpanded", isSignal: true, isRequired: false, transformFunction: null }, CascadeSelection: { classPropertyName: "CascadeSelection", publicName: "CascadeSelection", isSignal: true, isRequired: false, transformFunction: null }, _SavePreferences: { classPropertyName: "_SavePreferences", publicName: "SavePreferences", isSignal: true, isRequired: false, transformFunction: null }, Name: { classPropertyName: "Name", publicName: "Name", isSignal: true, isRequired: false, transformFunction: null }, _RowGroupingPagingStyle: { classPropertyName: "_RowGroupingPagingStyle", publicName: "RowGroupingPagingStyle", isSignal: true, isRequired: false, transformFunction: null }, _ShowItemGroupsColumns: { classPropertyName: "_ShowItemGroupsColumns", publicName: "ShowItemGroupsColumns", isSignal: true, isRequired: false, transformFunction: null }, Editable: { classPropertyName: "Editable", publicName: "Editable", isSignal: true, isRequired: false, transformFunction: null }, RangeSelection: { classPropertyName: "RangeSelection", publicName: "RangeSelection", isSignal: true, isRequired: false, transformFunction: null }, ItemSourceProperty: { classPropertyName: "ItemSourceProperty", publicName: "ItemSourceProperty", isSignal: true, isRequired: false, transformFunction: null }, HasHeaderGroup: { classPropertyName: "HasHeaderGroup", publicName: "HasHeaderGroup", isSignal: true, isRequired: false, transformFunction: null }, HasSecondaryHeaderGroup: { classPropertyName: "HasSecondaryHeaderGroup", publicName: "HasSecondaryHeaderGroup", isSignal: true, isRequired: false, transformFunction: null }, SearchView: { classPropertyName: "SearchView", publicName: "SearchView", isSignal: true, isRequired: false, transformFunction: null }, _AutoUpdate: { classPropertyName: "_AutoUpdate", publicName: "AutoUpdate", isSignal: true, isRequired: false, transformFunction: null }, EsThTdProvider: { classPropertyName: "EsThTdProvider", publicName: "EsThTdProvider", isSignal: false, isRequired: false, transformFunction: null }, globalCheck: { classPropertyName: "globalCheck", publicName: "globalCheck", isSignal: true, isRequired: false, transformFunction: null }, autoUpdate: { classPropertyName: "autoUpdate", publicName: "autoUpdate", isSignal: true, isRequired: false, transformFunction: null }, seconds: { classPropertyName: "seconds", publicName: "seconds", isSignal: true, isRequired: false, transformFunction: null }, researchInProgress: { classPropertyName: "researchInProgress", publicName: "researchInProgress", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { onOrderChanged: "onOrderChanged", onSearchRequest: "onSearchRequest", onSelectionChanged: "onSelectionChanged", onRemoval: "onRemoval", onAbortRemoval: "onAbortRemoval", onModelChange: "onModelChange", onOpenContextMenu: "onOpenContextMenu", onCornerAction: "onCornerAction", onDynamicOperation: "onDynamicOperation", globalCheck: "globalCheckChange", autoUpdate: "autoUpdateChange", seconds: "secondsChange", researchInProgress: "researchInProgressChange" }, host: { listeners: { "document:mouseup": "onDocMouseUp()", "document:copy": "onDocCopy($event)", "document:paste": "onDocPaste($event)", "document:click": "onDocClick()" }, properties: { "class.est2": "this.hostClass", "class.est2--dense": "this.denseClass" } }, queries: [{ propertyName: "headerRef", first: true, predicate: ["header"], descendants: true }, { propertyName: "bodyRef", first: true, predicate: ["body"], descendants: true }, { propertyName: "thDirectives", predicate: EsThDirective }, { propertyName: "tdDirectives", predicate: EsTdDirective }, { propertyName: "editorDirectives", predicate: EsTdEditorDirective }, { propertyName: "thTdProviders", predicate: ThTdProvider }], viewQueries: [{ propertyName: "tableEmptyMenu", first: true, predicate: ["emptyMenu"], descendants: true, static: true }, { propertyName: "theadRef", first: true, predicate: ["theadRef"], descendants: true }], ngImport: i0, template: "@if (view()) {\n <div class=\"est2-wrap {{ ContainerClass() }}\"\n [style.height]=\"Height()\"\n [style.background-color]=\"EmptySpaceBackgroundColor() || null\">\n\n <!-- Auto-aggiornamento: toggle + intervallo (in alto a destra) -->\n @if (AutoUpdate()) {\n <div class=\"est2-autoupdate\">\n <label class=\"est2-autoupdate__label\">\n Aggiorna ogni\n <input type=\"text\" maxlength=\"3\" class=\"est2-autoupdate__secs\"\n [ngModel]=\"seconds()\" (ngModelChange)=\"seconds.set($event)\"\n [ngModelOptions]=\"{ standalone: true }\"\n (change)=\"autoUpdateChanged()\" />\n secondi\n </label>\n <label class=\"est2-switch\" title=\"Attiva/disattiva auto-aggiornamento\">\n <input type=\"checkbox\" [ngModel]=\"autoUpdate()\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"autoUpdate.set($event); autoUpdateChanged()\" />\n <span class=\"est2-switch__slider\"></span>\n </label>\n </div>\n }\n\n <!-- Pager superiore -->\n @if (PagingStyle() === 'both' || PagingStyle() === 'top') {\n <ng-container *ngTemplateOutlet=\"pager\"></ng-container>\n }\n\n <!-- Barra \"seleziona tutto\" (visibile solo con selezione multipla attiva e righe presenti) -->\n @if (Selection() && !SingleSelection() && hasSelection) {\n <div class=\"est2-selectbar\" [style.height.px]=\"selbarHeight() ? selbarHeight() + 1 : null\">\n @if (allSelected) {\n <span>Tutti i <strong>{{ selectedCount }}</strong> elementi sono selezionati</span>\n } @else {\n <span><strong>{{ selectedCount }}</strong> {{ selectedCount === 1 ? 'elemento selezionato' : 'elementi selezionati' }}</span>\n @if (canSelectEverything) {\n <span class=\"est2-link\" (click)=\"selectEverything()\">Seleziona tutti i {{ totalCount() }} elementi</span>\n }\n }\n <span class=\"est2-selectbar__spacer\"></span>\n <span class=\"est2-link\" (click)=\"clearSelection()\">Azzera selezione</span>\n </div>\n }\n\n <div class=\"est2-scroll\" [style.max-height.px]=\"MaxHeight()\">\n <table class=\"est2-table {{ TableClass() }}\"\n [class.est2-table--range]=\"rangeActive()\"\n [class.est2-table--dragging]=\"rangeDragging()\"\n (mousedown)=\"onGridMouseDown($event)\"\n (mouseover)=\"onGridMouseOver($event)\">\n\n <!-- ================= HEADER ================= -->\n @if (!HeaderHidden()) {\n <thead #theadRef>\n <!-- Righe di header-group multi-livello (dall'alto verso il basso) -->\n @if (hasHeaderGroups()) {\n @for (grow of headerGroupRows(); track $index) {\n <tr class=\"est2-hgroup-row\">\n @if (Selection()) { <th class=\"est2-col-min est2-selcol\" [class.est2-pinned]=\"hasPinned()\" [style.left.px]=\"hasPinned() ? 0 : null\"></th> }\n @for (op of DynamicOperations(); track op.id) { <th class=\"est2-col-min\"></th> }\n @for (cell of grow; track cell.id; let gi = $index) {\n <th [attr.colspan]=\"cell.span\"\n [attr.data-groupid]=\"cell.isGroup ? cell.id : null\"\n [class.est2-hgroup]=\"cell.isGroup\"\n [class.est2-pinned]=\"gi < pinnedCount()\"\n [style.left.px]=\"gi < pinnedCount() ? pinnedLeftPx(gi) : null\"\n class=\"est2-hgroup-cell\">\n @if (cell.isGroup) {\n @if (cell.template) {\n <ng-container *ngTemplateOutlet=\"cell.template\"></ng-container>\n } @else {\n {{ cell.label }}\n }\n }\n </th>\n }\n @if (Removal()) { <th class=\"est2-col-min\"></th> }\n @if (hasChrome()) { <th class=\"est2-col-min\"></th> }\n </tr>\n }\n }\n <tr>\n <!-- Colonna di selezione -->\n @if (Selection()) {\n <th class=\"est2-col-min est2-selcol\"\n [class.est2-pinned]=\"hasPinned()\"\n [style.left.px]=\"hasPinned() ? 0 : null\">\n @if (!SingleSelection()) {\n <input type=\"checkbox\" class=\"est2-check\"\n [checked]=\"globalCheck()\"\n [indeterminate]=\"selectionIndeterminate\"\n [disabled]=\"SelectionDisabled()\"\n (change)=\"toggleAll()\"\n aria-label=\"Seleziona tutto\" />\n }\n </th>\n }\n\n <!-- Header da colonne (direttive / dinamica / report) -->\n @if (usesColumns()) {\n <!-- intestazioni vuote per le operazioni dinamiche -->\n @for (op of DynamicOperations(); track op.id) {\n <th class=\"est2-col-min\"></th>\n }\n @for (col of visibleColumns(); track trackCol($index, col); let ci = $index) {\n <th [attr.data-colid]=\"col.id\"\n [class]=\"col.cssClass\"\n [class.est2-th--orderable]=\"OrderByColumn() && col.orderable\"\n [class.est2-col-min]=\"col.header?.thShrink\"\n [class.est2-col-groupstart]=\"columnGroupBoundaries().has(ci)\"\n [class.est2-pinned]=\"col.pinned\"\n [style.left.px]=\"col.pinned ? pinnedLeftPx(ci) : null\"\n [style.min-width.px]=\"col.pinned && col.pinnedWidth ? col.pinnedWidth : null\"\n [style.max-width.px]=\"col.pinned && col.pinnedWidth ? col.pinnedWidth : null\"\n [style.text-align]=\"col.alignment\"\n [style.background-color]=\"col.headerBg || null\"\n (click)=\"toggleSort(col)\">\n <span class=\"est2-th__inner\">\n @if (col.header?.Template) {\n <ng-container *ngTemplateOutlet=\"col.header!.Template!; context: col.multiProp != null ? { $implicit: col.headerText } : null\"></ng-container>\n } @else {\n {{ col.headerText }}\n }\n @if (OrderByColumn() && col.orderable && orderOf(col.id)) {\n <span class=\"est2-sort est2-sort--active\"\n [class.est2-sort--desc]=\"orderOf(col.id) === 'DESC'\">\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 15l6-6 6 6\"/></svg>\n </span>\n @if (orderIndex(col.id) > 0 && MultipleOrderingDirectives()) {\n <span class=\"est2-sort__badge\">{{ orderIndex(col.id) }}</span>\n }\n }\n <!-- Indicatore/toggle di pin (visibile se pinnata o all'hover) -->\n @if (ColumnsPinnable() && col.multiProp == null) {\n <button type=\"button\" class=\"est2-pinbtn\"\n [class.est2-pinbtn--on]=\"col.pinned\"\n [title]=\"col.pinned ? 'Sblocca colonna' : 'Blocca colonna a sinistra'\"\n (click)=\"togglePin(col, $event)\">\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 17v5\"/><path d=\"M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z\"/></svg>\n </button>\n }\n </span>\n </th>\n }\n }\n <!-- Header da template semplice -->\n @else if (headerRef) {\n <ng-container *ngTemplateOutlet=\"headerRef\"></ng-container>\n }\n\n <!-- Colonna rimozione -->\n @if (Removal()) { <th class=\"est2-col-min\"></th> }\n <!-- Colonna chrome (export / gestione colonne / menu) -->\n @if (hasChrome()) {\n <th class=\"est2-col-min est2-chrome-th\">\n <div class=\"est2-chrome\">\n @if (Export()) {\n <div class=\"est2-chrome-wrap\">\n <button type=\"button\" class=\"est2-chrome-btn\" title=\"Esporta\" (click)=\"$event.stopPropagation(); toggleExportMenu()\">\n <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><path d=\"M7 10l5 5 5-5\"/><path d=\"M12 15V3\"/></svg>\n </button>\n @if (exportMenuOpen()) {\n <div class=\"est2-menu\" (click)=\"$event.stopPropagation()\">\n @if (CSVExport()) { <button type=\"button\" class=\"est2-menu-item\" (click)=\"export('CSV')\">Esporta CSV</button> }\n @if (XLSXExport()) { <button type=\"button\" class=\"est2-menu-item\" (click)=\"export('XLSX')\">Esporta Excel (XLSX)</button> }\n @if (!CSVExport() && !XLSXExport()) { <button type=\"button\" class=\"est2-menu-item\" (click)=\"export('CSV')\">Esporta CSV</button> }\n </div>\n }\n </div>\n }\n @if (HiddenColumns() || ColumnsOrdering()) {\n <button type=\"button\" class=\"est2-chrome-btn\" title=\"Colonne\" (click)=\"$event.stopPropagation(); openColumnsDialog()\">\n <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"1\"/><path d=\"M9 3v18\"/><path d=\"M15 3v18\"/></svg>\n </button>\n }\n @if (CornerMenuOptions().length > 0) {\n <div class=\"est2-chrome-wrap\">\n <button type=\"button\" class=\"est2-chrome-btn\" title=\"Opzioni\" (click)=\"$event.stopPropagation(); toggleCornerMenu()\">\u22EE</button>\n @if (cornerMenuOpen()) {\n <div class=\"est2-menu\" (click)=\"$event.stopPropagation()\">\n @for (opt of CornerMenuOptions(); track opt.id) {\n <button type=\"button\" class=\"est2-menu-item\" (click)=\"cornerAction(opt.id); cornerMenuOpen.set(false)\">{{ opt.description }}</button>\n }\n </div>\n }\n </div>\n }\n </div>\n </th>\n }\n </tr>\n </thead>\n }\n\n <!-- ================= BODY ================= -->\n @if (!BodyHidden()) {\n <tbody>\n @for (item of boundSource(); track trackRow($index, item); let ri = $index) {\n @if ((!grouped() && !Hierarchy()) || item._visible) {\n <tr [class]=\"rowClass(item)\"\n [class.est2-row--selected]=\"item._selected\"\n [class.est2-row--removed]=\"item.removed || item.deleted\"\n [class.est2-row--group]=\"item._group\"\n [class.est2-row--clickable]=\"Selection() || item._group\"\n [contextMenu]=\"ContextMenu || emptyMenu\"\n [contextMenuSubject]=\"item\"\n (click)=\"handleRowClick(item, $event)\">\n\n <!-- Cella di selezione -->\n @if (Selection()) {\n <td class=\"est2-col-min est2-selcol\"\n [class.est2-pinned]=\"hasPinned()\"\n [style.left.px]=\"hasPinned() ? 0 : null\">\n @if (item._group) {\n @if (!SingleSelection()) {\n <input type=\"checkbox\" class=\"est2-check\"\n [checked]=\"groupSelectionState(item) === 'all'\"\n [indeterminate]=\"groupSelectionState(item) === 'some'\"\n [disabled]=\"SelectionDisabled()\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleGroupSelection(item)\"\n aria-label=\"Seleziona gruppo\" />\n }\n } @else {\n <input type=\"checkbox\" class=\"est2-check\"\n [checked]=\"item._selected\"\n [indeterminate]=\"hierarchyIndeterminate(item)\"\n [disabled]=\"SelectionDisabled()\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleRow(item)\"\n aria-label=\"Seleziona riga\" />\n }\n </td>\n }\n\n <!-- Celle da colonne (direttive / dinamica / report) -->\n @if (usesColumns()) {\n <!-- Operazioni dinamiche (icone a sinistra) -->\n @for (op of DynamicOperations(); track op.id) {\n <td class=\"est2-col-min est2-op-cell\">\n @if (!item._group && operationVisible(op, item)) {\n <span class=\"est2-op\" [class]=\"op.iconClass || ''\" [title]=\"op.title\"\n (click)=\"$event.stopPropagation(); dynamicOperation(item, op.id)\">{{ op.text }}</span>\n }\n </td>\n }\n @for (col of visibleColumns(); track trackCol($index, col); let first = $first, ci = $index) {\n <td [class]=\"col.cssClass\"\n [style.text-align]=\"col.alignment\"\n [style.color]=\"cellColor(item, col, 'fore')\"\n [style.background-color]=\"cellColor(item, col, 'back')\"\n [class.est2-nowrap]=\"!col.wrap\"\n [class.est2-col-groupstart]=\"columnGroupBoundaries().has(ci)\"\n [class.est2-pinned]=\"col.pinned\"\n [style.left.px]=\"col.pinned ? pinnedLeftPx(ci) : null\"\n [style.min-width.px]=\"col.pinned && col.pinnedWidth ? col.pinnedWidth : null\"\n [style.max-width.px]=\"col.pinned && col.pinnedWidth ? col.pinnedWidth : null\"\n [class.est2-td--group-key]=\"item._group && item.column === col.id\"\n [attr.data-r]=\"rangeActive() && !item._group ? ri : null\"\n [attr.data-c]=\"rangeActive() && !item._group ? ci : null\"\n [class.est2-cell-sel]=\"rangeActive() && inRange(ri, ci)\"\n [class.est2-cell-sel-t]=\"rangeActive() && inRange(ri, ci) && ri === rangeRect()!.top\"\n [class.est2-cell-sel-b]=\"rangeActive() && inRange(ri, ci) && ri === rangeRect()!.bottom\"\n [class.est2-cell-sel-l]=\"rangeActive() && inRange(ri, ci) && ci === rangeRect()!.left\"\n [class.est2-cell-sel-r]=\"rangeActive() && inRange(ri, ci) && ci === rangeRect()!.right\"\n [class.est2-cell-editing]=\"isEditing(ri, ci)\"\n (dblclick)=\"onCellDblClick(item, col, ri, ci)\">\n @if (isEditing(ri, ci)) {\n @if (editorFor(col); as edTpl) {\n <ng-container *ngTemplateOutlet=\"edTpl; context: editorContext(item, col)\"></ng-container>\n } @else {\n <ng-container *ngTemplateOutlet=\"defaultEditor; context: { $implicit: item, col: col }\"></ng-container>\n }\n } @else if (item._group) {\n @if (item.column === col.id) {\n <span class=\"est2-group-key\" [style.padding-left.px]=\"groupIndent(item)\">\n <span class=\"est2-group-chevron\" [class.est2-group-chevron--open]=\"item._expanded\">\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 6l6 6-6 6\"/></svg>\n </span>\n {{ groupCellDisplay(item, col) }}\n </span>\n } @else {\n {{ groupCellDisplay(item, col) }}\n }\n } @else {\n <!-- Navigatore albero nella prima colonna -->\n @if (Hierarchy() && first) {\n <span class=\"est2-hier-lead\" [style.padding-left.px]=\"hierarchyIndent(item)\">\n @if (item.parent) {\n <span class=\"est2-group-chevron est2-hier-toggle\" [class.est2-group-chevron--open]=\"item._expanded\"\n (click)=\"$event.stopPropagation(); toggleHierarchyNode(item)\">\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 6l6 6-6 6\"/></svg>\n </span>\n } @else {\n <span class=\"est2-hier-toggle\"></span>\n }\n </span>\n }\n @if (itemCellHidden(col)) {\n <!-- colonna di gruppo nascosta sulla riga-oggetto -->\n } @else if (col.multiProp != null) {\n @if (col.cell?.Template) {\n <ng-container *ngTemplateOutlet=\"col.cell!.Template; context: { $implicit: multiCell(item, col) }\"></ng-container>\n } @else {\n {{ multiCell(item, col)?.value }}\n }\n } @else if (col.cell?.Template) {\n <ng-container *ngTemplateOutlet=\"col.cell!.Template; context: { $implicit: item }\"></ng-container>\n } @else if (col.routePath) {\n <a class=\"est2-link\" [routerLink]=\"routerLinkFor(item, col)\">{{ cellValue(item, col) }}</a>\n } @else if (col.propAccessor != null) {\n {{ reportCellDisplay(item, col) }}\n } @else if (col.type === 'enum') {\n {{ (cellValue(item, col) | est2_lookup : col.source).description }}\n } @else {\n {{ cellValue(item, col) | est2_format : col.type : col.format : locale }}\n }\n }\n </td>\n }\n }\n <!-- Celle da template semplice -->\n @else if (bodyRef) {\n <ng-container *ngTemplateOutlet=\"bodyRef; context: { $implicit: item }\"></ng-container>\n }\n\n <!-- Rimozione (non sulle righe-gruppo) -->\n @if (Removal()) {\n <td class=\"est2-col-min\">\n @if (!item._group && canRemove(item)) {\n @if (item.removed || item.deleted) {\n <button type=\"button\" class=\"est2-rowaction\" title=\"Ripristina\" (click)=\"abortRemoval(item)\">\u21BA</button>\n } @else {\n <button type=\"button\" class=\"est2-rowaction est2-rowaction--danger\" title=\"Rimuovi\" (click)=\"removeItem(item)\">\u2715</button>\n }\n }\n </td>\n }\n <!-- Chrome -->\n @if (hasChrome()) { <td class=\"est2-col-min\"></td> }\n </tr>\n }\n } @empty {\n <tr>\n <td class=\"est2-empty\" [attr.colspan]=\"totalColspan()\">Nessun elemento da visualizzare</td>\n </tr>\n }\n </tbody>\n }\n </table>\n\n <!-- Overlay di caricamento -->\n @if (researchInProgress() || (firstBind() && ShowLoadingOnBootstrap())) {\n <div class=\"est2-loading\">\n <span class=\"est2-spinner\"></span>\n <span>Caricamento\u2026</span>\n </div>\n }\n </div>\n\n <!-- Pager inferiore -->\n @if (PagingStyle() === 'both' || PagingStyle() === 'bottom') {\n <ng-container *ngTemplateOutlet=\"pager\"></ng-container>\n }\n\n <!-- Avviso transitorio (es. incolla con dimensioni incompatibili) -->\n @if (notice()) {\n <div class=\"est2-toast\" role=\"alert\">\n <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/><path d=\"M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/></svg>\n <span>{{ notice() }}</span>\n </div>\n }\n\n <!-- Dialog visibilit\u00E0 / ordine colonne -->\n @if (columnsDialogOpen()) {\n <div class=\"est2-dialog-backdrop\" (click)=\"closeColumnsDialog()\">\n <div class=\"est2-dialog\" (click)=\"$event.stopPropagation()\" role=\"dialog\" aria-modal=\"true\">\n <div class=\"est2-dialog__head\">\n <span>\n @if (HiddenColumns() && ColumnsOrdering()) { Visibilit\u00E0 e ordine colonne }\n @else if (HiddenColumns()) { Visibilit\u00E0 colonne }\n @else { Ordine colonne }\n </span>\n <button type=\"button\" class=\"est2-dialog__close\" (click)=\"closeColumnsDialog()\" aria-label=\"Chiudi\">\u2715</button>\n </div>\n\n @if (HiddenColumns()) {\n <div class=\"est2-dialog__tools\">\n <button type=\"button\" class=\"est2-link\" (click)=\"dialogSetAll(true)\">Mostra tutte</button>\n <span class=\"est2-dialog__sep\">\u00B7</span>\n <button type=\"button\" class=\"est2-link\" (click)=\"dialogSetAll(false)\">Nascondi tutte</button>\n </div>\n }\n\n <ul class=\"est2-collist\">\n @for (c of dialogCols(); track c.id; let i = $index) {\n <li class=\"est2-collist__row\">\n @if (HiddenColumns()) {\n <label class=\"est2-collist__vis\">\n <input type=\"checkbox\" class=\"est2-check\" [checked]=\"c.visible\" (change)=\"dialogToggle(i)\" />\n </label>\n }\n <span class=\"est2-collist__label\">{{ c.label }}</span>\n @if (ColumnsPinnable()) {\n <button type=\"button\" class=\"est2-iconbtn est2-collist__pin\" [class.est2-collist__pin--on]=\"c.pinned\"\n [disabled]=\"c.grouped\"\n [title]=\"c.grouped ? 'Le colonne di un gruppo non sono bloccabili' : (c.pinned ? 'Sblocca' : 'Blocca a sinistra')\"\n (click)=\"dialogTogglePin(i)\">\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 17v5\"/><path d=\"M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z\"/></svg>\n </button>\n }\n @if (ColumnsOrdering()) {\n <span class=\"est2-collist__ord\">\n <button type=\"button\" class=\"est2-iconbtn\" [disabled]=\"i === 0\" title=\"Su\" (click)=\"dialogMove(i, -1)\">\u2191</button>\n <button type=\"button\" class=\"est2-iconbtn\" [disabled]=\"i === dialogCols().length - 1\" title=\"Gi\u00F9\" (click)=\"dialogMove(i, 1)\">\u2193</button>\n </span>\n }\n </li>\n }\n </ul>\n\n <div class=\"est2-dialog__foot\">\n <button type=\"button\" class=\"est2-btn est2-btn--ghost\" (click)=\"resetColumnsDialog()\">Ripristina</button>\n <span class=\"est2-dialog__spacer\"></span>\n <button type=\"button\" class=\"est2-btn est2-btn--ghost\" (click)=\"closeColumnsDialog()\">Annulla</button>\n <button type=\"button\" class=\"est2-btn est2-btn--primary\" (click)=\"applyColumnsDialog()\">Applica</button>\n </div>\n </div>\n </div>\n }\n </div>\n}\n\n<!-- Template pager riutilizzabile sopra/sotto -->\n<ng-template #pager>\n @if (!HidePaging() && !grouped()) {\n <es-table2-pager\n [page]=\"currentPage()\"\n [pages]=\"totalPages()\"\n [total]=\"totalCount()\"\n [itemsPerPage]=\"viewMode() ? (view()?.itemsperpageoverride ?? 15) : ArraymodeItemsPerPage()\"\n [countLabel]=\"CountLabel()\"\n [showCount]=\"!HidePagingCount()\"\n [showButtons]=\"!HidePagingButtons()\"\n [showPagingOptions]=\"!HidePagingButtons()\"\n [allowAll]=\"AllSearch()\"\n (pageChange)=\"goToPage($event)\"\n (itemsPerPageChange)=\"changeItemsPerPage($event)\">\n </es-table2-pager>\n }\n</ng-template>\n\n<!-- Editor di cella di default (usato quando il consumer non fornisce un `*editor`) -->\n<ng-template #defaultEditor let-item let-col=\"col\">\n @switch (col.type) {\n @case ('enum') {\n <select class=\"est2-editor-input\"\n [value]=\"editDraft\"\n (change)=\"editDraft = $any($event.target).value\"\n (keydown.enter)=\"commitEdit(item, col)\"\n (keydown.escape)=\"cancelEdit()\"\n (blur)=\"commitEdit(item, col)\">\n @for (o of col.source || []; track o.id) {\n <option [value]=\"o.id\" [selected]=\"o.id == editDraft\">{{ o.description }}</option>\n }\n </select>\n }\n @case ('boolean') {\n <input type=\"checkbox\" class=\"est2-editor-input est2-check\"\n [checked]=\"editDraft === true || editDraft === 'true'\"\n (change)=\"editDraft = $any($event.target).checked; commitEdit(item, col)\"\n (keydown.escape)=\"cancelEdit()\" />\n }\n @default {\n <input class=\"est2-editor-input\"\n [type]=\"editorInputType(col.type)\"\n [value]=\"editDraft\"\n (input)=\"editDraft = $any($event.target).value\"\n (keydown.enter)=\"commitEdit(item, col)\"\n (keydown.escape)=\"cancelEdit()\"\n (blur)=\"commitEdit(item, col)\" />\n }\n }\n</ng-template>\n\n<!-- Menu di default (nessuna operazione) usato quando il consumer non passa [ContextMenu] -->\n<context-menu #emptyMenu>\n <ng-template contextMenuItem [passive]=\"true\"><em>Nessuna operazione disponibile\u2026</em></ng-template>\n</context-menu>\n", styles: [".est2{--est-bg: #ffffff;--est-bg-subtle: #f7f8fa;--est-bg-raised: #ffffff;--est-bg-hover: #f2f4f7;--est-bg-selected: color-mix(in srgb, var(--est-accent) 12%, transparent);--est-fg: #1a1d24;--est-fg-muted: #626b7a;--est-fg-faint: #9aa3b2;--est-border: #e6e9ef;--est-border-strong: #d3d8e0;--est-accent: #4f46e5;--est-accent-fg: #ffffff;--est-accent-weak: color-mix(in srgb, var(--est-accent) 14%, transparent);--est-danger: #dc2626;--est-warning: #d97706;--est-success: #059669;--est-shadow-sticky: 0 1px 0 var(--est-border), 0 4px 12px -8px rgba(16, 24, 40, .24);--est-shadow-pop: 0 8px 24px -6px rgba(16, 24, 40, .18), 0 2px 6px -2px rgba(16, 24, 40, .12);--est-ring: 0 0 0 3px color-mix(in srgb, var(--est-accent) 40%, transparent);--est-radius: 10px;--est-radius-sm: 6px;--est-radius-pill: 999px;--est-gap: 8px;--est-cell-py: 6px;--est-cell-px: 12px;--est-row-h: 33px;--est-font: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;--est-fs: 13px;--est-fs-sm: 12px;--est-fw-head: 700;--est-transition: .14s cubic-bezier(.4, 0, .2, 1);--est-bg-zebra: color-mix(in srgb, var(--est-fg) 3.5%, var(--est-bg));font-family:var(--est-font);font-size:var(--est-fs);color:var(--est-fg);position:relative;display:block}@media(prefers-color-scheme:dark){.est2{--est-bg: #14161c;--est-bg-subtle: #1a1d25;--est-bg-raised: #1e222b;--est-bg-hover: #232733;--est-bg-selected: color-mix(in srgb, var(--est-accent) 26%, transparent);--est-fg: #e7eaf0;--est-fg-muted: #9aa3b2;--est-fg-faint: #6b7484;--est-border: #2a2f3a;--est-border-strong: #39404d;--est-accent: #7c74ff;--est-accent-fg: #ffffff;--est-accent-weak: color-mix(in srgb, var(--est-accent) 22%, transparent);--est-danger: #f87171;--est-warning: #fbbf24;--est-success: #34d399;--est-shadow-sticky: 0 1px 0 var(--est-border), 0 6px 16px -10px rgba(0, 0, 0, .7);--est-shadow-pop: 0 10px 28px -8px rgba(0, 0, 0, .6), 0 2px 6px -2px rgba(0, 0, 0, .5);--est-ring: 0 0 0 3px color-mix(in srgb, var(--est-accent) 55%, transparent)}}.est2.est2--dark{--est-bg: #14161c;--est-bg-subtle: #1a1d25;--est-bg-raised: #1e222b;--est-bg-hover: #232733;--est-bg-selected: color-mix(in srgb, var(--est-accent) 26%, transparent);--est-fg: #e7eaf0;--est-fg-muted: #9aa3b2;--est-fg-faint: #6b7484;--est-border: #2a2f3a;--est-border-strong: #39404d;--est-accent: #7c74ff;--est-accent-fg: #ffffff;--est-accent-weak: color-mix(in srgb, var(--est-accent) 22%, transparent);--est-danger: #f87171;--est-warning: #fbbf24;--est-success: #34d399;--est-shadow-sticky: 0 1px 0 var(--est-border), 0 6px 16px -10px rgba(0, 0, 0, .7);--est-shadow-pop: 0 10px 28px -8px rgba(0, 0, 0, .6), 0 2px 6px -2px rgba(0, 0, 0, .5);--est-ring: 0 0 0 3px color-mix(in srgb, var(--est-accent) 55%, transparent)}.est2.est2--light{--est-bg: #ffffff;--est-bg-subtle: #f7f8fa;--est-bg-raised: #ffffff;--est-bg-hover: #f2f4f7;--est-bg-selected: color-mix(in srgb, var(--est-accent) 12%, transparent);--est-fg: #1a1d24;--est-fg-muted: #626b7a;--est-fg-faint: #9aa3b2;--est-border: #e6e9ef;--est-border-strong: #d3d8e0;--est-accent: #4f46e5;--est-accent-fg: #ffffff;--est-accent-weak: color-mix(in srgb, var(--est-accent) 14%, transparent);--est-danger: #dc2626;--est-warning: #d97706;--est-success: #059669;--est-shadow-sticky: 0 1px 0 var(--est-border), 0 4px 12px -8px rgba(16, 24, 40, .24);--est-shadow-pop: 0 8px 24px -6px rgba(16, 24, 40, .18), 0 2px 6px -2px rgba(16, 24, 40, .12);--est-ring: 0 0 0 3px color-mix(in srgb, var(--est-accent) 40%, transparent)}.est2.est2--dense{--est-cell-py: 3px;--est-cell-px: 9px;--est-row-h: 27px;--est-fs: 12.5px}.est2 *,.est2 *:before,.est2 *:after{box-sizing:border-box}.est2 .est2-scroll{position:relative;width:100%;overflow:auto;border:1px solid var(--est-border);border-radius:var(--est-radius);background:var(--est-bg);-webkit-overflow-scrolling:touch}.est2 table.est2-table{width:100%;border-collapse:separate;border-spacing:0;background:var(--est-bg)}.est2 thead th{position:sticky;top:0;z-index:3;background:var(--est-bg-subtle);color:var(--est-fg);font-weight:var(--est-fw-head);font-size:var(--est-fs);text-align:left;white-space:nowrap;padding:var(--est-cell-py) var(--est-cell-px);border-bottom:1px solid var(--est-border);box-shadow:var(--est-shadow-sticky);-webkit-user-select:none;user-select:none}.est2 thead tr.est2-hgroup-row th{font-size:var(--est-fs-sm);font-weight:700;color:var(--est-fg-muted);text-align:center;white-space:nowrap;padding:var(--est-cell-py) var(--est-cell-px);background:var(--est-bg-subtle);border-bottom:1px solid var(--est-border)}.est2 thead tr.est2-hgroup-row th.est2-hgroup{color:var(--est-fg);border-left:1px solid var(--est-border);border-right:1px solid var(--est-border)}.est2 thead tr.est2-hgroup-row th:not(.est2-hgroup){background:var(--est-bg-subtle);border-bottom-color:transparent}.est2 thead tr:last-child th.est2-col-groupstart,.est2 tbody td.est2-col-groupstart{border-left:1px solid var(--est-border)}.est2 tbody td{padding:var(--est-cell-py) var(--est-cell-px);border-bottom:1px solid var(--est-border);color:var(--est-fg);vertical-align:middle;background:transparent}.est2 tbody tr:nth-child(2n) td{background:var(--est-bg-zebra)}.est2 tbody tr:nth-child(2n) td.est2-pinned{background:var(--est-bg-zebra)}.est2 th.est2-pinned,.est2 td.est2-pinned{position:sticky;background:var(--est-bg);box-shadow:1px 0 0 var(--est-border)}.est2 .est2-selcol{width:44px;min-width:44px;max-width:44px}.est2 td.est2-pinned{z-index:3}.est2 thead th.est2-pinned,.est2 thead tr.est2-hgroup-row th.est2-pinned{z-index:6;background:var(--est-bg-subtle)}.est2 tbody tr:hover td.est2-pinned{background:color-mix(in srgb,var(--est-fg) 5%,var(--est-bg))}.est2 tbody tr.est2-row--selected td.est2-pinned{background:color-mix(in srgb,var(--est-accent) 12%,var(--est-bg))}.est2 tbody tr.est2-row--group td.est2-pinned{background:var(--est-bg-subtle)}.est2 .est2-pinbtn{display:inline-flex;align-items:center;justify-content:center;margin-left:4px;padding:2px;border:none;background:transparent;color:var(--est-fg-faint);border-radius:var(--est-radius-sm);cursor:pointer;opacity:0;transition:opacity var(--est-transition),color var(--est-transition),background var(--est-transition)}.est2 thead th:hover .est2-pinbtn{opacity:.7}.est2 .est2-pinbtn:hover{background:var(--est-bg-hover);color:var(--est-fg);opacity:1}.est2 .est2-pinbtn--on{opacity:1;color:var(--est-accent);transform:rotate(0)}.est2 thead th:hover .est2-pinbtn--on{opacity:1}.est2 .est2-collist__pin.est2-collist__pin--on{color:var(--est-accent);border-color:var(--est-accent)}.est2 tbody tr{height:var(--est-row-h);transition:background var(--est-transition)}.est2 tbody tr:last-child td{border-bottom:none}.est2 tbody tr:hover td{background:var(--est-bg-hover)}.est2 tbody tr.est2-row--selected td{background:var(--est-bg-selected)}.est2 tbody tr.est2-row--clickable{cursor:pointer;-webkit-user-select:none;user-select:none}.est2 tbody tr.est2-row--removed td{text-decoration:line-through;color:var(--est-fg-faint)}.est2 .est2-table--range tbody td[data-r]{cursor:cell}.est2 .est2-table--dragging,.est2 .est2-table--dragging tbody td{-webkit-user-select:none;user-select:none}.est2 tbody td.est2-cell-sel{background:color-mix(in srgb,var(--est-accent) 14%,transparent)}.est2 tbody td.est2-cell-sel-t{box-shadow:inset 0 2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-b{box-shadow:inset 0 -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-l{box-shadow:inset 2px 0 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-r{box-shadow:inset -2px 0 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-l{box-shadow:inset 2px 2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-r{box-shadow:inset -2px 2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-b.est2-cell-sel-l{box-shadow:inset 2px -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-b.est2-cell-sel-r{box-shadow:inset -2px -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-b{box-shadow:inset 0 2px 0 0 var(--est-accent),inset 0 -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-l.est2-cell-sel-r{box-shadow:inset 2px 0 0 0 var(--est-accent),inset -2px 0 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-b.est2-cell-sel-l{box-shadow:inset 2px 2px 0 0 var(--est-accent),inset 0 -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-b.est2-cell-sel-r{box-shadow:inset -2px 2px 0 0 var(--est-accent),inset 0 -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-l.est2-cell-sel-r{box-shadow:inset 2px 2px 0 0 var(--est-accent),inset -2px 0 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-b.est2-cell-sel-l.est2-cell-sel-r{box-shadow:inset 2px -2px 0 0 var(--est-accent),inset -2px 0 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-b.est2-cell-sel-l.est2-cell-sel-r{box-shadow:inset 2px 2px 0 0 var(--est-accent),inset -2px -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-editing{padding:2px 6px}.est2 .est2-editor-input{width:100%;box-sizing:border-box;height:calc(var(--est-row-h) - 8px);padding:2px 6px;font:inherit;color:var(--est-fg);background:var(--est-bg);border:1.5px solid var(--est-accent);border-radius:var(--est-radius-sm);outline:none}.est2 .est2-editor-input:focus-visible{box-shadow:var(--est-ring)}.est2 input.est2-editor-input[type=checkbox]{width:16px;height:16px}.est2 .est2-autoupdate{display:flex;align-items:center;justify-content:flex-end;gap:12px;padding:4px 2px 8px;font-size:var(--est-fs-sm);color:var(--est-fg-muted)}.est2 .est2-autoupdate__label{display:inline-flex;align-items:center;gap:6px}.est2 .est2-autoupdate__secs{width:44px;text-align:center;padding:3px 4px;font:inherit;color:var(--est-fg);background:var(--est-bg);border:1px solid var(--est-border);border-radius:var(--est-radius-sm);outline:none}.est2 .est2-autoupdate__secs:focus-visible{box-shadow:var(--est-ring);border-color:var(--est-accent)}.est2 .est2-switch{position:relative;display:inline-flex;width:38px;height:20px;cursor:pointer}.est2 .est2-switch input{position:absolute;opacity:0;width:0;height:0}.est2 .est2-switch__slider{flex:1;border-radius:var(--est-radius-pill);background:var(--est-border-strong);transition:background var(--est-transition)}.est2 .est2-switch__slider:before{content:\"\";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;box-shadow:0 1px 2px #00000040;transition:transform var(--est-transition)}.est2 .est2-switch input:checked+.est2-switch__slider{background:var(--est-accent)}.est2 .est2-switch input:checked+.est2-switch__slider:before{transform:translate(18px)}.est2 .est2-switch input:focus-visible+.est2-switch__slider{box-shadow:var(--est-ring)}.est2 .est2-toast{position:absolute;left:50%;bottom:14px;transform:translate(-50%);z-index:20;display:inline-flex;align-items:center;gap:8px;max-width:calc(100% - 24px);padding:8px 14px;font-size:13px;font-weight:500;color:#fff;background:#b91c1c;border-radius:var(--est-radius);box-shadow:0 6px 20px #00000040;animation:est2-toast-in .16s ease-out}.est2 .est2-toast svg{flex:0 0 auto}@keyframes est2-toast-in{0%{opacity:0;transform:translate(-50%,8px)}to{opacity:1;transform:translate(-50%)}}@media(prefers-reduced-motion:reduce){.est2 .est2-toast{animation:none}}.est2 tbody tr.est2-row--group{cursor:pointer;-webkit-user-select:none;user-select:none}.est2 tbody tr.est2-row--group td{background:var(--est-bg-subtle);font-weight:600;color:var(--est-fg);border-bottom:1px solid var(--est-border)}.est2 tbody tr.est2-row--group:hover td{background:var(--est-bg-hover)}.est2 .est2-td--group-key{color:var(--est-fg)}.est2 .est2-group-key{display:inline-flex;align-items:center;gap:6px}.est2 .est2-group-chevron{display:inline-flex;color:var(--est-fg-muted);transition:transform var(--est-transition)}.est2 .est2-group-chevron--open{transform:rotate(90deg)}.est2 .est2-hier-lead{display:inline-flex;align-items:center;vertical-align:middle;margin-right:4px}.est2 .est2-hier-toggle{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;flex:none}.est2 .est2-group-chevron.est2-hier-toggle{cursor:pointer;border-radius:var(--est-radius-sm);transition:transform var(--est-transition),background var(--est-transition),color var(--est-transition)}.est2 .est2-group-chevron.est2-hier-toggle:hover{background:var(--est-bg-hover);color:var(--est-fg)}.est2 th.est2-th--orderable{cursor:pointer;transition:color var(--est-transition)}.est2 th.est2-th--orderable:hover{color:var(--est-fg)}.est2 .est2-th__inner{display:inline-flex;align-items:center;gap:6px}.est2 .est2-sort{display:inline-flex;width:14px;height:14px;opacity:.35;transition:opacity var(--est-transition),transform var(--est-transition)}.est2 .est2-sort--active{opacity:1;color:var(--est-accent)}.est2 .est2-sort--desc{transform:rotate(180deg)}.est2 .est2-sort__badge{font-size:9px;font-weight:700;color:var(--est-accent);margin-left:2px}.est2 .est2-check{appearance:none;width:16px;height:16px;border:1.5px solid var(--est-border-strong);border-radius:var(--est-radius-sm);background:var(--est-bg);cursor:pointer;position:relative;transition:border-color var(--est-transition),background var(--est-transition);vertical-align:middle;flex:none}.est2 .est2-check:hover{border-color:var(--est-accent)}.est2 .est2-check:checked{background:var(--est-accent);border-color:var(--est-accent)}.est2 .est2-check:checked:after{content:\"\";position:absolute;left:4.5px;top:1.5px;width:4px;height:8px;border:solid var(--est-accent-fg);border-width:0 2px 2px 0;transform:rotate(45deg)}.est2 .est2-check:focus-visible{outline:none;box-shadow:var(--est-ring)}.est2 .est2-check:indeterminate{background:var(--est-accent);border-color:var(--est-accent)}.est2 .est2-check:indeterminate:after{content:\"\";position:absolute;left:3px;top:6px;width:8px;height:2px;background:var(--est-accent-fg);transform:none;border:none}.est2 th.est2-col-min,.est2 td.est2-col-min{width:1%;white-space:nowrap}.est2 .est2-wrap{position:relative}.est2 .est2-selectbar{position:absolute;top:0;left:0;right:0;z-index:15;display:flex;align-items:center;gap:12px;padding:9px 14px;font-size:var(--est-fs-sm);color:var(--est-fg);background:var(--est-bg-subtle);border:none;border-radius:var(--est-radius) var(--est-radius) 0 0;box-shadow:inset 3px 0 0 var(--est-accent)}.est2 .est2-selectbar strong{color:var(--est-fg);font-weight:700}.est2 .est2-selectbar__spacer{flex:1 1 auto}.est2 .est2-link{color:var(--est-accent);cursor:pointer;font-weight:600}.est2 .est2-link:hover{text-decoration:underline}.est2 .est2-rowaction{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:var(--est-radius-sm);border:none;background:transparent;color:var(--est-fg-faint);cursor:pointer;transition:background var(--est-transition),color var(--est-transition)}.est2 .est2-rowaction:hover{background:var(--est-bg-hover);color:var(--est-fg)}.est2 .est2-rowaction--danger:hover{color:var(--est-danger)}.est2 .est2-op-cell{text-align:center}.est2 .est2-op{display:inline-flex;align-items:center;justify-content:center;min-width:26px;height:26px;padding:0 6px;border-radius:var(--est-radius-sm);color:var(--est-accent);cursor:pointer;font-size:var(--est-fs-sm);transition:background var(--est-transition)}.est2 .est2-op:hover{background:var(--est-accent-weak)}.est2 .est2-loading{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:10px;background:color-mix(in srgb,var(--est-bg) 70%,transparent);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);z-index:5;color:var(--est-fg-muted);font-size:var(--est-fs-sm)}.est2 .est2-spinner{width:16px;height:16px;border:2px solid var(--est-border-strong);border-top-color:var(--est-accent);border-radius:50%;animation:est2-spin .7s linear infinite}@keyframes est2-spin{to{transform:rotate(360deg)}}.est2 .est2-nowrap{white-space:nowrap}.est2 .est2-cornermenu{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:var(--est-radius-sm);cursor:pointer;color:var(--est-fg-muted);transition:background var(--est-transition)}.est2 .est2-cornermenu:hover{background:var(--est-bg-hover);color:var(--est-fg)}.est2 .est2-chrome-th{position:relative}.est2 .est2-chrome{display:inline-flex;align-items:center;gap:2px}.est2 .est2-chrome-wrap{position:relative;display:inline-flex}.est2 .est2-chrome-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:none;background:transparent;border-radius:var(--est-radius-sm);color:var(--est-fg-muted);cursor:pointer;font-size:16px;line-height:1;transition:background var(--est-transition),color var(--est-transition)}.est2 .est2-chrome-btn:hover{background:var(--est-bg-hover);color:var(--est-fg)}.est2 .est2-menu{position:absolute;top:calc(100% + 4px);right:0;z-index:30;min-width:180px;padding:4px;background:var(--est-bg);border:1px solid var(--est-border);border-radius:var(--est-radius);box-shadow:0 8px 24px #0000002e;display:flex;flex-direction:column}.est2 .est2-menu-item{display:block;width:100%;padding:8px 10px;border:none;background:transparent;text-align:left;font:inherit;color:var(--est-fg);border-radius:var(--est-radius-sm);cursor:pointer}.est2 .est2-menu-item:hover{background:var(--est-bg-hover)}.est2 .est2-dialog-backdrop{position:absolute;inset:0;z-index:40;display:flex;align-items:center;justify-content:center;padding:16px;background:color-mix(in srgb,#000 32%,transparent)}.est2 .est2-dialog{width:380px;max-width:100%;max-height:100%;display:flex;flex-direction:column;background:var(--est-bg);color:var(--est-fg);border:1px solid var(--est-border);border-radius:var(--est-radius);box-shadow:0 16px 48px #0000004d;overflow:hidden}.est2 .est2-dialog__head{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;font-weight:700;border-bottom:1px solid var(--est-border)}.est2 .est2-dialog__close{border:none;background:transparent;cursor:pointer;color:var(--est-fg-muted);font-size:15px;line-height:1;padding:4px;border-radius:var(--est-radius-sm)}.est2 .est2-dialog__close:hover{background:var(--est-bg-hover);color:var(--est-fg)}.est2 .est2-dialog__tools{padding:8px 14px;font-size:var(--est-fs-sm);border-bottom:1px solid var(--est-border)}.est2 .est2-dialog__tools .est2-link{border:none;background:none;padding:0;font:inherit;font-weight:600}.est2 .est2-dialog__sep{margin:0 6px;color:var(--est-fg-faint)}.est2 .est2-collist{list-style:none;margin:0;padding:6px;overflow-y:auto}.est2 .est2-collist__row{display:flex;align-items:center;gap:10px;padding:6px 8px;border-radius:var(--est-radius-sm)}.est2 .est2-collist__row:hover{background:var(--est-bg-hover)}.est2 .est2-collist__vis{display:inline-flex}.est2 .est2-collist__label{flex:1 1 auto}.est2 .est2-collist__ord{display:inline-flex;gap:4px}.est2 .est2-iconbtn{width:26px;height:26px;border:1px solid var(--est-border);background:var(--est-bg);border-radius:var(--est-radius-sm);color:var(--est-fg-muted);cursor:pointer}.est2 .est2-iconbtn:hover:not(:disabled){background:var(--est-bg-hover);color:var(--est-fg)}.est2 .est2-iconbtn:disabled{opacity:.4;cursor:default}.est2 .est2-dialog__foot{display:flex;align-items:center;gap:8px;padding:12px 14px;border-top:1px solid var(--est-border)}.est2 .est2-dialog__spacer{flex:1 1 auto}.est2 .est2-btn{padding:7px 14px;border-radius:var(--est-radius-sm);font:inherit;font-weight:600;cursor:pointer;border:1px solid transparent}.est2 .est2-btn--ghost{background:transparent;color:var(--est-fg);border-color:var(--est-border)}.est2 .est2-btn--ghost:hover{background:var(--est-bg-hover)}.est2 .est2-btn--primary{background:var(--est-accent);color:var(--est-accent-fg)}.est2 .est2-btn--primary:hover{filter:brightness(1.05)}.est2 .est2-empty{padding:48px 16px;text-align:center;color:var(--est-fg-faint);font-size:var(--est-fs-sm)}.ngx-contextmenu{--ctx-bg: #ffffff;--ctx-fg: #1a1d24;--ctx-muted: #626b7a;--ctx-border: #e6e9ef;--ctx-hover: #f2f4f7;--ctx-accent: #4f46e5;--ctx-shadow: 0 10px 28px -8px rgba(16, 24, 40, .22), 0 2px 8px -3px rgba(16, 24, 40, .14);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.ngx-contextmenu .dropdown-menu{display:block;min-width:200px;margin:0;padding:6px;list-style:none;background:var(--ctx-bg);border:1px solid var(--ctx-border);border-radius:12px;box-shadow:var(--ctx-shadow);animation:est2-ctx-in .12s cubic-bezier(.16,1,.3,1)}.ngx-contextmenu li{list-style:none;margin:0}.ngx-contextmenu li>a{display:flex;align-items:center;gap:8px;padding:8px 12px;border-radius:7px;color:var(--ctx-fg);font-size:13.5px;line-height:1.2;text-decoration:none;cursor:pointer;white-space:nowrap;transition:background .12s ease,color .12s ease}.ngx-contextmenu li>a:hover,.ngx-contextmenu li>a:focus{background:var(--ctx-hover);color:var(--ctx-fg);text-decoration:none;outline:none}.ngx-contextmenu li.divider,.ngx-contextmenu li[role=separator]{height:1px;margin:6px 8px;padding:0;background:var(--ctx-border)}.ngx-contextmenu li.disabled>a,.ngx-contextmenu li[aria-disabled=true]>a{color:var(--ctx-muted);opacity:.55;pointer-events:none}@media(prefers-color-scheme:dark){.ngx-contextmenu{--ctx-bg: #1e222b;--ctx-fg: #e7eaf0;--ctx-muted: #9aa3b2;--ctx-border: #2a2f3a;--ctx-hover: #232733;--ctx-accent: #7c74ff;--ctx-shadow: 0 12px 30px -8px rgba(0, 0, 0, .6), 0 2px 8px -3px rgba(0, 0, 0, .5)}}@keyframes est2-ctx-in{0%{opacity:0;transform:translateY(-4px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}@media(prefers-reduced-motion:reduce){.ngx-contextmenu .dropdown-menu{animation:none}}\n"], dependencies: [{ kind: "directive", type: i8.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.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: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i9.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i12.ContextMenuAttachDirective, selector: "[contextMenu]", inputs: ["contextMenuSubject", "contextMenu"] }, { kind: "component", type: i12.ContextMenuComponent, selector: "context-menu", inputs: ["menuClass", "autoFocus", "useBootstrap4", "disabled"], outputs: ["close", "open"] }, { kind: "directive", type: i12.ContextMenuItemDirective, selector: "[contextMenuItem]", inputs: ["subMenu", "divider", "enabled", "passive", "visible"], outputs: ["execute"] }, { kind: "component", type: EsTable2PagerComponent, selector: "es-table2-pager", inputs: ["page", "pages", "total", "itemsPerPage", "countLabel", "showCount", "showButtons", "showPagingOptions", "allowAll"], outputs: ["pageChange", "itemsPerPageChange"] }, { kind: "pipe", type: Est2FormatPipe, name: "est2_format" }, { kind: "pipe", type: Est2LookupPipe, name: "est2_lookup" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
8935
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: EsTable2Component, isStandalone: false, selector: "es-table2", inputs: { ContextMenu: { classPropertyName: "ContextMenu", publicName: "ContextMenu", isSignal: false, isRequired: false, transformFunction: null }, _Selection: { classPropertyName: "_Selection", publicName: "Selection", isSignal: true, isRequired: false, transformFunction: null }, SingleSelection: { classPropertyName: "SingleSelection", publicName: "SingleSelection", isSignal: true, isRequired: false, transformFunction: null }, SelectionDisabled: { classPropertyName: "SelectionDisabled", publicName: "SelectionDisabled", isSignal: true, isRequired: false, transformFunction: null }, _SelectAll: { classPropertyName: "_SelectAll", publicName: "SelectAll", isSignal: true, isRequired: false, transformFunction: null }, _UseSelectionCache: { classPropertyName: "_UseSelectionCache", publicName: "UseSelectionCache", isSignal: true, isRequired: false, transformFunction: null }, _ShiftClick: { classPropertyName: "_ShiftClick", publicName: "ShiftClick", isSignal: true, isRequired: false, transformFunction: null }, _OrderByColumn: { classPropertyName: "_OrderByColumn", publicName: "OrderByColumn", isSignal: true, isRequired: false, transformFunction: null }, MultipleOrderingDirectives: { classPropertyName: "MultipleOrderingDirectives", publicName: "MultipleOrderingDirectives", isSignal: true, isRequired: false, transformFunction: null }, Removal: { classPropertyName: "Removal", publicName: "Removal", isSignal: true, isRequired: false, transformFunction: null }, RemovalCondition: { classPropertyName: "RemovalCondition", publicName: "RemovalCondition", isSignal: true, isRequired: false, transformFunction: null }, RowClassAssigner: { classPropertyName: "RowClassAssigner", publicName: "RowClassAssigner", isSignal: false, isRequired: false, transformFunction: null }, _HidePaging: { classPropertyName: "_HidePaging", publicName: "HidePaging", isSignal: true, isRequired: false, transformFunction: null }, _HidePagingCount: { classPropertyName: "_HidePagingCount", publicName: "HidePagingCount", isSignal: true, isRequired: false, transformFunction: null }, _HidePagingButtons: { classPropertyName: "_HidePagingButtons", publicName: "HidePagingButtons", isSignal: true, isRequired: false, transformFunction: null }, _AllSearch: { classPropertyName: "_AllSearch", publicName: "AllSearch", isSignal: true, isRequired: false, transformFunction: null }, _PagingStyle: { classPropertyName: "_PagingStyle", publicName: "PagingStyle", isSignal: true, isRequired: false, transformFunction: null }, _ArraymodeItemsPerPage: { classPropertyName: "_ArraymodeItemsPerPage", publicName: "ArraymodeItemsPerPage", isSignal: true, isRequired: false, transformFunction: null }, _UseArrayModePaging: { classPropertyName: "_UseArrayModePaging", publicName: "UseArrayModePaging", isSignal: true, isRequired: false, transformFunction: null }, CountLabel: { classPropertyName: "CountLabel", publicName: "CountLabel", isSignal: true, isRequired: false, transformFunction: null }, Height: { classPropertyName: "Height", publicName: "Height", isSignal: true, isRequired: false, transformFunction: null }, MaxHeight: { classPropertyName: "MaxHeight", publicName: "MaxHeight", isSignal: true, isRequired: false, transformFunction: null }, EmptySpaceBackgroundColor: { classPropertyName: "EmptySpaceBackgroundColor", publicName: "EmptySpaceBackgroundColor", isSignal: true, isRequired: false, transformFunction: null }, HighCellDensity: { classPropertyName: "HighCellDensity", publicName: "HighCellDensity", isSignal: true, isRequired: false, transformFunction: null }, HeaderHidden: { classPropertyName: "HeaderHidden", publicName: "HeaderHidden", isSignal: true, isRequired: false, transformFunction: null }, BodyHidden: { classPropertyName: "BodyHidden", publicName: "BodyHidden", isSignal: true, isRequired: false, transformFunction: null }, ShowLoadingOnBootstrap: { classPropertyName: "ShowLoadingOnBootstrap", publicName: "ShowLoadingOnBootstrap", isSignal: true, isRequired: false, transformFunction: null }, _DefaultAlignment: { classPropertyName: "_DefaultAlignment", publicName: "DefaultAlignment", isSignal: true, isRequired: false, transformFunction: null }, _TableClass: { classPropertyName: "_TableClass", publicName: "TableClass", isSignal: true, isRequired: false, transformFunction: null }, _ContainerClass: { classPropertyName: "_ContainerClass", publicName: "ContainerClass", isSignal: true, isRequired: false, transformFunction: null }, EsTableHandledSearch: { classPropertyName: "EsTableHandledSearch", publicName: "EsTableHandledSearch", isSignal: true, isRequired: false, transformFunction: null }, SearchThrottle: { classPropertyName: "SearchThrottle", publicName: "SearchThrottle", isSignal: true, isRequired: false, transformFunction: null }, _ColumnsResizable: { classPropertyName: "_ColumnsResizable", publicName: "ColumnsResizable", isSignal: true, isRequired: false, transformFunction: null }, _ColumnsPinnable: { classPropertyName: "_ColumnsPinnable", publicName: "ColumnsPinnable", isSignal: true, isRequired: false, transformFunction: null }, _HiddenColumns: { classPropertyName: "_HiddenColumns", publicName: "HiddenColumns", isSignal: true, isRequired: false, transformFunction: null }, _ColumnsOrdering: { classPropertyName: "_ColumnsOrdering", publicName: "ColumnsOrdering", isSignal: true, isRequired: false, transformFunction: null }, _Export: { classPropertyName: "_Export", publicName: "Export", isSignal: true, isRequired: false, transformFunction: null }, XLSXExport: { classPropertyName: "XLSXExport", publicName: "XLSXExport", isSignal: true, isRequired: false, transformFunction: null }, CSVExport: { classPropertyName: "CSVExport", publicName: "CSVExport", isSignal: true, isRequired: false, transformFunction: null }, ExportFileName: { classPropertyName: "ExportFileName", publicName: "ExportFileName", isSignal: true, isRequired: false, transformFunction: null }, ExportOnlyVisibleColumns: { classPropertyName: "ExportOnlyVisibleColumns", publicName: "ExportOnlyVisibleColumns", isSignal: true, isRequired: false, transformFunction: null }, ExportFunction: { classPropertyName: "ExportFunction", publicName: "ExportFunction", isSignal: false, isRequired: false, transformFunction: null }, CornerMenuOptions: { classPropertyName: "CornerMenuOptions", publicName: "CornerMenuOptions", isSignal: true, isRequired: false, transformFunction: null }, DynamicOperations: { classPropertyName: "DynamicOperations", publicName: "DynamicOperations", isSignal: true, isRequired: false, transformFunction: null }, _DynamicRowColumnsDefinition: { classPropertyName: "_DynamicRowColumnsDefinition", publicName: "DynamicRowColumnsDefinition", isSignal: true, isRequired: false, transformFunction: null }, Hierarchy: { classPropertyName: "Hierarchy", publicName: "Hierarchy", isSignal: true, isRequired: false, transformFunction: null }, _ParentKey: { classPropertyName: "_ParentKey", publicName: "ParentKey", isSignal: true, isRequired: false, transformFunction: null }, _OwnKey: { classPropertyName: "_OwnKey", publicName: "OwnKey", isSignal: true, isRequired: false, transformFunction: null }, _AutoSortHierarchy: { classPropertyName: "_AutoSortHierarchy", publicName: "AutoSortHierarchy", isSignal: true, isRequired: false, transformFunction: null }, StartsExpanded: { classPropertyName: "StartsExpanded", publicName: "StartsExpanded", isSignal: true, isRequired: false, transformFunction: null }, CascadeSelection: { classPropertyName: "CascadeSelection", publicName: "CascadeSelection", isSignal: true, isRequired: false, transformFunction: null }, _SavePreferences: { classPropertyName: "_SavePreferences", publicName: "SavePreferences", isSignal: true, isRequired: false, transformFunction: null }, Name: { classPropertyName: "Name", publicName: "Name", isSignal: true, isRequired: false, transformFunction: null }, _RowGroupingPagingStyle: { classPropertyName: "_RowGroupingPagingStyle", publicName: "RowGroupingPagingStyle", isSignal: true, isRequired: false, transformFunction: null }, _ShowItemGroupsColumns: { classPropertyName: "_ShowItemGroupsColumns", publicName: "ShowItemGroupsColumns", isSignal: true, isRequired: false, transformFunction: null }, Editable: { classPropertyName: "Editable", publicName: "Editable", isSignal: true, isRequired: false, transformFunction: null }, RangeSelection: { classPropertyName: "RangeSelection", publicName: "RangeSelection", isSignal: true, isRequired: false, transformFunction: null }, ItemSourceProperty: { classPropertyName: "ItemSourceProperty", publicName: "ItemSourceProperty", isSignal: true, isRequired: false, transformFunction: null }, HasHeaderGroup: { classPropertyName: "HasHeaderGroup", publicName: "HasHeaderGroup", isSignal: true, isRequired: false, transformFunction: null }, HasSecondaryHeaderGroup: { classPropertyName: "HasSecondaryHeaderGroup", publicName: "HasSecondaryHeaderGroup", isSignal: true, isRequired: false, transformFunction: null }, SearchView: { classPropertyName: "SearchView", publicName: "SearchView", isSignal: true, isRequired: false, transformFunction: null }, _AutoUpdate: { classPropertyName: "_AutoUpdate", publicName: "AutoUpdate", isSignal: true, isRequired: false, transformFunction: null }, EsThTdProvider: { classPropertyName: "EsThTdProvider", publicName: "EsThTdProvider", isSignal: false, isRequired: false, transformFunction: null }, globalCheck: { classPropertyName: "globalCheck", publicName: "globalCheck", isSignal: true, isRequired: false, transformFunction: null }, autoUpdate: { classPropertyName: "autoUpdate", publicName: "autoUpdate", isSignal: true, isRequired: false, transformFunction: null }, seconds: { classPropertyName: "seconds", publicName: "seconds", isSignal: true, isRequired: false, transformFunction: null }, researchInProgress: { classPropertyName: "researchInProgress", publicName: "researchInProgress", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { onOrderChanged: "onOrderChanged", onSearchRequest: "onSearchRequest", onSelectionChanged: "onSelectionChanged", onRemoval: "onRemoval", onAbortRemoval: "onAbortRemoval", onModelChange: "onModelChange", onOpenContextMenu: "onOpenContextMenu", onCornerAction: "onCornerAction", onDynamicOperation: "onDynamicOperation", globalCheck: "globalCheckChange", autoUpdate: "autoUpdateChange", seconds: "secondsChange", researchInProgress: "researchInProgressChange" }, host: { listeners: { "document:mouseup": "onResizeEnd()", "document:copy": "onDocCopy($event)", "document:paste": "onDocPaste($event)", "document:click": "onDocClick()", "document:mousemove": "onResizeMove($event)" }, properties: { "class.est2": "this.hostClass", "class.est2--dense": "this.denseClass" } }, queries: [{ propertyName: "headerRef", first: true, predicate: ["header"], descendants: true }, { propertyName: "bodyRef", first: true, predicate: ["body"], descendants: true }, { propertyName: "thDirectives", predicate: EsThDirective }, { propertyName: "tdDirectives", predicate: EsTdDirective }, { propertyName: "editorDirectives", predicate: EsTdEditorDirective }, { propertyName: "thTdProviders", predicate: ThTdProvider }], viewQueries: [{ propertyName: "tableEmptyMenu", first: true, predicate: ["emptyMenu"], descendants: true, static: true }, { propertyName: "theadRef", first: true, predicate: ["theadRef"], descendants: true }], ngImport: i0, template: "@if (view()) {\n <div class=\"est2-wrap {{ ContainerClass() }}\"\n [style.height]=\"Height()\"\n [style.background-color]=\"EmptySpaceBackgroundColor() || null\">\n\n <!-- Auto-aggiornamento: toggle + intervallo (in alto a destra) -->\n @if (AutoUpdate()) {\n <div class=\"est2-autoupdate\">\n <label class=\"est2-autoupdate__label\">\n Aggiorna ogni\n <input type=\"text\" maxlength=\"3\" class=\"est2-autoupdate__secs\"\n [ngModel]=\"seconds()\" (ngModelChange)=\"seconds.set($event)\"\n [ngModelOptions]=\"{ standalone: true }\"\n (change)=\"autoUpdateChanged()\" />\n secondi\n </label>\n <label class=\"est2-switch\" title=\"Attiva/disattiva auto-aggiornamento\">\n <input type=\"checkbox\" [ngModel]=\"autoUpdate()\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"autoUpdate.set($event); autoUpdateChanged()\" />\n <span class=\"est2-switch__slider\"></span>\n </label>\n </div>\n }\n\n <!-- Pager superiore -->\n @if (PagingStyle() === 'both' || PagingStyle() === 'top') {\n <ng-container *ngTemplateOutlet=\"pager\"></ng-container>\n }\n\n <!-- Barra \"seleziona tutto\" (visibile solo con selezione multipla attiva e righe presenti) -->\n @if (Selection() && !SingleSelection() && hasSelection) {\n <div class=\"est2-selectbar\" [style.height.px]=\"selbarHeight() ? selbarHeight() + 1 : null\">\n @if (allSelected) {\n <span>Tutti i <strong>{{ selectedCount }}</strong> elementi sono selezionati</span>\n } @else {\n <span><strong>{{ selectedCount }}</strong> {{ selectedCount === 1 ? 'elemento selezionato' : 'elementi selezionati' }}</span>\n @if (canSelectEverything) {\n <span class=\"est2-link\" (click)=\"selectEverything()\">Seleziona tutti i {{ totalCount() }} elementi</span>\n }\n }\n <span class=\"est2-selectbar__spacer\"></span>\n <span class=\"est2-link\" (click)=\"clearSelection()\">Azzera selezione</span>\n </div>\n }\n\n <div class=\"est2-scroll\" [style.max-height.px]=\"MaxHeight()\">\n <table class=\"est2-table {{ TableClass() }}\"\n [class.est2-table--range]=\"rangeActive()\"\n [class.est2-table--dragging]=\"rangeDragging()\"\n (mousedown)=\"onGridMouseDown($event)\"\n (mouseover)=\"onGridMouseOver($event)\">\n\n <!-- ================= HEADER ================= -->\n @if (!HeaderHidden()) {\n <thead #theadRef>\n <!-- Righe di header-group multi-livello (dall'alto verso il basso) -->\n @if (hasHeaderGroups()) {\n @for (grow of headerGroupRows(); track $index) {\n <tr class=\"est2-hgroup-row\">\n @if (Selection()) { <th class=\"est2-col-min est2-selcol\" [class.est2-pinned]=\"hasPinned()\" [style.left.px]=\"hasPinned() ? 0 : null\"></th> }\n @for (op of DynamicOperations(); track op.id) { <th class=\"est2-col-min\"></th> }\n @for (cell of grow; track cell.id; let gi = $index) {\n <th [attr.colspan]=\"cell.span\"\n [attr.data-groupid]=\"cell.isGroup ? cell.id : null\"\n [class.est2-hgroup]=\"cell.isGroup\"\n [class.est2-pinned]=\"gi < pinnedCount()\"\n [style.left.px]=\"gi < pinnedCount() ? pinnedLeftPx(gi) : null\"\n class=\"est2-hgroup-cell\">\n @if (cell.isGroup) {\n @if (cell.template) {\n <ng-container *ngTemplateOutlet=\"cell.template\"></ng-container>\n } @else {\n {{ cell.label }}\n }\n }\n </th>\n }\n @if (Removal()) { <th class=\"est2-col-min\"></th> }\n @if (hasChrome()) { <th class=\"est2-col-min\"></th> }\n </tr>\n }\n }\n <tr>\n <!-- Colonna di selezione -->\n @if (Selection()) {\n <th class=\"est2-col-min est2-selcol\"\n [class.est2-pinned]=\"hasPinned()\"\n [style.left.px]=\"hasPinned() ? 0 : null\">\n @if (!SingleSelection()) {\n <input type=\"checkbox\" class=\"est2-check\"\n [checked]=\"globalCheck()\"\n [indeterminate]=\"selectionIndeterminate\"\n [disabled]=\"SelectionDisabled()\"\n (change)=\"toggleAll()\"\n aria-label=\"Seleziona tutto\" />\n }\n </th>\n }\n\n <!-- Header da colonne (direttive / dinamica / report) -->\n @if (usesColumns()) {\n <!-- intestazioni vuote per le operazioni dinamiche -->\n @for (op of DynamicOperations(); track op.id) {\n <th class=\"est2-col-min\"></th>\n }\n @for (col of visibleColumns(); track trackCol($index, col); let ci = $index) {\n <th [attr.data-colid]=\"col.id\"\n [class]=\"col.headerClass\"\n [class.est2-th--orderable]=\"OrderByColumn() && col.orderable\"\n [class.est2-col-min]=\"col.header?.thShrink\"\n [class.est2-col-groupstart]=\"columnGroupBoundaries().has(ci)\"\n [class.est2-pinned]=\"col.pinned\"\n [style.left.px]=\"col.pinned ? pinnedLeftPx(ci) : null\"\n [style.width.px]=\"col.pinned ? pinnedWidthPx(col) : null\"\n [style.min-width.px]=\"col.pinned ? pinnedWidthPx(col) : null\"\n [style.max-width.px]=\"col.pinned ? pinnedWidthPx(col) : null\"\n [style.text-align]=\"col.alignment\"\n [style.background-color]=\"col.headerBg || null\"\n (click)=\"toggleSort(col)\">\n <span class=\"est2-th__inner\">\n @if (col.header?.Template) {\n <ng-container *ngTemplateOutlet=\"col.header!.Template!; context: col.multiProp != null ? { $implicit: col.headerText } : null\"></ng-container>\n } @else {\n {{ col.headerText }}\n }\n @if (OrderByColumn() && col.orderable && orderOf(col.id)) {\n <span class=\"est2-sort est2-sort--active\"\n [class.est2-sort--desc]=\"orderOf(col.id) === 'DESC'\">\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 15l6-6 6 6\"/></svg>\n </span>\n @if (orderIndex(col.id) > 0 && MultipleOrderingDirectives()) {\n <span class=\"est2-sort__badge\">{{ orderIndex(col.id) }}</span>\n }\n }\n <!-- Indicatore/toggle di pin (visibile se pinnata o all'hover) -->\n @if (ColumnsPinnable() && col.multiProp == null) {\n <button type=\"button\" class=\"est2-pinbtn\"\n [class.est2-pinbtn--on]=\"col.pinned\"\n [title]=\"col.pinned ? 'Sblocca colonna' : 'Blocca colonna a sinistra'\"\n (click)=\"togglePin(col, $event)\">\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 17v5\"/><path d=\"M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z\"/></svg>\n </button>\n }\n </span>\n <!-- Handle di ridimensionamento (colonne pinnate, gated da ColumnsResizable) -->\n @if (ColumnsResizable() && col.pinned) {\n <span class=\"est2-resize-handle\" title=\"Trascina per ridimensionare\"\n (mousedown)=\"startColumnResize(col, $event)\"\n (click)=\"$event.stopPropagation()\"></span>\n }\n </th>\n }\n }\n <!-- Header da template semplice -->\n @else if (headerRef) {\n <ng-container *ngTemplateOutlet=\"headerRef\"></ng-container>\n }\n\n <!-- Colonna rimozione -->\n @if (Removal()) { <th class=\"est2-col-min\"></th> }\n <!-- Colonna chrome (export / gestione colonne / menu) -->\n @if (hasChrome()) {\n <th class=\"est2-col-min est2-chrome-th\">\n <div class=\"est2-chrome\">\n @if (Export()) {\n <div class=\"est2-chrome-wrap\">\n <button type=\"button\" class=\"est2-chrome-btn\" title=\"Esporta\" (click)=\"$event.stopPropagation(); toggleExportMenu()\">\n <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><path d=\"M7 10l5 5 5-5\"/><path d=\"M12 15V3\"/></svg>\n </button>\n @if (exportMenuOpen()) {\n <div class=\"est2-menu\" (click)=\"$event.stopPropagation()\">\n @if (CSVExport()) { <button type=\"button\" class=\"est2-menu-item\" (click)=\"export('CSV')\">Esporta CSV</button> }\n @if (XLSXExport()) { <button type=\"button\" class=\"est2-menu-item\" (click)=\"export('XLSX')\">Esporta Excel (XLSX)</button> }\n @if (!CSVExport() && !XLSXExport()) { <button type=\"button\" class=\"est2-menu-item\" (click)=\"export('CSV')\">Esporta CSV</button> }\n </div>\n }\n </div>\n }\n @if (HiddenColumns() || ColumnsOrdering()) {\n <button type=\"button\" class=\"est2-chrome-btn\" title=\"Colonne\" (click)=\"$event.stopPropagation(); openColumnsDialog()\">\n <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"1\"/><path d=\"M9 3v18\"/><path d=\"M15 3v18\"/></svg>\n </button>\n }\n @if (CornerMenuOptions().length > 0) {\n <div class=\"est2-chrome-wrap\">\n <button type=\"button\" class=\"est2-chrome-btn\" title=\"Opzioni\" (click)=\"$event.stopPropagation(); toggleCornerMenu()\">\u22EE</button>\n @if (cornerMenuOpen()) {\n <div class=\"est2-menu\" (click)=\"$event.stopPropagation()\">\n @for (opt of CornerMenuOptions(); track opt.id) {\n <button type=\"button\" class=\"est2-menu-item\" (click)=\"cornerAction(opt.id); cornerMenuOpen.set(false)\">{{ opt.description }}</button>\n }\n </div>\n }\n </div>\n }\n </div>\n </th>\n }\n </tr>\n </thead>\n }\n\n <!-- ================= BODY ================= -->\n @if (!BodyHidden()) {\n <tbody>\n @for (item of boundSource(); track trackRow($index, item); let ri = $index) {\n @if ((!grouped() && !Hierarchy()) || item._visible) {\n <tr [class]=\"rowClass(item)\"\n [class.est2-row--selected]=\"item._selected\"\n [class.est2-row--removed]=\"item.removed || item.deleted\"\n [class.est2-row--group]=\"item._group\"\n [class.est2-row--clickable]=\"Selection() || item._group\"\n [contextMenu]=\"ContextMenu || emptyMenu\"\n [contextMenuSubject]=\"item\"\n (click)=\"handleRowClick(item, $event)\">\n\n <!-- Cella di selezione -->\n @if (Selection()) {\n <td class=\"est2-col-min est2-selcol\"\n [class.est2-pinned]=\"hasPinned()\"\n [style.left.px]=\"hasPinned() ? 0 : null\">\n @if (item._group) {\n @if (!SingleSelection()) {\n <input type=\"checkbox\" class=\"est2-check\"\n [checked]=\"groupSelectionState(item) === 'all'\"\n [indeterminate]=\"groupSelectionState(item) === 'some'\"\n [disabled]=\"SelectionDisabled()\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleGroupSelection(item)\"\n aria-label=\"Seleziona gruppo\" />\n }\n } @else {\n <input type=\"checkbox\" class=\"est2-check\"\n [checked]=\"item._selected\"\n [indeterminate]=\"hierarchyIndeterminate(item)\"\n [disabled]=\"SelectionDisabled()\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleRow(item)\"\n aria-label=\"Seleziona riga\" />\n }\n </td>\n }\n\n <!-- Celle da colonne (direttive / dinamica / report) -->\n @if (usesColumns()) {\n <!-- Operazioni dinamiche (icone a sinistra) -->\n @for (op of DynamicOperations(); track op.id) {\n <td class=\"est2-col-min est2-op-cell\">\n @if (!item._group && operationVisible(op, item)) {\n <span class=\"est2-op\" [class]=\"op.iconClass || ''\" [title]=\"op.title\"\n (click)=\"$event.stopPropagation(); dynamicOperation(item, op.id)\">{{ op.text }}</span>\n }\n </td>\n }\n @for (col of visibleColumns(); track trackCol($index, col); let first = $first, ci = $index) {\n <td [class]=\"col.cssClass\"\n [style.text-align]=\"col.alignment\"\n [style.color]=\"cellColor(item, col, 'fore')\"\n [style.background-color]=\"cellColor(item, col, 'back')\"\n [class.est2-nowrap]=\"!col.wrap\"\n [class.est2-col-groupstart]=\"columnGroupBoundaries().has(ci)\"\n [class.est2-pinned]=\"col.pinned\"\n [style.left.px]=\"col.pinned ? pinnedLeftPx(ci) : null\"\n [style.width.px]=\"col.pinned ? pinnedWidthPx(col) : null\"\n [style.min-width.px]=\"col.pinned ? pinnedWidthPx(col) : null\"\n [style.max-width.px]=\"col.pinned ? pinnedWidthPx(col) : null\"\n (mouseenter)=\"onCellHover($event)\"\n [class.est2-td--group-key]=\"item._group && item.column === col.id\"\n [attr.data-r]=\"rangeActive() && !item._group ? ri : null\"\n [attr.data-c]=\"rangeActive() && !item._group ? ci : null\"\n [class.est2-cell-sel]=\"rangeActive() && inRange(ri, ci)\"\n [class.est2-cell-sel-t]=\"rangeActive() && inRange(ri, ci) && ri === rangeRect()!.top\"\n [class.est2-cell-sel-b]=\"rangeActive() && inRange(ri, ci) && ri === rangeRect()!.bottom\"\n [class.est2-cell-sel-l]=\"rangeActive() && inRange(ri, ci) && ci === rangeRect()!.left\"\n [class.est2-cell-sel-r]=\"rangeActive() && inRange(ri, ci) && ci === rangeRect()!.right\"\n [class.est2-cell-editing]=\"isEditing(ri, ci)\"\n (dblclick)=\"onCellDblClick(item, col, ri, ci)\">\n @if (isEditing(ri, ci)) {\n @if (editorFor(col); as edTpl) {\n <ng-container *ngTemplateOutlet=\"edTpl; context: editorContext(item, col)\"></ng-container>\n } @else {\n <ng-container *ngTemplateOutlet=\"defaultEditor; context: { $implicit: item, col: col }\"></ng-container>\n }\n } @else if (item._group) {\n @if (item.column === col.id) {\n <span class=\"est2-group-key\" [style.padding-left.px]=\"groupIndent(item)\">\n <span class=\"est2-group-chevron\" [class.est2-group-chevron--open]=\"item._expanded\">\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 6l6 6-6 6\"/></svg>\n </span>\n {{ groupCellDisplay(item, col) }}\n </span>\n } @else {\n {{ groupCellDisplay(item, col) }}\n }\n } @else {\n <!-- Navigatore albero nella prima colonna -->\n @if (Hierarchy() && first) {\n <span class=\"est2-hier-lead\" [style.padding-left.px]=\"hierarchyIndent(item)\">\n @if (item.parent) {\n <span class=\"est2-group-chevron est2-hier-toggle\" [class.est2-group-chevron--open]=\"item._expanded\"\n (click)=\"$event.stopPropagation(); toggleHierarchyNode(item)\">\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 6l6 6-6 6\"/></svg>\n </span>\n } @else {\n <span class=\"est2-hier-toggle\"></span>\n }\n </span>\n }\n @if (itemCellHidden(col)) {\n <!-- colonna di gruppo nascosta sulla riga-oggetto -->\n } @else if (col.multiProp != null) {\n @if (col.cell?.Template) {\n <ng-container *ngTemplateOutlet=\"col.cell!.Template; context: { $implicit: multiCell(item, col) }\"></ng-container>\n } @else {\n {{ multiCell(item, col)?.value }}\n }\n } @else if (col.cell?.Template) {\n <ng-container *ngTemplateOutlet=\"col.cell!.Template; context: { $implicit: item }\"></ng-container>\n } @else if (col.routePath) {\n <a class=\"est2-link\" [routerLink]=\"routerLinkFor(item, col)\">{{ cellValue(item, col) }}</a>\n } @else if (col.propAccessor != null) {\n {{ reportCellDisplay(item, col) }}\n } @else if (col.type === 'enum') {\n {{ (cellValue(item, col) | est2_lookup : col.source).description }}\n } @else {\n {{ cellValue(item, col) | est2_format : col.type : col.format : locale }}\n }\n }\n </td>\n }\n }\n <!-- Celle da template semplice -->\n @else if (bodyRef) {\n <ng-container *ngTemplateOutlet=\"bodyRef; context: { $implicit: item }\"></ng-container>\n }\n\n <!-- Rimozione (non sulle righe-gruppo) -->\n @if (Removal()) {\n <td class=\"est2-col-min\">\n @if (!item._group && canRemove(item)) {\n @if (item.removed || item.deleted) {\n <button type=\"button\" class=\"est2-rowaction\" title=\"Ripristina\" (click)=\"abortRemoval(item)\">\u21BA</button>\n } @else {\n <button type=\"button\" class=\"est2-rowaction est2-rowaction--danger\" title=\"Rimuovi\" (click)=\"removeItem(item)\">\u2715</button>\n }\n }\n </td>\n }\n <!-- Chrome -->\n @if (hasChrome()) { <td class=\"est2-col-min\"></td> }\n </tr>\n }\n } @empty {\n <tr>\n <td class=\"est2-empty\" [attr.colspan]=\"totalColspan()\">Nessun elemento da visualizzare</td>\n </tr>\n }\n </tbody>\n }\n </table>\n\n <!-- Overlay di caricamento -->\n @if (researchInProgress() || (firstBind() && ShowLoadingOnBootstrap())) {\n <div class=\"est2-loading\">\n <span class=\"est2-spinner\"></span>\n <span>Caricamento\u2026</span>\n </div>\n }\n </div>\n\n <!-- Pager inferiore -->\n @if (PagingStyle() === 'both' || PagingStyle() === 'bottom') {\n <ng-container *ngTemplateOutlet=\"pager\"></ng-container>\n }\n\n <!-- Avviso transitorio (es. incolla con dimensioni incompatibili) -->\n @if (notice()) {\n <div class=\"est2-toast\" role=\"alert\">\n <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/><path d=\"M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/></svg>\n <span>{{ notice() }}</span>\n </div>\n }\n\n <!-- Dialog visibilit\u00E0 / ordine colonne -->\n @if (columnsDialogOpen()) {\n <div class=\"est2-dialog-backdrop\" (click)=\"closeColumnsDialog()\">\n <div class=\"est2-dialog\" (click)=\"$event.stopPropagation()\" role=\"dialog\" aria-modal=\"true\">\n <div class=\"est2-dialog__head\">\n <span>\n @if (HiddenColumns() && ColumnsOrdering()) { Visibilit\u00E0 e ordine colonne }\n @else if (HiddenColumns()) { Visibilit\u00E0 colonne }\n @else { Ordine colonne }\n </span>\n <button type=\"button\" class=\"est2-dialog__close\" (click)=\"closeColumnsDialog()\" aria-label=\"Chiudi\">\u2715</button>\n </div>\n\n @if (HiddenColumns()) {\n <div class=\"est2-dialog__tools\">\n <button type=\"button\" class=\"est2-link\" (click)=\"dialogSetAll(true)\">Mostra tutte</button>\n <span class=\"est2-dialog__sep\">\u00B7</span>\n <button type=\"button\" class=\"est2-link\" (click)=\"dialogSetAll(false)\">Nascondi tutte</button>\n </div>\n }\n\n <ul class=\"est2-collist\">\n @for (c of dialogCols(); track c.id; let i = $index) {\n <li class=\"est2-collist__row\"\n [attr.draggable]=\"ColumnsOrdering() ? true : null\"\n [class.est2-collist__row--drag]=\"ColumnsOrdering()\"\n [class.est2-collist__row--dragging]=\"dragIndex() === i\"\n [class.est2-collist__row--drop-above]=\"dragOverIndex() === i && dragIndex() !== null && dragIndex()! > i\"\n [class.est2-collist__row--drop-below]=\"dragOverIndex() === i && dragIndex() !== null && dragIndex()! < i\"\n (dragstart)=\"dialogDragStart(i, $event)\"\n (dragover)=\"dialogDragOver(i, $event)\"\n (drop)=\"dialogDrop(i, $event)\"\n (dragend)=\"dialogDragEnd()\">\n @if (ColumnsOrdering()) {\n <span class=\"est2-collist__grip\" title=\"Trascina per riordinare\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"currentColor\"><circle cx=\"9\" cy=\"6\" r=\"1.6\"/><circle cx=\"15\" cy=\"6\" r=\"1.6\"/><circle cx=\"9\" cy=\"12\" r=\"1.6\"/><circle cx=\"15\" cy=\"12\" r=\"1.6\"/><circle cx=\"9\" cy=\"18\" r=\"1.6\"/><circle cx=\"15\" cy=\"18\" r=\"1.6\"/></svg>\n </span>\n }\n @if (HiddenColumns()) {\n <label class=\"est2-collist__vis\">\n <input type=\"checkbox\" class=\"est2-check\" [checked]=\"c.visible\" (change)=\"dialogToggle(i)\" />\n </label>\n }\n <span class=\"est2-collist__label\">{{ c.label }}</span>\n @if (ColumnsPinnable()) {\n <button type=\"button\" class=\"est2-iconbtn est2-collist__pin\" [class.est2-collist__pin--on]=\"c.pinned\"\n [disabled]=\"c.grouped\"\n [title]=\"c.grouped ? 'Le colonne di un gruppo non sono bloccabili' : (c.pinned ? 'Sblocca' : 'Blocca a sinistra')\"\n (click)=\"dialogTogglePin(i)\">\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 17v5\"/><path d=\"M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z\"/></svg>\n </button>\n }\n @if (ColumnsOrdering()) {\n <span class=\"est2-collist__ord\">\n <button type=\"button\" class=\"est2-iconbtn\" [disabled]=\"i === 0\" title=\"Su\" (click)=\"dialogMove(i, -1)\">\u2191</button>\n <button type=\"button\" class=\"est2-iconbtn\" [disabled]=\"i === dialogCols().length - 1\" title=\"Gi\u00F9\" (click)=\"dialogMove(i, 1)\">\u2193</button>\n </span>\n }\n </li>\n }\n </ul>\n\n <div class=\"est2-dialog__foot\">\n <button type=\"button\" class=\"est2-btn est2-btn--ghost\" (click)=\"resetColumnsDialog()\">Ripristina</button>\n <span class=\"est2-dialog__spacer\"></span>\n <button type=\"button\" class=\"est2-btn est2-btn--ghost\" (click)=\"closeColumnsDialog()\">Annulla</button>\n <button type=\"button\" class=\"est2-btn est2-btn--primary\" (click)=\"applyColumnsDialog()\">Applica</button>\n </div>\n </div>\n </div>\n }\n </div>\n}\n\n<!-- Template pager riutilizzabile sopra/sotto -->\n<ng-template #pager>\n @if (!HidePaging() && !grouped()) {\n <es-table2-pager\n [page]=\"currentPage()\"\n [pages]=\"totalPages()\"\n [total]=\"totalCount()\"\n [itemsPerPage]=\"viewMode() ? (view()?.itemsperpageoverride ?? 15) : ArraymodeItemsPerPage()\"\n [countLabel]=\"CountLabel()\"\n [showCount]=\"!HidePagingCount()\"\n [showButtons]=\"!HidePagingButtons()\"\n [showPagingOptions]=\"!HidePagingButtons()\"\n [allowAll]=\"AllSearch()\"\n (pageChange)=\"goToPage($event)\"\n (itemsPerPageChange)=\"changeItemsPerPage($event)\">\n </es-table2-pager>\n }\n</ng-template>\n\n<!-- Editor di cella di default (usato quando il consumer non fornisce un `*editor`) -->\n<ng-template #defaultEditor let-item let-col=\"col\">\n @switch (col.type) {\n @case ('enum') {\n <select class=\"est2-editor-input\"\n [value]=\"editDraft\"\n (change)=\"editDraft = $any($event.target).value\"\n (keydown.enter)=\"commitEdit(item, col)\"\n (keydown.escape)=\"cancelEdit()\"\n (blur)=\"commitEdit(item, col)\">\n @for (o of col.source || []; track o.id) {\n <option [value]=\"o.id\" [selected]=\"o.id == editDraft\">{{ o.description }}</option>\n }\n </select>\n }\n @case ('boolean') {\n <input type=\"checkbox\" class=\"est2-editor-input est2-check\"\n [checked]=\"editDraft === true || editDraft === 'true'\"\n (change)=\"editDraft = $any($event.target).checked; commitEdit(item, col)\"\n (keydown.escape)=\"cancelEdit()\" />\n }\n @default {\n <input class=\"est2-editor-input\"\n [type]=\"editorInputType(col.type)\"\n [value]=\"editDraft\"\n (input)=\"editDraft = $any($event.target).value\"\n (keydown.enter)=\"commitEdit(item, col)\"\n (keydown.escape)=\"cancelEdit()\"\n (blur)=\"commitEdit(item, col)\" />\n }\n }\n</ng-template>\n\n<!-- Menu di default (nessuna operazione) usato quando il consumer non passa [ContextMenu] -->\n<context-menu #emptyMenu>\n <ng-template contextMenuItem [passive]=\"true\"><em>Nessuna operazione disponibile\u2026</em></ng-template>\n</context-menu>\n", styles: [".est2{--est-bg: #ffffff;--est-bg-subtle: #f7f8fa;--est-bg-raised: #ffffff;--est-bg-hover: #f2f4f7;--est-bg-selected: color-mix(in srgb, var(--est-accent) 12%, transparent);--est-fg: #1a1d24;--est-fg-muted: #626b7a;--est-fg-faint: #9aa3b2;--est-border: #e6e9ef;--est-border-strong: #d3d8e0;--est-accent: #4f46e5;--est-accent-fg: #ffffff;--est-accent-weak: color-mix(in srgb, var(--est-accent) 14%, transparent);--est-danger: #dc2626;--est-warning: #d97706;--est-success: #059669;--est-shadow-sticky: 0 1px 0 var(--est-border), 0 4px 12px -8px rgba(16, 24, 40, .24);--est-shadow-pop: 0 8px 24px -6px rgba(16, 24, 40, .18), 0 2px 6px -2px rgba(16, 24, 40, .12);--est-ring: 0 0 0 3px color-mix(in srgb, var(--est-accent) 40%, transparent);--est-radius: 10px;--est-radius-sm: 6px;--est-radius-pill: 999px;--est-gap: 8px;--est-cell-py: 6px;--est-cell-px: 12px;--est-row-h: 33px;--est-font: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;--est-fs: 13px;--est-fs-sm: 12px;--est-fw-head: 700;--est-transition: .14s cubic-bezier(.4, 0, .2, 1);--est-bg-zebra: color-mix(in srgb, var(--est-fg) 3.5%, var(--est-bg));font-family:var(--est-font);font-size:var(--est-fs);color:var(--est-fg);position:relative;display:block}@media(prefers-color-scheme:dark){.est2{--est-bg: #14161c;--est-bg-subtle: #1a1d25;--est-bg-raised: #1e222b;--est-bg-hover: #232733;--est-bg-selected: color-mix(in srgb, var(--est-accent) 26%, transparent);--est-fg: #e7eaf0;--est-fg-muted: #9aa3b2;--est-fg-faint: #6b7484;--est-border: #2a2f3a;--est-border-strong: #39404d;--est-accent: #7c74ff;--est-accent-fg: #ffffff;--est-accent-weak: color-mix(in srgb, var(--est-accent) 22%, transparent);--est-danger: #f87171;--est-warning: #fbbf24;--est-success: #34d399;--est-shadow-sticky: 0 1px 0 var(--est-border), 0 6px 16px -10px rgba(0, 0, 0, .7);--est-shadow-pop: 0 10px 28px -8px rgba(0, 0, 0, .6), 0 2px 6px -2px rgba(0, 0, 0, .5);--est-ring: 0 0 0 3px color-mix(in srgb, var(--est-accent) 55%, transparent)}}.est2.est2--dark{--est-bg: #14161c;--est-bg-subtle: #1a1d25;--est-bg-raised: #1e222b;--est-bg-hover: #232733;--est-bg-selected: color-mix(in srgb, var(--est-accent) 26%, transparent);--est-fg: #e7eaf0;--est-fg-muted: #9aa3b2;--est-fg-faint: #6b7484;--est-border: #2a2f3a;--est-border-strong: #39404d;--est-accent: #7c74ff;--est-accent-fg: #ffffff;--est-accent-weak: color-mix(in srgb, var(--est-accent) 22%, transparent);--est-danger: #f87171;--est-warning: #fbbf24;--est-success: #34d399;--est-shadow-sticky: 0 1px 0 var(--est-border), 0 6px 16px -10px rgba(0, 0, 0, .7);--est-shadow-pop: 0 10px 28px -8px rgba(0, 0, 0, .6), 0 2px 6px -2px rgba(0, 0, 0, .5);--est-ring: 0 0 0 3px color-mix(in srgb, var(--est-accent) 55%, transparent)}.est2.est2--light{--est-bg: #ffffff;--est-bg-subtle: #f7f8fa;--est-bg-raised: #ffffff;--est-bg-hover: #f2f4f7;--est-bg-selected: color-mix(in srgb, var(--est-accent) 12%, transparent);--est-fg: #1a1d24;--est-fg-muted: #626b7a;--est-fg-faint: #9aa3b2;--est-border: #e6e9ef;--est-border-strong: #d3d8e0;--est-accent: #4f46e5;--est-accent-fg: #ffffff;--est-accent-weak: color-mix(in srgb, var(--est-accent) 14%, transparent);--est-danger: #dc2626;--est-warning: #d97706;--est-success: #059669;--est-shadow-sticky: 0 1px 0 var(--est-border), 0 4px 12px -8px rgba(16, 24, 40, .24);--est-shadow-pop: 0 8px 24px -6px rgba(16, 24, 40, .18), 0 2px 6px -2px rgba(16, 24, 40, .12);--est-ring: 0 0 0 3px color-mix(in srgb, var(--est-accent) 40%, transparent)}.est2.est2--dense{--est-cell-py: 3px;--est-cell-px: 9px;--est-row-h: 27px;--est-fs: 12.5px}.est2 *,.est2 *:before,.est2 *:after{box-sizing:border-box}.est2 .est2-scroll{position:relative;width:100%;overflow:auto;border:1px solid var(--est-border);border-radius:var(--est-radius);background:var(--est-bg);-webkit-overflow-scrolling:touch}.est2 table.est2-table{width:100%;border-collapse:separate;border-spacing:0;background:var(--est-bg)}.est2 thead th{position:sticky;top:0;z-index:3;background:var(--est-bg-subtle);color:var(--est-fg);font-weight:var(--est-fw-head);font-size:var(--est-fs);text-align:left;white-space:nowrap;padding:var(--est-cell-py) var(--est-cell-px);border-bottom:1px solid var(--est-border);box-shadow:var(--est-shadow-sticky);-webkit-user-select:none;user-select:none}.est2 thead tr.est2-hgroup-row th{font-size:var(--est-fs-sm);font-weight:700;color:var(--est-fg-muted);text-align:center;white-space:nowrap;padding:var(--est-cell-py) var(--est-cell-px);background:var(--est-bg-subtle);border-bottom:1px solid var(--est-border)}.est2 thead tr.est2-hgroup-row th.est2-hgroup{color:var(--est-fg);border-left:1px solid var(--est-border);border-right:1px solid var(--est-border)}.est2 thead tr.est2-hgroup-row th:not(.est2-hgroup){background:var(--est-bg-subtle);border-bottom-color:transparent}.est2 thead tr:last-child th.est2-col-groupstart,.est2 tbody td.est2-col-groupstart{border-left:1px solid var(--est-border)}.est2 tbody td{padding:var(--est-cell-py) var(--est-cell-px);border-bottom:1px solid var(--est-border);color:var(--est-fg);vertical-align:middle;background:transparent}.est2 tbody tr:nth-child(2n) td{background:var(--est-bg-zebra)}.est2 tbody tr:nth-child(2n) td.est2-pinned{background:var(--est-bg-zebra)}.est2 th.est2-pinned,.est2 td.est2-pinned{position:sticky;background:var(--est-bg);box-shadow:1px 0 0 var(--est-border)}.est2 thead th.est2-pinned{position:sticky}.est2 .est2-resize-handle{position:absolute;top:0;right:-3px;width:10px;height:100%;cursor:col-resize;z-index:2;touch-action:none;-webkit-user-select:none;user-select:none}.est2 .est2-resize-handle:after{content:\"\";position:absolute;top:28%;bottom:28%;right:3px;width:2px;border-radius:2px;background:color-mix(in srgb,var(--est-fg) 30%,transparent);transition:background var(--est-transition),top var(--est-transition),bottom var(--est-transition)}.est2 .est2-resize-handle:hover:after{background:var(--est-accent);top:15%;bottom:15%}.est2 thead th.est2-pinned .est2-resize-handle:active:after{background:var(--est-accent);top:6%;bottom:6%}.est2 td.est2-pinned{overflow:hidden;text-overflow:ellipsis}.est2 .est2-selcol{width:44px;min-width:44px;max-width:44px}.est2 td.est2-pinned{z-index:3}.est2 thead th.est2-pinned,.est2 thead tr.est2-hgroup-row th.est2-pinned{z-index:6;background:var(--est-bg-subtle)}.est2 tbody tr:hover td.est2-pinned{background:color-mix(in srgb,var(--est-fg) 5%,var(--est-bg))}.est2 tbody tr.est2-row--selected td.est2-pinned{background:color-mix(in srgb,var(--est-accent) 12%,var(--est-bg))}.est2 tbody tr.est2-row--group td.est2-pinned{background:var(--est-bg-subtle)}.est2 .est2-pinbtn{display:inline-flex;align-items:center;justify-content:center;margin-left:4px;padding:2px;border:none;background:transparent;color:var(--est-fg-faint);border-radius:var(--est-radius-sm);cursor:pointer;opacity:0;transition:opacity var(--est-transition),color var(--est-transition),background var(--est-transition)}.est2 thead th:hover .est2-pinbtn{opacity:.7}.est2 .est2-pinbtn:hover{background:var(--est-bg-hover);color:var(--est-fg);opacity:1}.est2 .est2-pinbtn--on{opacity:1;color:var(--est-accent);transform:rotate(0)}.est2 thead th:hover .est2-pinbtn--on{opacity:1}.est2 .est2-collist__pin.est2-collist__pin--on{color:var(--est-accent);border-color:var(--est-accent)}.est2 tbody tr{height:var(--est-row-h);transition:background var(--est-transition)}.est2 tbody tr:last-child td{border-bottom:none}.est2 tbody tr:hover td{background:var(--est-bg-hover)}.est2 tbody tr.est2-row--selected td{background:var(--est-bg-selected)}.est2 tbody tr.est2-row--clickable{cursor:pointer;-webkit-user-select:none;user-select:none}.est2 tbody tr.est2-row--removed td{text-decoration:line-through;color:var(--est-fg-faint)}.est2 .est2-table--range tbody td[data-r]{cursor:cell}.est2 .est2-table--dragging,.est2 .est2-table--dragging tbody td{-webkit-user-select:none;user-select:none}.est2 tbody td.est2-cell-sel{background:color-mix(in srgb,var(--est-accent) 14%,transparent)}.est2 tbody td.est2-cell-sel-t{box-shadow:inset 0 2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-b{box-shadow:inset 0 -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-l{box-shadow:inset 2px 0 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-r{box-shadow:inset -2px 0 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-l{box-shadow:inset 2px 2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-r{box-shadow:inset -2px 2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-b.est2-cell-sel-l{box-shadow:inset 2px -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-b.est2-cell-sel-r{box-shadow:inset -2px -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-b{box-shadow:inset 0 2px 0 0 var(--est-accent),inset 0 -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-l.est2-cell-sel-r{box-shadow:inset 2px 0 0 0 var(--est-accent),inset -2px 0 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-b.est2-cell-sel-l{box-shadow:inset 2px 2px 0 0 var(--est-accent),inset 0 -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-b.est2-cell-sel-r{box-shadow:inset -2px 2px 0 0 var(--est-accent),inset 0 -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-l.est2-cell-sel-r{box-shadow:inset 2px 2px 0 0 var(--est-accent),inset -2px 0 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-b.est2-cell-sel-l.est2-cell-sel-r{box-shadow:inset 2px -2px 0 0 var(--est-accent),inset -2px 0 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-b.est2-cell-sel-l.est2-cell-sel-r{box-shadow:inset 2px 2px 0 0 var(--est-accent),inset -2px -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-editing{padding:2px 6px}.est2 .est2-editor-input{width:100%;box-sizing:border-box;height:calc(var(--est-row-h) - 8px);padding:2px 6px;font:inherit;color:var(--est-fg);background:var(--est-bg);border:1.5px solid var(--est-accent);border-radius:var(--est-radius-sm);outline:none}.est2 .est2-editor-input:focus-visible{box-shadow:var(--est-ring)}.est2 input.est2-editor-input[type=checkbox]{width:16px;height:16px}.est2 .est2-autoupdate{display:flex;align-items:center;justify-content:flex-end;gap:12px;padding:4px 2px 8px;font-size:var(--est-fs-sm);color:var(--est-fg-muted)}.est2 .est2-autoupdate__label{display:inline-flex;align-items:center;gap:6px}.est2 .est2-autoupdate__secs{width:44px;text-align:center;padding:3px 4px;font:inherit;color:var(--est-fg);background:var(--est-bg);border:1px solid var(--est-border);border-radius:var(--est-radius-sm);outline:none}.est2 .est2-autoupdate__secs:focus-visible{box-shadow:var(--est-ring);border-color:var(--est-accent)}.est2 .est2-switch{position:relative;display:inline-flex;width:38px;height:20px;cursor:pointer}.est2 .est2-switch input{position:absolute;opacity:0;width:0;height:0}.est2 .est2-switch__slider{flex:1;border-radius:var(--est-radius-pill);background:var(--est-border-strong);transition:background var(--est-transition)}.est2 .est2-switch__slider:before{content:\"\";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;box-shadow:0 1px 2px #00000040;transition:transform var(--est-transition)}.est2 .est2-switch input:checked+.est2-switch__slider{background:var(--est-accent)}.est2 .est2-switch input:checked+.est2-switch__slider:before{transform:translate(18px)}.est2 .est2-switch input:focus-visible+.est2-switch__slider{box-shadow:var(--est-ring)}.est2 .est2-toast{position:absolute;left:50%;bottom:14px;transform:translate(-50%);z-index:20;display:inline-flex;align-items:center;gap:8px;max-width:calc(100% - 24px);padding:8px 14px;font-size:13px;font-weight:500;color:#fff;background:#b91c1c;border-radius:var(--est-radius);box-shadow:0 6px 20px #00000040;animation:est2-toast-in .16s ease-out}.est2 .est2-toast svg{flex:0 0 auto}@keyframes est2-toast-in{0%{opacity:0;transform:translate(-50%,8px)}to{opacity:1;transform:translate(-50%)}}@media(prefers-reduced-motion:reduce){.est2 .est2-toast{animation:none}}.est2 tbody tr.est2-row--group{cursor:pointer;-webkit-user-select:none;user-select:none}.est2 tbody tr.est2-row--group td{background:var(--est-bg-subtle);font-weight:600;color:var(--est-fg);border-bottom:1px solid var(--est-border)}.est2 tbody tr.est2-row--group:hover td{background:var(--est-bg-hover)}.est2 .est2-td--group-key{color:var(--est-fg)}.est2 .est2-group-key{display:inline-flex;align-items:center;gap:6px}.est2 .est2-group-chevron{display:inline-flex;color:var(--est-fg-muted);transition:transform var(--est-transition)}.est2 .est2-group-chevron--open{transform:rotate(90deg)}.est2 .est2-hier-lead{display:inline-flex;align-items:center;vertical-align:middle;margin-right:4px}.est2 .est2-hier-toggle{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;flex:none}.est2 .est2-group-chevron.est2-hier-toggle{cursor:pointer;border-radius:var(--est-radius-sm);transition:transform var(--est-transition),background var(--est-transition),color var(--est-transition)}.est2 .est2-group-chevron.est2-hier-toggle:hover{background:var(--est-bg-hover);color:var(--est-fg)}.est2 th.est2-th--orderable{cursor:pointer;transition:color var(--est-transition)}.est2 th.est2-th--orderable:hover{color:var(--est-fg)}.est2 .est2-th__inner{display:inline-flex;align-items:center;gap:6px}.est2 .est2-sort{display:inline-flex;width:14px;height:14px;opacity:.35;transition:opacity var(--est-transition),transform var(--est-transition)}.est2 .est2-sort--active{opacity:1;color:var(--est-accent)}.est2 .est2-sort--desc{transform:rotate(180deg)}.est2 .est2-sort__badge{font-size:9px;font-weight:700;color:var(--est-accent);margin-left:2px}.est2 .est2-check{appearance:none;width:16px;height:16px;border:1.5px solid var(--est-border-strong);border-radius:var(--est-radius-sm);background:var(--est-bg);cursor:pointer;position:relative;transition:border-color var(--est-transition),background var(--est-transition);vertical-align:middle;flex:none}.est2 .est2-check:hover{border-color:var(--est-accent)}.est2 .est2-check:checked{background:var(--est-accent);border-color:var(--est-accent)}.est2 .est2-check:checked:after{content:\"\";position:absolute;left:4.5px;top:1.5px;width:4px;height:8px;border:solid var(--est-accent-fg);border-width:0 2px 2px 0;transform:rotate(45deg)}.est2 .est2-check:focus-visible{outline:none;box-shadow:var(--est-ring)}.est2 .est2-check:indeterminate{background:var(--est-accent);border-color:var(--est-accent)}.est2 .est2-check:indeterminate:after{content:\"\";position:absolute;left:3px;top:6px;width:8px;height:2px;background:var(--est-accent-fg);transform:none;border:none}.est2 th.est2-col-min,.est2 td.est2-col-min{width:1%;white-space:nowrap}.est2 .est2-wrap{position:relative}.est2 .est2-selectbar{position:absolute;top:0;left:0;right:0;z-index:15;display:flex;align-items:center;gap:12px;padding:9px 14px;font-size:var(--est-fs-sm);color:var(--est-fg);background:var(--est-bg-subtle);border:none;border-radius:var(--est-radius) var(--est-radius) 0 0;box-shadow:inset 3px 0 0 var(--est-accent)}.est2 .est2-selectbar strong{color:var(--est-fg);font-weight:700}.est2 .est2-selectbar__spacer{flex:1 1 auto}.est2 .est2-link{color:var(--est-accent);cursor:pointer;font-weight:600}.est2 .est2-link:hover{text-decoration:underline}.est2 .est2-rowaction{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:var(--est-radius-sm);border:none;background:transparent;color:var(--est-fg-faint);cursor:pointer;transition:background var(--est-transition),color var(--est-transition)}.est2 .est2-rowaction:hover{background:var(--est-bg-hover);color:var(--est-fg)}.est2 .est2-rowaction--danger:hover{color:var(--est-danger)}.est2 .est2-op-cell{text-align:center}.est2 .est2-op{display:inline-flex;align-items:center;justify-content:center;min-width:26px;height:26px;padding:0 6px;border-radius:var(--est-radius-sm);color:var(--est-accent);cursor:pointer;font-size:var(--est-fs-sm);transition:background var(--est-transition)}.est2 .est2-op:hover{background:var(--est-accent-weak)}.est2 .est2-loading{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:10px;background:color-mix(in srgb,var(--est-bg) 70%,transparent);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);z-index:5;color:var(--est-fg-muted);font-size:var(--est-fs-sm)}.est2 .est2-spinner{width:16px;height:16px;border:2px solid var(--est-border-strong);border-top-color:var(--est-accent);border-radius:50%;animation:est2-spin .7s linear infinite}@keyframes est2-spin{to{transform:rotate(360deg)}}.est2 .est2-nowrap{white-space:nowrap}.est2 .est2-cornermenu{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:var(--est-radius-sm);cursor:pointer;color:var(--est-fg-muted);transition:background var(--est-transition)}.est2 .est2-cornermenu:hover{background:var(--est-bg-hover);color:var(--est-fg)}.est2 .est2-chrome-th{position:relative}.est2 .est2-chrome{display:inline-flex;align-items:center;gap:2px}.est2 .est2-chrome-wrap{position:relative;display:inline-flex}.est2 .est2-chrome-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:none;background:transparent;border-radius:var(--est-radius-sm);color:var(--est-fg-muted);cursor:pointer;font-size:16px;line-height:1;transition:background var(--est-transition),color var(--est-transition)}.est2 .est2-chrome-btn:hover{background:var(--est-bg-hover);color:var(--est-fg)}.est2 .est2-menu{position:absolute;top:calc(100% + 4px);right:0;z-index:30;min-width:180px;padding:4px;background:var(--est-bg);border:1px solid var(--est-border);border-radius:var(--est-radius);box-shadow:0 8px 24px #0000002e;display:flex;flex-direction:column}.est2 .est2-menu-item{display:block;width:100%;padding:8px 10px;border:none;background:transparent;text-align:left;font:inherit;color:var(--est-fg);border-radius:var(--est-radius-sm);cursor:pointer}.est2 .est2-menu-item:hover{background:var(--est-bg-hover)}.est2 .est2-dialog-backdrop{position:absolute;inset:0;z-index:40;display:flex;align-items:center;justify-content:center;padding:16px;background:color-mix(in srgb,#000 32%,transparent)}.est2 .est2-dialog{width:520px;max-width:calc(100vw - 32px);max-height:100%;display:flex;flex-direction:column;background:var(--est-bg);color:var(--est-fg);border:1px solid var(--est-border);border-radius:var(--est-radius);box-shadow:0 16px 48px #0000004d;overflow:hidden}.est2 .est2-dialog__head{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;font-weight:700;border-bottom:1px solid var(--est-border)}.est2 .est2-dialog__close{border:none;background:transparent;cursor:pointer;color:var(--est-fg-muted);font-size:15px;line-height:1;padding:4px;border-radius:var(--est-radius-sm)}.est2 .est2-dialog__close:hover{background:var(--est-bg-hover);color:var(--est-fg)}.est2 .est2-dialog__tools{padding:8px 14px;font-size:var(--est-fs-sm);border-bottom:1px solid var(--est-border)}.est2 .est2-dialog__tools .est2-link{border:none;background:none;padding:0;font:inherit;font-weight:600}.est2 .est2-dialog__sep{margin:0 6px;color:var(--est-fg-faint)}.est2 .est2-collist{list-style:none;margin:0;padding:6px;overflow-y:auto}.est2 .est2-collist__row{display:flex;align-items:center;gap:10px;padding:6px 8px;border-radius:var(--est-radius-sm)}.est2 .est2-collist__row:hover{background:var(--est-bg-hover)}.est2 .est2-collist__row--drag{cursor:grab}.est2 .est2-collist__row--drag:active{cursor:grabbing}.est2 .est2-collist__row--dragging{opacity:.45;background:var(--est-bg-hover)}.est2 .est2-collist__row--drop-above{box-shadow:inset 0 2px 0 0 var(--est-accent)}.est2 .est2-collist__row--drop-below{box-shadow:inset 0 -2px 0 0 var(--est-accent)}.est2 .est2-collist__grip{display:inline-flex;align-items:center;justify-content:center;color:var(--est-fg-faint);cursor:grab;flex:0 0 auto}.est2 .est2-collist__grip:active{cursor:grabbing}.est2 .est2-collist__row--drag:hover .est2-collist__grip{color:var(--est-fg-muted)}.est2 .est2-collist__vis{display:inline-flex}.est2 .est2-collist__label{flex:1 1 auto}.est2 .est2-collist__ord{display:inline-flex;gap:4px}.est2 .est2-iconbtn{width:26px;height:26px;border:1px solid var(--est-border);background:var(--est-bg);border-radius:var(--est-radius-sm);color:var(--est-fg-muted);cursor:pointer}.est2 .est2-iconbtn:hover:not(:disabled){background:var(--est-bg-hover);color:var(--est-fg)}.est2 .est2-iconbtn:disabled{opacity:.4;cursor:default}.est2 .est2-dialog__foot{display:flex;align-items:center;gap:8px;padding:12px 14px;border-top:1px solid var(--est-border)}.est2 .est2-dialog__spacer{flex:1 1 auto}.est2 .est2-btn{padding:7px 14px;border-radius:var(--est-radius-sm);font:inherit;font-weight:600;cursor:pointer;border:1px solid transparent}.est2 .est2-btn--ghost{background:transparent;color:var(--est-fg);border-color:var(--est-border)}.est2 .est2-btn--ghost:hover{background:var(--est-bg-hover)}.est2 .est2-btn--primary{background:var(--est-accent);color:var(--est-accent-fg)}.est2 .est2-btn--primary:hover{filter:brightness(1.05)}.est2 .est2-empty{padding:48px 16px;text-align:center;color:var(--est-fg-faint);font-size:var(--est-fs-sm)}.ngx-contextmenu{--ctx-bg: #ffffff;--ctx-fg: #1a1d24;--ctx-muted: #626b7a;--ctx-border: #e6e9ef;--ctx-hover: #f2f4f7;--ctx-accent: #4f46e5;--ctx-shadow: 0 10px 28px -8px rgba(16, 24, 40, .22), 0 2px 8px -3px rgba(16, 24, 40, .14);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.ngx-contextmenu .dropdown-menu{display:block;min-width:200px;margin:0;padding:6px;list-style:none;background:var(--ctx-bg);border:1px solid var(--ctx-border);border-radius:12px;box-shadow:var(--ctx-shadow);animation:est2-ctx-in .12s cubic-bezier(.16,1,.3,1)}.ngx-contextmenu li{list-style:none;margin:0}.ngx-contextmenu li>a{display:flex;align-items:center;gap:8px;padding:8px 12px;border-radius:7px;color:var(--ctx-fg);font-size:13.5px;line-height:1.2;text-decoration:none;cursor:pointer;white-space:nowrap;transition:background .12s ease,color .12s ease}.ngx-contextmenu li>a:hover,.ngx-contextmenu li>a:focus{background:var(--ctx-hover);color:var(--ctx-fg);text-decoration:none;outline:none}.ngx-contextmenu li.divider,.ngx-contextmenu li[role=separator]{height:1px;margin:6px 8px;padding:0;background:var(--ctx-border)}.ngx-contextmenu li.disabled>a,.ngx-contextmenu li[aria-disabled=true]>a{color:var(--ctx-muted);opacity:.55;pointer-events:none}@media(prefers-color-scheme:dark){.ngx-contextmenu{--ctx-bg: #1e222b;--ctx-fg: #e7eaf0;--ctx-muted: #9aa3b2;--ctx-border: #2a2f3a;--ctx-hover: #232733;--ctx-accent: #7c74ff;--ctx-shadow: 0 12px 30px -8px rgba(0, 0, 0, .6), 0 2px 8px -3px rgba(0, 0, 0, .5)}}@keyframes est2-ctx-in{0%{opacity:0;transform:translateY(-4px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}@media(prefers-reduced-motion:reduce){.ngx-contextmenu .dropdown-menu{animation:none}}\n"], dependencies: [{ kind: "directive", type: i8.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.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: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i9.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i12.ContextMenuAttachDirective, selector: "[contextMenu]", inputs: ["contextMenuSubject", "contextMenu"] }, { kind: "component", type: i12.ContextMenuComponent, selector: "context-menu", inputs: ["menuClass", "autoFocus", "useBootstrap4", "disabled"], outputs: ["close", "open"] }, { kind: "directive", type: i12.ContextMenuItemDirective, selector: "[contextMenuItem]", inputs: ["subMenu", "divider", "enabled", "passive", "visible"], outputs: ["execute"] }, { kind: "component", type: EsTable2PagerComponent, selector: "es-table2-pager", inputs: ["page", "pages", "total", "itemsPerPage", "countLabel", "showCount", "showButtons", "showPagingOptions", "allowAll"], outputs: ["pageChange", "itemsPerPageChange"] }, { kind: "pipe", type: Est2FormatPipe, name: "est2_format" }, { kind: "pipe", type: Est2LookupPipe, name: "est2_lookup" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
8698
8936
|
}
|
|
8699
8937
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: EsTable2Component, decorators: [{
|
|
8700
8938
|
type: Component,
|
|
8701
|
-
args: [{ selector: 'es-table2', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "@if (view()) {\n <div class=\"est2-wrap {{ ContainerClass() }}\"\n [style.height]=\"Height()\"\n [style.background-color]=\"EmptySpaceBackgroundColor() || null\">\n\n <!-- Auto-aggiornamento: toggle + intervallo (in alto a destra) -->\n @if (AutoUpdate()) {\n <div class=\"est2-autoupdate\">\n <label class=\"est2-autoupdate__label\">\n Aggiorna ogni\n <input type=\"text\" maxlength=\"3\" class=\"est2-autoupdate__secs\"\n [ngModel]=\"seconds()\" (ngModelChange)=\"seconds.set($event)\"\n [ngModelOptions]=\"{ standalone: true }\"\n (change)=\"autoUpdateChanged()\" />\n secondi\n </label>\n <label class=\"est2-switch\" title=\"Attiva/disattiva auto-aggiornamento\">\n <input type=\"checkbox\" [ngModel]=\"autoUpdate()\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"autoUpdate.set($event); autoUpdateChanged()\" />\n <span class=\"est2-switch__slider\"></span>\n </label>\n </div>\n }\n\n <!-- Pager superiore -->\n @if (PagingStyle() === 'both' || PagingStyle() === 'top') {\n <ng-container *ngTemplateOutlet=\"pager\"></ng-container>\n }\n\n <!-- Barra \"seleziona tutto\" (visibile solo con selezione multipla attiva e righe presenti) -->\n @if (Selection() && !SingleSelection() && hasSelection) {\n <div class=\"est2-selectbar\" [style.height.px]=\"selbarHeight() ? selbarHeight() + 1 : null\">\n @if (allSelected) {\n <span>Tutti i <strong>{{ selectedCount }}</strong> elementi sono selezionati</span>\n } @else {\n <span><strong>{{ selectedCount }}</strong> {{ selectedCount === 1 ? 'elemento selezionato' : 'elementi selezionati' }}</span>\n @if (canSelectEverything) {\n <span class=\"est2-link\" (click)=\"selectEverything()\">Seleziona tutti i {{ totalCount() }} elementi</span>\n }\n }\n <span class=\"est2-selectbar__spacer\"></span>\n <span class=\"est2-link\" (click)=\"clearSelection()\">Azzera selezione</span>\n </div>\n }\n\n <div class=\"est2-scroll\" [style.max-height.px]=\"MaxHeight()\">\n <table class=\"est2-table {{ TableClass() }}\"\n [class.est2-table--range]=\"rangeActive()\"\n [class.est2-table--dragging]=\"rangeDragging()\"\n (mousedown)=\"onGridMouseDown($event)\"\n (mouseover)=\"onGridMouseOver($event)\">\n\n <!-- ================= HEADER ================= -->\n @if (!HeaderHidden()) {\n <thead #theadRef>\n <!-- Righe di header-group multi-livello (dall'alto verso il basso) -->\n @if (hasHeaderGroups()) {\n @for (grow of headerGroupRows(); track $index) {\n <tr class=\"est2-hgroup-row\">\n @if (Selection()) { <th class=\"est2-col-min est2-selcol\" [class.est2-pinned]=\"hasPinned()\" [style.left.px]=\"hasPinned() ? 0 : null\"></th> }\n @for (op of DynamicOperations(); track op.id) { <th class=\"est2-col-min\"></th> }\n @for (cell of grow; track cell.id; let gi = $index) {\n <th [attr.colspan]=\"cell.span\"\n [attr.data-groupid]=\"cell.isGroup ? cell.id : null\"\n [class.est2-hgroup]=\"cell.isGroup\"\n [class.est2-pinned]=\"gi < pinnedCount()\"\n [style.left.px]=\"gi < pinnedCount() ? pinnedLeftPx(gi) : null\"\n class=\"est2-hgroup-cell\">\n @if (cell.isGroup) {\n @if (cell.template) {\n <ng-container *ngTemplateOutlet=\"cell.template\"></ng-container>\n } @else {\n {{ cell.label }}\n }\n }\n </th>\n }\n @if (Removal()) { <th class=\"est2-col-min\"></th> }\n @if (hasChrome()) { <th class=\"est2-col-min\"></th> }\n </tr>\n }\n }\n <tr>\n <!-- Colonna di selezione -->\n @if (Selection()) {\n <th class=\"est2-col-min est2-selcol\"\n [class.est2-pinned]=\"hasPinned()\"\n [style.left.px]=\"hasPinned() ? 0 : null\">\n @if (!SingleSelection()) {\n <input type=\"checkbox\" class=\"est2-check\"\n [checked]=\"globalCheck()\"\n [indeterminate]=\"selectionIndeterminate\"\n [disabled]=\"SelectionDisabled()\"\n (change)=\"toggleAll()\"\n aria-label=\"Seleziona tutto\" />\n }\n </th>\n }\n\n <!-- Header da colonne (direttive / dinamica / report) -->\n @if (usesColumns()) {\n <!-- intestazioni vuote per le operazioni dinamiche -->\n @for (op of DynamicOperations(); track op.id) {\n <th class=\"est2-col-min\"></th>\n }\n @for (col of visibleColumns(); track trackCol($index, col); let ci = $index) {\n <th [attr.data-colid]=\"col.id\"\n [class]=\"col.cssClass\"\n [class.est2-th--orderable]=\"OrderByColumn() && col.orderable\"\n [class.est2-col-min]=\"col.header?.thShrink\"\n [class.est2-col-groupstart]=\"columnGroupBoundaries().has(ci)\"\n [class.est2-pinned]=\"col.pinned\"\n [style.left.px]=\"col.pinned ? pinnedLeftPx(ci) : null\"\n [style.min-width.px]=\"col.pinned && col.pinnedWidth ? col.pinnedWidth : null\"\n [style.max-width.px]=\"col.pinned && col.pinnedWidth ? col.pinnedWidth : null\"\n [style.text-align]=\"col.alignment\"\n [style.background-color]=\"col.headerBg || null\"\n (click)=\"toggleSort(col)\">\n <span class=\"est2-th__inner\">\n @if (col.header?.Template) {\n <ng-container *ngTemplateOutlet=\"col.header!.Template!; context: col.multiProp != null ? { $implicit: col.headerText } : null\"></ng-container>\n } @else {\n {{ col.headerText }}\n }\n @if (OrderByColumn() && col.orderable && orderOf(col.id)) {\n <span class=\"est2-sort est2-sort--active\"\n [class.est2-sort--desc]=\"orderOf(col.id) === 'DESC'\">\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 15l6-6 6 6\"/></svg>\n </span>\n @if (orderIndex(col.id) > 0 && MultipleOrderingDirectives()) {\n <span class=\"est2-sort__badge\">{{ orderIndex(col.id) }}</span>\n }\n }\n <!-- Indicatore/toggle di pin (visibile se pinnata o all'hover) -->\n @if (ColumnsPinnable() && col.multiProp == null) {\n <button type=\"button\" class=\"est2-pinbtn\"\n [class.est2-pinbtn--on]=\"col.pinned\"\n [title]=\"col.pinned ? 'Sblocca colonna' : 'Blocca colonna a sinistra'\"\n (click)=\"togglePin(col, $event)\">\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 17v5\"/><path d=\"M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z\"/></svg>\n </button>\n }\n </span>\n </th>\n }\n }\n <!-- Header da template semplice -->\n @else if (headerRef) {\n <ng-container *ngTemplateOutlet=\"headerRef\"></ng-container>\n }\n\n <!-- Colonna rimozione -->\n @if (Removal()) { <th class=\"est2-col-min\"></th> }\n <!-- Colonna chrome (export / gestione colonne / menu) -->\n @if (hasChrome()) {\n <th class=\"est2-col-min est2-chrome-th\">\n <div class=\"est2-chrome\">\n @if (Export()) {\n <div class=\"est2-chrome-wrap\">\n <button type=\"button\" class=\"est2-chrome-btn\" title=\"Esporta\" (click)=\"$event.stopPropagation(); toggleExportMenu()\">\n <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><path d=\"M7 10l5 5 5-5\"/><path d=\"M12 15V3\"/></svg>\n </button>\n @if (exportMenuOpen()) {\n <div class=\"est2-menu\" (click)=\"$event.stopPropagation()\">\n @if (CSVExport()) { <button type=\"button\" class=\"est2-menu-item\" (click)=\"export('CSV')\">Esporta CSV</button> }\n @if (XLSXExport()) { <button type=\"button\" class=\"est2-menu-item\" (click)=\"export('XLSX')\">Esporta Excel (XLSX)</button> }\n @if (!CSVExport() && !XLSXExport()) { <button type=\"button\" class=\"est2-menu-item\" (click)=\"export('CSV')\">Esporta CSV</button> }\n </div>\n }\n </div>\n }\n @if (HiddenColumns() || ColumnsOrdering()) {\n <button type=\"button\" class=\"est2-chrome-btn\" title=\"Colonne\" (click)=\"$event.stopPropagation(); openColumnsDialog()\">\n <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"1\"/><path d=\"M9 3v18\"/><path d=\"M15 3v18\"/></svg>\n </button>\n }\n @if (CornerMenuOptions().length > 0) {\n <div class=\"est2-chrome-wrap\">\n <button type=\"button\" class=\"est2-chrome-btn\" title=\"Opzioni\" (click)=\"$event.stopPropagation(); toggleCornerMenu()\">\u22EE</button>\n @if (cornerMenuOpen()) {\n <div class=\"est2-menu\" (click)=\"$event.stopPropagation()\">\n @for (opt of CornerMenuOptions(); track opt.id) {\n <button type=\"button\" class=\"est2-menu-item\" (click)=\"cornerAction(opt.id); cornerMenuOpen.set(false)\">{{ opt.description }}</button>\n }\n </div>\n }\n </div>\n }\n </div>\n </th>\n }\n </tr>\n </thead>\n }\n\n <!-- ================= BODY ================= -->\n @if (!BodyHidden()) {\n <tbody>\n @for (item of boundSource(); track trackRow($index, item); let ri = $index) {\n @if ((!grouped() && !Hierarchy()) || item._visible) {\n <tr [class]=\"rowClass(item)\"\n [class.est2-row--selected]=\"item._selected\"\n [class.est2-row--removed]=\"item.removed || item.deleted\"\n [class.est2-row--group]=\"item._group\"\n [class.est2-row--clickable]=\"Selection() || item._group\"\n [contextMenu]=\"ContextMenu || emptyMenu\"\n [contextMenuSubject]=\"item\"\n (click)=\"handleRowClick(item, $event)\">\n\n <!-- Cella di selezione -->\n @if (Selection()) {\n <td class=\"est2-col-min est2-selcol\"\n [class.est2-pinned]=\"hasPinned()\"\n [style.left.px]=\"hasPinned() ? 0 : null\">\n @if (item._group) {\n @if (!SingleSelection()) {\n <input type=\"checkbox\" class=\"est2-check\"\n [checked]=\"groupSelectionState(item) === 'all'\"\n [indeterminate]=\"groupSelectionState(item) === 'some'\"\n [disabled]=\"SelectionDisabled()\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleGroupSelection(item)\"\n aria-label=\"Seleziona gruppo\" />\n }\n } @else {\n <input type=\"checkbox\" class=\"est2-check\"\n [checked]=\"item._selected\"\n [indeterminate]=\"hierarchyIndeterminate(item)\"\n [disabled]=\"SelectionDisabled()\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleRow(item)\"\n aria-label=\"Seleziona riga\" />\n }\n </td>\n }\n\n <!-- Celle da colonne (direttive / dinamica / report) -->\n @if (usesColumns()) {\n <!-- Operazioni dinamiche (icone a sinistra) -->\n @for (op of DynamicOperations(); track op.id) {\n <td class=\"est2-col-min est2-op-cell\">\n @if (!item._group && operationVisible(op, item)) {\n <span class=\"est2-op\" [class]=\"op.iconClass || ''\" [title]=\"op.title\"\n (click)=\"$event.stopPropagation(); dynamicOperation(item, op.id)\">{{ op.text }}</span>\n }\n </td>\n }\n @for (col of visibleColumns(); track trackCol($index, col); let first = $first, ci = $index) {\n <td [class]=\"col.cssClass\"\n [style.text-align]=\"col.alignment\"\n [style.color]=\"cellColor(item, col, 'fore')\"\n [style.background-color]=\"cellColor(item, col, 'back')\"\n [class.est2-nowrap]=\"!col.wrap\"\n [class.est2-col-groupstart]=\"columnGroupBoundaries().has(ci)\"\n [class.est2-pinned]=\"col.pinned\"\n [style.left.px]=\"col.pinned ? pinnedLeftPx(ci) : null\"\n [style.min-width.px]=\"col.pinned && col.pinnedWidth ? col.pinnedWidth : null\"\n [style.max-width.px]=\"col.pinned && col.pinnedWidth ? col.pinnedWidth : null\"\n [class.est2-td--group-key]=\"item._group && item.column === col.id\"\n [attr.data-r]=\"rangeActive() && !item._group ? ri : null\"\n [attr.data-c]=\"rangeActive() && !item._group ? ci : null\"\n [class.est2-cell-sel]=\"rangeActive() && inRange(ri, ci)\"\n [class.est2-cell-sel-t]=\"rangeActive() && inRange(ri, ci) && ri === rangeRect()!.top\"\n [class.est2-cell-sel-b]=\"rangeActive() && inRange(ri, ci) && ri === rangeRect()!.bottom\"\n [class.est2-cell-sel-l]=\"rangeActive() && inRange(ri, ci) && ci === rangeRect()!.left\"\n [class.est2-cell-sel-r]=\"rangeActive() && inRange(ri, ci) && ci === rangeRect()!.right\"\n [class.est2-cell-editing]=\"isEditing(ri, ci)\"\n (dblclick)=\"onCellDblClick(item, col, ri, ci)\">\n @if (isEditing(ri, ci)) {\n @if (editorFor(col); as edTpl) {\n <ng-container *ngTemplateOutlet=\"edTpl; context: editorContext(item, col)\"></ng-container>\n } @else {\n <ng-container *ngTemplateOutlet=\"defaultEditor; context: { $implicit: item, col: col }\"></ng-container>\n }\n } @else if (item._group) {\n @if (item.column === col.id) {\n <span class=\"est2-group-key\" [style.padding-left.px]=\"groupIndent(item)\">\n <span class=\"est2-group-chevron\" [class.est2-group-chevron--open]=\"item._expanded\">\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 6l6 6-6 6\"/></svg>\n </span>\n {{ groupCellDisplay(item, col) }}\n </span>\n } @else {\n {{ groupCellDisplay(item, col) }}\n }\n } @else {\n <!-- Navigatore albero nella prima colonna -->\n @if (Hierarchy() && first) {\n <span class=\"est2-hier-lead\" [style.padding-left.px]=\"hierarchyIndent(item)\">\n @if (item.parent) {\n <span class=\"est2-group-chevron est2-hier-toggle\" [class.est2-group-chevron--open]=\"item._expanded\"\n (click)=\"$event.stopPropagation(); toggleHierarchyNode(item)\">\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 6l6 6-6 6\"/></svg>\n </span>\n } @else {\n <span class=\"est2-hier-toggle\"></span>\n }\n </span>\n }\n @if (itemCellHidden(col)) {\n <!-- colonna di gruppo nascosta sulla riga-oggetto -->\n } @else if (col.multiProp != null) {\n @if (col.cell?.Template) {\n <ng-container *ngTemplateOutlet=\"col.cell!.Template; context: { $implicit: multiCell(item, col) }\"></ng-container>\n } @else {\n {{ multiCell(item, col)?.value }}\n }\n } @else if (col.cell?.Template) {\n <ng-container *ngTemplateOutlet=\"col.cell!.Template; context: { $implicit: item }\"></ng-container>\n } @else if (col.routePath) {\n <a class=\"est2-link\" [routerLink]=\"routerLinkFor(item, col)\">{{ cellValue(item, col) }}</a>\n } @else if (col.propAccessor != null) {\n {{ reportCellDisplay(item, col) }}\n } @else if (col.type === 'enum') {\n {{ (cellValue(item, col) | est2_lookup : col.source).description }}\n } @else {\n {{ cellValue(item, col) | est2_format : col.type : col.format : locale }}\n }\n }\n </td>\n }\n }\n <!-- Celle da template semplice -->\n @else if (bodyRef) {\n <ng-container *ngTemplateOutlet=\"bodyRef; context: { $implicit: item }\"></ng-container>\n }\n\n <!-- Rimozione (non sulle righe-gruppo) -->\n @if (Removal()) {\n <td class=\"est2-col-min\">\n @if (!item._group && canRemove(item)) {\n @if (item.removed || item.deleted) {\n <button type=\"button\" class=\"est2-rowaction\" title=\"Ripristina\" (click)=\"abortRemoval(item)\">\u21BA</button>\n } @else {\n <button type=\"button\" class=\"est2-rowaction est2-rowaction--danger\" title=\"Rimuovi\" (click)=\"removeItem(item)\">\u2715</button>\n }\n }\n </td>\n }\n <!-- Chrome -->\n @if (hasChrome()) { <td class=\"est2-col-min\"></td> }\n </tr>\n }\n } @empty {\n <tr>\n <td class=\"est2-empty\" [attr.colspan]=\"totalColspan()\">Nessun elemento da visualizzare</td>\n </tr>\n }\n </tbody>\n }\n </table>\n\n <!-- Overlay di caricamento -->\n @if (researchInProgress() || (firstBind() && ShowLoadingOnBootstrap())) {\n <div class=\"est2-loading\">\n <span class=\"est2-spinner\"></span>\n <span>Caricamento\u2026</span>\n </div>\n }\n </div>\n\n <!-- Pager inferiore -->\n @if (PagingStyle() === 'both' || PagingStyle() === 'bottom') {\n <ng-container *ngTemplateOutlet=\"pager\"></ng-container>\n }\n\n <!-- Avviso transitorio (es. incolla con dimensioni incompatibili) -->\n @if (notice()) {\n <div class=\"est2-toast\" role=\"alert\">\n <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/><path d=\"M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/></svg>\n <span>{{ notice() }}</span>\n </div>\n }\n\n <!-- Dialog visibilit\u00E0 / ordine colonne -->\n @if (columnsDialogOpen()) {\n <div class=\"est2-dialog-backdrop\" (click)=\"closeColumnsDialog()\">\n <div class=\"est2-dialog\" (click)=\"$event.stopPropagation()\" role=\"dialog\" aria-modal=\"true\">\n <div class=\"est2-dialog__head\">\n <span>\n @if (HiddenColumns() && ColumnsOrdering()) { Visibilit\u00E0 e ordine colonne }\n @else if (HiddenColumns()) { Visibilit\u00E0 colonne }\n @else { Ordine colonne }\n </span>\n <button type=\"button\" class=\"est2-dialog__close\" (click)=\"closeColumnsDialog()\" aria-label=\"Chiudi\">\u2715</button>\n </div>\n\n @if (HiddenColumns()) {\n <div class=\"est2-dialog__tools\">\n <button type=\"button\" class=\"est2-link\" (click)=\"dialogSetAll(true)\">Mostra tutte</button>\n <span class=\"est2-dialog__sep\">\u00B7</span>\n <button type=\"button\" class=\"est2-link\" (click)=\"dialogSetAll(false)\">Nascondi tutte</button>\n </div>\n }\n\n <ul class=\"est2-collist\">\n @for (c of dialogCols(); track c.id; let i = $index) {\n <li class=\"est2-collist__row\">\n @if (HiddenColumns()) {\n <label class=\"est2-collist__vis\">\n <input type=\"checkbox\" class=\"est2-check\" [checked]=\"c.visible\" (change)=\"dialogToggle(i)\" />\n </label>\n }\n <span class=\"est2-collist__label\">{{ c.label }}</span>\n @if (ColumnsPinnable()) {\n <button type=\"button\" class=\"est2-iconbtn est2-collist__pin\" [class.est2-collist__pin--on]=\"c.pinned\"\n [disabled]=\"c.grouped\"\n [title]=\"c.grouped ? 'Le colonne di un gruppo non sono bloccabili' : (c.pinned ? 'Sblocca' : 'Blocca a sinistra')\"\n (click)=\"dialogTogglePin(i)\">\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 17v5\"/><path d=\"M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z\"/></svg>\n </button>\n }\n @if (ColumnsOrdering()) {\n <span class=\"est2-collist__ord\">\n <button type=\"button\" class=\"est2-iconbtn\" [disabled]=\"i === 0\" title=\"Su\" (click)=\"dialogMove(i, -1)\">\u2191</button>\n <button type=\"button\" class=\"est2-iconbtn\" [disabled]=\"i === dialogCols().length - 1\" title=\"Gi\u00F9\" (click)=\"dialogMove(i, 1)\">\u2193</button>\n </span>\n }\n </li>\n }\n </ul>\n\n <div class=\"est2-dialog__foot\">\n <button type=\"button\" class=\"est2-btn est2-btn--ghost\" (click)=\"resetColumnsDialog()\">Ripristina</button>\n <span class=\"est2-dialog__spacer\"></span>\n <button type=\"button\" class=\"est2-btn est2-btn--ghost\" (click)=\"closeColumnsDialog()\">Annulla</button>\n <button type=\"button\" class=\"est2-btn est2-btn--primary\" (click)=\"applyColumnsDialog()\">Applica</button>\n </div>\n </div>\n </div>\n }\n </div>\n}\n\n<!-- Template pager riutilizzabile sopra/sotto -->\n<ng-template #pager>\n @if (!HidePaging() && !grouped()) {\n <es-table2-pager\n [page]=\"currentPage()\"\n [pages]=\"totalPages()\"\n [total]=\"totalCount()\"\n [itemsPerPage]=\"viewMode() ? (view()?.itemsperpageoverride ?? 15) : ArraymodeItemsPerPage()\"\n [countLabel]=\"CountLabel()\"\n [showCount]=\"!HidePagingCount()\"\n [showButtons]=\"!HidePagingButtons()\"\n [showPagingOptions]=\"!HidePagingButtons()\"\n [allowAll]=\"AllSearch()\"\n (pageChange)=\"goToPage($event)\"\n (itemsPerPageChange)=\"changeItemsPerPage($event)\">\n </es-table2-pager>\n }\n</ng-template>\n\n<!-- Editor di cella di default (usato quando il consumer non fornisce un `*editor`) -->\n<ng-template #defaultEditor let-item let-col=\"col\">\n @switch (col.type) {\n @case ('enum') {\n <select class=\"est2-editor-input\"\n [value]=\"editDraft\"\n (change)=\"editDraft = $any($event.target).value\"\n (keydown.enter)=\"commitEdit(item, col)\"\n (keydown.escape)=\"cancelEdit()\"\n (blur)=\"commitEdit(item, col)\">\n @for (o of col.source || []; track o.id) {\n <option [value]=\"o.id\" [selected]=\"o.id == editDraft\">{{ o.description }}</option>\n }\n </select>\n }\n @case ('boolean') {\n <input type=\"checkbox\" class=\"est2-editor-input est2-check\"\n [checked]=\"editDraft === true || editDraft === 'true'\"\n (change)=\"editDraft = $any($event.target).checked; commitEdit(item, col)\"\n (keydown.escape)=\"cancelEdit()\" />\n }\n @default {\n <input class=\"est2-editor-input\"\n [type]=\"editorInputType(col.type)\"\n [value]=\"editDraft\"\n (input)=\"editDraft = $any($event.target).value\"\n (keydown.enter)=\"commitEdit(item, col)\"\n (keydown.escape)=\"cancelEdit()\"\n (blur)=\"commitEdit(item, col)\" />\n }\n }\n</ng-template>\n\n<!-- Menu di default (nessuna operazione) usato quando il consumer non passa [ContextMenu] -->\n<context-menu #emptyMenu>\n <ng-template contextMenuItem [passive]=\"true\"><em>Nessuna operazione disponibile\u2026</em></ng-template>\n</context-menu>\n", styles: [".est2{--est-bg: #ffffff;--est-bg-subtle: #f7f8fa;--est-bg-raised: #ffffff;--est-bg-hover: #f2f4f7;--est-bg-selected: color-mix(in srgb, var(--est-accent) 12%, transparent);--est-fg: #1a1d24;--est-fg-muted: #626b7a;--est-fg-faint: #9aa3b2;--est-border: #e6e9ef;--est-border-strong: #d3d8e0;--est-accent: #4f46e5;--est-accent-fg: #ffffff;--est-accent-weak: color-mix(in srgb, var(--est-accent) 14%, transparent);--est-danger: #dc2626;--est-warning: #d97706;--est-success: #059669;--est-shadow-sticky: 0 1px 0 var(--est-border), 0 4px 12px -8px rgba(16, 24, 40, .24);--est-shadow-pop: 0 8px 24px -6px rgba(16, 24, 40, .18), 0 2px 6px -2px rgba(16, 24, 40, .12);--est-ring: 0 0 0 3px color-mix(in srgb, var(--est-accent) 40%, transparent);--est-radius: 10px;--est-radius-sm: 6px;--est-radius-pill: 999px;--est-gap: 8px;--est-cell-py: 6px;--est-cell-px: 12px;--est-row-h: 33px;--est-font: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;--est-fs: 13px;--est-fs-sm: 12px;--est-fw-head: 700;--est-transition: .14s cubic-bezier(.4, 0, .2, 1);--est-bg-zebra: color-mix(in srgb, var(--est-fg) 3.5%, var(--est-bg));font-family:var(--est-font);font-size:var(--est-fs);color:var(--est-fg);position:relative;display:block}@media(prefers-color-scheme:dark){.est2{--est-bg: #14161c;--est-bg-subtle: #1a1d25;--est-bg-raised: #1e222b;--est-bg-hover: #232733;--est-bg-selected: color-mix(in srgb, var(--est-accent) 26%, transparent);--est-fg: #e7eaf0;--est-fg-muted: #9aa3b2;--est-fg-faint: #6b7484;--est-border: #2a2f3a;--est-border-strong: #39404d;--est-accent: #7c74ff;--est-accent-fg: #ffffff;--est-accent-weak: color-mix(in srgb, var(--est-accent) 22%, transparent);--est-danger: #f87171;--est-warning: #fbbf24;--est-success: #34d399;--est-shadow-sticky: 0 1px 0 var(--est-border), 0 6px 16px -10px rgba(0, 0, 0, .7);--est-shadow-pop: 0 10px 28px -8px rgba(0, 0, 0, .6), 0 2px 6px -2px rgba(0, 0, 0, .5);--est-ring: 0 0 0 3px color-mix(in srgb, var(--est-accent) 55%, transparent)}}.est2.est2--dark{--est-bg: #14161c;--est-bg-subtle: #1a1d25;--est-bg-raised: #1e222b;--est-bg-hover: #232733;--est-bg-selected: color-mix(in srgb, var(--est-accent) 26%, transparent);--est-fg: #e7eaf0;--est-fg-muted: #9aa3b2;--est-fg-faint: #6b7484;--est-border: #2a2f3a;--est-border-strong: #39404d;--est-accent: #7c74ff;--est-accent-fg: #ffffff;--est-accent-weak: color-mix(in srgb, var(--est-accent) 22%, transparent);--est-danger: #f87171;--est-warning: #fbbf24;--est-success: #34d399;--est-shadow-sticky: 0 1px 0 var(--est-border), 0 6px 16px -10px rgba(0, 0, 0, .7);--est-shadow-pop: 0 10px 28px -8px rgba(0, 0, 0, .6), 0 2px 6px -2px rgba(0, 0, 0, .5);--est-ring: 0 0 0 3px color-mix(in srgb, var(--est-accent) 55%, transparent)}.est2.est2--light{--est-bg: #ffffff;--est-bg-subtle: #f7f8fa;--est-bg-raised: #ffffff;--est-bg-hover: #f2f4f7;--est-bg-selected: color-mix(in srgb, var(--est-accent) 12%, transparent);--est-fg: #1a1d24;--est-fg-muted: #626b7a;--est-fg-faint: #9aa3b2;--est-border: #e6e9ef;--est-border-strong: #d3d8e0;--est-accent: #4f46e5;--est-accent-fg: #ffffff;--est-accent-weak: color-mix(in srgb, var(--est-accent) 14%, transparent);--est-danger: #dc2626;--est-warning: #d97706;--est-success: #059669;--est-shadow-sticky: 0 1px 0 var(--est-border), 0 4px 12px -8px rgba(16, 24, 40, .24);--est-shadow-pop: 0 8px 24px -6px rgba(16, 24, 40, .18), 0 2px 6px -2px rgba(16, 24, 40, .12);--est-ring: 0 0 0 3px color-mix(in srgb, var(--est-accent) 40%, transparent)}.est2.est2--dense{--est-cell-py: 3px;--est-cell-px: 9px;--est-row-h: 27px;--est-fs: 12.5px}.est2 *,.est2 *:before,.est2 *:after{box-sizing:border-box}.est2 .est2-scroll{position:relative;width:100%;overflow:auto;border:1px solid var(--est-border);border-radius:var(--est-radius);background:var(--est-bg);-webkit-overflow-scrolling:touch}.est2 table.est2-table{width:100%;border-collapse:separate;border-spacing:0;background:var(--est-bg)}.est2 thead th{position:sticky;top:0;z-index:3;background:var(--est-bg-subtle);color:var(--est-fg);font-weight:var(--est-fw-head);font-size:var(--est-fs);text-align:left;white-space:nowrap;padding:var(--est-cell-py) var(--est-cell-px);border-bottom:1px solid var(--est-border);box-shadow:var(--est-shadow-sticky);-webkit-user-select:none;user-select:none}.est2 thead tr.est2-hgroup-row th{font-size:var(--est-fs-sm);font-weight:700;color:var(--est-fg-muted);text-align:center;white-space:nowrap;padding:var(--est-cell-py) var(--est-cell-px);background:var(--est-bg-subtle);border-bottom:1px solid var(--est-border)}.est2 thead tr.est2-hgroup-row th.est2-hgroup{color:var(--est-fg);border-left:1px solid var(--est-border);border-right:1px solid var(--est-border)}.est2 thead tr.est2-hgroup-row th:not(.est2-hgroup){background:var(--est-bg-subtle);border-bottom-color:transparent}.est2 thead tr:last-child th.est2-col-groupstart,.est2 tbody td.est2-col-groupstart{border-left:1px solid var(--est-border)}.est2 tbody td{padding:var(--est-cell-py) var(--est-cell-px);border-bottom:1px solid var(--est-border);color:var(--est-fg);vertical-align:middle;background:transparent}.est2 tbody tr:nth-child(2n) td{background:var(--est-bg-zebra)}.est2 tbody tr:nth-child(2n) td.est2-pinned{background:var(--est-bg-zebra)}.est2 th.est2-pinned,.est2 td.est2-pinned{position:sticky;background:var(--est-bg);box-shadow:1px 0 0 var(--est-border)}.est2 .est2-selcol{width:44px;min-width:44px;max-width:44px}.est2 td.est2-pinned{z-index:3}.est2 thead th.est2-pinned,.est2 thead tr.est2-hgroup-row th.est2-pinned{z-index:6;background:var(--est-bg-subtle)}.est2 tbody tr:hover td.est2-pinned{background:color-mix(in srgb,var(--est-fg) 5%,var(--est-bg))}.est2 tbody tr.est2-row--selected td.est2-pinned{background:color-mix(in srgb,var(--est-accent) 12%,var(--est-bg))}.est2 tbody tr.est2-row--group td.est2-pinned{background:var(--est-bg-subtle)}.est2 .est2-pinbtn{display:inline-flex;align-items:center;justify-content:center;margin-left:4px;padding:2px;border:none;background:transparent;color:var(--est-fg-faint);border-radius:var(--est-radius-sm);cursor:pointer;opacity:0;transition:opacity var(--est-transition),color var(--est-transition),background var(--est-transition)}.est2 thead th:hover .est2-pinbtn{opacity:.7}.est2 .est2-pinbtn:hover{background:var(--est-bg-hover);color:var(--est-fg);opacity:1}.est2 .est2-pinbtn--on{opacity:1;color:var(--est-accent);transform:rotate(0)}.est2 thead th:hover .est2-pinbtn--on{opacity:1}.est2 .est2-collist__pin.est2-collist__pin--on{color:var(--est-accent);border-color:var(--est-accent)}.est2 tbody tr{height:var(--est-row-h);transition:background var(--est-transition)}.est2 tbody tr:last-child td{border-bottom:none}.est2 tbody tr:hover td{background:var(--est-bg-hover)}.est2 tbody tr.est2-row--selected td{background:var(--est-bg-selected)}.est2 tbody tr.est2-row--clickable{cursor:pointer;-webkit-user-select:none;user-select:none}.est2 tbody tr.est2-row--removed td{text-decoration:line-through;color:var(--est-fg-faint)}.est2 .est2-table--range tbody td[data-r]{cursor:cell}.est2 .est2-table--dragging,.est2 .est2-table--dragging tbody td{-webkit-user-select:none;user-select:none}.est2 tbody td.est2-cell-sel{background:color-mix(in srgb,var(--est-accent) 14%,transparent)}.est2 tbody td.est2-cell-sel-t{box-shadow:inset 0 2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-b{box-shadow:inset 0 -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-l{box-shadow:inset 2px 0 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-r{box-shadow:inset -2px 0 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-l{box-shadow:inset 2px 2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-r{box-shadow:inset -2px 2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-b.est2-cell-sel-l{box-shadow:inset 2px -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-b.est2-cell-sel-r{box-shadow:inset -2px -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-b{box-shadow:inset 0 2px 0 0 var(--est-accent),inset 0 -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-l.est2-cell-sel-r{box-shadow:inset 2px 0 0 0 var(--est-accent),inset -2px 0 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-b.est2-cell-sel-l{box-shadow:inset 2px 2px 0 0 var(--est-accent),inset 0 -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-b.est2-cell-sel-r{box-shadow:inset -2px 2px 0 0 var(--est-accent),inset 0 -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-l.est2-cell-sel-r{box-shadow:inset 2px 2px 0 0 var(--est-accent),inset -2px 0 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-b.est2-cell-sel-l.est2-cell-sel-r{box-shadow:inset 2px -2px 0 0 var(--est-accent),inset -2px 0 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-b.est2-cell-sel-l.est2-cell-sel-r{box-shadow:inset 2px 2px 0 0 var(--est-accent),inset -2px -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-editing{padding:2px 6px}.est2 .est2-editor-input{width:100%;box-sizing:border-box;height:calc(var(--est-row-h) - 8px);padding:2px 6px;font:inherit;color:var(--est-fg);background:var(--est-bg);border:1.5px solid var(--est-accent);border-radius:var(--est-radius-sm);outline:none}.est2 .est2-editor-input:focus-visible{box-shadow:var(--est-ring)}.est2 input.est2-editor-input[type=checkbox]{width:16px;height:16px}.est2 .est2-autoupdate{display:flex;align-items:center;justify-content:flex-end;gap:12px;padding:4px 2px 8px;font-size:var(--est-fs-sm);color:var(--est-fg-muted)}.est2 .est2-autoupdate__label{display:inline-flex;align-items:center;gap:6px}.est2 .est2-autoupdate__secs{width:44px;text-align:center;padding:3px 4px;font:inherit;color:var(--est-fg);background:var(--est-bg);border:1px solid var(--est-border);border-radius:var(--est-radius-sm);outline:none}.est2 .est2-autoupdate__secs:focus-visible{box-shadow:var(--est-ring);border-color:var(--est-accent)}.est2 .est2-switch{position:relative;display:inline-flex;width:38px;height:20px;cursor:pointer}.est2 .est2-switch input{position:absolute;opacity:0;width:0;height:0}.est2 .est2-switch__slider{flex:1;border-radius:var(--est-radius-pill);background:var(--est-border-strong);transition:background var(--est-transition)}.est2 .est2-switch__slider:before{content:\"\";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;box-shadow:0 1px 2px #00000040;transition:transform var(--est-transition)}.est2 .est2-switch input:checked+.est2-switch__slider{background:var(--est-accent)}.est2 .est2-switch input:checked+.est2-switch__slider:before{transform:translate(18px)}.est2 .est2-switch input:focus-visible+.est2-switch__slider{box-shadow:var(--est-ring)}.est2 .est2-toast{position:absolute;left:50%;bottom:14px;transform:translate(-50%);z-index:20;display:inline-flex;align-items:center;gap:8px;max-width:calc(100% - 24px);padding:8px 14px;font-size:13px;font-weight:500;color:#fff;background:#b91c1c;border-radius:var(--est-radius);box-shadow:0 6px 20px #00000040;animation:est2-toast-in .16s ease-out}.est2 .est2-toast svg{flex:0 0 auto}@keyframes est2-toast-in{0%{opacity:0;transform:translate(-50%,8px)}to{opacity:1;transform:translate(-50%)}}@media(prefers-reduced-motion:reduce){.est2 .est2-toast{animation:none}}.est2 tbody tr.est2-row--group{cursor:pointer;-webkit-user-select:none;user-select:none}.est2 tbody tr.est2-row--group td{background:var(--est-bg-subtle);font-weight:600;color:var(--est-fg);border-bottom:1px solid var(--est-border)}.est2 tbody tr.est2-row--group:hover td{background:var(--est-bg-hover)}.est2 .est2-td--group-key{color:var(--est-fg)}.est2 .est2-group-key{display:inline-flex;align-items:center;gap:6px}.est2 .est2-group-chevron{display:inline-flex;color:var(--est-fg-muted);transition:transform var(--est-transition)}.est2 .est2-group-chevron--open{transform:rotate(90deg)}.est2 .est2-hier-lead{display:inline-flex;align-items:center;vertical-align:middle;margin-right:4px}.est2 .est2-hier-toggle{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;flex:none}.est2 .est2-group-chevron.est2-hier-toggle{cursor:pointer;border-radius:var(--est-radius-sm);transition:transform var(--est-transition),background var(--est-transition),color var(--est-transition)}.est2 .est2-group-chevron.est2-hier-toggle:hover{background:var(--est-bg-hover);color:var(--est-fg)}.est2 th.est2-th--orderable{cursor:pointer;transition:color var(--est-transition)}.est2 th.est2-th--orderable:hover{color:var(--est-fg)}.est2 .est2-th__inner{display:inline-flex;align-items:center;gap:6px}.est2 .est2-sort{display:inline-flex;width:14px;height:14px;opacity:.35;transition:opacity var(--est-transition),transform var(--est-transition)}.est2 .est2-sort--active{opacity:1;color:var(--est-accent)}.est2 .est2-sort--desc{transform:rotate(180deg)}.est2 .est2-sort__badge{font-size:9px;font-weight:700;color:var(--est-accent);margin-left:2px}.est2 .est2-check{appearance:none;width:16px;height:16px;border:1.5px solid var(--est-border-strong);border-radius:var(--est-radius-sm);background:var(--est-bg);cursor:pointer;position:relative;transition:border-color var(--est-transition),background var(--est-transition);vertical-align:middle;flex:none}.est2 .est2-check:hover{border-color:var(--est-accent)}.est2 .est2-check:checked{background:var(--est-accent);border-color:var(--est-accent)}.est2 .est2-check:checked:after{content:\"\";position:absolute;left:4.5px;top:1.5px;width:4px;height:8px;border:solid var(--est-accent-fg);border-width:0 2px 2px 0;transform:rotate(45deg)}.est2 .est2-check:focus-visible{outline:none;box-shadow:var(--est-ring)}.est2 .est2-check:indeterminate{background:var(--est-accent);border-color:var(--est-accent)}.est2 .est2-check:indeterminate:after{content:\"\";position:absolute;left:3px;top:6px;width:8px;height:2px;background:var(--est-accent-fg);transform:none;border:none}.est2 th.est2-col-min,.est2 td.est2-col-min{width:1%;white-space:nowrap}.est2 .est2-wrap{position:relative}.est2 .est2-selectbar{position:absolute;top:0;left:0;right:0;z-index:15;display:flex;align-items:center;gap:12px;padding:9px 14px;font-size:var(--est-fs-sm);color:var(--est-fg);background:var(--est-bg-subtle);border:none;border-radius:var(--est-radius) var(--est-radius) 0 0;box-shadow:inset 3px 0 0 var(--est-accent)}.est2 .est2-selectbar strong{color:var(--est-fg);font-weight:700}.est2 .est2-selectbar__spacer{flex:1 1 auto}.est2 .est2-link{color:var(--est-accent);cursor:pointer;font-weight:600}.est2 .est2-link:hover{text-decoration:underline}.est2 .est2-rowaction{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:var(--est-radius-sm);border:none;background:transparent;color:var(--est-fg-faint);cursor:pointer;transition:background var(--est-transition),color var(--est-transition)}.est2 .est2-rowaction:hover{background:var(--est-bg-hover);color:var(--est-fg)}.est2 .est2-rowaction--danger:hover{color:var(--est-danger)}.est2 .est2-op-cell{text-align:center}.est2 .est2-op{display:inline-flex;align-items:center;justify-content:center;min-width:26px;height:26px;padding:0 6px;border-radius:var(--est-radius-sm);color:var(--est-accent);cursor:pointer;font-size:var(--est-fs-sm);transition:background var(--est-transition)}.est2 .est2-op:hover{background:var(--est-accent-weak)}.est2 .est2-loading{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:10px;background:color-mix(in srgb,var(--est-bg) 70%,transparent);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);z-index:5;color:var(--est-fg-muted);font-size:var(--est-fs-sm)}.est2 .est2-spinner{width:16px;height:16px;border:2px solid var(--est-border-strong);border-top-color:var(--est-accent);border-radius:50%;animation:est2-spin .7s linear infinite}@keyframes est2-spin{to{transform:rotate(360deg)}}.est2 .est2-nowrap{white-space:nowrap}.est2 .est2-cornermenu{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:var(--est-radius-sm);cursor:pointer;color:var(--est-fg-muted);transition:background var(--est-transition)}.est2 .est2-cornermenu:hover{background:var(--est-bg-hover);color:var(--est-fg)}.est2 .est2-chrome-th{position:relative}.est2 .est2-chrome{display:inline-flex;align-items:center;gap:2px}.est2 .est2-chrome-wrap{position:relative;display:inline-flex}.est2 .est2-chrome-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:none;background:transparent;border-radius:var(--est-radius-sm);color:var(--est-fg-muted);cursor:pointer;font-size:16px;line-height:1;transition:background var(--est-transition),color var(--est-transition)}.est2 .est2-chrome-btn:hover{background:var(--est-bg-hover);color:var(--est-fg)}.est2 .est2-menu{position:absolute;top:calc(100% + 4px);right:0;z-index:30;min-width:180px;padding:4px;background:var(--est-bg);border:1px solid var(--est-border);border-radius:var(--est-radius);box-shadow:0 8px 24px #0000002e;display:flex;flex-direction:column}.est2 .est2-menu-item{display:block;width:100%;padding:8px 10px;border:none;background:transparent;text-align:left;font:inherit;color:var(--est-fg);border-radius:var(--est-radius-sm);cursor:pointer}.est2 .est2-menu-item:hover{background:var(--est-bg-hover)}.est2 .est2-dialog-backdrop{position:absolute;inset:0;z-index:40;display:flex;align-items:center;justify-content:center;padding:16px;background:color-mix(in srgb,#000 32%,transparent)}.est2 .est2-dialog{width:380px;max-width:100%;max-height:100%;display:flex;flex-direction:column;background:var(--est-bg);color:var(--est-fg);border:1px solid var(--est-border);border-radius:var(--est-radius);box-shadow:0 16px 48px #0000004d;overflow:hidden}.est2 .est2-dialog__head{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;font-weight:700;border-bottom:1px solid var(--est-border)}.est2 .est2-dialog__close{border:none;background:transparent;cursor:pointer;color:var(--est-fg-muted);font-size:15px;line-height:1;padding:4px;border-radius:var(--est-radius-sm)}.est2 .est2-dialog__close:hover{background:var(--est-bg-hover);color:var(--est-fg)}.est2 .est2-dialog__tools{padding:8px 14px;font-size:var(--est-fs-sm);border-bottom:1px solid var(--est-border)}.est2 .est2-dialog__tools .est2-link{border:none;background:none;padding:0;font:inherit;font-weight:600}.est2 .est2-dialog__sep{margin:0 6px;color:var(--est-fg-faint)}.est2 .est2-collist{list-style:none;margin:0;padding:6px;overflow-y:auto}.est2 .est2-collist__row{display:flex;align-items:center;gap:10px;padding:6px 8px;border-radius:var(--est-radius-sm)}.est2 .est2-collist__row:hover{background:var(--est-bg-hover)}.est2 .est2-collist__vis{display:inline-flex}.est2 .est2-collist__label{flex:1 1 auto}.est2 .est2-collist__ord{display:inline-flex;gap:4px}.est2 .est2-iconbtn{width:26px;height:26px;border:1px solid var(--est-border);background:var(--est-bg);border-radius:var(--est-radius-sm);color:var(--est-fg-muted);cursor:pointer}.est2 .est2-iconbtn:hover:not(:disabled){background:var(--est-bg-hover);color:var(--est-fg)}.est2 .est2-iconbtn:disabled{opacity:.4;cursor:default}.est2 .est2-dialog__foot{display:flex;align-items:center;gap:8px;padding:12px 14px;border-top:1px solid var(--est-border)}.est2 .est2-dialog__spacer{flex:1 1 auto}.est2 .est2-btn{padding:7px 14px;border-radius:var(--est-radius-sm);font:inherit;font-weight:600;cursor:pointer;border:1px solid transparent}.est2 .est2-btn--ghost{background:transparent;color:var(--est-fg);border-color:var(--est-border)}.est2 .est2-btn--ghost:hover{background:var(--est-bg-hover)}.est2 .est2-btn--primary{background:var(--est-accent);color:var(--est-accent-fg)}.est2 .est2-btn--primary:hover{filter:brightness(1.05)}.est2 .est2-empty{padding:48px 16px;text-align:center;color:var(--est-fg-faint);font-size:var(--est-fs-sm)}.ngx-contextmenu{--ctx-bg: #ffffff;--ctx-fg: #1a1d24;--ctx-muted: #626b7a;--ctx-border: #e6e9ef;--ctx-hover: #f2f4f7;--ctx-accent: #4f46e5;--ctx-shadow: 0 10px 28px -8px rgba(16, 24, 40, .22), 0 2px 8px -3px rgba(16, 24, 40, .14);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.ngx-contextmenu .dropdown-menu{display:block;min-width:200px;margin:0;padding:6px;list-style:none;background:var(--ctx-bg);border:1px solid var(--ctx-border);border-radius:12px;box-shadow:var(--ctx-shadow);animation:est2-ctx-in .12s cubic-bezier(.16,1,.3,1)}.ngx-contextmenu li{list-style:none;margin:0}.ngx-contextmenu li>a{display:flex;align-items:center;gap:8px;padding:8px 12px;border-radius:7px;color:var(--ctx-fg);font-size:13.5px;line-height:1.2;text-decoration:none;cursor:pointer;white-space:nowrap;transition:background .12s ease,color .12s ease}.ngx-contextmenu li>a:hover,.ngx-contextmenu li>a:focus{background:var(--ctx-hover);color:var(--ctx-fg);text-decoration:none;outline:none}.ngx-contextmenu li.divider,.ngx-contextmenu li[role=separator]{height:1px;margin:6px 8px;padding:0;background:var(--ctx-border)}.ngx-contextmenu li.disabled>a,.ngx-contextmenu li[aria-disabled=true]>a{color:var(--ctx-muted);opacity:.55;pointer-events:none}@media(prefers-color-scheme:dark){.ngx-contextmenu{--ctx-bg: #1e222b;--ctx-fg: #e7eaf0;--ctx-muted: #9aa3b2;--ctx-border: #2a2f3a;--ctx-hover: #232733;--ctx-accent: #7c74ff;--ctx-shadow: 0 12px 30px -8px rgba(0, 0, 0, .6), 0 2px 8px -3px rgba(0, 0, 0, .5)}}@keyframes est2-ctx-in{0%{opacity:0;transform:translateY(-4px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}@media(prefers-reduced-motion:reduce){.ngx-contextmenu .dropdown-menu{animation:none}}\n"] }]
|
|
8939
|
+
args: [{ selector: 'es-table2', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "@if (view()) {\n <div class=\"est2-wrap {{ ContainerClass() }}\"\n [style.height]=\"Height()\"\n [style.background-color]=\"EmptySpaceBackgroundColor() || null\">\n\n <!-- Auto-aggiornamento: toggle + intervallo (in alto a destra) -->\n @if (AutoUpdate()) {\n <div class=\"est2-autoupdate\">\n <label class=\"est2-autoupdate__label\">\n Aggiorna ogni\n <input type=\"text\" maxlength=\"3\" class=\"est2-autoupdate__secs\"\n [ngModel]=\"seconds()\" (ngModelChange)=\"seconds.set($event)\"\n [ngModelOptions]=\"{ standalone: true }\"\n (change)=\"autoUpdateChanged()\" />\n secondi\n </label>\n <label class=\"est2-switch\" title=\"Attiva/disattiva auto-aggiornamento\">\n <input type=\"checkbox\" [ngModel]=\"autoUpdate()\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"autoUpdate.set($event); autoUpdateChanged()\" />\n <span class=\"est2-switch__slider\"></span>\n </label>\n </div>\n }\n\n <!-- Pager superiore -->\n @if (PagingStyle() === 'both' || PagingStyle() === 'top') {\n <ng-container *ngTemplateOutlet=\"pager\"></ng-container>\n }\n\n <!-- Barra \"seleziona tutto\" (visibile solo con selezione multipla attiva e righe presenti) -->\n @if (Selection() && !SingleSelection() && hasSelection) {\n <div class=\"est2-selectbar\" [style.height.px]=\"selbarHeight() ? selbarHeight() + 1 : null\">\n @if (allSelected) {\n <span>Tutti i <strong>{{ selectedCount }}</strong> elementi sono selezionati</span>\n } @else {\n <span><strong>{{ selectedCount }}</strong> {{ selectedCount === 1 ? 'elemento selezionato' : 'elementi selezionati' }}</span>\n @if (canSelectEverything) {\n <span class=\"est2-link\" (click)=\"selectEverything()\">Seleziona tutti i {{ totalCount() }} elementi</span>\n }\n }\n <span class=\"est2-selectbar__spacer\"></span>\n <span class=\"est2-link\" (click)=\"clearSelection()\">Azzera selezione</span>\n </div>\n }\n\n <div class=\"est2-scroll\" [style.max-height.px]=\"MaxHeight()\">\n <table class=\"est2-table {{ TableClass() }}\"\n [class.est2-table--range]=\"rangeActive()\"\n [class.est2-table--dragging]=\"rangeDragging()\"\n (mousedown)=\"onGridMouseDown($event)\"\n (mouseover)=\"onGridMouseOver($event)\">\n\n <!-- ================= HEADER ================= -->\n @if (!HeaderHidden()) {\n <thead #theadRef>\n <!-- Righe di header-group multi-livello (dall'alto verso il basso) -->\n @if (hasHeaderGroups()) {\n @for (grow of headerGroupRows(); track $index) {\n <tr class=\"est2-hgroup-row\">\n @if (Selection()) { <th class=\"est2-col-min est2-selcol\" [class.est2-pinned]=\"hasPinned()\" [style.left.px]=\"hasPinned() ? 0 : null\"></th> }\n @for (op of DynamicOperations(); track op.id) { <th class=\"est2-col-min\"></th> }\n @for (cell of grow; track cell.id; let gi = $index) {\n <th [attr.colspan]=\"cell.span\"\n [attr.data-groupid]=\"cell.isGroup ? cell.id : null\"\n [class.est2-hgroup]=\"cell.isGroup\"\n [class.est2-pinned]=\"gi < pinnedCount()\"\n [style.left.px]=\"gi < pinnedCount() ? pinnedLeftPx(gi) : null\"\n class=\"est2-hgroup-cell\">\n @if (cell.isGroup) {\n @if (cell.template) {\n <ng-container *ngTemplateOutlet=\"cell.template\"></ng-container>\n } @else {\n {{ cell.label }}\n }\n }\n </th>\n }\n @if (Removal()) { <th class=\"est2-col-min\"></th> }\n @if (hasChrome()) { <th class=\"est2-col-min\"></th> }\n </tr>\n }\n }\n <tr>\n <!-- Colonna di selezione -->\n @if (Selection()) {\n <th class=\"est2-col-min est2-selcol\"\n [class.est2-pinned]=\"hasPinned()\"\n [style.left.px]=\"hasPinned() ? 0 : null\">\n @if (!SingleSelection()) {\n <input type=\"checkbox\" class=\"est2-check\"\n [checked]=\"globalCheck()\"\n [indeterminate]=\"selectionIndeterminate\"\n [disabled]=\"SelectionDisabled()\"\n (change)=\"toggleAll()\"\n aria-label=\"Seleziona tutto\" />\n }\n </th>\n }\n\n <!-- Header da colonne (direttive / dinamica / report) -->\n @if (usesColumns()) {\n <!-- intestazioni vuote per le operazioni dinamiche -->\n @for (op of DynamicOperations(); track op.id) {\n <th class=\"est2-col-min\"></th>\n }\n @for (col of visibleColumns(); track trackCol($index, col); let ci = $index) {\n <th [attr.data-colid]=\"col.id\"\n [class]=\"col.headerClass\"\n [class.est2-th--orderable]=\"OrderByColumn() && col.orderable\"\n [class.est2-col-min]=\"col.header?.thShrink\"\n [class.est2-col-groupstart]=\"columnGroupBoundaries().has(ci)\"\n [class.est2-pinned]=\"col.pinned\"\n [style.left.px]=\"col.pinned ? pinnedLeftPx(ci) : null\"\n [style.width.px]=\"col.pinned ? pinnedWidthPx(col) : null\"\n [style.min-width.px]=\"col.pinned ? pinnedWidthPx(col) : null\"\n [style.max-width.px]=\"col.pinned ? pinnedWidthPx(col) : null\"\n [style.text-align]=\"col.alignment\"\n [style.background-color]=\"col.headerBg || null\"\n (click)=\"toggleSort(col)\">\n <span class=\"est2-th__inner\">\n @if (col.header?.Template) {\n <ng-container *ngTemplateOutlet=\"col.header!.Template!; context: col.multiProp != null ? { $implicit: col.headerText } : null\"></ng-container>\n } @else {\n {{ col.headerText }}\n }\n @if (OrderByColumn() && col.orderable && orderOf(col.id)) {\n <span class=\"est2-sort est2-sort--active\"\n [class.est2-sort--desc]=\"orderOf(col.id) === 'DESC'\">\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 15l6-6 6 6\"/></svg>\n </span>\n @if (orderIndex(col.id) > 0 && MultipleOrderingDirectives()) {\n <span class=\"est2-sort__badge\">{{ orderIndex(col.id) }}</span>\n }\n }\n <!-- Indicatore/toggle di pin (visibile se pinnata o all'hover) -->\n @if (ColumnsPinnable() && col.multiProp == null) {\n <button type=\"button\" class=\"est2-pinbtn\"\n [class.est2-pinbtn--on]=\"col.pinned\"\n [title]=\"col.pinned ? 'Sblocca colonna' : 'Blocca colonna a sinistra'\"\n (click)=\"togglePin(col, $event)\">\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 17v5\"/><path d=\"M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z\"/></svg>\n </button>\n }\n </span>\n <!-- Handle di ridimensionamento (colonne pinnate, gated da ColumnsResizable) -->\n @if (ColumnsResizable() && col.pinned) {\n <span class=\"est2-resize-handle\" title=\"Trascina per ridimensionare\"\n (mousedown)=\"startColumnResize(col, $event)\"\n (click)=\"$event.stopPropagation()\"></span>\n }\n </th>\n }\n }\n <!-- Header da template semplice -->\n @else if (headerRef) {\n <ng-container *ngTemplateOutlet=\"headerRef\"></ng-container>\n }\n\n <!-- Colonna rimozione -->\n @if (Removal()) { <th class=\"est2-col-min\"></th> }\n <!-- Colonna chrome (export / gestione colonne / menu) -->\n @if (hasChrome()) {\n <th class=\"est2-col-min est2-chrome-th\">\n <div class=\"est2-chrome\">\n @if (Export()) {\n <div class=\"est2-chrome-wrap\">\n <button type=\"button\" class=\"est2-chrome-btn\" title=\"Esporta\" (click)=\"$event.stopPropagation(); toggleExportMenu()\">\n <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><path d=\"M7 10l5 5 5-5\"/><path d=\"M12 15V3\"/></svg>\n </button>\n @if (exportMenuOpen()) {\n <div class=\"est2-menu\" (click)=\"$event.stopPropagation()\">\n @if (CSVExport()) { <button type=\"button\" class=\"est2-menu-item\" (click)=\"export('CSV')\">Esporta CSV</button> }\n @if (XLSXExport()) { <button type=\"button\" class=\"est2-menu-item\" (click)=\"export('XLSX')\">Esporta Excel (XLSX)</button> }\n @if (!CSVExport() && !XLSXExport()) { <button type=\"button\" class=\"est2-menu-item\" (click)=\"export('CSV')\">Esporta CSV</button> }\n </div>\n }\n </div>\n }\n @if (HiddenColumns() || ColumnsOrdering()) {\n <button type=\"button\" class=\"est2-chrome-btn\" title=\"Colonne\" (click)=\"$event.stopPropagation(); openColumnsDialog()\">\n <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"1\"/><path d=\"M9 3v18\"/><path d=\"M15 3v18\"/></svg>\n </button>\n }\n @if (CornerMenuOptions().length > 0) {\n <div class=\"est2-chrome-wrap\">\n <button type=\"button\" class=\"est2-chrome-btn\" title=\"Opzioni\" (click)=\"$event.stopPropagation(); toggleCornerMenu()\">\u22EE</button>\n @if (cornerMenuOpen()) {\n <div class=\"est2-menu\" (click)=\"$event.stopPropagation()\">\n @for (opt of CornerMenuOptions(); track opt.id) {\n <button type=\"button\" class=\"est2-menu-item\" (click)=\"cornerAction(opt.id); cornerMenuOpen.set(false)\">{{ opt.description }}</button>\n }\n </div>\n }\n </div>\n }\n </div>\n </th>\n }\n </tr>\n </thead>\n }\n\n <!-- ================= BODY ================= -->\n @if (!BodyHidden()) {\n <tbody>\n @for (item of boundSource(); track trackRow($index, item); let ri = $index) {\n @if ((!grouped() && !Hierarchy()) || item._visible) {\n <tr [class]=\"rowClass(item)\"\n [class.est2-row--selected]=\"item._selected\"\n [class.est2-row--removed]=\"item.removed || item.deleted\"\n [class.est2-row--group]=\"item._group\"\n [class.est2-row--clickable]=\"Selection() || item._group\"\n [contextMenu]=\"ContextMenu || emptyMenu\"\n [contextMenuSubject]=\"item\"\n (click)=\"handleRowClick(item, $event)\">\n\n <!-- Cella di selezione -->\n @if (Selection()) {\n <td class=\"est2-col-min est2-selcol\"\n [class.est2-pinned]=\"hasPinned()\"\n [style.left.px]=\"hasPinned() ? 0 : null\">\n @if (item._group) {\n @if (!SingleSelection()) {\n <input type=\"checkbox\" class=\"est2-check\"\n [checked]=\"groupSelectionState(item) === 'all'\"\n [indeterminate]=\"groupSelectionState(item) === 'some'\"\n [disabled]=\"SelectionDisabled()\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleGroupSelection(item)\"\n aria-label=\"Seleziona gruppo\" />\n }\n } @else {\n <input type=\"checkbox\" class=\"est2-check\"\n [checked]=\"item._selected\"\n [indeterminate]=\"hierarchyIndeterminate(item)\"\n [disabled]=\"SelectionDisabled()\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleRow(item)\"\n aria-label=\"Seleziona riga\" />\n }\n </td>\n }\n\n <!-- Celle da colonne (direttive / dinamica / report) -->\n @if (usesColumns()) {\n <!-- Operazioni dinamiche (icone a sinistra) -->\n @for (op of DynamicOperations(); track op.id) {\n <td class=\"est2-col-min est2-op-cell\">\n @if (!item._group && operationVisible(op, item)) {\n <span class=\"est2-op\" [class]=\"op.iconClass || ''\" [title]=\"op.title\"\n (click)=\"$event.stopPropagation(); dynamicOperation(item, op.id)\">{{ op.text }}</span>\n }\n </td>\n }\n @for (col of visibleColumns(); track trackCol($index, col); let first = $first, ci = $index) {\n <td [class]=\"col.cssClass\"\n [style.text-align]=\"col.alignment\"\n [style.color]=\"cellColor(item, col, 'fore')\"\n [style.background-color]=\"cellColor(item, col, 'back')\"\n [class.est2-nowrap]=\"!col.wrap\"\n [class.est2-col-groupstart]=\"columnGroupBoundaries().has(ci)\"\n [class.est2-pinned]=\"col.pinned\"\n [style.left.px]=\"col.pinned ? pinnedLeftPx(ci) : null\"\n [style.width.px]=\"col.pinned ? pinnedWidthPx(col) : null\"\n [style.min-width.px]=\"col.pinned ? pinnedWidthPx(col) : null\"\n [style.max-width.px]=\"col.pinned ? pinnedWidthPx(col) : null\"\n (mouseenter)=\"onCellHover($event)\"\n [class.est2-td--group-key]=\"item._group && item.column === col.id\"\n [attr.data-r]=\"rangeActive() && !item._group ? ri : null\"\n [attr.data-c]=\"rangeActive() && !item._group ? ci : null\"\n [class.est2-cell-sel]=\"rangeActive() && inRange(ri, ci)\"\n [class.est2-cell-sel-t]=\"rangeActive() && inRange(ri, ci) && ri === rangeRect()!.top\"\n [class.est2-cell-sel-b]=\"rangeActive() && inRange(ri, ci) && ri === rangeRect()!.bottom\"\n [class.est2-cell-sel-l]=\"rangeActive() && inRange(ri, ci) && ci === rangeRect()!.left\"\n [class.est2-cell-sel-r]=\"rangeActive() && inRange(ri, ci) && ci === rangeRect()!.right\"\n [class.est2-cell-editing]=\"isEditing(ri, ci)\"\n (dblclick)=\"onCellDblClick(item, col, ri, ci)\">\n @if (isEditing(ri, ci)) {\n @if (editorFor(col); as edTpl) {\n <ng-container *ngTemplateOutlet=\"edTpl; context: editorContext(item, col)\"></ng-container>\n } @else {\n <ng-container *ngTemplateOutlet=\"defaultEditor; context: { $implicit: item, col: col }\"></ng-container>\n }\n } @else if (item._group) {\n @if (item.column === col.id) {\n <span class=\"est2-group-key\" [style.padding-left.px]=\"groupIndent(item)\">\n <span class=\"est2-group-chevron\" [class.est2-group-chevron--open]=\"item._expanded\">\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 6l6 6-6 6\"/></svg>\n </span>\n {{ groupCellDisplay(item, col) }}\n </span>\n } @else {\n {{ groupCellDisplay(item, col) }}\n }\n } @else {\n <!-- Navigatore albero nella prima colonna -->\n @if (Hierarchy() && first) {\n <span class=\"est2-hier-lead\" [style.padding-left.px]=\"hierarchyIndent(item)\">\n @if (item.parent) {\n <span class=\"est2-group-chevron est2-hier-toggle\" [class.est2-group-chevron--open]=\"item._expanded\"\n (click)=\"$event.stopPropagation(); toggleHierarchyNode(item)\">\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 6l6 6-6 6\"/></svg>\n </span>\n } @else {\n <span class=\"est2-hier-toggle\"></span>\n }\n </span>\n }\n @if (itemCellHidden(col)) {\n <!-- colonna di gruppo nascosta sulla riga-oggetto -->\n } @else if (col.multiProp != null) {\n @if (col.cell?.Template) {\n <ng-container *ngTemplateOutlet=\"col.cell!.Template; context: { $implicit: multiCell(item, col) }\"></ng-container>\n } @else {\n {{ multiCell(item, col)?.value }}\n }\n } @else if (col.cell?.Template) {\n <ng-container *ngTemplateOutlet=\"col.cell!.Template; context: { $implicit: item }\"></ng-container>\n } @else if (col.routePath) {\n <a class=\"est2-link\" [routerLink]=\"routerLinkFor(item, col)\">{{ cellValue(item, col) }}</a>\n } @else if (col.propAccessor != null) {\n {{ reportCellDisplay(item, col) }}\n } @else if (col.type === 'enum') {\n {{ (cellValue(item, col) | est2_lookup : col.source).description }}\n } @else {\n {{ cellValue(item, col) | est2_format : col.type : col.format : locale }}\n }\n }\n </td>\n }\n }\n <!-- Celle da template semplice -->\n @else if (bodyRef) {\n <ng-container *ngTemplateOutlet=\"bodyRef; context: { $implicit: item }\"></ng-container>\n }\n\n <!-- Rimozione (non sulle righe-gruppo) -->\n @if (Removal()) {\n <td class=\"est2-col-min\">\n @if (!item._group && canRemove(item)) {\n @if (item.removed || item.deleted) {\n <button type=\"button\" class=\"est2-rowaction\" title=\"Ripristina\" (click)=\"abortRemoval(item)\">\u21BA</button>\n } @else {\n <button type=\"button\" class=\"est2-rowaction est2-rowaction--danger\" title=\"Rimuovi\" (click)=\"removeItem(item)\">\u2715</button>\n }\n }\n </td>\n }\n <!-- Chrome -->\n @if (hasChrome()) { <td class=\"est2-col-min\"></td> }\n </tr>\n }\n } @empty {\n <tr>\n <td class=\"est2-empty\" [attr.colspan]=\"totalColspan()\">Nessun elemento da visualizzare</td>\n </tr>\n }\n </tbody>\n }\n </table>\n\n <!-- Overlay di caricamento -->\n @if (researchInProgress() || (firstBind() && ShowLoadingOnBootstrap())) {\n <div class=\"est2-loading\">\n <span class=\"est2-spinner\"></span>\n <span>Caricamento\u2026</span>\n </div>\n }\n </div>\n\n <!-- Pager inferiore -->\n @if (PagingStyle() === 'both' || PagingStyle() === 'bottom') {\n <ng-container *ngTemplateOutlet=\"pager\"></ng-container>\n }\n\n <!-- Avviso transitorio (es. incolla con dimensioni incompatibili) -->\n @if (notice()) {\n <div class=\"est2-toast\" role=\"alert\">\n <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/><path d=\"M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/></svg>\n <span>{{ notice() }}</span>\n </div>\n }\n\n <!-- Dialog visibilit\u00E0 / ordine colonne -->\n @if (columnsDialogOpen()) {\n <div class=\"est2-dialog-backdrop\" (click)=\"closeColumnsDialog()\">\n <div class=\"est2-dialog\" (click)=\"$event.stopPropagation()\" role=\"dialog\" aria-modal=\"true\">\n <div class=\"est2-dialog__head\">\n <span>\n @if (HiddenColumns() && ColumnsOrdering()) { Visibilit\u00E0 e ordine colonne }\n @else if (HiddenColumns()) { Visibilit\u00E0 colonne }\n @else { Ordine colonne }\n </span>\n <button type=\"button\" class=\"est2-dialog__close\" (click)=\"closeColumnsDialog()\" aria-label=\"Chiudi\">\u2715</button>\n </div>\n\n @if (HiddenColumns()) {\n <div class=\"est2-dialog__tools\">\n <button type=\"button\" class=\"est2-link\" (click)=\"dialogSetAll(true)\">Mostra tutte</button>\n <span class=\"est2-dialog__sep\">\u00B7</span>\n <button type=\"button\" class=\"est2-link\" (click)=\"dialogSetAll(false)\">Nascondi tutte</button>\n </div>\n }\n\n <ul class=\"est2-collist\">\n @for (c of dialogCols(); track c.id; let i = $index) {\n <li class=\"est2-collist__row\"\n [attr.draggable]=\"ColumnsOrdering() ? true : null\"\n [class.est2-collist__row--drag]=\"ColumnsOrdering()\"\n [class.est2-collist__row--dragging]=\"dragIndex() === i\"\n [class.est2-collist__row--drop-above]=\"dragOverIndex() === i && dragIndex() !== null && dragIndex()! > i\"\n [class.est2-collist__row--drop-below]=\"dragOverIndex() === i && dragIndex() !== null && dragIndex()! < i\"\n (dragstart)=\"dialogDragStart(i, $event)\"\n (dragover)=\"dialogDragOver(i, $event)\"\n (drop)=\"dialogDrop(i, $event)\"\n (dragend)=\"dialogDragEnd()\">\n @if (ColumnsOrdering()) {\n <span class=\"est2-collist__grip\" title=\"Trascina per riordinare\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"currentColor\"><circle cx=\"9\" cy=\"6\" r=\"1.6\"/><circle cx=\"15\" cy=\"6\" r=\"1.6\"/><circle cx=\"9\" cy=\"12\" r=\"1.6\"/><circle cx=\"15\" cy=\"12\" r=\"1.6\"/><circle cx=\"9\" cy=\"18\" r=\"1.6\"/><circle cx=\"15\" cy=\"18\" r=\"1.6\"/></svg>\n </span>\n }\n @if (HiddenColumns()) {\n <label class=\"est2-collist__vis\">\n <input type=\"checkbox\" class=\"est2-check\" [checked]=\"c.visible\" (change)=\"dialogToggle(i)\" />\n </label>\n }\n <span class=\"est2-collist__label\">{{ c.label }}</span>\n @if (ColumnsPinnable()) {\n <button type=\"button\" class=\"est2-iconbtn est2-collist__pin\" [class.est2-collist__pin--on]=\"c.pinned\"\n [disabled]=\"c.grouped\"\n [title]=\"c.grouped ? 'Le colonne di un gruppo non sono bloccabili' : (c.pinned ? 'Sblocca' : 'Blocca a sinistra')\"\n (click)=\"dialogTogglePin(i)\">\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 17v5\"/><path d=\"M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z\"/></svg>\n </button>\n }\n @if (ColumnsOrdering()) {\n <span class=\"est2-collist__ord\">\n <button type=\"button\" class=\"est2-iconbtn\" [disabled]=\"i === 0\" title=\"Su\" (click)=\"dialogMove(i, -1)\">\u2191</button>\n <button type=\"button\" class=\"est2-iconbtn\" [disabled]=\"i === dialogCols().length - 1\" title=\"Gi\u00F9\" (click)=\"dialogMove(i, 1)\">\u2193</button>\n </span>\n }\n </li>\n }\n </ul>\n\n <div class=\"est2-dialog__foot\">\n <button type=\"button\" class=\"est2-btn est2-btn--ghost\" (click)=\"resetColumnsDialog()\">Ripristina</button>\n <span class=\"est2-dialog__spacer\"></span>\n <button type=\"button\" class=\"est2-btn est2-btn--ghost\" (click)=\"closeColumnsDialog()\">Annulla</button>\n <button type=\"button\" class=\"est2-btn est2-btn--primary\" (click)=\"applyColumnsDialog()\">Applica</button>\n </div>\n </div>\n </div>\n }\n </div>\n}\n\n<!-- Template pager riutilizzabile sopra/sotto -->\n<ng-template #pager>\n @if (!HidePaging() && !grouped()) {\n <es-table2-pager\n [page]=\"currentPage()\"\n [pages]=\"totalPages()\"\n [total]=\"totalCount()\"\n [itemsPerPage]=\"viewMode() ? (view()?.itemsperpageoverride ?? 15) : ArraymodeItemsPerPage()\"\n [countLabel]=\"CountLabel()\"\n [showCount]=\"!HidePagingCount()\"\n [showButtons]=\"!HidePagingButtons()\"\n [showPagingOptions]=\"!HidePagingButtons()\"\n [allowAll]=\"AllSearch()\"\n (pageChange)=\"goToPage($event)\"\n (itemsPerPageChange)=\"changeItemsPerPage($event)\">\n </es-table2-pager>\n }\n</ng-template>\n\n<!-- Editor di cella di default (usato quando il consumer non fornisce un `*editor`) -->\n<ng-template #defaultEditor let-item let-col=\"col\">\n @switch (col.type) {\n @case ('enum') {\n <select class=\"est2-editor-input\"\n [value]=\"editDraft\"\n (change)=\"editDraft = $any($event.target).value\"\n (keydown.enter)=\"commitEdit(item, col)\"\n (keydown.escape)=\"cancelEdit()\"\n (blur)=\"commitEdit(item, col)\">\n @for (o of col.source || []; track o.id) {\n <option [value]=\"o.id\" [selected]=\"o.id == editDraft\">{{ o.description }}</option>\n }\n </select>\n }\n @case ('boolean') {\n <input type=\"checkbox\" class=\"est2-editor-input est2-check\"\n [checked]=\"editDraft === true || editDraft === 'true'\"\n (change)=\"editDraft = $any($event.target).checked; commitEdit(item, col)\"\n (keydown.escape)=\"cancelEdit()\" />\n }\n @default {\n <input class=\"est2-editor-input\"\n [type]=\"editorInputType(col.type)\"\n [value]=\"editDraft\"\n (input)=\"editDraft = $any($event.target).value\"\n (keydown.enter)=\"commitEdit(item, col)\"\n (keydown.escape)=\"cancelEdit()\"\n (blur)=\"commitEdit(item, col)\" />\n }\n }\n</ng-template>\n\n<!-- Menu di default (nessuna operazione) usato quando il consumer non passa [ContextMenu] -->\n<context-menu #emptyMenu>\n <ng-template contextMenuItem [passive]=\"true\"><em>Nessuna operazione disponibile\u2026</em></ng-template>\n</context-menu>\n", styles: [".est2{--est-bg: #ffffff;--est-bg-subtle: #f7f8fa;--est-bg-raised: #ffffff;--est-bg-hover: #f2f4f7;--est-bg-selected: color-mix(in srgb, var(--est-accent) 12%, transparent);--est-fg: #1a1d24;--est-fg-muted: #626b7a;--est-fg-faint: #9aa3b2;--est-border: #e6e9ef;--est-border-strong: #d3d8e0;--est-accent: #4f46e5;--est-accent-fg: #ffffff;--est-accent-weak: color-mix(in srgb, var(--est-accent) 14%, transparent);--est-danger: #dc2626;--est-warning: #d97706;--est-success: #059669;--est-shadow-sticky: 0 1px 0 var(--est-border), 0 4px 12px -8px rgba(16, 24, 40, .24);--est-shadow-pop: 0 8px 24px -6px rgba(16, 24, 40, .18), 0 2px 6px -2px rgba(16, 24, 40, .12);--est-ring: 0 0 0 3px color-mix(in srgb, var(--est-accent) 40%, transparent);--est-radius: 10px;--est-radius-sm: 6px;--est-radius-pill: 999px;--est-gap: 8px;--est-cell-py: 6px;--est-cell-px: 12px;--est-row-h: 33px;--est-font: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;--est-fs: 13px;--est-fs-sm: 12px;--est-fw-head: 700;--est-transition: .14s cubic-bezier(.4, 0, .2, 1);--est-bg-zebra: color-mix(in srgb, var(--est-fg) 3.5%, var(--est-bg));font-family:var(--est-font);font-size:var(--est-fs);color:var(--est-fg);position:relative;display:block}@media(prefers-color-scheme:dark){.est2{--est-bg: #14161c;--est-bg-subtle: #1a1d25;--est-bg-raised: #1e222b;--est-bg-hover: #232733;--est-bg-selected: color-mix(in srgb, var(--est-accent) 26%, transparent);--est-fg: #e7eaf0;--est-fg-muted: #9aa3b2;--est-fg-faint: #6b7484;--est-border: #2a2f3a;--est-border-strong: #39404d;--est-accent: #7c74ff;--est-accent-fg: #ffffff;--est-accent-weak: color-mix(in srgb, var(--est-accent) 22%, transparent);--est-danger: #f87171;--est-warning: #fbbf24;--est-success: #34d399;--est-shadow-sticky: 0 1px 0 var(--est-border), 0 6px 16px -10px rgba(0, 0, 0, .7);--est-shadow-pop: 0 10px 28px -8px rgba(0, 0, 0, .6), 0 2px 6px -2px rgba(0, 0, 0, .5);--est-ring: 0 0 0 3px color-mix(in srgb, var(--est-accent) 55%, transparent)}}.est2.est2--dark{--est-bg: #14161c;--est-bg-subtle: #1a1d25;--est-bg-raised: #1e222b;--est-bg-hover: #232733;--est-bg-selected: color-mix(in srgb, var(--est-accent) 26%, transparent);--est-fg: #e7eaf0;--est-fg-muted: #9aa3b2;--est-fg-faint: #6b7484;--est-border: #2a2f3a;--est-border-strong: #39404d;--est-accent: #7c74ff;--est-accent-fg: #ffffff;--est-accent-weak: color-mix(in srgb, var(--est-accent) 22%, transparent);--est-danger: #f87171;--est-warning: #fbbf24;--est-success: #34d399;--est-shadow-sticky: 0 1px 0 var(--est-border), 0 6px 16px -10px rgba(0, 0, 0, .7);--est-shadow-pop: 0 10px 28px -8px rgba(0, 0, 0, .6), 0 2px 6px -2px rgba(0, 0, 0, .5);--est-ring: 0 0 0 3px color-mix(in srgb, var(--est-accent) 55%, transparent)}.est2.est2--light{--est-bg: #ffffff;--est-bg-subtle: #f7f8fa;--est-bg-raised: #ffffff;--est-bg-hover: #f2f4f7;--est-bg-selected: color-mix(in srgb, var(--est-accent) 12%, transparent);--est-fg: #1a1d24;--est-fg-muted: #626b7a;--est-fg-faint: #9aa3b2;--est-border: #e6e9ef;--est-border-strong: #d3d8e0;--est-accent: #4f46e5;--est-accent-fg: #ffffff;--est-accent-weak: color-mix(in srgb, var(--est-accent) 14%, transparent);--est-danger: #dc2626;--est-warning: #d97706;--est-success: #059669;--est-shadow-sticky: 0 1px 0 var(--est-border), 0 4px 12px -8px rgba(16, 24, 40, .24);--est-shadow-pop: 0 8px 24px -6px rgba(16, 24, 40, .18), 0 2px 6px -2px rgba(16, 24, 40, .12);--est-ring: 0 0 0 3px color-mix(in srgb, var(--est-accent) 40%, transparent)}.est2.est2--dense{--est-cell-py: 3px;--est-cell-px: 9px;--est-row-h: 27px;--est-fs: 12.5px}.est2 *,.est2 *:before,.est2 *:after{box-sizing:border-box}.est2 .est2-scroll{position:relative;width:100%;overflow:auto;border:1px solid var(--est-border);border-radius:var(--est-radius);background:var(--est-bg);-webkit-overflow-scrolling:touch}.est2 table.est2-table{width:100%;border-collapse:separate;border-spacing:0;background:var(--est-bg)}.est2 thead th{position:sticky;top:0;z-index:3;background:var(--est-bg-subtle);color:var(--est-fg);font-weight:var(--est-fw-head);font-size:var(--est-fs);text-align:left;white-space:nowrap;padding:var(--est-cell-py) var(--est-cell-px);border-bottom:1px solid var(--est-border);box-shadow:var(--est-shadow-sticky);-webkit-user-select:none;user-select:none}.est2 thead tr.est2-hgroup-row th{font-size:var(--est-fs-sm);font-weight:700;color:var(--est-fg-muted);text-align:center;white-space:nowrap;padding:var(--est-cell-py) var(--est-cell-px);background:var(--est-bg-subtle);border-bottom:1px solid var(--est-border)}.est2 thead tr.est2-hgroup-row th.est2-hgroup{color:var(--est-fg);border-left:1px solid var(--est-border);border-right:1px solid var(--est-border)}.est2 thead tr.est2-hgroup-row th:not(.est2-hgroup){background:var(--est-bg-subtle);border-bottom-color:transparent}.est2 thead tr:last-child th.est2-col-groupstart,.est2 tbody td.est2-col-groupstart{border-left:1px solid var(--est-border)}.est2 tbody td{padding:var(--est-cell-py) var(--est-cell-px);border-bottom:1px solid var(--est-border);color:var(--est-fg);vertical-align:middle;background:transparent}.est2 tbody tr:nth-child(2n) td{background:var(--est-bg-zebra)}.est2 tbody tr:nth-child(2n) td.est2-pinned{background:var(--est-bg-zebra)}.est2 th.est2-pinned,.est2 td.est2-pinned{position:sticky;background:var(--est-bg);box-shadow:1px 0 0 var(--est-border)}.est2 thead th.est2-pinned{position:sticky}.est2 .est2-resize-handle{position:absolute;top:0;right:-3px;width:10px;height:100%;cursor:col-resize;z-index:2;touch-action:none;-webkit-user-select:none;user-select:none}.est2 .est2-resize-handle:after{content:\"\";position:absolute;top:28%;bottom:28%;right:3px;width:2px;border-radius:2px;background:color-mix(in srgb,var(--est-fg) 30%,transparent);transition:background var(--est-transition),top var(--est-transition),bottom var(--est-transition)}.est2 .est2-resize-handle:hover:after{background:var(--est-accent);top:15%;bottom:15%}.est2 thead th.est2-pinned .est2-resize-handle:active:after{background:var(--est-accent);top:6%;bottom:6%}.est2 td.est2-pinned{overflow:hidden;text-overflow:ellipsis}.est2 .est2-selcol{width:44px;min-width:44px;max-width:44px}.est2 td.est2-pinned{z-index:3}.est2 thead th.est2-pinned,.est2 thead tr.est2-hgroup-row th.est2-pinned{z-index:6;background:var(--est-bg-subtle)}.est2 tbody tr:hover td.est2-pinned{background:color-mix(in srgb,var(--est-fg) 5%,var(--est-bg))}.est2 tbody tr.est2-row--selected td.est2-pinned{background:color-mix(in srgb,var(--est-accent) 12%,var(--est-bg))}.est2 tbody tr.est2-row--group td.est2-pinned{background:var(--est-bg-subtle)}.est2 .est2-pinbtn{display:inline-flex;align-items:center;justify-content:center;margin-left:4px;padding:2px;border:none;background:transparent;color:var(--est-fg-faint);border-radius:var(--est-radius-sm);cursor:pointer;opacity:0;transition:opacity var(--est-transition),color var(--est-transition),background var(--est-transition)}.est2 thead th:hover .est2-pinbtn{opacity:.7}.est2 .est2-pinbtn:hover{background:var(--est-bg-hover);color:var(--est-fg);opacity:1}.est2 .est2-pinbtn--on{opacity:1;color:var(--est-accent);transform:rotate(0)}.est2 thead th:hover .est2-pinbtn--on{opacity:1}.est2 .est2-collist__pin.est2-collist__pin--on{color:var(--est-accent);border-color:var(--est-accent)}.est2 tbody tr{height:var(--est-row-h);transition:background var(--est-transition)}.est2 tbody tr:last-child td{border-bottom:none}.est2 tbody tr:hover td{background:var(--est-bg-hover)}.est2 tbody tr.est2-row--selected td{background:var(--est-bg-selected)}.est2 tbody tr.est2-row--clickable{cursor:pointer;-webkit-user-select:none;user-select:none}.est2 tbody tr.est2-row--removed td{text-decoration:line-through;color:var(--est-fg-faint)}.est2 .est2-table--range tbody td[data-r]{cursor:cell}.est2 .est2-table--dragging,.est2 .est2-table--dragging tbody td{-webkit-user-select:none;user-select:none}.est2 tbody td.est2-cell-sel{background:color-mix(in srgb,var(--est-accent) 14%,transparent)}.est2 tbody td.est2-cell-sel-t{box-shadow:inset 0 2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-b{box-shadow:inset 0 -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-l{box-shadow:inset 2px 0 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-r{box-shadow:inset -2px 0 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-l{box-shadow:inset 2px 2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-r{box-shadow:inset -2px 2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-b.est2-cell-sel-l{box-shadow:inset 2px -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-b.est2-cell-sel-r{box-shadow:inset -2px -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-b{box-shadow:inset 0 2px 0 0 var(--est-accent),inset 0 -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-l.est2-cell-sel-r{box-shadow:inset 2px 0 0 0 var(--est-accent),inset -2px 0 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-b.est2-cell-sel-l{box-shadow:inset 2px 2px 0 0 var(--est-accent),inset 0 -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-b.est2-cell-sel-r{box-shadow:inset -2px 2px 0 0 var(--est-accent),inset 0 -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-l.est2-cell-sel-r{box-shadow:inset 2px 2px 0 0 var(--est-accent),inset -2px 0 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-b.est2-cell-sel-l.est2-cell-sel-r{box-shadow:inset 2px -2px 0 0 var(--est-accent),inset -2px 0 0 0 var(--est-accent)}.est2 tbody td.est2-cell-sel-t.est2-cell-sel-b.est2-cell-sel-l.est2-cell-sel-r{box-shadow:inset 2px 2px 0 0 var(--est-accent),inset -2px -2px 0 0 var(--est-accent)}.est2 tbody td.est2-cell-editing{padding:2px 6px}.est2 .est2-editor-input{width:100%;box-sizing:border-box;height:calc(var(--est-row-h) - 8px);padding:2px 6px;font:inherit;color:var(--est-fg);background:var(--est-bg);border:1.5px solid var(--est-accent);border-radius:var(--est-radius-sm);outline:none}.est2 .est2-editor-input:focus-visible{box-shadow:var(--est-ring)}.est2 input.est2-editor-input[type=checkbox]{width:16px;height:16px}.est2 .est2-autoupdate{display:flex;align-items:center;justify-content:flex-end;gap:12px;padding:4px 2px 8px;font-size:var(--est-fs-sm);color:var(--est-fg-muted)}.est2 .est2-autoupdate__label{display:inline-flex;align-items:center;gap:6px}.est2 .est2-autoupdate__secs{width:44px;text-align:center;padding:3px 4px;font:inherit;color:var(--est-fg);background:var(--est-bg);border:1px solid var(--est-border);border-radius:var(--est-radius-sm);outline:none}.est2 .est2-autoupdate__secs:focus-visible{box-shadow:var(--est-ring);border-color:var(--est-accent)}.est2 .est2-switch{position:relative;display:inline-flex;width:38px;height:20px;cursor:pointer}.est2 .est2-switch input{position:absolute;opacity:0;width:0;height:0}.est2 .est2-switch__slider{flex:1;border-radius:var(--est-radius-pill);background:var(--est-border-strong);transition:background var(--est-transition)}.est2 .est2-switch__slider:before{content:\"\";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;box-shadow:0 1px 2px #00000040;transition:transform var(--est-transition)}.est2 .est2-switch input:checked+.est2-switch__slider{background:var(--est-accent)}.est2 .est2-switch input:checked+.est2-switch__slider:before{transform:translate(18px)}.est2 .est2-switch input:focus-visible+.est2-switch__slider{box-shadow:var(--est-ring)}.est2 .est2-toast{position:absolute;left:50%;bottom:14px;transform:translate(-50%);z-index:20;display:inline-flex;align-items:center;gap:8px;max-width:calc(100% - 24px);padding:8px 14px;font-size:13px;font-weight:500;color:#fff;background:#b91c1c;border-radius:var(--est-radius);box-shadow:0 6px 20px #00000040;animation:est2-toast-in .16s ease-out}.est2 .est2-toast svg{flex:0 0 auto}@keyframes est2-toast-in{0%{opacity:0;transform:translate(-50%,8px)}to{opacity:1;transform:translate(-50%)}}@media(prefers-reduced-motion:reduce){.est2 .est2-toast{animation:none}}.est2 tbody tr.est2-row--group{cursor:pointer;-webkit-user-select:none;user-select:none}.est2 tbody tr.est2-row--group td{background:var(--est-bg-subtle);font-weight:600;color:var(--est-fg);border-bottom:1px solid var(--est-border)}.est2 tbody tr.est2-row--group:hover td{background:var(--est-bg-hover)}.est2 .est2-td--group-key{color:var(--est-fg)}.est2 .est2-group-key{display:inline-flex;align-items:center;gap:6px}.est2 .est2-group-chevron{display:inline-flex;color:var(--est-fg-muted);transition:transform var(--est-transition)}.est2 .est2-group-chevron--open{transform:rotate(90deg)}.est2 .est2-hier-lead{display:inline-flex;align-items:center;vertical-align:middle;margin-right:4px}.est2 .est2-hier-toggle{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;flex:none}.est2 .est2-group-chevron.est2-hier-toggle{cursor:pointer;border-radius:var(--est-radius-sm);transition:transform var(--est-transition),background var(--est-transition),color var(--est-transition)}.est2 .est2-group-chevron.est2-hier-toggle:hover{background:var(--est-bg-hover);color:var(--est-fg)}.est2 th.est2-th--orderable{cursor:pointer;transition:color var(--est-transition)}.est2 th.est2-th--orderable:hover{color:var(--est-fg)}.est2 .est2-th__inner{display:inline-flex;align-items:center;gap:6px}.est2 .est2-sort{display:inline-flex;width:14px;height:14px;opacity:.35;transition:opacity var(--est-transition),transform var(--est-transition)}.est2 .est2-sort--active{opacity:1;color:var(--est-accent)}.est2 .est2-sort--desc{transform:rotate(180deg)}.est2 .est2-sort__badge{font-size:9px;font-weight:700;color:var(--est-accent);margin-left:2px}.est2 .est2-check{appearance:none;width:16px;height:16px;border:1.5px solid var(--est-border-strong);border-radius:var(--est-radius-sm);background:var(--est-bg);cursor:pointer;position:relative;transition:border-color var(--est-transition),background var(--est-transition);vertical-align:middle;flex:none}.est2 .est2-check:hover{border-color:var(--est-accent)}.est2 .est2-check:checked{background:var(--est-accent);border-color:var(--est-accent)}.est2 .est2-check:checked:after{content:\"\";position:absolute;left:4.5px;top:1.5px;width:4px;height:8px;border:solid var(--est-accent-fg);border-width:0 2px 2px 0;transform:rotate(45deg)}.est2 .est2-check:focus-visible{outline:none;box-shadow:var(--est-ring)}.est2 .est2-check:indeterminate{background:var(--est-accent);border-color:var(--est-accent)}.est2 .est2-check:indeterminate:after{content:\"\";position:absolute;left:3px;top:6px;width:8px;height:2px;background:var(--est-accent-fg);transform:none;border:none}.est2 th.est2-col-min,.est2 td.est2-col-min{width:1%;white-space:nowrap}.est2 .est2-wrap{position:relative}.est2 .est2-selectbar{position:absolute;top:0;left:0;right:0;z-index:15;display:flex;align-items:center;gap:12px;padding:9px 14px;font-size:var(--est-fs-sm);color:var(--est-fg);background:var(--est-bg-subtle);border:none;border-radius:var(--est-radius) var(--est-radius) 0 0;box-shadow:inset 3px 0 0 var(--est-accent)}.est2 .est2-selectbar strong{color:var(--est-fg);font-weight:700}.est2 .est2-selectbar__spacer{flex:1 1 auto}.est2 .est2-link{color:var(--est-accent);cursor:pointer;font-weight:600}.est2 .est2-link:hover{text-decoration:underline}.est2 .est2-rowaction{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:var(--est-radius-sm);border:none;background:transparent;color:var(--est-fg-faint);cursor:pointer;transition:background var(--est-transition),color var(--est-transition)}.est2 .est2-rowaction:hover{background:var(--est-bg-hover);color:var(--est-fg)}.est2 .est2-rowaction--danger:hover{color:var(--est-danger)}.est2 .est2-op-cell{text-align:center}.est2 .est2-op{display:inline-flex;align-items:center;justify-content:center;min-width:26px;height:26px;padding:0 6px;border-radius:var(--est-radius-sm);color:var(--est-accent);cursor:pointer;font-size:var(--est-fs-sm);transition:background var(--est-transition)}.est2 .est2-op:hover{background:var(--est-accent-weak)}.est2 .est2-loading{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:10px;background:color-mix(in srgb,var(--est-bg) 70%,transparent);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);z-index:5;color:var(--est-fg-muted);font-size:var(--est-fs-sm)}.est2 .est2-spinner{width:16px;height:16px;border:2px solid var(--est-border-strong);border-top-color:var(--est-accent);border-radius:50%;animation:est2-spin .7s linear infinite}@keyframes est2-spin{to{transform:rotate(360deg)}}.est2 .est2-nowrap{white-space:nowrap}.est2 .est2-cornermenu{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:var(--est-radius-sm);cursor:pointer;color:var(--est-fg-muted);transition:background var(--est-transition)}.est2 .est2-cornermenu:hover{background:var(--est-bg-hover);color:var(--est-fg)}.est2 .est2-chrome-th{position:relative}.est2 .est2-chrome{display:inline-flex;align-items:center;gap:2px}.est2 .est2-chrome-wrap{position:relative;display:inline-flex}.est2 .est2-chrome-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:none;background:transparent;border-radius:var(--est-radius-sm);color:var(--est-fg-muted);cursor:pointer;font-size:16px;line-height:1;transition:background var(--est-transition),color var(--est-transition)}.est2 .est2-chrome-btn:hover{background:var(--est-bg-hover);color:var(--est-fg)}.est2 .est2-menu{position:absolute;top:calc(100% + 4px);right:0;z-index:30;min-width:180px;padding:4px;background:var(--est-bg);border:1px solid var(--est-border);border-radius:var(--est-radius);box-shadow:0 8px 24px #0000002e;display:flex;flex-direction:column}.est2 .est2-menu-item{display:block;width:100%;padding:8px 10px;border:none;background:transparent;text-align:left;font:inherit;color:var(--est-fg);border-radius:var(--est-radius-sm);cursor:pointer}.est2 .est2-menu-item:hover{background:var(--est-bg-hover)}.est2 .est2-dialog-backdrop{position:absolute;inset:0;z-index:40;display:flex;align-items:center;justify-content:center;padding:16px;background:color-mix(in srgb,#000 32%,transparent)}.est2 .est2-dialog{width:520px;max-width:calc(100vw - 32px);max-height:100%;display:flex;flex-direction:column;background:var(--est-bg);color:var(--est-fg);border:1px solid var(--est-border);border-radius:var(--est-radius);box-shadow:0 16px 48px #0000004d;overflow:hidden}.est2 .est2-dialog__head{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;font-weight:700;border-bottom:1px solid var(--est-border)}.est2 .est2-dialog__close{border:none;background:transparent;cursor:pointer;color:var(--est-fg-muted);font-size:15px;line-height:1;padding:4px;border-radius:var(--est-radius-sm)}.est2 .est2-dialog__close:hover{background:var(--est-bg-hover);color:var(--est-fg)}.est2 .est2-dialog__tools{padding:8px 14px;font-size:var(--est-fs-sm);border-bottom:1px solid var(--est-border)}.est2 .est2-dialog__tools .est2-link{border:none;background:none;padding:0;font:inherit;font-weight:600}.est2 .est2-dialog__sep{margin:0 6px;color:var(--est-fg-faint)}.est2 .est2-collist{list-style:none;margin:0;padding:6px;overflow-y:auto}.est2 .est2-collist__row{display:flex;align-items:center;gap:10px;padding:6px 8px;border-radius:var(--est-radius-sm)}.est2 .est2-collist__row:hover{background:var(--est-bg-hover)}.est2 .est2-collist__row--drag{cursor:grab}.est2 .est2-collist__row--drag:active{cursor:grabbing}.est2 .est2-collist__row--dragging{opacity:.45;background:var(--est-bg-hover)}.est2 .est2-collist__row--drop-above{box-shadow:inset 0 2px 0 0 var(--est-accent)}.est2 .est2-collist__row--drop-below{box-shadow:inset 0 -2px 0 0 var(--est-accent)}.est2 .est2-collist__grip{display:inline-flex;align-items:center;justify-content:center;color:var(--est-fg-faint);cursor:grab;flex:0 0 auto}.est2 .est2-collist__grip:active{cursor:grabbing}.est2 .est2-collist__row--drag:hover .est2-collist__grip{color:var(--est-fg-muted)}.est2 .est2-collist__vis{display:inline-flex}.est2 .est2-collist__label{flex:1 1 auto}.est2 .est2-collist__ord{display:inline-flex;gap:4px}.est2 .est2-iconbtn{width:26px;height:26px;border:1px solid var(--est-border);background:var(--est-bg);border-radius:var(--est-radius-sm);color:var(--est-fg-muted);cursor:pointer}.est2 .est2-iconbtn:hover:not(:disabled){background:var(--est-bg-hover);color:var(--est-fg)}.est2 .est2-iconbtn:disabled{opacity:.4;cursor:default}.est2 .est2-dialog__foot{display:flex;align-items:center;gap:8px;padding:12px 14px;border-top:1px solid var(--est-border)}.est2 .est2-dialog__spacer{flex:1 1 auto}.est2 .est2-btn{padding:7px 14px;border-radius:var(--est-radius-sm);font:inherit;font-weight:600;cursor:pointer;border:1px solid transparent}.est2 .est2-btn--ghost{background:transparent;color:var(--est-fg);border-color:var(--est-border)}.est2 .est2-btn--ghost:hover{background:var(--est-bg-hover)}.est2 .est2-btn--primary{background:var(--est-accent);color:var(--est-accent-fg)}.est2 .est2-btn--primary:hover{filter:brightness(1.05)}.est2 .est2-empty{padding:48px 16px;text-align:center;color:var(--est-fg-faint);font-size:var(--est-fs-sm)}.ngx-contextmenu{--ctx-bg: #ffffff;--ctx-fg: #1a1d24;--ctx-muted: #626b7a;--ctx-border: #e6e9ef;--ctx-hover: #f2f4f7;--ctx-accent: #4f46e5;--ctx-shadow: 0 10px 28px -8px rgba(16, 24, 40, .22), 0 2px 8px -3px rgba(16, 24, 40, .14);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.ngx-contextmenu .dropdown-menu{display:block;min-width:200px;margin:0;padding:6px;list-style:none;background:var(--ctx-bg);border:1px solid var(--ctx-border);border-radius:12px;box-shadow:var(--ctx-shadow);animation:est2-ctx-in .12s cubic-bezier(.16,1,.3,1)}.ngx-contextmenu li{list-style:none;margin:0}.ngx-contextmenu li>a{display:flex;align-items:center;gap:8px;padding:8px 12px;border-radius:7px;color:var(--ctx-fg);font-size:13.5px;line-height:1.2;text-decoration:none;cursor:pointer;white-space:nowrap;transition:background .12s ease,color .12s ease}.ngx-contextmenu li>a:hover,.ngx-contextmenu li>a:focus{background:var(--ctx-hover);color:var(--ctx-fg);text-decoration:none;outline:none}.ngx-contextmenu li.divider,.ngx-contextmenu li[role=separator]{height:1px;margin:6px 8px;padding:0;background:var(--ctx-border)}.ngx-contextmenu li.disabled>a,.ngx-contextmenu li[aria-disabled=true]>a{color:var(--ctx-muted);opacity:.55;pointer-events:none}@media(prefers-color-scheme:dark){.ngx-contextmenu{--ctx-bg: #1e222b;--ctx-fg: #e7eaf0;--ctx-muted: #9aa3b2;--ctx-border: #2a2f3a;--ctx-hover: #232733;--ctx-accent: #7c74ff;--ctx-shadow: 0 12px 30px -8px rgba(0, 0, 0, .6), 0 2px 8px -3px rgba(0, 0, 0, .5)}}@keyframes est2-ctx-in{0%{opacity:0;transform:translateY(-4px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}@media(prefers-reduced-motion:reduce){.ngx-contextmenu .dropdown-menu{animation:none}}\n"] }]
|
|
8702
8940
|
}], ctorParameters: () => [{ type: i1.NgControl, decorators: [{
|
|
8703
8941
|
type: Self
|
|
8704
8942
|
}, {
|
|
@@ -8790,6 +9028,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
8790
9028
|
}], onDocClick: [{
|
|
8791
9029
|
type: HostListener,
|
|
8792
9030
|
args: ['document:click']
|
|
9031
|
+
}], onResizeMove: [{
|
|
9032
|
+
type: HostListener,
|
|
9033
|
+
args: ['document:mousemove', ['$event']]
|
|
9034
|
+
}], onResizeEnd: [{
|
|
9035
|
+
type: HostListener,
|
|
9036
|
+
args: ['document:mouseup']
|
|
8793
9037
|
}] } });
|
|
8794
9038
|
|
|
8795
9039
|
const MODULES = [
|