@esfaenza/es-table 20.3.13 → 20.3.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, input, EventEmitter, signal, computed, effect, Output, ChangeDetectionStrategy, ViewEncapsulation, Component, Input, Self, Directive, Optional, ContentChildren, InjectionToken, Inject, HostListener, Pipe, model, ViewChild, ContentChild, NgModule } from '@angular/core';
2
+ import { Injectable, input, EventEmitter, signal, computed, effect, Output, ChangeDetectionStrategy, ViewEncapsulation, Component, Input, Self, Directive, Optional, ContentChildren, InjectionToken, Inject, HostListener, Pipe, model, ViewChild, ContentChild, output, linkedSignal, HostBinding, NgModule } from '@angular/core';
3
3
  import * as i2 from '@esfaenza/localizations';
4
4
  import { LocalizationService, LocalizationModule } from '@esfaenza/localizations';
5
5
  import * as i5 from '@esfaenza/extensions';
@@ -1263,8 +1263,15 @@ class ColumnMetadataHandler {
1263
1263
  let newHeaders = [];
1264
1264
  // Se devo finalizzare aggancio tutte le variabili di bind
1265
1265
  if (finalize) {
1266
+ // Mappa ID -> header costruita una sola volta (evita il .filter() lineare per ogni OrderDirective)
1267
+ let headersById = new Map();
1268
+ for (let h = 0; h < this.est.TMPRowTHs.length; h++) {
1269
+ let header = this.est.TMPRowTHs[h];
1270
+ if (!headersById.has(header.ID))
1271
+ headersById.set(header.ID, header);
1272
+ }
1266
1273
  this.est.OrderDirectives.forEach(o => {
1267
- let heaedrItem = this.est.TMPRowTHs.filter(t => t.ID == o)[0];
1274
+ let heaedrItem = headersById.get(o);
1268
1275
  if (heaedrItem)
1269
1276
  newHeaders.push(heaedrItem);
1270
1277
  });
@@ -1273,8 +1280,16 @@ class ColumnMetadataHandler {
1273
1280
  for (let i = 0; i < this.est.RowContexts.length; i++) {
1274
1281
  let context = this.est.TMPRowTDs[this.est.RowContexts[i]];
1275
1282
  let newContext = [];
1283
+ // Mappa ID -> signal costruita una sola volta per riga: il getter del signal viene invocato
1284
+ // una volta per cella invece di una volta per ogni (colonna × OrderDirective)
1285
+ let contextById = new Map();
1286
+ for (let c = 0; c < context.length; c++) {
1287
+ let id = context[c]().ID;
1288
+ if (!contextById.has(id))
1289
+ contextById.set(id, context[c]);
1290
+ }
1276
1291
  this.est.OrderDirectives.forEach(o => {
1277
- let contextItem = context.filter(t => t().ID == o)[0];
1292
+ let contextItem = contextById.get(o);
1278
1293
  // potrebbe esser undefined nel caso ci fossero colonne sotto thIf
1279
1294
  if (contextItem)
1280
1295
  newContext.push(contextItem);
@@ -1669,7 +1684,6 @@ class HierarchyModeHandler {
1669
1684
  this.orderItemsInPlace(objs);
1670
1685
  }
1671
1686
  orderItemsInPlace(items) {
1672
- debugger;
1673
1687
  let ownKey = this.est.OwnKey();
1674
1688
  let parentKey = this.est.ParentKey();
1675
1689
  const childrenMap = new Map();
@@ -3529,9 +3543,15 @@ class Logger {
3529
3543
  constructor(debugMode) {
3530
3544
  this.debugMode = debugMode;
3531
3545
  }
3546
+ /**
3547
+ * Emette un log a console solo se la modalità debug è attiva.
3548
+ *
3549
+ * Il testo può essere passato come funzione (**lazy**): in tal caso viene valutato solo quando il debug è attivo,
3550
+ * evitando di costruire stringhe costose (es. interpolazioni per-cella) quando i log sono disabilitati.
3551
+ */
3532
3552
  log(text) {
3533
3553
  if (this.debugMode)
3534
- console.log("[@esfaenza/es-table @ " + (new Date()).toISOString() + "] " + text);
3554
+ console.log("[@esfaenza/es-table @ " + (new Date()).toISOString() + "] " + (typeof text === 'function' ? text() : text));
3535
3555
  }
3536
3556
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: Logger, deps: [{ token: EST_DEBUG, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
3537
3557
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: Logger, providedIn: "root" }); }
@@ -3861,7 +3881,7 @@ class EstRendererPipe {
3861
3881
  this.logger.log(`Renderer retrieved in a non performant way`);
3862
3882
  EsTd = params.src.find((item) => item.tdForProperty === propName);
3863
3883
  }
3864
- this.logger.log(`Renderer ${EsTd ? 'found' : 'NOT found'} for '${propName}${params.name ? '/' + params.name : ''}'${!EsTd ? '. Using fallback renderer' : ''}`);
3884
+ this.logger.log(() => `Renderer ${EsTd ? 'found' : 'NOT found'} for '${propName}${params.name ? '/' + params.name : ''}'${!EsTd ? '. Using fallback renderer' : ''}`);
3865
3885
  var ret = EsTd ? EsTd.Template : params.fallback;
3866
3886
  return ret;
3867
3887
  }
@@ -3898,7 +3918,7 @@ class EstEditorPipe {
3898
3918
  this.logger.log(`Editor retrieved in a non performant way`);
3899
3919
  Editor = params.src.find((item) => item.editorForProperty === propName);
3900
3920
  }
3901
- this.logger.log(`Editor ${Editor ? 'found' : 'NOT found'} for '${propName}${params.name ? '/' + params.name : ''}'${!Editor ? '. Using fallback editor' : ''}`);
3921
+ this.logger.log(() => `Editor ${Editor ? 'found' : 'NOT found'} for '${propName}${params.name ? '/' + params.name : ''}'${!Editor ? '. Using fallback editor' : ''}`);
3902
3922
  var ret = Editor ? Editor.Template : params.fallback;
3903
3923
  return ret;
3904
3924
  }
@@ -4957,69 +4977,77 @@ class EsTableComponent {
4957
4977
  let contextFixeds = {};
4958
4978
  for (let i = 0; i < tds.length; i++) {
4959
4979
  let td = tds[i];
4980
+ // Valori costanti per colonna: calcolati una sola volta invece che per ogni riga
4981
+ let key = td.td;
4982
+ let th = this.thsCache[td.td];
4983
+ if (!!th)
4984
+ th._referenced = true;
4985
+ // Skippare senza th (casistica tabella dinamica)
4986
+ if (!(th && th.thIf && (td.tdIf || th.thGroup)))
4987
+ continue;
4988
+ let thCasted = th;
4989
+ let colDef = columnsCache[td.td];
4990
+ let isFixed = th.thFixed || th.thGroup || th.thPinned;
4991
+ let multiProp = td._multi ? td.td.split('_')[0] : null;
4992
+ let multiIndex = td._multi ? td.td.split('_')[1] : null;
4993
+ let alignment = (td.tdAlignment || th.thAlignment || defaultAlignment).toLowerCase();
4994
+ let format = td.tdFormat || th.thFormat;
4995
+ let template = thCasted.thGroup && !this.ShowItemGroupsColumns ? null : td.Template;
4996
+ let groupClass = thCasted.thGroupClass || '';
4960
4997
  for (let i = 0; i < bs.length; i++) {
4961
4998
  let item = bs[i];
4962
4999
  let ctx = item._hash;
4963
- let key = td.td;
4964
- let th = this.thsCache[td.td];
4965
- if (!!th)
4966
- th._referenced = true;
4967
- // Skippare senza th (casistica tabella dinamica)
4968
- if (th && th.thIf && (td.tdIf || th.thGroup)) {
4969
- let thCasted = th;
4970
- let isFixed = th.thFixed || th.thGroup || th.thPinned;
4971
- let itemToAdd = item._item ?
4972
- {
4973
- LeftBorder: thCasted._lbord,
4974
- RightBorder: thCasted._rbord,
4975
- ID: key,
4976
- Pinned: columnsCache[td.td].pinned,
4977
- PinnedWidth: thCasted.thPinnedWidth,
4978
- GroupHeader: false,
4979
- GroupAggregation: false,
4980
- Visible: columnsCache[td.td].visible,
4981
- Wraps: false,
4982
- Class: td.tdClass,
4983
- IconClass: td.tdIconClass,
4984
- ConditionField: td.tdConditionField,
4985
- ConditionValue: td.tdConditionValue,
4986
- Title: td.tdTitle,
4987
- Content: td.tdContent,
4988
- Template: thCasted.thGroup && !this.ShowItemGroupsColumns ? null : td.Template,
4989
- Multi: td._multi,
4990
- MultiProp: td._multi ? td.td.split('_')[0] : null,
4991
- MultiIndex: td._multi ? td.td.split('_')[1] : null,
4992
- Alignment: (td.tdAlignment || th.thAlignment || defaultAlignment).toLowerCase(),
4993
- Format: td.tdFormat || th.thFormat,
4994
- PropertyName: th.thProperty,
4995
- PropertyType: th.thType,
4996
- PropertySource: th.thSource,
4997
- EditorOptions: th.thEditorOptions,
4998
- PropAccessor: td.tdPropertyAccessor,
4999
- RoutePath: thCasted.thRoutePath,
5000
- RouteParameterProperties: thCasted.thRouteParameterProperties
5001
- } :
5002
- {
5003
- LeftBorder: thCasted._lbord,
5004
- RightBorder: thCasted._rbord,
5005
- ID: key,
5006
- Pinned: columnsCache[td.td].pinned,
5007
- PinnedWidth: thCasted.thPinnedWidth,
5008
- GroupHeader: thCasted.thGroup,
5009
- GroupAggregation: !!thCasted.thAggregation,
5010
- Visible: columnsCache[td.td].visible,
5011
- Wraps: false,
5012
- Class: thCasted.thGroupClass || '',
5013
- Content: this.rowGrouping_H.getGroupCellDisplay(item, key, th)
5014
- };
5015
- if (this.TMPRowTDs[ctx]) {
5016
- this.TMPRowTDs[ctx].push(signal(itemToAdd));
5017
- }
5018
- else {
5019
- this.RowContexts.push(ctx);
5020
- this.TMPRowTDs[ctx] = [signal(itemToAdd)];
5021
- contextFixeds[ctx] = isFixed ? 1 : 0;
5022
- }
5000
+ let itemToAdd = item._item ?
5001
+ {
5002
+ LeftBorder: thCasted._lbord,
5003
+ RightBorder: thCasted._rbord,
5004
+ ID: key,
5005
+ Pinned: colDef.pinned,
5006
+ PinnedWidth: thCasted.thPinnedWidth,
5007
+ GroupHeader: false,
5008
+ GroupAggregation: false,
5009
+ Visible: colDef.visible,
5010
+ Wraps: false,
5011
+ Class: td.tdClass,
5012
+ IconClass: td.tdIconClass,
5013
+ ConditionField: td.tdConditionField,
5014
+ ConditionValue: td.tdConditionValue,
5015
+ Title: td.tdTitle,
5016
+ Content: td.tdContent,
5017
+ Template: template,
5018
+ Multi: td._multi,
5019
+ MultiProp: multiProp,
5020
+ MultiIndex: multiIndex,
5021
+ Alignment: alignment,
5022
+ Format: format,
5023
+ PropertyName: th.thProperty,
5024
+ PropertyType: th.thType,
5025
+ PropertySource: th.thSource,
5026
+ EditorOptions: th.thEditorOptions,
5027
+ PropAccessor: td.tdPropertyAccessor,
5028
+ RoutePath: thCasted.thRoutePath,
5029
+ RouteParameterProperties: thCasted.thRouteParameterProperties
5030
+ } :
5031
+ {
5032
+ LeftBorder: thCasted._lbord,
5033
+ RightBorder: thCasted._rbord,
5034
+ ID: key,
5035
+ Pinned: colDef.pinned,
5036
+ PinnedWidth: thCasted.thPinnedWidth,
5037
+ GroupHeader: thCasted.thGroup,
5038
+ GroupAggregation: !!thCasted.thAggregation,
5039
+ Visible: colDef.visible,
5040
+ Wraps: false,
5041
+ Class: groupClass,
5042
+ Content: this.rowGrouping_H.getGroupCellDisplay(item, key, th)
5043
+ };
5044
+ if (this.TMPRowTDs[ctx]) {
5045
+ this.TMPRowTDs[ctx].push(signal(itemToAdd));
5046
+ }
5047
+ else {
5048
+ this.RowContexts.push(ctx);
5049
+ this.TMPRowTDs[ctx] = [signal(itemToAdd)];
5050
+ contextFixeds[ctx] = isFixed ? 1 : 0;
5023
5051
  }
5024
5052
  }
5025
5053
  }
@@ -5196,7 +5224,6 @@ class EsTableComponent {
5196
5224
  * @returns {boolean} **true** se l'operaizone è andata a buon fine, **false** altrimenti
5197
5225
  */
5198
5226
  handleClick(event, item) {
5199
- debugger;
5200
5227
  this.selection_H.clearSelectionStatusExcluding(undefined);
5201
5228
  let selecteditems = [];
5202
5229
  if (event.shiftKey && this.ShiftClick() && !this.SingleSelection()) {
@@ -5636,7 +5663,6 @@ class EsTableComponent {
5636
5663
  //* Piango ancora
5637
5664
  // Gestione Embedding
5638
5665
  showDialog(dlg, elementRef) {
5639
- debugger;
5640
5666
  dlg.show();
5641
5667
  this.retries = 0;
5642
5668
  if (this.emb.Embedded)
@@ -5768,6 +5794,2591 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
5768
5794
  class EsTableModuleConfig {
5769
5795
  }
5770
5796
 
5797
+ /**
5798
+ * Formattazione di presentazione per il renderer di default (celle senza template).
5799
+ * Self-contained (Intl / Date nativi) per non trascinare dipendenze esterne.
5800
+ *
5801
+ * `format` opzionale sovrascrive il default per tipo:
5802
+ * - numerici → "F2" = 2 decimali fissi
5803
+ * - date → pattern accettato da Intl (fallback a locale corto)
5804
+ */
5805
+ class Est2FormatPipe {
5806
+ transform(value, type, format, locale) {
5807
+ if (value == null || value === '')
5808
+ return '';
5809
+ switch (type) {
5810
+ case 'int':
5811
+ case 'number':
5812
+ return this.num(value, format, locale, 0);
5813
+ case 'float':
5814
+ return this.num(value, format, locale, undefined);
5815
+ case 'currency':
5816
+ return this.num(value, format, locale, 2);
5817
+ case 'date':
5818
+ return this.date(value, locale, false);
5819
+ case 'datetime':
5820
+ return this.date(value, locale, true);
5821
+ case 'boolean':
5822
+ return value === true || value === 'true' ? '✓' : value === false || value === 'false' ? '—' : String(value);
5823
+ default:
5824
+ return String(value);
5825
+ }
5826
+ }
5827
+ num(value, format, locale, defDigits) {
5828
+ const n = typeof value === 'number' ? value : parseFloat(String(value).replace(',', '.'));
5829
+ if (isNaN(n))
5830
+ return String(value);
5831
+ let digits = defDigits;
5832
+ if (format && /^F\d+$/.test(format))
5833
+ digits = parseInt(format.slice(1), 10);
5834
+ return n.toLocaleString(locale || undefined, digits != null ? { minimumFractionDigits: digits, maximumFractionDigits: digits } : undefined);
5835
+ }
5836
+ date(value, locale, withTime) {
5837
+ const d = value instanceof Date ? value : new Date(value);
5838
+ if (isNaN(d.getTime()))
5839
+ return String(value);
5840
+ return d.toLocaleString(locale || undefined, withTime
5841
+ ? { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }
5842
+ : { year: 'numeric', month: '2-digit', day: '2-digit' });
5843
+ }
5844
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: Est2FormatPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
5845
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.28", ngImport: i0, type: Est2FormatPipe, isStandalone: false, name: "est2_format" }); }
5846
+ }
5847
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: Est2FormatPipe, decorators: [{
5848
+ type: Pipe,
5849
+ args: [{ name: 'est2_format', standalone: false }]
5850
+ }] });
5851
+
5852
+ /**
5853
+ * Risolve un valore verso il suo `{ id, description }` in una sorgente enum.
5854
+ * Pura: ricalcola solo quando cambiano `value` o `source`.
5855
+ */
5856
+ class Est2LookupPipe {
5857
+ transform(value, source) {
5858
+ if (source)
5859
+ for (const entry of source)
5860
+ if (entry.id == value)
5861
+ return entry;
5862
+ return { id: '', description: value == null ? '' : String(value) };
5863
+ }
5864
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: Est2LookupPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
5865
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.28", ngImport: i0, type: Est2LookupPipe, isStandalone: false, name: "est2_lookup" }); }
5866
+ }
5867
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: Est2LookupPipe, decorators: [{
5868
+ type: Pipe,
5869
+ args: [{ name: 'est2_lookup', standalone: false }]
5870
+ }] });
5871
+
5872
+ /** Abilita il logging a console */
5873
+ const EST2_DEBUG = new InjectionToken('EST2_DEBUG');
5874
+ /** Espressione ACL globale che abilita/disabilita l'export */
5875
+ const EST2_EXPORT_GLOBAL_ACL = new InjectionToken('EST2_EXPORT_GLOBAL_ACL');
5876
+ /** Valori di default per-progetto degli Input */
5877
+ const EST2_DEFAULTS = new InjectionToken('EST2_DEFAULTS');
5878
+
5879
+ // =============================================================================
5880
+ // es-table2 — motore di raggruppamento/aggregazione lato client (array mode)
5881
+ // -----------------------------------------------------------------------------
5882
+ // Funzioni pure: dato un array piatto, le colonne di raggruppamento (in ordine)
5883
+ // e le specifiche di aggregazione, produce una lista piatta di righe-gruppo e
5884
+ // righe-oggetto già ordinate per la visualizzazione ad albero.
5885
+ // =============================================================================
5886
+ /**
5887
+ * Estrae le specifiche di aggregazione da un template come `'{min:long} - {max:long}'`.
5888
+ * Token senza `:` vengono trattati come formato assente (datatype string).
5889
+ */
5890
+ function parseAggregationSpecs(column, template) {
5891
+ if (!template)
5892
+ return [];
5893
+ const tokens = template.match(/\{[a-zA-Z0-9/:]*\}/g);
5894
+ if (!tokens)
5895
+ return [];
5896
+ const specs = [];
5897
+ for (const token of tokens) {
5898
+ const inner = token.substring(1, token.length - 1);
5899
+ const [func, format = ''] = inner.split(':');
5900
+ if (func !== 'min' && func !== 'max' && func !== 'sum')
5901
+ continue;
5902
+ let datatype = 'string';
5903
+ if (format === 'small' || format === 'long' || format === 'verylong')
5904
+ datatype = 'date';
5905
+ else if (/^F\d$/.test(format))
5906
+ datatype = 'number';
5907
+ specs.push({ column, func, format, match: token, datatype });
5908
+ }
5909
+ return specs;
5910
+ }
5911
+ /** Calcola il valore grezzo (stringa) di una aggregazione sugli item di un gruppo */
5912
+ function computeAggValue(items, spec) {
5913
+ const raw = items.map(i => i?.[spec.column]);
5914
+ if (spec.datatype === 'number') {
5915
+ const nums = raw.map(v => parseFloat(v)).filter(n => !isNaN(n));
5916
+ if (nums.length === 0)
5917
+ return '';
5918
+ if (spec.func === 'sum')
5919
+ return nums.reduce((a, b) => a + b, 0).toString();
5920
+ return (spec.func === 'max' ? Math.max(...nums) : Math.min(...nums)).toString();
5921
+ }
5922
+ if (spec.datatype === 'date') {
5923
+ if (spec.func === 'sum')
5924
+ return '';
5925
+ const times = raw.map(v => new Date(v).getTime()).filter(t => !isNaN(t));
5926
+ if (times.length === 0)
5927
+ return '';
5928
+ return new Date(spec.func === 'max' ? Math.max(...times) : Math.min(...times)).toISOString();
5929
+ }
5930
+ const strs = raw.map(v => (v == null ? '' : String(v)));
5931
+ if (spec.func === 'sum')
5932
+ return strs.join(', ');
5933
+ const sorted = [...strs].sort();
5934
+ return spec.func === 'max' ? sorted[sorted.length - 1] : sorted[0];
5935
+ }
5936
+ /** Formatta il valore grezzo di un'aggregazione secondo il formato richiesto */
5937
+ function formatAggValue(value, format, locale) {
5938
+ if (value == null || value === '' || !format)
5939
+ return value ?? '';
5940
+ if (/^F\d$/.test(format)) {
5941
+ const n = parseFloat(value);
5942
+ return isNaN(n) ? value : n.toFixed(parseInt(format[1], 10));
5943
+ }
5944
+ if (format === 'small' || format === 'long' || format === 'verylong') {
5945
+ const d = new Date(value);
5946
+ if (isNaN(d.getTime()))
5947
+ return value;
5948
+ const opts = format === 'small'
5949
+ ? { year: 'numeric', month: '2-digit', day: '2-digit' }
5950
+ : format === 'long'
5951
+ ? { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }
5952
+ : { weekday: 'short', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' };
5953
+ return d.toLocaleString(locale || undefined, opts);
5954
+ }
5955
+ return value;
5956
+ }
5957
+ /** Raggruppa un array per il valore di una proprietà, preservando l'ordine di prima apparizione */
5958
+ function groupBy(items, key) {
5959
+ const out = new Map();
5960
+ for (const it of items) {
5961
+ const k = String(it?.[key] ?? '');
5962
+ const bucket = out.get(k);
5963
+ if (bucket)
5964
+ bucket.push(it);
5965
+ else
5966
+ out.set(k, [it]);
5967
+ }
5968
+ return out;
5969
+ }
5970
+ function computeAggMap(items, specs) {
5971
+ const byCol = new Map();
5972
+ for (const s of specs) {
5973
+ let m = byCol.get(s.column);
5974
+ if (!m) {
5975
+ m = new Map();
5976
+ byCol.set(s.column, m);
5977
+ }
5978
+ if (!m.has(s.func))
5979
+ m.set(s.func, computeAggValue(items, s));
5980
+ }
5981
+ return byCol;
5982
+ }
5983
+ function buildLevel(items, groupCols, specs, level, parent) {
5984
+ const col = groupCols[level];
5985
+ const last = level + 1 === groupCols.length;
5986
+ const buckets = groupBy(items, col);
5987
+ const ret = [];
5988
+ for (const [value, groupItems] of buckets) {
5989
+ const key = (parent ? parent._thisKey : '') + '¦' + col + '=' + value;
5990
+ const g = {
5991
+ _group: true,
5992
+ _expanded: false,
5993
+ column: col,
5994
+ value,
5995
+ count: groupItems.length,
5996
+ _grouplevel: parent ? parent._grouplevel + 1 : 0,
5997
+ _thisKey: key,
5998
+ _parent: parent ? parent._thisKey : null,
5999
+ _visible: level === 0,
6000
+ _agg: computeAggMap(groupItems, specs)
6001
+ };
6002
+ ret.push(g);
6003
+ if (!last) {
6004
+ const sub = buildLevel(groupItems, groupCols, specs, level + 1, g);
6005
+ g._groupscount = sub.filter(r => r._group && r._parent === key).length;
6006
+ ret.push(...sub);
6007
+ }
6008
+ else {
6009
+ for (const it of groupItems) {
6010
+ it._thisKey = key;
6011
+ it._visible = false;
6012
+ }
6013
+ ret.push(...groupItems);
6014
+ }
6015
+ }
6016
+ return ret;
6017
+ }
6018
+ /**
6019
+ * Costruisce la lista piatta di righe (gruppi + oggetti) per la visualizzazione.
6020
+ * Gli oggetti vengono marcati `_item`, i gruppi `_group`; solo i gruppi di primo
6021
+ * livello partono `_visible`.
6022
+ */
6023
+ function buildGroupedRows(items, groupCols, specs) {
6024
+ for (const i of items) {
6025
+ i._item = true;
6026
+ i._visible = false;
6027
+ }
6028
+ return buildLevel(items, groupCols, specs, 0, null);
6029
+ }
6030
+
6031
+ // =============================================================================
6032
+ // es-table2 — modalità gerarchica (albero padre/figlio)
6033
+ // -----------------------------------------------------------------------------
6034
+ // Dato un array piatto in cui ogni oggetto conosce la propria chiave (ownKey) e
6035
+ // quella del padre (parentKey), assegna livello/visibilità/espansione e riordina
6036
+ // gli elementi in ordine depth-first (padre seguito dal proprio sottoalbero).
6037
+ //
6038
+ // Algoritmo O(n) basato su mappe (l'originale era O(n²)). Gli orfani (padre non
6039
+ // presente nel set) sono trattati come radici, così nessuna riga sparisce.
6040
+ // =============================================================================
6041
+ /** Confronto per l'ordinamento dei fratelli: numerico se possibile, altrimenti stringa */
6042
+ function compareKeys(a, b) {
6043
+ const na = parseFloat(a), nb = parseFloat(b);
6044
+ if (!isNaN(na) && !isNaN(nb) && String(na) === String(a) && String(nb) === String(b))
6045
+ return na - nb;
6046
+ return String(a ?? '').localeCompare(String(b ?? ''));
6047
+ }
6048
+ /**
6049
+ * Assegna i metadati gerarchici (`_level`, `_visible`, `_expanded`, `parent`) e
6050
+ * restituisce gli elementi in ordine depth-first pronti per la visualizzazione.
6051
+ *
6052
+ * @param items array piatto di oggetti
6053
+ * @param ownKey proprietà con l'id dell'oggetto
6054
+ * @param parentKey proprietà con l'id del padre
6055
+ * @param startsExpanded se true l'albero parte tutto espanso
6056
+ * @param autoSort se true ordina i fratelli per `ownKey`
6057
+ */
6058
+ function assignHierarchyLevels(items, ownKey, parentKey, startsExpanded, autoSort) {
6059
+ if (!items || items.length === 0)
6060
+ return [];
6061
+ const byOwn = new Map();
6062
+ for (const it of items)
6063
+ byOwn.set(String(it[ownKey]), it);
6064
+ const childrenOf = new Map();
6065
+ const roots = [];
6066
+ for (const it of items) {
6067
+ it.parent = false; // reset flag "ha figli"
6068
+ const pv = it[parentKey];
6069
+ const pKey = (pv === null || pv === undefined || pv === '') ? null : String(pv);
6070
+ const hasParent = pKey !== null && byOwn.has(pKey) && byOwn.get(pKey) !== it;
6071
+ if (hasParent) {
6072
+ let arr = childrenOf.get(pKey);
6073
+ if (!arr) {
6074
+ arr = [];
6075
+ childrenOf.set(pKey, arr);
6076
+ }
6077
+ arr.push(it);
6078
+ }
6079
+ else {
6080
+ roots.push(it);
6081
+ }
6082
+ }
6083
+ if (autoSort) {
6084
+ roots.sort((a, b) => compareKeys(a[ownKey], b[ownKey]));
6085
+ for (const arr of childrenOf.values())
6086
+ arr.sort((a, b) => compareKeys(a[ownKey], b[ownKey]));
6087
+ }
6088
+ const ordered = [];
6089
+ const visit = (node, level, ancestorsOpen) => {
6090
+ const kids = childrenOf.get(String(node[ownKey]));
6091
+ node._level = level;
6092
+ node._visible = level === 1 ? true : ancestorsOpen;
6093
+ node.parent = !!(kids && kids.length);
6094
+ node._expanded = node.parent ? startsExpanded : false;
6095
+ ordered.push(node);
6096
+ if (kids)
6097
+ for (const k of kids)
6098
+ visit(k, level + 1, node._visible && node._expanded);
6099
+ };
6100
+ for (const r of roots)
6101
+ visit(r, 1, true);
6102
+ return ordered;
6103
+ }
6104
+ /** Espande un nodo: rende visibili i suoi figli diretti (i nipoti restano nascosti) */
6105
+ function expandNode(rows, ownKey, parentKey, key) {
6106
+ const k = String(key);
6107
+ for (const r of rows)
6108
+ if (String(r[ownKey]) === k) {
6109
+ r._expanded = true;
6110
+ break;
6111
+ }
6112
+ for (const r of rows)
6113
+ if (String(r[parentKey]) === k)
6114
+ r._visible = true;
6115
+ }
6116
+ /** Collassa un nodo: nasconde ricorsivamente tutto il sottoalbero */
6117
+ function collapseNode(rows, ownKey, parentKey, key) {
6118
+ const k = String(key);
6119
+ for (const r of rows)
6120
+ if (String(r[ownKey]) === k) {
6121
+ r._expanded = false;
6122
+ break;
6123
+ }
6124
+ collapseChildren(rows, ownKey, parentKey, k);
6125
+ }
6126
+ function collapseChildren(rows, ownKey, parentKey, parentK) {
6127
+ for (const child of rows) {
6128
+ if (String(child[parentKey]) !== parentK)
6129
+ continue;
6130
+ child._visible = false;
6131
+ if (child.parent) {
6132
+ child._expanded = false;
6133
+ collapseChildren(rows, ownKey, parentKey, String(child[ownKey]));
6134
+ }
6135
+ }
6136
+ }
6137
+
6138
+ /**
6139
+ * Paginatore dell'es-table2.
6140
+ *
6141
+ * Componente di sola presentazione: riceve lo stato di paginazione via input e
6142
+ * notifica le intenzioni dell'utente via output. Non conosce il concetto di
6143
+ * viewMode/arrayMode — la logica di applicazione vive nel componente padre.
6144
+ */
6145
+ class EsTable2PagerComponent {
6146
+ constructor() {
6147
+ /** Valore convenzionale di items-per-page che rappresenta "Tutti" */
6148
+ this.allValue = 999999;
6149
+ this.page = input(1, ...(ngDevMode ? [{ debugName: "page" }] : []));
6150
+ this.pages = input(1, ...(ngDevMode ? [{ debugName: "pages" }] : []));
6151
+ this.total = input(0, ...(ngDevMode ? [{ debugName: "total" }] : []));
6152
+ this.itemsPerPage = input(15, ...(ngDevMode ? [{ debugName: "itemsPerPage" }] : []));
6153
+ this.countLabel = input('', ...(ngDevMode ? [{ debugName: "countLabel" }] : []));
6154
+ this.showCount = input(true, ...(ngDevMode ? [{ debugName: "showCount" }] : []));
6155
+ this.showButtons = input(true, ...(ngDevMode ? [{ debugName: "showButtons" }] : []));
6156
+ this.showPagingOptions = input(true, ...(ngDevMode ? [{ debugName: "showPagingOptions" }] : []));
6157
+ this.allowAll = input(true, ...(ngDevMode ? [{ debugName: "allowAll" }] : []));
6158
+ this.pageChange = output();
6159
+ this.itemsPerPageChange = output();
6160
+ /** Opzioni della select di items-per-page (con eventuale "Tutti") */
6161
+ this.ippOptions = computed(() => {
6162
+ const base = [15, 25, 50, 100];
6163
+ if (!base.includes(this.itemsPerPage()) && this.itemsPerPage() !== this.allValue)
6164
+ base.push(this.itemsPerPage());
6165
+ base.sort((a, b) => a - b);
6166
+ return this.allowAll() ? [...base, this.allValue] : base;
6167
+ }, ...(ngDevMode ? [{ debugName: "ippOptions" }] : []));
6168
+ /** Finestra di pagine visibili con ellissi (1 … n-1 n n+1 … tot) */
6169
+ this.visiblePages = computed(() => {
6170
+ const current = this.page() ?? 1;
6171
+ const total = this.pages();
6172
+ const out = [];
6173
+ const add = (p) => out.push({ key: 'p' + p, page: p });
6174
+ const gap = (id) => out.push({ key: id, page: -1 });
6175
+ const window = new Set([1, total, current - 1, current, current + 1]);
6176
+ let last = 0;
6177
+ for (let p = 1; p <= total; p++) {
6178
+ if (!window.has(p))
6179
+ continue;
6180
+ if (p - last > 1)
6181
+ gap('g' + p);
6182
+ add(p);
6183
+ last = p;
6184
+ }
6185
+ return out;
6186
+ }, ...(ngDevMode ? [{ debugName: "visiblePages" }] : []));
6187
+ }
6188
+ goto(p) {
6189
+ if (p < 1 || p > this.pages() || p === this.page())
6190
+ return;
6191
+ this.pageChange.emit(p);
6192
+ }
6193
+ onIppChange(ev) {
6194
+ this.itemsPerPageChange.emit(+ev.target.value);
6195
+ }
6196
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: EsTable2PagerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6197
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: EsTable2PagerComponent, isStandalone: false, selector: "es-table2-pager", inputs: { page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null }, pages: { classPropertyName: "pages", publicName: "pages", isSignal: true, isRequired: false, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: false, transformFunction: null }, itemsPerPage: { classPropertyName: "itemsPerPage", publicName: "itemsPerPage", isSignal: true, isRequired: false, transformFunction: null }, countLabel: { classPropertyName: "countLabel", publicName: "countLabel", isSignal: true, isRequired: false, transformFunction: null }, showCount: { classPropertyName: "showCount", publicName: "showCount", isSignal: true, isRequired: false, transformFunction: null }, showButtons: { classPropertyName: "showButtons", publicName: "showButtons", isSignal: true, isRequired: false, transformFunction: null }, showPagingOptions: { classPropertyName: "showPagingOptions", publicName: "showPagingOptions", isSignal: true, isRequired: false, transformFunction: null }, allowAll: { classPropertyName: "allowAll", publicName: "allowAll", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageChange: "pageChange", itemsPerPageChange: "itemsPerPageChange" }, ngImport: i0, template: `
6198
+ <div class="est2-pager" *ngIf="page() != null">
6199
+ <div class="est2-pager__count" *ngIf="showCount()">
6200
+ {{ countLabel() || 'Elementi' }}: <strong>{{ total() }}</strong>
6201
+ </div>
6202
+
6203
+ <div class="est2-pager__spacer"></div>
6204
+
6205
+ <label class="est2-pager__ipp" *ngIf="showPagingOptions()">
6206
+ <span>Per pagina</span>
6207
+ <select class="est2-pager__select"
6208
+ [value]="itemsPerPage()"
6209
+ (change)="onIppChange($event)">
6210
+ @for (opt of ippOptions(); track opt) {
6211
+ <option [value]="opt">{{ opt === allValue ? 'Tutti' : opt }}</option>
6212
+ }
6213
+ </select>
6214
+ </label>
6215
+
6216
+ <div class="est2-pager__nav" *ngIf="showButtons() && pages() > 1">
6217
+ <button type="button" class="est2-pager__btn"
6218
+ [disabled]="page()! <= 1"
6219
+ (click)="goto(page()! - 1)"
6220
+ aria-label="Pagina precedente">‹</button>
6221
+
6222
+ @for (p of visiblePages(); track p.key) {
6223
+ @if (p.page === -1) {
6224
+ <span class="est2-pager__ellipsis">…</span>
6225
+ } @else {
6226
+ <button type="button"
6227
+ class="est2-pager__btn"
6228
+ [class.est2-pager__btn--active]="p.page === page()"
6229
+ (click)="goto(p.page)">{{ p.page }}</button>
6230
+ }
6231
+ }
6232
+
6233
+ <button type="button" class="est2-pager__btn"
6234
+ [disabled]="page()! >= pages()"
6235
+ (click)="goto(page()! + 1)"
6236
+ aria-label="Pagina successiva">›</button>
6237
+ </div>
6238
+ </div>
6239
+ `, isInline: true, styles: [".est2-pager{--est-ctrl-h: 30px;display:flex;align-items:center;gap:12px;padding:10px 4px 2px;font-size:var(--est-fs-sm);color:var(--est-fg-muted);flex-wrap:wrap;line-height:1}.est2-pager__spacer{flex:1 1 auto}.est2-pager__count{display:inline-flex;align-items:center;height:var(--est-ctrl-h)}.est2-pager__count strong{color:var(--est-fg);font-weight:600}.est2-pager__ipp{display:inline-flex;align-items:center;height:var(--est-ctrl-h);margin:0;gap:6px}.est2-pager__select{font:inherit;height:var(--est-ctrl-h);color:var(--est-fg);background:var(--est-bg);border:1px solid var(--est-border);border-radius:var(--est-radius-sm);padding:0 8px;cursor:pointer;transition:border-color var(--est-transition)}.est2-pager__select:hover{border-color:var(--est-border-strong)}.est2-pager__select:focus-visible{outline:none;box-shadow:var(--est-ring);border-color:var(--est-accent)}.est2-pager__nav{display:inline-flex;align-items:center;gap:4px}.est2-pager__btn{display:inline-flex;align-items:center;justify-content:center;min-width:var(--est-ctrl-h);height:var(--est-ctrl-h);padding:0 8px;border:1px solid var(--est-border);border-radius:var(--est-radius-sm);background:var(--est-bg);color:var(--est-fg);font:inherit;cursor:pointer;transition:background var(--est-transition),border-color var(--est-transition),color var(--est-transition)}.est2-pager__btn:hover:not(:disabled){background:var(--est-bg-hover);border-color:var(--est-border-strong)}.est2-pager__btn:disabled{opacity:.4;cursor:default}.est2-pager__btn:focus-visible{outline:none;box-shadow:var(--est-ring)}.est2-pager__btn--active{background:var(--est-accent);border-color:var(--est-accent);color:var(--est-accent-fg);font-weight:600}.est2-pager__btn--active:hover:not(:disabled){background:var(--est-accent)}.est2-pager__ellipsis{color:var(--est-fg-faint);padding:0 2px}\n"], dependencies: [{ kind: "directive", type: i8.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
6240
+ }
6241
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: EsTable2PagerComponent, decorators: [{
6242
+ type: Component,
6243
+ args: [{ selector: 'es-table2-pager', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
6244
+ <div class="est2-pager" *ngIf="page() != null">
6245
+ <div class="est2-pager__count" *ngIf="showCount()">
6246
+ {{ countLabel() || 'Elementi' }}: <strong>{{ total() }}</strong>
6247
+ </div>
6248
+
6249
+ <div class="est2-pager__spacer"></div>
6250
+
6251
+ <label class="est2-pager__ipp" *ngIf="showPagingOptions()">
6252
+ <span>Per pagina</span>
6253
+ <select class="est2-pager__select"
6254
+ [value]="itemsPerPage()"
6255
+ (change)="onIppChange($event)">
6256
+ @for (opt of ippOptions(); track opt) {
6257
+ <option [value]="opt">{{ opt === allValue ? 'Tutti' : opt }}</option>
6258
+ }
6259
+ </select>
6260
+ </label>
6261
+
6262
+ <div class="est2-pager__nav" *ngIf="showButtons() && pages() > 1">
6263
+ <button type="button" class="est2-pager__btn"
6264
+ [disabled]="page()! <= 1"
6265
+ (click)="goto(page()! - 1)"
6266
+ aria-label="Pagina precedente">‹</button>
6267
+
6268
+ @for (p of visiblePages(); track p.key) {
6269
+ @if (p.page === -1) {
6270
+ <span class="est2-pager__ellipsis">…</span>
6271
+ } @else {
6272
+ <button type="button"
6273
+ class="est2-pager__btn"
6274
+ [class.est2-pager__btn--active]="p.page === page()"
6275
+ (click)="goto(p.page)">{{ p.page }}</button>
6276
+ }
6277
+ }
6278
+
6279
+ <button type="button" class="est2-pager__btn"
6280
+ [disabled]="page()! >= pages()"
6281
+ (click)="goto(page()! + 1)"
6282
+ aria-label="Pagina successiva">›</button>
6283
+ </div>
6284
+ </div>
6285
+ `, styles: [".est2-pager{--est-ctrl-h: 30px;display:flex;align-items:center;gap:12px;padding:10px 4px 2px;font-size:var(--est-fs-sm);color:var(--est-fg-muted);flex-wrap:wrap;line-height:1}.est2-pager__spacer{flex:1 1 auto}.est2-pager__count{display:inline-flex;align-items:center;height:var(--est-ctrl-h)}.est2-pager__count strong{color:var(--est-fg);font-weight:600}.est2-pager__ipp{display:inline-flex;align-items:center;height:var(--est-ctrl-h);margin:0;gap:6px}.est2-pager__select{font:inherit;height:var(--est-ctrl-h);color:var(--est-fg);background:var(--est-bg);border:1px solid var(--est-border);border-radius:var(--est-radius-sm);padding:0 8px;cursor:pointer;transition:border-color var(--est-transition)}.est2-pager__select:hover{border-color:var(--est-border-strong)}.est2-pager__select:focus-visible{outline:none;box-shadow:var(--est-ring);border-color:var(--est-accent)}.est2-pager__nav{display:inline-flex;align-items:center;gap:4px}.est2-pager__btn{display:inline-flex;align-items:center;justify-content:center;min-width:var(--est-ctrl-h);height:var(--est-ctrl-h);padding:0 8px;border:1px solid var(--est-border);border-radius:var(--est-radius-sm);background:var(--est-bg);color:var(--est-fg);font:inherit;cursor:pointer;transition:background var(--est-transition),border-color var(--est-transition),color var(--est-transition)}.est2-pager__btn:hover:not(:disabled){background:var(--est-bg-hover);border-color:var(--est-border-strong)}.est2-pager__btn:disabled{opacity:.4;cursor:default}.est2-pager__btn:focus-visible{outline:none;box-shadow:var(--est-ring)}.est2-pager__btn--active{background:var(--est-accent);border-color:var(--est-accent);color:var(--est-accent-fg);font-weight:600}.est2-pager__btn--active:hover:not(:disabled){background:var(--est-accent)}.est2-pager__ellipsis{color:var(--est-fg-faint);padding:0 2px}\n"] }]
6286
+ }], 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"] }] } });
6287
+
6288
+ // Angular
6289
+ /**
6290
+ * es-table2 — riscrittura 2026 dell'es-table.
6291
+ *
6292
+ * Mantiene la superficie pubblica (Input/Output/direttive `*th`/`*td`) dell'es-table
6293
+ * originale ma con implementazione basata su signal, control-flow moderno e un design
6294
+ * system a token (tema chiaro/scuro).
6295
+ *
6296
+ * FASE 1: modalità view/array, celle template + direttive, ordinamento, paginazione,
6297
+ * selezione singola/multipla. Le modalità avanzate (grouping, hierarchy, dynamic,
6298
+ * report, range-paste, export) sono dichiarate nella superficie ma verranno collegate
6299
+ * nelle fasi successive.
6300
+ */
6301
+ class EsTable2Component {
6302
+ /** ViewChild-setter: (ri)osserva l'header appena compare/cambia, mantenendo `selbarHeight` allineata */
6303
+ set theadRef(ref) {
6304
+ const el = ref?.nativeElement;
6305
+ if (el === this.headerEl)
6306
+ return;
6307
+ this.headerEl = el;
6308
+ this.headerRO?.disconnect();
6309
+ if (el && typeof ResizeObserver !== 'undefined') {
6310
+ this.headerRO = new ResizeObserver(() => {
6311
+ const h = el.offsetHeight;
6312
+ if (h && h !== this.selbarHeight()) {
6313
+ this.selbarHeight.set(h);
6314
+ this.cdr.markForCheck();
6315
+ }
6316
+ });
6317
+ this.headerRO.observe(el);
6318
+ this.selbarHeight.set(el.offsetHeight);
6319
+ }
6320
+ }
6321
+ get denseClass() { return this.HighCellDensity(); }
6322
+ /** Offset `left` (px) di una colonna sticky all'indice `ci` in visibleColumns */
6323
+ pinnedLeftPx(ci) {
6324
+ const cols = this.visibleColumns();
6325
+ let left = this.Selection() ? this.SEL_STICKY_W : 0;
6326
+ for (let i = 0; i < ci && i < cols.length; i++)
6327
+ if (cols[i].pinned)
6328
+ left += (cols[i].pinnedWidth || 120);
6329
+ return left;
6330
+ }
6331
+ constructor(ngControl, cdr, hostEl, prefService, defaults, debugMode, exportGlobalAcl) {
6332
+ this.ngControl = ngControl;
6333
+ this.cdr = cdr;
6334
+ this.hostEl = hostEl;
6335
+ this.prefService = prefService;
6336
+ this.defaults = defaults;
6337
+ this.debugMode = debugMode;
6338
+ this.exportGlobalAcl = exportGlobalAcl;
6339
+ /** Altezza dell'header (misurata): la barra di selezione la usa per coprirlo esattamente */
6340
+ this.selbarHeight = signal(0, ...(ngDevMode ? [{ debugName: "selbarHeight" }] : []));
6341
+ // ===========================================================================
6342
+ // Superficie pubblica — Input risolti (pattern _alias -> signal risolto)
6343
+ // ===========================================================================
6344
+ this._Selection = input(null, ...(ngDevMode ? [{ debugName: "_Selection", alias: 'Selection' }] : [{ alias: 'Selection' }]));
6345
+ this.Selection = linkedSignal(() => this.io(this._Selection(), this.defaults?.Selection, false), ...(ngDevMode ? [{ debugName: "Selection" }] : []));
6346
+ this.SingleSelection = input(false, ...(ngDevMode ? [{ debugName: "SingleSelection" }] : []));
6347
+ this.SelectionDisabled = input(false, ...(ngDevMode ? [{ debugName: "SelectionDisabled" }] : []));
6348
+ this._SelectAll = input(null, ...(ngDevMode ? [{ debugName: "_SelectAll", alias: 'SelectAll' }] : [{ alias: 'SelectAll' }]));
6349
+ this.SelectAll = linkedSignal(() => this.io(this._SelectAll(), null, false), ...(ngDevMode ? [{ debugName: "SelectAll" }] : []));
6350
+ this._UseSelectionCache = input(null, ...(ngDevMode ? [{ debugName: "_UseSelectionCache", alias: 'UseSelectionCache' }] : [{ alias: 'UseSelectionCache' }]));
6351
+ this.UseSelectionCache = linkedSignal(() => this.io(this._UseSelectionCache(), this.defaults?.UseSelectionCache, true), ...(ngDevMode ? [{ debugName: "UseSelectionCache" }] : []));
6352
+ this._ShiftClick = input(null, ...(ngDevMode ? [{ debugName: "_ShiftClick", alias: 'ShiftClick' }] : [{ alias: 'ShiftClick' }]));
6353
+ this.ShiftClick = linkedSignal(() => this.io(this._ShiftClick(), null, false), ...(ngDevMode ? [{ debugName: "ShiftClick" }] : []));
6354
+ this._OrderByColumn = input(null, ...(ngDevMode ? [{ debugName: "_OrderByColumn", alias: 'OrderByColumn' }] : [{ alias: 'OrderByColumn' }]));
6355
+ this.OrderByColumn = linkedSignal(() => this.io(this._OrderByColumn(), null, true), ...(ngDevMode ? [{ debugName: "OrderByColumn" }] : []));
6356
+ this.MultipleOrderingDirectives = input(true, ...(ngDevMode ? [{ debugName: "MultipleOrderingDirectives" }] : []));
6357
+ this.Removal = input(false, ...(ngDevMode ? [{ debugName: "Removal" }] : []));
6358
+ this.RemovalCondition = input('', ...(ngDevMode ? [{ debugName: "RemovalCondition" }] : []));
6359
+ this._HidePaging = input(null, ...(ngDevMode ? [{ debugName: "_HidePaging", alias: 'HidePaging' }] : [{ alias: 'HidePaging' }]));
6360
+ this.HidePaging = linkedSignal(() => this.io(this._HidePaging(), this.defaults?.HidePaging, false), ...(ngDevMode ? [{ debugName: "HidePaging" }] : []));
6361
+ this._HidePagingCount = input(null, ...(ngDevMode ? [{ debugName: "_HidePagingCount", alias: 'HidePagingCount' }] : [{ alias: 'HidePagingCount' }]));
6362
+ this.HidePagingCount = linkedSignal(() => this.io(this._HidePagingCount(), this.defaults?.HidePagingCount, false), ...(ngDevMode ? [{ debugName: "HidePagingCount" }] : []));
6363
+ this._HidePagingButtons = input(null, ...(ngDevMode ? [{ debugName: "_HidePagingButtons", alias: 'HidePagingButtons' }] : [{ alias: 'HidePagingButtons' }]));
6364
+ this.HidePagingButtons = linkedSignal(() => this.io(this._HidePagingButtons(), this.defaults?.HidePagingButtons, false), ...(ngDevMode ? [{ debugName: "HidePagingButtons" }] : []));
6365
+ this._AllSearch = input(null, ...(ngDevMode ? [{ debugName: "_AllSearch", alias: 'AllSearch' }] : [{ alias: 'AllSearch' }]));
6366
+ this.AllSearch = linkedSignal(() => this.io(this._AllSearch(), this.defaults?.AllSearch, true), ...(ngDevMode ? [{ debugName: "AllSearch" }] : []));
6367
+ this._PagingStyle = input(null, ...(ngDevMode ? [{ debugName: "_PagingStyle", alias: 'PagingStyle' }] : [{ alias: 'PagingStyle' }]));
6368
+ this.PagingStyle = linkedSignal(() => this.io(this._PagingStyle(), this.defaults?.PagingStyle, 'bottom'), ...(ngDevMode ? [{ debugName: "PagingStyle" }] : []));
6369
+ // Read-write: `linkedSignal` risolve dall'input (reattivo) MA resta scrivibile
6370
+ // dal pager. L'override interno sopravvive finché l'input non cambia davvero.
6371
+ this._ArraymodeItemsPerPage = input(null, ...(ngDevMode ? [{ debugName: "_ArraymodeItemsPerPage", alias: 'ArraymodeItemsPerPage' }] : [{ alias: 'ArraymodeItemsPerPage' }]));
6372
+ this.ArraymodeItemsPerPage = linkedSignal(() => this.io(this._ArraymodeItemsPerPage(), this.defaults?.ArraymodeItemsPerPage, 15), ...(ngDevMode ? [{ debugName: "ArraymodeItemsPerPage" }] : []));
6373
+ this._UseArrayModePaging = input(true, ...(ngDevMode ? [{ debugName: "_UseArrayModePaging", alias: 'UseArrayModePaging' }] : [{ alias: 'UseArrayModePaging' }]));
6374
+ this.UseArrayModePaging = linkedSignal(() => this.io(this._UseArrayModePaging(), null, true), ...(ngDevMode ? [{ debugName: "UseArrayModePaging" }] : []));
6375
+ this.CountLabel = input('', ...(ngDevMode ? [{ debugName: "CountLabel" }] : []));
6376
+ this.Height = input(undefined, ...(ngDevMode ? [{ debugName: "Height" }] : []));
6377
+ this.MaxHeight = input(undefined, ...(ngDevMode ? [{ debugName: "MaxHeight" }] : []));
6378
+ this.EmptySpaceBackgroundColor = input('', ...(ngDevMode ? [{ debugName: "EmptySpaceBackgroundColor" }] : []));
6379
+ this.HighCellDensity = input(false, ...(ngDevMode ? [{ debugName: "HighCellDensity" }] : []));
6380
+ this.HeaderHidden = input(false, ...(ngDevMode ? [{ debugName: "HeaderHidden" }] : []));
6381
+ this.BodyHidden = input(false, ...(ngDevMode ? [{ debugName: "BodyHidden" }] : []));
6382
+ this.ShowLoadingOnBootstrap = input(false, ...(ngDevMode ? [{ debugName: "ShowLoadingOnBootstrap" }] : []));
6383
+ this._DefaultAlignment = input(null, ...(ngDevMode ? [{ debugName: "_DefaultAlignment", alias: 'DefaultAlignment' }] : [{ alias: 'DefaultAlignment' }]));
6384
+ this.DefaultAlignment = linkedSignal(() => this.io(this._DefaultAlignment(), this.defaults?.DefaultAlignment, 'Left'), ...(ngDevMode ? [{ debugName: "DefaultAlignment" }] : []));
6385
+ this._TableClass = input(null, ...(ngDevMode ? [{ debugName: "_TableClass", alias: 'TableClass' }] : [{ alias: 'TableClass' }]));
6386
+ this.TableClass = linkedSignal(() => this.io(this._TableClass(), this.defaults?.TableClass, ''), ...(ngDevMode ? [{ debugName: "TableClass" }] : []));
6387
+ this._ContainerClass = input(null, ...(ngDevMode ? [{ debugName: "_ContainerClass", alias: 'ContainerClass' }] : [{ alias: 'ContainerClass' }]));
6388
+ this.ContainerClass = linkedSignal(() => this.io(this._ContainerClass(), this.defaults?.ContainerClass, ''), ...(ngDevMode ? [{ debugName: "ContainerClass" }] : []));
6389
+ this.EsTableHandledSearch = input(false, ...(ngDevMode ? [{ debugName: "EsTableHandledSearch" }] : []));
6390
+ this.SearchThrottle = input(50, ...(ngDevMode ? [{ debugName: "SearchThrottle" }] : []));
6391
+ // --- Superficie dichiarata, wiring nelle fasi successive -------------------
6392
+ this._ColumnsResizable = input(null, ...(ngDevMode ? [{ debugName: "_ColumnsResizable", alias: 'ColumnsResizable' }] : [{ alias: 'ColumnsResizable' }]));
6393
+ this.ColumnsResizable = linkedSignal(() => this.io(this._ColumnsResizable(), this.defaults?.ColumnsResizable, false), ...(ngDevMode ? [{ debugName: "ColumnsResizable" }] : []));
6394
+ this._ColumnsPinnable = input(null, ...(ngDevMode ? [{ debugName: "_ColumnsPinnable", alias: 'ColumnsPinnable' }] : [{ alias: 'ColumnsPinnable' }]));
6395
+ this.ColumnsPinnable = linkedSignal(() => this.io(this._ColumnsPinnable(), this.defaults?.ColumnsPinnable, true), ...(ngDevMode ? [{ debugName: "ColumnsPinnable" }] : []));
6396
+ this._HiddenColumns = input(null, ...(ngDevMode ? [{ debugName: "_HiddenColumns", alias: 'HiddenColumns' }] : [{ alias: 'HiddenColumns' }]));
6397
+ this.HiddenColumns = linkedSignal(() => this.io(this._HiddenColumns(), null, false), ...(ngDevMode ? [{ debugName: "HiddenColumns" }] : []));
6398
+ this._ColumnsOrdering = input(null, ...(ngDevMode ? [{ debugName: "_ColumnsOrdering", alias: 'ColumnsOrdering' }] : [{ alias: 'ColumnsOrdering' }]));
6399
+ this.ColumnsOrdering = linkedSignal(() => this.io(this._ColumnsOrdering(), null, false), ...(ngDevMode ? [{ debugName: "ColumnsOrdering" }] : []));
6400
+ this._Export = input(null, ...(ngDevMode ? [{ debugName: "_Export", alias: 'Export' }] : [{ alias: 'Export' }]));
6401
+ this.Export = linkedSignal(() => this.io(this._Export(), this.defaults?.Export, false), ...(ngDevMode ? [{ debugName: "Export" }] : []));
6402
+ this.XLSXExport = input(false, ...(ngDevMode ? [{ debugName: "XLSXExport" }] : []));
6403
+ this.CSVExport = input(true, ...(ngDevMode ? [{ debugName: "CSVExport" }] : []));
6404
+ this.ExportFileName = input('Export.csv', ...(ngDevMode ? [{ debugName: "ExportFileName" }] : []));
6405
+ this.ExportOnlyVisibleColumns = input(false, ...(ngDevMode ? [{ debugName: "ExportOnlyVisibleColumns" }] : []));
6406
+ this.CornerMenuOptions = input([], ...(ngDevMode ? [{ debugName: "CornerMenuOptions" }] : []));
6407
+ this.DynamicOperations = input([], ...(ngDevMode ? [{ debugName: "DynamicOperations" }] : []));
6408
+ this._DynamicRowColumnsDefinition = input(null, ...(ngDevMode ? [{ debugName: "_DynamicRowColumnsDefinition", alias: 'DynamicRowColumnsDefinition' }] : [{ alias: 'DynamicRowColumnsDefinition' }]));
6409
+ this.Hierarchy = input(false, ...(ngDevMode ? [{ debugName: "Hierarchy" }] : []));
6410
+ // Read-write: risolti dall'input ma scrivibili dall'auto-detect gerarchia
6411
+ this._ParentKey = input('', ...(ngDevMode ? [{ debugName: "_ParentKey", alias: 'ParentKey' }] : [{ alias: 'ParentKey' }]));
6412
+ this.ParentKey = linkedSignal(() => this.io(this._ParentKey(), null, '') || '', ...(ngDevMode ? [{ debugName: "ParentKey" }] : []));
6413
+ this._OwnKey = input('', ...(ngDevMode ? [{ debugName: "_OwnKey", alias: 'OwnKey' }] : [{ alias: 'OwnKey' }]));
6414
+ this.OwnKey = linkedSignal(() => this.io(this._OwnKey(), null, '') || '', ...(ngDevMode ? [{ debugName: "OwnKey" }] : []));
6415
+ this._AutoSortHierarchy = input(null, ...(ngDevMode ? [{ debugName: "_AutoSortHierarchy", alias: 'AutoSortHierarchy' }] : [{ alias: 'AutoSortHierarchy' }]));
6416
+ this.AutoSortHierarchy = linkedSignal(() => this.io(this._AutoSortHierarchy(), null, false), ...(ngDevMode ? [{ debugName: "AutoSortHierarchy" }] : []));
6417
+ this.StartsExpanded = input(true, ...(ngDevMode ? [{ debugName: "StartsExpanded" }] : []));
6418
+ /** In modalità gerarchica: selezionando un nodo si selezionano tutti i discendenti, e un padre
6419
+ * risulta selezionato quando tutti i figli lo sono (indeterminato se solo alcuni). */
6420
+ this.CascadeSelection = input(false, ...(ngDevMode ? [{ debugName: "CascadeSelection" }] : []));
6421
+ this._SavePreferences = input(null, ...(ngDevMode ? [{ debugName: "_SavePreferences", alias: 'SavePreferences' }] : [{ alias: 'SavePreferences' }]));
6422
+ this.SavePreferences = linkedSignal(() => this.io(this._SavePreferences(), this.defaults?.SavePreferences, false), ...(ngDevMode ? [{ debugName: "SavePreferences" }] : []));
6423
+ /** Nome della tabella: chiave per la persistenza delle preferenze colonne (localStorage) */
6424
+ this.Name = input('', ...(ngDevMode ? [{ debugName: "Name" }] : []));
6425
+ this._RowGroupingPagingStyle = input(null, ...(ngDevMode ? [{ debugName: "_RowGroupingPagingStyle", alias: 'RowGroupingPagingStyle' }] : [{ alias: 'RowGroupingPagingStyle' }]));
6426
+ this._ShowItemGroupsColumns = input(null, ...(ngDevMode ? [{ debugName: "_ShowItemGroupsColumns", alias: 'ShowItemGroupsColumns' }] : [{ alias: 'ShowItemGroupsColumns' }]));
6427
+ this.Editable = input(false, ...(ngDevMode ? [{ debugName: "Editable" }] : []));
6428
+ this.RangeSelection = input(false, ...(ngDevMode ? [{ debugName: "RangeSelection" }] : []));
6429
+ this.ItemSourceProperty = input('', ...(ngDevMode ? [{ debugName: "ItemSourceProperty" }] : []));
6430
+ this.HasHeaderGroup = input(false, ...(ngDevMode ? [{ debugName: "HasHeaderGroup" }] : []));
6431
+ this.HasSecondaryHeaderGroup = input(false, ...(ngDevMode ? [{ debugName: "HasSecondaryHeaderGroup" }] : []));
6432
+ this.SearchView = input(null, ...(ngDevMode ? [{ debugName: "SearchView" }] : []));
6433
+ this._AutoUpdate = input(null, ...(ngDevMode ? [{ debugName: "_AutoUpdate", alias: 'AutoUpdate' }] : [{ alias: 'AutoUpdate' }]));
6434
+ // ===========================================================================
6435
+ // Superficie pubblica — Output
6436
+ // ===========================================================================
6437
+ this.onOrderChanged = new EventEmitter();
6438
+ this.onSearchRequest = new EventEmitter();
6439
+ this.onSelectionChanged = new EventEmitter();
6440
+ this.onRemoval = new EventEmitter();
6441
+ this.onAbortRemoval = new EventEmitter();
6442
+ this.onModelChange = new EventEmitter();
6443
+ this.onOpenContextMenu = new EventEmitter();
6444
+ this.onCornerAction = new EventEmitter();
6445
+ this.onDynamicOperation = new EventEmitter();
6446
+ // --- Models (superficie parità) --------------------------------------------
6447
+ this.globalCheck = model(false, ...(ngDevMode ? [{ debugName: "globalCheck" }] : []));
6448
+ this.autoUpdate = model(false, ...(ngDevMode ? [{ debugName: "autoUpdate" }] : []));
6449
+ this.seconds = model('10', ...(ngDevMode ? [{ debugName: "seconds" }] : []));
6450
+ this.researchInProgress = model(false, ...(ngDevMode ? [{ debugName: "researchInProgress" }] : []));
6451
+ /** Modello bindato: AppSearch (viewMode) oppure any[] (arrayMode) */
6452
+ this.view = signal(null, ...(ngDevMode ? [{ debugName: "view" }] : []));
6453
+ this.viewMode = signal(false, ...(ngDevMode ? [{ debugName: "viewMode" }] : []));
6454
+ this.arrayMode = signal(false, ...(ngDevMode ? [{ debugName: "arrayMode" }] : []));
6455
+ this.firstBind = signal(true, ...(ngDevMode ? [{ debugName: "firstBind" }] : []));
6456
+ /** Righe attualmente visualizzate (già paginate/ordinate) */
6457
+ this.boundSource = signal([], ...(ngDevMode ? [{ debugName: "boundSource" }] : []));
6458
+ /** Colonne derivate dalle direttive `*th`/`*td` */
6459
+ this.columns = signal([], ...(ngDevMode ? [{ debugName: "columns" }] : []));
6460
+ /** true se la tabella è pilotata dalle direttive (`*th`), false se template semplice */
6461
+ this.directivesBased = signal(false, ...(ngDevMode ? [{ debugName: "directivesBased" }] : []));
6462
+ /** true se è attivo il raggruppamento client-side (array mode con colonne `thGroup`) */
6463
+ this.grouped = signal(false, ...(ngDevMode ? [{ debugName: "grouped" }] : []));
6464
+ /** true se le colonne provengono da `DynamicRowColumnsDefinition` */
6465
+ this.dynamicMode = signal(false, ...(ngDevMode ? [{ debugName: "dynamicMode" }] : []));
6466
+ /** true se il modello è una view report (`report_columns`) */
6467
+ this.reportMode = signal(false, ...(ngDevMode ? [{ debugName: "reportMode" }] : []));
6468
+ /** Definizione colonne dinamica: risolta dall'input, ma scrivibile (auto-derivata dagli item generici) */
6469
+ this.DynamicRowColumnsDefinition = linkedSignal(() => this._DynamicRowColumnsDefinition(), ...(ngDevMode ? [{ debugName: "DynamicRowColumnsDefinition" }] : []));
6470
+ /** Se true mostra i valori delle colonne di gruppo anche nelle righe-oggetto */
6471
+ this.ShowItemGroupsColumns = linkedSignal(() => this.io(this._ShowItemGroupsColumns(), this.defaults?.ShowItemGroupsColumns, false), ...(ngDevMode ? [{ debugName: "ShowItemGroupsColumns" }] : []));
6472
+ /** Mappa ownKey(string) -> figli diretti, per la modalità gerarchica */
6473
+ this.hierChildren = new Map();
6474
+ /** Paginazione arrayMode */
6475
+ this.arrayPage = signal(1, ...(ngDevMode ? [{ debugName: "arrayPage" }] : []));
6476
+ /** Ordinamenti attivi (id -> 'ASC'|'DESC') */
6477
+ this.orders = signal([], ...(ngDevMode ? [{ debugName: "orders" }] : []));
6478
+ /** Header-group: mappa id→direttiva th (inclusi i gruppi) e insieme degli id-gruppo */
6479
+ this.hgThById = signal(new Map(), ...(ngDevMode ? [{ debugName: "hgThById" }] : []));
6480
+ this.hgParentIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "hgParentIds" }] : []));
6481
+ /** Base (solo direttive) di hgThById/hgParentIds, per ri-espandere i Multi a ogni refresh */
6482
+ this.baseThById = new Map();
6483
+ this.baseParentIds = new Set();
6484
+ /** Colonne derivate dalle direttive (senza le Multi, che dipendono dai dati) */
6485
+ this.directiveColumns = [];
6486
+ /** Definizioni Multi da srotolare sui dati (`*th Multi:true`) */
6487
+ this.multiDefs = [];
6488
+ // ===========================================================================
6489
+ // Theme host bindings
6490
+ // ===========================================================================
6491
+ this.hostClass = true;
6492
+ // ===========================================================================
6493
+ // Derived
6494
+ // ===========================================================================
6495
+ /** Colonne visibili, con le colonne pinnate spostate in testa (stabile) */
6496
+ this.visibleColumns = computed(() => {
6497
+ const vis = this.columns().filter(c => c.visible);
6498
+ if (!vis.some(c => c.pinned))
6499
+ return vis;
6500
+ return [...vis.filter(c => c.pinned), ...vis.filter(c => !c.pinned)];
6501
+ }, ...(ngDevMode ? [{ debugName: "visibleColumns" }] : []));
6502
+ /** Numero di colonne pinnate visibili */
6503
+ this.pinnedCount = computed(() => this.visibleColumns().filter(c => c.pinned).length, ...(ngDevMode ? [{ debugName: "pinnedCount" }] : []));
6504
+ /** true se c'è almeno una colonna pinnata */
6505
+ this.hasPinned = computed(() => this.pinnedCount() > 0, ...(ngDevMode ? [{ debugName: "hasPinned" }] : []));
6506
+ /** Larghezza (px) della colonna di selezione quando sticky */
6507
+ this.SEL_STICKY_W = 44;
6508
+ /** true se la tabella renderizza colonne strutturate (direttive/dinamica/report), non template semplici */
6509
+ this.usesColumns = computed(() => this.columns().length > 0, ...(ngDevMode ? [{ debugName: "usesColumns" }] : []));
6510
+ /**
6511
+ * Righe di header-group multi-livello, dall'alto (root) verso il basso (padre
6512
+ * immediato). Ricalcolate automaticamente su riordino/visibilità colonne.
6513
+ *
6514
+ * Ogni gruppo si allinea in BASSO: il padre immediato di una colonna sta sempre
6515
+ * nella riga direttamente sopra la colonna; gli antenati più alti salgono. Le
6516
+ * colonne senza gruppo (o con catena più corta) hanno celle-spacer vuote in alto.
6517
+ */
6518
+ this.headerGroupRows = computed(() => {
6519
+ const parentIds = this.hgParentIds();
6520
+ if (parentIds.size === 0)
6521
+ return [];
6522
+ const thById = this.hgThById();
6523
+ const leaves = this.visibleColumns();
6524
+ if (leaves.length === 0)
6525
+ return [];
6526
+ // catena antenati (padre-immediato → root) per un id, via thParent
6527
+ const chainOf = (id) => {
6528
+ const chain = [];
6529
+ let p = thById.get(id)?.thParent ?? null;
6530
+ let guard = 0;
6531
+ while (p && guard++ < 32) {
6532
+ chain.push(p);
6533
+ p = thById.get(p)?.thParent ?? null;
6534
+ }
6535
+ return chain;
6536
+ };
6537
+ const chains = leaves.map(c => chainOf(c.id));
6538
+ const maxDepth = chains.reduce((m, ch) => Math.max(m, ch.length), 0);
6539
+ if (maxDepth === 0)
6540
+ return [];
6541
+ const rows = [];
6542
+ for (let r = 0; r < maxDepth; r++) {
6543
+ const row = [];
6544
+ let i = 0;
6545
+ while (i < leaves.length) {
6546
+ // allineamento in basso: alla riga r (0=alto) corrisponde l'antenato a
6547
+ // distanza (maxDepth-1-r) dalla colonna (0 = padre immediato)
6548
+ const anc = chains[i][maxDepth - 1 - r];
6549
+ if (anc == null) {
6550
+ row.push({ id: `_hg_spacer_${r}_${i}`, span: 1, isGroup: false, label: '', template: null });
6551
+ i++;
6552
+ }
6553
+ else {
6554
+ let span = 0;
6555
+ while (i < leaves.length && chains[i][maxDepth - 1 - r] === anc) {
6556
+ span++;
6557
+ i++;
6558
+ }
6559
+ const gth = thById.get(anc);
6560
+ row.push({ id: anc, span, isGroup: true, label: gth?.thStaticContent ?? anc, template: gth?.Template ?? null });
6561
+ }
6562
+ }
6563
+ rows.push(row);
6564
+ }
6565
+ return rows;
6566
+ }, ...(ngDevMode ? [{ debugName: "headerGroupRows" }] : []));
6567
+ /** true se ci sono header-group da renderizzare */
6568
+ this.hasHeaderGroups = computed(() => this.headerGroupRows().length > 0, ...(ngDevMode ? [{ debugName: "hasHeaderGroups" }] : []));
6569
+ /** Numero di elementi totali (per il pager) */
6570
+ this.totalCount = computed(() => {
6571
+ const v = this.view();
6572
+ if (this.viewMode() && v)
6573
+ return v.objectcount ?? v.items?.length ?? 0;
6574
+ if (this.arrayMode() && Array.isArray(v))
6575
+ return v.length;
6576
+ return 0;
6577
+ }, ...(ngDevMode ? [{ debugName: "totalCount" }] : []));
6578
+ /** Pagina corrente per il pager */
6579
+ this.currentPage = computed(() => this.viewMode() ? (this.view()?.page ?? 1) : this.arrayPage(), ...(ngDevMode ? [{ debugName: "currentPage" }] : []));
6580
+ /** Numero totale di pagine per il pager */
6581
+ this.totalPages = computed(() => {
6582
+ if (this.viewMode())
6583
+ return this.view()?.pages ?? 1;
6584
+ const ipp = this.ArraymodeItemsPerPage();
6585
+ if (!ipp || ipp >= this.effectiveAllValue)
6586
+ return 1;
6587
+ return Math.max(1, Math.ceil(this.totalCount() / ipp));
6588
+ }, ...(ngDevMode ? [{ debugName: "totalPages" }] : []));
6589
+ this.effectiveAllValue = 999999;
6590
+ this.inited = false;
6591
+ // ===========================================================================
6592
+ // CVA plumbing
6593
+ // ===========================================================================
6594
+ this.onChange = () => { };
6595
+ this.onTouched = () => { };
6596
+ /** Ancora per la selezione a range stile Windows (Shift+click) */
6597
+ this.selectionAnchor = null;
6598
+ // ===========================================================================
6599
+ // Fase 5 — Selezione a range (drag cella-per-cella), copia/incolla, editing
6600
+ //
6601
+ // Rispetto all'es-table originale (matrice `selectionStatus` con 8 flag di
6602
+ // bordo per cella, ricalcolata a ogni mousemove + subscription per-cella e
6603
+ // debounce anti-doppio-evento) qui il range è descritto da DUE sole
6604
+ // coordinate (ancora + fuoco); "selezionata"/bordi sono DERIVATI dal
6605
+ // rettangolo normalizzato. Gli eventi sono in delega sulla `<table>`
6606
+ // (un solo mousedown/mouseover/mouseup) e leggono `data-r`/`data-c` dal
6607
+ // `<td>`. Il "debounce" sparisce: `mouseover` aggiorna il fuoco solo se la
6608
+ // cella cambia davvero, quindi non ci sono doppie operazioni da smorzare.
6609
+ // ===========================================================================
6610
+ /** Pipe riusate per il testo di copia (dependency-free) */
6611
+ this.fmt = new Est2FormatPipe();
6612
+ this.lk = new Est2LookupPipe();
6613
+ /** Range di celle: ancora (a) + fuoco (f), indici in boundSource() / visibleColumns() */
6614
+ this.rangeSel = signal(null, ...(ngDevMode ? [{ debugName: "rangeSel" }] : []));
6615
+ /** true durante il trascinamento (per disabilitare la selezione testo via CSS) */
6616
+ this.rangeDragging = signal(false, ...(ngDevMode ? [{ debugName: "rangeDragging" }] : []));
6617
+ /** Cella attualmente in editing (indici r/c) */
6618
+ this.editing = signal(null, ...(ngDevMode ? [{ debugName: "editing" }] : []));
6619
+ /** Bozza del valore in editing */
6620
+ this.editDraft = '';
6621
+ /** true se la modalità range è realmente utilizzabile (colonne strutturate, no gruppi/gerarchia) */
6622
+ this.rangeActive = computed(() => this.RangeSelection() && this.usesColumns() && !this.grouped() && !this.Hierarchy(), ...(ngDevMode ? [{ debugName: "rangeActive" }] : []));
6623
+ /** Rettangolo normalizzato del range corrente (o null) */
6624
+ this.rangeRect = computed(() => {
6625
+ const s = this.rangeSel();
6626
+ if (!s)
6627
+ return null;
6628
+ return {
6629
+ top: Math.min(s.a.r, s.f.r), bottom: Math.max(s.a.r, s.f.r),
6630
+ left: Math.min(s.a.c, s.f.c), right: Math.max(s.a.c, s.f.c)
6631
+ };
6632
+ }, ...(ngDevMode ? [{ debugName: "rangeRect" }] : []));
6633
+ /** Avviso transitorio mostrato in un piccolo toast (es. incolla incompatibile) */
6634
+ this.notice = signal(null, ...(ngDevMode ? [{ debugName: "notice" }] : []));
6635
+ this.noticeTimer = null;
6636
+ // ===========================================================================
6637
+ // Fase 6 — Colonne (visibilità/ordine), preferenze, export
6638
+ // ===========================================================================
6639
+ /** Preferenze colonne in memoria (ordine + nascoste). Persistite se `SavePreferences`. */
6640
+ this.columnPrefs = null;
6641
+ /** true una volta caricate le preferenze dal server (evita retrieve multipli) */
6642
+ this.prefsLoaded = false;
6643
+ // --- Dialog visibilità/ordine/pin colonne ----------------------------------
6644
+ this.columnsDialogOpen = signal(false, ...(ngDevMode ? [{ debugName: "columnsDialogOpen" }] : []));
6645
+ /** Lista di lavoro della dialog (copia, per poter annullare) */
6646
+ this.dialogCols = signal([], ...(ngDevMode ? [{ debugName: "dialogCols" }] : []));
6647
+ // --- Export (CSV self-contained / XLSX via SheetJS opzionale) --------------
6648
+ this.exportMenuOpen = signal(false, ...(ngDevMode ? [{ debugName: "exportMenuOpen" }] : []));
6649
+ this.cornerMenuOpen = signal(false, ...(ngDevMode ? [{ debugName: "cornerMenuOpen" }] : []));
6650
+ this.trackByIndex = (i) => i;
6651
+ this.trackRow = (i, item) => item?._hash ?? item?.id ?? item;
6652
+ this.trackCol = (_, col) => col.id;
6653
+ if (ngControl)
6654
+ ngControl.valueAccessor = this;
6655
+ }
6656
+ ngOnInit() {
6657
+ // Tutti gli input risolti sono ora `computed()`/`linkedSignal()` reattivi
6658
+ // (vedi dichiarazioni): niente più risoluzione one-shot qui.
6659
+ this.inited = true;
6660
+ // Click destro: alla apertura del menu seleziono l'elemento (se non già selezionato)
6661
+ const menu = this.ContextMenu || this.tableEmptyMenu;
6662
+ if (menu)
6663
+ this.ctxMenuSub = menu.open.subscribe((e) => this.onContextMenuOpen(e?.item));
6664
+ }
6665
+ ngOnDestroy() {
6666
+ this.ctxMenuSub?.unsubscribe();
6667
+ this.headerRO?.disconnect();
6668
+ if (this.noticeTimer)
6669
+ clearTimeout(this.noticeTimer);
6670
+ }
6671
+ /** Risoluzione input: valore esplicito || default di progetto || hard default */
6672
+ io(v, d, hard) {
6673
+ return (v !== null && v !== undefined) ? v : (d !== null && d !== undefined) ? d : hard;
6674
+ }
6675
+ ngAfterContentInit() {
6676
+ this.buildColumns();
6677
+ this.thDirectives?.changes.subscribe(() => { this.buildColumns(); this.refresh(); });
6678
+ this.tdDirectives?.changes.subscribe(() => { this.buildColumns(); this.refresh(); });
6679
+ }
6680
+ // ===========================================================================
6681
+ // ControlValueAccessor
6682
+ // ===========================================================================
6683
+ writeValue(obj) {
6684
+ this.view.set(obj ?? null);
6685
+ this.detectMode(obj);
6686
+ this.syncOrdersFromView();
6687
+ this.refresh();
6688
+ if (obj)
6689
+ this.firstBind.set(false);
6690
+ this.cdr.markForCheck();
6691
+ }
6692
+ registerOnChange(fn) { this.onChange = fn; }
6693
+ registerOnTouched(fn) { this.onTouched = fn; }
6694
+ setDisabledState() { }
6695
+ changed() { this.onChange(this.view()); }
6696
+ // ===========================================================================
6697
+ // Mode & column model
6698
+ // ===========================================================================
6699
+ detectMode(obj) {
6700
+ const isSearch = obj instanceof AppSearch ||
6701
+ (obj && typeof obj === 'object' && typeof obj.itemsperpageoverride !== 'undefined' && !Array.isArray(obj));
6702
+ this.viewMode.set(!!isSearch);
6703
+ this.arrayMode.set(!isSearch && Array.isArray(obj));
6704
+ }
6705
+ buildColumns() {
6706
+ const ths = this.thDirectives?.toArray() ?? [];
6707
+ this.directivesBased.set(ths.length > 0);
6708
+ if (ths.length === 0) {
6709
+ this.columns.set([]);
6710
+ this.directiveColumns = [];
6711
+ this.multiDefs = [];
6712
+ this.hgThById.set(new Map());
6713
+ this.hgParentIds.set(new Set());
6714
+ this.baseThById = new Map();
6715
+ this.baseParentIds = new Set();
6716
+ return;
6717
+ }
6718
+ const tdByCol = new Map();
6719
+ for (const td of this.tdDirectives?.toArray() ?? [])
6720
+ if (!tdByCol.has(td.td))
6721
+ tdByCol.set(td.td, td);
6722
+ // Colonne Multi (`*th Multi:true`): srotolate sui dati in resolveColumns()
6723
+ this.multiDefs = ths.filter(t => t.thMulti && t.thIf).map(t => ({ prop: t.th, th: t, td: tdByCol.get(t.th) }));
6724
+ // Header groups multi-livello: un `*th` è un GRUPPO (non una colonna-dato) se
6725
+ // il suo id è referenziato da un altro `*th` come `Parent` (`thParent`).
6726
+ const parentIds = new Set();
6727
+ for (const th of ths)
6728
+ if (th.thParent)
6729
+ parentIds.add(th.thParent);
6730
+ const thById = new Map();
6731
+ for (const th of ths)
6732
+ thById.set(th.th, th);
6733
+ this.baseThById = thById;
6734
+ this.baseParentIds = parentIds;
6735
+ this.hgThById.set(new Map(thById));
6736
+ this.hgParentIds.set(new Set(parentIds));
6737
+ const def = this.DefaultAlignment().toLowerCase();
6738
+ const cols = [];
6739
+ for (const th of ths) {
6740
+ if (!th.thIf)
6741
+ continue;
6742
+ if (th.thMulti)
6743
+ continue; // srotolata separatamente
6744
+ if (parentIds.has(th.th))
6745
+ continue; // è un header di gruppo, non una colonna-dato
6746
+ const td = tdByCol.get(th.th);
6747
+ cols.push({
6748
+ id: th.th,
6749
+ header: th,
6750
+ cell: td,
6751
+ visible: th.thVisible,
6752
+ orderable: th.thOrderable,
6753
+ alignment: (td?.tdAlignment || th.thAlignment)?.toLowerCase() || def,
6754
+ property: th.thProperty,
6755
+ type: th.thType ?? null,
6756
+ source: th.thSource,
6757
+ format: td?.tdFormat || th.thFormat || null,
6758
+ cssClass: td?.tdClass || th.thClass || '',
6759
+ wrap: th.thWrap,
6760
+ isGroup: th.thGroup,
6761
+ aggSpecs: parseAggregationSpecs(th.th, th.thAggregation),
6762
+ headerText: th.thStaticContent,
6763
+ headerBg: th.thBackgroundColor,
6764
+ routePath: th.thRoutePath,
6765
+ routeParams: th.thRouteParameterProperties,
6766
+ rendererName: th.thRendererName,
6767
+ pinned: !!th.thPinned,
6768
+ pinnedWidth: th.thPinnedWidth ?? null
6769
+ });
6770
+ }
6771
+ this.directiveColumns = cols;
6772
+ this.setColumns(cols);
6773
+ }
6774
+ /**
6775
+ * Srotola le colonne `*th Multi:true` sui dati: ogni entry unica
6776
+ * `(header_group, header)` della proprietà-array diventa una colonna sintetica,
6777
+ * e i livelli di `header_group` (separati da ">") alimentano gli header-group.
6778
+ */
6779
+ buildMultiColumns(items) {
6780
+ const cols = [];
6781
+ const thById = new Map(this.baseThById);
6782
+ const parentIds = new Set(this.baseParentIds);
6783
+ const seen = new Set();
6784
+ for (const def of this.multiDefs) {
6785
+ const prop = def.prop;
6786
+ for (const item of (items || [])) {
6787
+ const arr = item?.[prop];
6788
+ if (!Array.isArray(arr))
6789
+ continue;
6790
+ for (let ii = 0; ii < arr.length; ii++) {
6791
+ const mv = arr[ii] || {};
6792
+ const header = mv.header ?? '';
6793
+ const group = mv.header_group ?? null;
6794
+ const key = `${group}__${header}`;
6795
+ if (seen.has(key))
6796
+ continue;
6797
+ seen.add(key);
6798
+ const hier = group ? String(group).split('>').map((s) => s.trim()).filter(Boolean) : [];
6799
+ const immediateParent = hier.length ? hier[hier.length - 1] : null;
6800
+ const colId = `${prop}_${ii}`;
6801
+ // th sintetico foglia: fornisce la catena thParent + il template header
6802
+ const leafTh = new EsThDirective(def.th.Template);
6803
+ leafTh.th = colId;
6804
+ leafTh.thStaticContent = header;
6805
+ leafTh.thParent = immediateParent;
6806
+ leafTh.thOrderable = false;
6807
+ thById.set(colId, leafTh);
6808
+ // th sintetici dei gruppi padre (una volta ciascuno)
6809
+ for (let h = 0; h < hier.length; h++) {
6810
+ const pid = hier[h];
6811
+ parentIds.add(pid);
6812
+ if (!thById.has(pid)) {
6813
+ const pth = new EsThDirective(null);
6814
+ pth.th = pid;
6815
+ pth.thStaticContent = pid;
6816
+ pth.thParent = h > 0 ? hier[h - 1] : null;
6817
+ thById.set(pid, pth);
6818
+ }
6819
+ }
6820
+ cols.push({
6821
+ id: colId,
6822
+ header: leafTh,
6823
+ cell: def.td,
6824
+ visible: true,
6825
+ orderable: false,
6826
+ alignment: 'right',
6827
+ property: null,
6828
+ type: null, source: null, format: null,
6829
+ cssClass: def.td?.tdClass || def.th.thClass || '',
6830
+ wrap: false, isGroup: false, aggSpecs: [],
6831
+ headerText: header,
6832
+ headerBg: null,
6833
+ multiProp: prop, multiIndex: ii,
6834
+ pinned: false, pinnedWidth: null
6835
+ });
6836
+ }
6837
+ }
6838
+ }
6839
+ this.hgThById.set(thById);
6840
+ this.hgParentIds.set(parentIds);
6841
+ return cols;
6842
+ }
6843
+ /** Costruisce le colonne da una `EsTableColumnsDefinition[]` (modalità dinamica) */
6844
+ buildDynamicColumns(defs) {
6845
+ // renderer custom forniti dal consumer come `*td tdForProperty="..."`
6846
+ const tds = this.tdDirectives?.toArray() ?? [];
6847
+ const cols = [];
6848
+ for (let i = 0; i < defs.length; i++) {
6849
+ const d = defs[i];
6850
+ if (!d.Visible)
6851
+ continue;
6852
+ const renderer = tds.find(t => (d.RendererName && t.td === d.RendererName) || t.tdForProperty === d.PropertyName);
6853
+ cols.push({
6854
+ id: d.PropertyName,
6855
+ cell: renderer,
6856
+ visible: true,
6857
+ orderable: d.Orderable !== false,
6858
+ alignment: 'left',
6859
+ property: d.PropertyName,
6860
+ type: d.PropertyType ?? null,
6861
+ source: d.PropertySource ?? null,
6862
+ format: d.Format ?? null,
6863
+ cssClass: d.Clss || '',
6864
+ wrap: !!d.HeaderWraps,
6865
+ isGroup: false,
6866
+ aggSpecs: [],
6867
+ headerText: d.Description,
6868
+ routePath: d.RoutePath,
6869
+ routeParams: d.RouteParameterProperties,
6870
+ rendererName: d.RendererName,
6871
+ // colori per-cella opzionali (forecolors/backcolors allineati all'ordine delle colonne)
6872
+ colorIndex: i
6873
+ });
6874
+ }
6875
+ this.setColumns(cols);
6876
+ }
6877
+ /** Costruisce le colonne da `report_columns` (modalità report): il valore è preso per indice */
6878
+ buildReportColumns(reportColumns) {
6879
+ const cols = [];
6880
+ for (let i = 0; i < reportColumns.length; i++) {
6881
+ const c = reportColumns[i];
6882
+ cols.push({
6883
+ id: c.id,
6884
+ visible: true,
6885
+ orderable: false,
6886
+ alignment: c.alignment?.toLowerCase() || 'left',
6887
+ property: null,
6888
+ type: null,
6889
+ source: null,
6890
+ format: c.format ?? null,
6891
+ cssClass: '',
6892
+ wrap: false,
6893
+ isGroup: false,
6894
+ aggSpecs: [],
6895
+ headerText: c.description,
6896
+ headerBg: c.color || null,
6897
+ propAccessor: String(i),
6898
+ colorIndex: i
6899
+ });
6900
+ }
6901
+ this.setColumns(cols);
6902
+ }
6903
+ /** Sceglie la sorgente delle colonne in base al modello: report → dinamica → direttive */
6904
+ resolveColumns(items) {
6905
+ const v = this.view();
6906
+ if (this.viewMode() && v?.report_columns?.length) {
6907
+ this.reportMode.set(true);
6908
+ this.dynamicMode.set(false);
6909
+ this.buildReportColumns(v.report_columns);
6910
+ return;
6911
+ }
6912
+ this.reportMode.set(false);
6913
+ let defs = this.DynamicRowColumnsDefinition();
6914
+ const hasTh = (this.thDirectives?.length || 0) > 0;
6915
+ // Auto-derivazione da "generic items" (oggetti con bag `.properties`) se non ci sono direttive né definizioni
6916
+ if ((!defs || defs.length === 0) && !hasTh && items.length && items[0]?.properties) {
6917
+ defs = Object.keys(items[0].properties).map(k => new EsTableColumnsDefinition(k, k));
6918
+ this.DynamicRowColumnsDefinition.set(defs);
6919
+ }
6920
+ if (defs && defs.length > 0) {
6921
+ this.dynamicMode.set(true);
6922
+ this.buildDynamicColumns(defs);
6923
+ return;
6924
+ }
6925
+ this.dynamicMode.set(false);
6926
+ // Colonne da direttive (costruite in ngAfterContentInit). Se ci sono `*th Multi`
6927
+ // le srotolo sui dati correnti e le accodo alle colonne-direttiva.
6928
+ if (this.multiDefs.length && this.directiveColumns.length) {
6929
+ const multiCols = this.buildMultiColumns(items);
6930
+ this.setColumns([...this.directiveColumns, ...multiCols]);
6931
+ }
6932
+ }
6933
+ // ===========================================================================
6934
+ // Operazioni dinamiche (colonne-icona a sinistra)
6935
+ // ===========================================================================
6936
+ /** Visibilità di un'operazione su una riga (gate conditionField/conditionValue, case-insensitive) */
6937
+ operationVisible(op, item) {
6938
+ if (!op.conditionField)
6939
+ return true;
6940
+ const src = item?.properties ?? item;
6941
+ const key = Object.keys(src ?? {}).find(k => k.toLowerCase() === op.conditionField.toLowerCase());
6942
+ return key != null && String(src[key]) === String(op.conditionValue);
6943
+ }
6944
+ dynamicOperation(item, id) {
6945
+ this.onDynamicOperation.emit({ id, item });
6946
+ }
6947
+ // ===========================================================================
6948
+ // Valore/renderer di cella per modalità dinamica/report
6949
+ // ===========================================================================
6950
+ /** Valore di una cella report: `values[indice]` (fallback su accesso diretto) */
6951
+ reportCellValue(item, col) {
6952
+ const idx = col.propAccessor;
6953
+ return item?.values ? item.values[idx] : item?.[idx];
6954
+ }
6955
+ /** Colore per-cella (fore/back) da `forecolors[i]`/`backcolors[i]`; vale sia report che dinamica */
6956
+ cellColor(item, col, kind) {
6957
+ if (col.colorIndex == null)
6958
+ return null;
6959
+ const arr = kind === 'fore' ? item?.forecolors : item?.backcolors;
6960
+ return arr ? (arr[col.colorIndex] || null) : null;
6961
+ }
6962
+ /** Valore report formattato (usa il formatter delle aggregazioni: F0-9 / small-long-verylong) */
6963
+ reportCellDisplay(item, col) {
6964
+ const raw = this.reportCellValue(item, col);
6965
+ return formatAggValue(raw == null ? '' : String(raw), col.format || '', this.locale);
6966
+ }
6967
+ /** Costruisce il routerLink di una cella dinamica da `RoutePath` + `RouteParameterProperties` */
6968
+ routerLinkFor(item, col) {
6969
+ let path = col.routePath || '';
6970
+ const pars = col.routeParams || [];
6971
+ const src = item?.properties ?? item;
6972
+ for (let i = 0; i < pars.length; i++)
6973
+ path = path.replace('{' + i + '}', src?.[pars[i]]);
6974
+ path = path.replace(/'/g, '"').replace(/ /g, '');
6975
+ try {
6976
+ return JSON.parse(path);
6977
+ }
6978
+ catch {
6979
+ return path;
6980
+ }
6981
+ }
6982
+ // ===========================================================================
6983
+ // Refresh: ricostruisce boundSource in base a modalità/ordinamento/paginazione
6984
+ // ===========================================================================
6985
+ refresh() {
6986
+ const v = this.view();
6987
+ if (!v) {
6988
+ this.boundSource.set([]);
6989
+ return;
6990
+ }
6991
+ // Determina la sorgente delle colonne (report / dinamica / direttive)
6992
+ const rawItems = this.viewMode()
6993
+ ? ((this.ItemSourceProperty() ? v[this.ItemSourceProperty()] : v.items) ?? [])
6994
+ : (Array.isArray(v) ? v : []);
6995
+ this.resolveColumns(rawItems);
6996
+ if (this.viewMode()) {
6997
+ // Il backend (consumer) fornisce già la pagina ordinata in v.items
6998
+ const items = rawItems;
6999
+ if (this.Hierarchy()) {
7000
+ this.grouped.set(false);
7001
+ this.boundSource.set(this.applyHierarchy(items, v.selection));
7002
+ return;
7003
+ }
7004
+ this.applySelectionFlags(items, v.selection);
7005
+ this.boundSource.set(items);
7006
+ return;
7007
+ }
7008
+ if (this.arrayMode()) {
7009
+ let items = [...this.view()];
7010
+ // Modalità gerarchica: albero padre/figlio (ignora paginazione)
7011
+ if (this.Hierarchy()) {
7012
+ this.grouped.set(false);
7013
+ this.boundSource.set(this.applyHierarchy(items, this.arraySelection));
7014
+ return;
7015
+ }
7016
+ // Raggruppamento client-side: se ci sono colonne `thGroup`, ignora la paginazione
7017
+ const groupCols = this.groupColumns();
7018
+ if (groupCols.length > 0) {
7019
+ this.grouped.set(true);
7020
+ const rows = buildGroupedRows(this.clientSort(items), groupCols, this.aggregationSpecs());
7021
+ this.applySelectionFlags(rows.filter(r => r._item), this.arraySelection);
7022
+ this.boundSource.set(rows);
7023
+ return;
7024
+ }
7025
+ this.grouped.set(false);
7026
+ items = this.clientSort(items);
7027
+ const ipp = this.ArraymodeItemsPerPage();
7028
+ if (this.UseArrayModePaging() && ipp && ipp < this.effectiveAllValue) {
7029
+ const page = Math.min(this.arrayPage(), Math.max(1, Math.ceil(items.length / ipp)));
7030
+ this.arrayPage.set(page);
7031
+ items = items.slice((page - 1) * ipp, page * ipp);
7032
+ }
7033
+ this.applySelectionFlags(items, this.arraySelection);
7034
+ this.boundSource.set(items);
7035
+ return;
7036
+ }
7037
+ this.boundSource.set([]);
7038
+ }
7039
+ /** Colonne chiave di raggruppamento, nell'ordine di dichiarazione */
7040
+ groupColumns() {
7041
+ return this.columns().filter(c => c.isGroup).map(c => c.id);
7042
+ }
7043
+ /** Tutte le specifiche di aggregazione, appiattite su tutte le colonne */
7044
+ aggregationSpecs() {
7045
+ const specs = [];
7046
+ for (const c of this.columns())
7047
+ specs.push(...c.aggSpecs);
7048
+ return specs;
7049
+ }
7050
+ clientSort(items) {
7051
+ const ord = this.orders();
7052
+ if (ord.length === 0)
7053
+ return items;
7054
+ return items.sort((a, b) => {
7055
+ for (const o of ord) {
7056
+ const dir = o.order === 'DESC' ? -1 : 1;
7057
+ const av = a?.[o.id], bv = b?.[o.id];
7058
+ if (av == null && bv == null)
7059
+ continue;
7060
+ if (av == null)
7061
+ return -dir;
7062
+ if (bv == null)
7063
+ return dir;
7064
+ if (av < bv)
7065
+ return -dir;
7066
+ if (av > bv)
7067
+ return dir;
7068
+ }
7069
+ return 0;
7070
+ });
7071
+ }
7072
+ // ===========================================================================
7073
+ // Ordinamento
7074
+ // ===========================================================================
7075
+ orderOf(id) {
7076
+ return this.orders().find(o => o.id === id)?.order ?? null;
7077
+ }
7078
+ orderIndex(id) {
7079
+ const idx = this.orders().findIndex(o => o.id === id);
7080
+ return idx < 0 ? -1 : idx + 1;
7081
+ }
7082
+ toggleSort(col) {
7083
+ if (!this.OrderByColumn() || !col.orderable)
7084
+ return;
7085
+ const current = this.orderOf(col.id);
7086
+ const next = current === 'ASC' ? 'DESC' : current === 'DESC' ? null : 'ASC';
7087
+ let list = this.MultipleOrderingDirectives()
7088
+ ? this.orders().filter(o => o.id !== col.id)
7089
+ : [];
7090
+ if (next)
7091
+ list = [...list, new AppOrdering(col.id, next)];
7092
+ this.orders.set(list);
7093
+ if (this.viewMode()) {
7094
+ const v = this.view();
7095
+ v.orders = list;
7096
+ v.internalsearchrequest = true;
7097
+ this.onOrderChanged.emit(new AppOrdering(col.id, next ?? undefined));
7098
+ this.onSearchRequest.emit(false);
7099
+ }
7100
+ else {
7101
+ this.refresh();
7102
+ }
7103
+ if (this.SavePreferences())
7104
+ this.saveColumnPrefs();
7105
+ this.cdr.markForCheck();
7106
+ }
7107
+ syncOrdersFromView() {
7108
+ if (this.viewMode() && this.view()?.orders)
7109
+ this.orders.set([...this.view().orders]);
7110
+ }
7111
+ // ===========================================================================
7112
+ // Paginazione
7113
+ // ===========================================================================
7114
+ goToPage(p) {
7115
+ if (this.viewMode()) {
7116
+ const v = this.view();
7117
+ v.page = p;
7118
+ v.internalsearchrequest = true;
7119
+ this.onSearchRequest.emit(false);
7120
+ }
7121
+ else {
7122
+ this.arrayPage.set(p);
7123
+ this.refresh();
7124
+ }
7125
+ this.cdr.markForCheck();
7126
+ }
7127
+ changeItemsPerPage(n) {
7128
+ if (this.viewMode()) {
7129
+ const v = this.view();
7130
+ v.itemsperpageoverride = n;
7131
+ v.page = 1;
7132
+ v.internalsearchrequest = true;
7133
+ this.onSearchRequest.emit(false);
7134
+ }
7135
+ else {
7136
+ this.ArraymodeItemsPerPage.set(n);
7137
+ this.arrayPage.set(1);
7138
+ this.refresh();
7139
+ }
7140
+ this.cdr.markForCheck();
7141
+ }
7142
+ currentSelection() {
7143
+ if (this.viewMode()) {
7144
+ const v = this.view();
7145
+ if (!v.selection)
7146
+ v.selection = new ItemsSelection([]);
7147
+ return v.selection;
7148
+ }
7149
+ if (!this.arraySelection)
7150
+ this.arraySelection = new ItemsSelection([]);
7151
+ return this.arraySelection;
7152
+ }
7153
+ /** Righe realmente selezionabili: esclude le righe-gruppo quando il raggruppamento è attivo */
7154
+ selectableRows() {
7155
+ const rows = this.boundSource();
7156
+ return this.grouped() ? rows.filter(r => r._item) : rows;
7157
+ }
7158
+ /**
7159
+ * Applica il flag _selected alle righe correnti in base allo stato di selezione.
7160
+ * In modalità **all** una riga è selezionata se NON è tra le esclusioni (vale anche
7161
+ * per le pagine non ancora visitate, dove tutte le righe risultano selezionate).
7162
+ */
7163
+ applySelectionFlags(items, sel) {
7164
+ if (!sel) {
7165
+ for (const it of items)
7166
+ it._selected = false;
7167
+ return;
7168
+ }
7169
+ for (const it of items)
7170
+ it._selected = sel.all ? !sel.exclusions.includes(it) : sel.items.includes(it);
7171
+ }
7172
+ /** Ricalcola il conteggio: in modalità **all** è (totale - esclusioni) */
7173
+ refreshSelectionCount(sel) {
7174
+ sel.count = sel.all ? Math.max(0, this.totalCount() - sel.exclusions.length) : sel.items.length;
7175
+ }
7176
+ /** Toggle della selezione di una singola riga (checkbox, click su riga o click destro) */
7177
+ toggleRow(item) {
7178
+ if (this.SelectionDisabled() || item?._sep)
7179
+ return;
7180
+ const sel = this.currentSelection();
7181
+ // Selezione in cascata (albero): propaga a discendenti e antenati
7182
+ if (this.Hierarchy() && this.CascadeSelection() && !this.SingleSelection()) {
7183
+ this.cascadeSelection(item, !item._selected);
7184
+ this.rebuildSelectionFromFlags(sel);
7185
+ this.syncGlobalCheck();
7186
+ this.emitSelection(sel);
7187
+ return;
7188
+ }
7189
+ if (this.SingleSelection()) {
7190
+ const wasSelected = item._selected;
7191
+ for (const it of this.selectableRows())
7192
+ it._selected = false;
7193
+ sel.all = false;
7194
+ sel.exclusions = [];
7195
+ sel.items = wasSelected ? [] : [item];
7196
+ item._selected = !wasSelected;
7197
+ }
7198
+ else if (sel.all) {
7199
+ // "Tutto selezionato": deselezionare = aggiungere alle esclusioni
7200
+ item._selected = !item._selected;
7201
+ if (item._selected)
7202
+ sel.exclusions = sel.exclusions.filter(i => i !== item);
7203
+ else if (!sel.exclusions.includes(item))
7204
+ sel.exclusions.push(item);
7205
+ }
7206
+ else {
7207
+ item._selected = !item._selected;
7208
+ if (item._selected) {
7209
+ if (!sel.items.includes(item))
7210
+ sel.items.push(item);
7211
+ }
7212
+ else
7213
+ sel.items = sel.items.filter(i => i !== item);
7214
+ this.selectionAnchor = item;
7215
+ }
7216
+ this.refreshSelectionCount(sel);
7217
+ this.syncGlobalCheck();
7218
+ this.emitSelection(sel);
7219
+ }
7220
+ /**
7221
+ * Checkbox globale in testata: seleziona/azzera la **pagina corrente**.
7222
+ * Per estendere la selezione a tutte le pagine si usa `selectEverything()`
7223
+ * (link mostrato nella barra di selezione).
7224
+ */
7225
+ toggleAll() {
7226
+ if (this.SelectionDisabled() || this.SingleSelection())
7227
+ return;
7228
+ const sel = this.currentSelection();
7229
+ const rows = this.selectableRows();
7230
+ const pageFully = !sel.all && rows.length > 0 && rows.every(r => r._selected);
7231
+ if (sel.all || pageFully) {
7232
+ this.clearSelectionState(sel);
7233
+ }
7234
+ else {
7235
+ sel.all = false;
7236
+ sel.exclusions = [];
7237
+ sel.items = [...rows];
7238
+ for (const r of rows)
7239
+ r._selected = true;
7240
+ }
7241
+ this.refreshSelectionCount(sel);
7242
+ this.syncGlobalCheck();
7243
+ this.emitSelection(sel);
7244
+ }
7245
+ /** Estende la selezione a TUTTI gli elementi, anche quelli delle altre pagine (`selection.all`) */
7246
+ selectEverything() {
7247
+ if (this.SelectionDisabled() || this.SingleSelection())
7248
+ return;
7249
+ const sel = this.currentSelection();
7250
+ sel.all = true;
7251
+ sel.items = [];
7252
+ sel.exclusions = [];
7253
+ for (const r of this.selectableRows())
7254
+ r._selected = true;
7255
+ this.refreshSelectionCount(sel);
7256
+ this.globalCheck.set(true);
7257
+ this.emitSelection(sel);
7258
+ }
7259
+ /** Azzera completamente la selezione */
7260
+ clearSelection() {
7261
+ const sel = this.currentSelection();
7262
+ this.clearSelectionState(sel);
7263
+ this.refreshSelectionCount(sel);
7264
+ this.globalCheck.set(false);
7265
+ this.emitSelection(sel);
7266
+ }
7267
+ clearSelectionState(sel) {
7268
+ sel.all = false;
7269
+ sel.items = [];
7270
+ sel.exclusions = [];
7271
+ for (const r of this.selectableRows())
7272
+ r._selected = false;
7273
+ }
7274
+ syncGlobalCheck() {
7275
+ const sel = this.currentSelection();
7276
+ const rows = this.selectableRows();
7277
+ this.globalCheck.set(sel.all ? sel.exclusions.length === 0 : (rows.length > 0 && rows.every(r => r._selected)));
7278
+ }
7279
+ emitSelection(sel) {
7280
+ // Una sola selezione alla volta: qualunque selezione di riga azzera il range di celle
7281
+ if (this.rangeSel())
7282
+ this.rangeSel.set(null);
7283
+ this.onSelectionChanged.emit(sel);
7284
+ this.changed();
7285
+ this.cdr.markForCheck();
7286
+ }
7287
+ /** Click su una riga: espande/collassa un gruppo, oppure gestisce la selezione. */
7288
+ handleRowClick(item, event) {
7289
+ if (item?._group) {
7290
+ this.toggleGroup(item);
7291
+ return;
7292
+ }
7293
+ // In modalità range la selezione di riga avviene solo tramite le checkbox
7294
+ // (il click sulla cella pilota il range), così i due meccanismi non collidono.
7295
+ if (this.rangeActive())
7296
+ return;
7297
+ if (!this.Selection())
7298
+ return;
7299
+ if (this.SingleSelection()) {
7300
+ this.toggleRow(item);
7301
+ return;
7302
+ }
7303
+ // Selezione stile Windows (plain / Ctrl / Shift) quando ShiftClick è attivo
7304
+ if (this.ShiftClick() && !this.Hierarchy()) {
7305
+ this.windowsSelect(item, event);
7306
+ return;
7307
+ }
7308
+ // Comportamento classico: Ctrl+click lascia il default del browser (link, ecc.)
7309
+ if (event?.ctrlKey || event?.metaKey)
7310
+ return;
7311
+ this.toggleRow(item);
7312
+ }
7313
+ /** Selezione a click stile Esplora risorse: plain=solo questo, Ctrl=toggle, Shift=range dall'ancora */
7314
+ windowsSelect(item, event) {
7315
+ if (this.SelectionDisabled() || item?._sep)
7316
+ return;
7317
+ const rows = this.selectableRows();
7318
+ const sel = this.currentSelection();
7319
+ const shift = !!event?.shiftKey;
7320
+ const ctrl = !!(event?.ctrlKey || event?.metaKey);
7321
+ if (shift && this.selectionAnchor != null && rows.indexOf(this.selectionAnchor) >= 0) {
7322
+ const a = rows.indexOf(this.selectionAnchor);
7323
+ const b = rows.indexOf(item);
7324
+ if (b >= 0) {
7325
+ const [lo, hi] = a <= b ? [a, b] : [b, a];
7326
+ if (!ctrl) {
7327
+ for (const r of rows)
7328
+ r._selected = false;
7329
+ sel.items = [];
7330
+ }
7331
+ for (let i = lo; i <= hi; i++) {
7332
+ rows[i]._selected = true;
7333
+ if (!sel.items.includes(rows[i]))
7334
+ sel.items.push(rows[i]);
7335
+ }
7336
+ // l'ancora resta invariata per estensioni successive
7337
+ }
7338
+ }
7339
+ else if (ctrl) {
7340
+ item._selected = !item._selected;
7341
+ if (item._selected) {
7342
+ if (!sel.items.includes(item))
7343
+ sel.items.push(item);
7344
+ }
7345
+ else
7346
+ sel.items = sel.items.filter(i => i !== item);
7347
+ this.selectionAnchor = item;
7348
+ }
7349
+ else {
7350
+ // Re-click sull'unica riga selezionata => deseleziona; altrimenti seleziona solo questa
7351
+ const onlyThisSelected = item._selected && !sel.all && sel.items.length === 1 && sel.items[0] === item;
7352
+ for (const r of rows)
7353
+ r._selected = false;
7354
+ if (onlyThisSelected) {
7355
+ sel.items = [];
7356
+ this.selectionAnchor = null;
7357
+ }
7358
+ else {
7359
+ sel.items = [item];
7360
+ item._selected = true;
7361
+ this.selectionAnchor = item;
7362
+ }
7363
+ }
7364
+ sel.all = false;
7365
+ sel.exclusions = [];
7366
+ this.refreshSelectionCount(sel);
7367
+ this.syncGlobalCheck();
7368
+ this.emitSelection(sel);
7369
+ }
7370
+ /** Owner globale del range attivo: solo la tabella "attiva" reagisce a Ctrl+C/V */
7371
+ static { this.activeRangeOwner = null; }
7372
+ /** true se la cella (r,c) è dentro il range */
7373
+ inRange(r, c) {
7374
+ const rr = this.rangeRect();
7375
+ return !!rr && r >= rr.top && r <= rr.bottom && c >= rr.left && c <= rr.right;
7376
+ }
7377
+ /** Estrae gli indici r/c dal `<td data-r data-c>` più vicino al target */
7378
+ cellCoords(target) {
7379
+ const td = target?.closest?.('td[data-r]');
7380
+ if (!td)
7381
+ return null;
7382
+ const r = parseInt(td.getAttribute('data-r'), 10);
7383
+ const c = parseInt(td.getAttribute('data-c'), 10);
7384
+ return isNaN(r) || isNaN(c) ? null : { r, c };
7385
+ }
7386
+ /** mousedown sulla griglia: apre un nuovo range sulla cella-ancora */
7387
+ onGridMouseDown(event) {
7388
+ if (!this.rangeActive() || event.button !== 0 || this.editing())
7389
+ return;
7390
+ const co = this.cellCoords(event.target);
7391
+ if (!co)
7392
+ return;
7393
+ EsTable2Component.activeRangeOwner = this;
7394
+ // Una sola selezione alla volta: avviare un range azzera l'eventuale selezione di riga
7395
+ if (this.hasSelection)
7396
+ this.clearSelection();
7397
+ this.rangeDragging.set(true);
7398
+ this.rangeSel.set({ a: co, f: co });
7399
+ this.clearTextSelection();
7400
+ this.cdr.markForCheck();
7401
+ }
7402
+ /** mouseover durante il drag: estende il range (solo se la cella cambia davvero) */
7403
+ onGridMouseOver(event) {
7404
+ if (!this.rangeDragging() || !this.rangeActive())
7405
+ return;
7406
+ if ((event.buttons & 1) === 0) {
7407
+ this.rangeDragging.set(false);
7408
+ return;
7409
+ } // tasto già rilasciato
7410
+ const s = this.rangeSel();
7411
+ const co = this.cellCoords(event.target);
7412
+ if (!s || !co)
7413
+ return;
7414
+ if (s.f.r === co.r && s.f.c === co.c)
7415
+ return; // nessun cambiamento → niente ricalcolo (no debounce necessario)
7416
+ this.rangeSel.set({ a: s.a, f: co });
7417
+ this.clearTextSelection();
7418
+ this.cdr.markForCheck();
7419
+ }
7420
+ onDocMouseUp() { if (this.rangeDragging())
7421
+ this.rangeDragging.set(false); }
7422
+ flashNotice(msg) {
7423
+ this.notice.set(msg);
7424
+ this.cdr.markForCheck();
7425
+ if (this.noticeTimer)
7426
+ clearTimeout(this.noticeTimer);
7427
+ this.noticeTimer = setTimeout(() => { this.notice.set(null); this.cdr.markForCheck(); }, 4500);
7428
+ }
7429
+ clearTextSelection() {
7430
+ const sel = typeof window !== 'undefined' ? window.getSelection?.() : null;
7431
+ if (sel && !sel.isCollapsed)
7432
+ sel.removeAllRanges();
7433
+ }
7434
+ // --- Copia / Incolla -------------------------------------------------------
7435
+ // Uso gli eventi nativi `copy`/`paste` (sincroni, dentro il gesto utente):
7436
+ // niente prompt di permessi né rifiuti silenziosi come con
7437
+ // navigator.clipboard.readText(), che in molti browser è bloccata.
7438
+ onDocCopy(event) {
7439
+ if (EsTable2Component.activeRangeOwner !== this || this.editing())
7440
+ return;
7441
+ if (this.isFieldTarget(event.target))
7442
+ return; // lascio la copia nativa nei campi
7443
+ const cells = this.rangeCells();
7444
+ if (!cells.length)
7445
+ return;
7446
+ const tsv = cells.map(row => row.map(({ item, col }) => this.cellDisplayText(item, col)).join('\t')).join('\n');
7447
+ event.clipboardData?.setData('text/plain', tsv);
7448
+ event.preventDefault();
7449
+ }
7450
+ onDocPaste(event) {
7451
+ if (EsTable2Component.activeRangeOwner !== this || this.editing() || !this.Editable())
7452
+ return;
7453
+ if (this.isFieldTarget(event.target))
7454
+ return; // lascio l'incolla nativo nei campi
7455
+ if (!this.rangeRect())
7456
+ return;
7457
+ const text = event.clipboardData?.getData('text/plain') ?? '';
7458
+ event.preventDefault();
7459
+ this.applyPaste(text);
7460
+ }
7461
+ /** true se il target dell'evento è un campo di input (copia/incolla nativi) */
7462
+ isFieldTarget(t) {
7463
+ const tag = t?.tagName;
7464
+ return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';
7465
+ }
7466
+ /** Celle (item+colonna) comprese nel rettangolo di selezione */
7467
+ rangeCells() {
7468
+ const rr = this.rangeRect();
7469
+ if (!rr)
7470
+ return [];
7471
+ const rows = this.boundSource();
7472
+ const cols = this.visibleColumns();
7473
+ const out = [];
7474
+ for (let r = rr.top; r <= rr.bottom; r++) {
7475
+ const item = rows[r];
7476
+ if (!item)
7477
+ continue;
7478
+ const line = [];
7479
+ for (let c = rr.left; c <= rr.right; c++)
7480
+ if (cols[c])
7481
+ line.push({ item, col: cols[c] });
7482
+ if (line.length)
7483
+ out.push(line);
7484
+ }
7485
+ return out;
7486
+ }
7487
+ applyPaste(text) {
7488
+ if (text == null)
7489
+ return;
7490
+ const matrix = text.replace(/\r/g, '').replace(/\n$/, '').split('\n').map(l => l.split('\t'));
7491
+ const cells = this.rangeCells();
7492
+ if (!cells.length || !matrix.length)
7493
+ return;
7494
+ // Un singolo valore si applica all'intera selezione; altrimenti le dimensioni devono coincidere
7495
+ const single = matrix.length === 1 && matrix[0].length === 1;
7496
+ if (!single && (matrix.length !== cells.length || matrix[0].length !== cells[0].length)) {
7497
+ this.flashNotice(`Impossibile incollare: i dati (${matrix.length}×${matrix[0].length}) non corrispondono alla selezione (${cells.length}×${cells[0].length}).`);
7498
+ return;
7499
+ }
7500
+ // Pass 1 — convalido TUTTE le celle per tipo di colonna. Se anche una sola
7501
+ // è incompatibile, avviso e non applico NULLA (operazione atomica): meglio
7502
+ // un avviso chiaro che svuotare silenziosamente le celle.
7503
+ const coerced = [];
7504
+ for (let r = 0; r < cells.length; r++) {
7505
+ coerced[r] = [];
7506
+ for (let c = 0; c < cells[r].length; c++) {
7507
+ const { col } = cells[r][c];
7508
+ const raw = single ? matrix[0][0] : matrix[r][c];
7509
+ const res = this.coerceValue(raw, col);
7510
+ if (!res.ok) {
7511
+ this.flashNotice(`Impossibile incollare: «${raw}» non è compatibile con la colonna «${col.headerText ?? col.id}» (tipo ${col.type ?? 'testo'}).`);
7512
+ return;
7513
+ }
7514
+ coerced[r][c] = res.value;
7515
+ }
7516
+ }
7517
+ // Pass 2 — applico i valori già convertiti
7518
+ const changes = [];
7519
+ for (let r = 0; r < cells.length; r++)
7520
+ for (let c = 0; c < cells[r].length; c++) {
7521
+ const { item, col } = cells[r][c];
7522
+ const change = this.applyCoerced(item, col, coerced[r][c]);
7523
+ if (change)
7524
+ changes.push(change);
7525
+ }
7526
+ if (changes.length) {
7527
+ this.onModelChange.emit(changes);
7528
+ this.changed();
7529
+ }
7530
+ this.cdr.markForCheck();
7531
+ }
7532
+ // --- Editing in cella ------------------------------------------------------
7533
+ /** Template di editor custom (`*editor`) per una colonna, se fornito dal consumer */
7534
+ editorFor(col) {
7535
+ const eds = this.editorDirectives?.toArray() ?? [];
7536
+ const e = eds.find(x => (x.editor && x.editor === col.id) || (!!col.property && x.editorForProperty === col.property));
7537
+ return e?.Template ?? null;
7538
+ }
7539
+ /** Contesto passato ai template `*editor` custom */
7540
+ editorContext(item, col) {
7541
+ return {
7542
+ $implicit: item,
7543
+ item, column: col,
7544
+ value: this.editDraft,
7545
+ setValue: (v) => { this.editDraft = v; },
7546
+ commit: () => this.commitEdit(item, col),
7547
+ cancel: () => this.cancelEdit()
7548
+ };
7549
+ }
7550
+ isEditing(r, c) {
7551
+ const e = this.editing();
7552
+ return !!e && e.r === r && e.c === c;
7553
+ }
7554
+ /** Tipo dell'`<input>` di default in base al tipo di colonna */
7555
+ editorInputType(type) {
7556
+ switch (type) {
7557
+ case 'number':
7558
+ case 'int':
7559
+ case 'float':
7560
+ case 'currency': return 'number';
7561
+ default: return 'text';
7562
+ }
7563
+ }
7564
+ onCellDblClick(item, col, r, c) {
7565
+ if (!this.Editable() || !col.property || item?._group)
7566
+ return;
7567
+ this.rangeSel.set(null);
7568
+ this.editDraft = this.rawCellValue(item, col) ?? '';
7569
+ this.editing.set({ r, c });
7570
+ this.cdr.markForCheck();
7571
+ requestAnimationFrame(() => {
7572
+ const el = this.hostEl?.nativeElement?.querySelector('.est2-editor-input');
7573
+ el?.focus();
7574
+ el?.select?.();
7575
+ });
7576
+ }
7577
+ commitEdit(item, col) {
7578
+ if (!this.editing())
7579
+ return; // già chiusa (es. blur dopo Enter/Escape)
7580
+ const res = this.coerceValue(this.editDraft, col);
7581
+ this.editing.set(null);
7582
+ if (!res.ok) {
7583
+ // valore incompatibile col tipo: avviso e NON modifico (nessun svuotamento)
7584
+ this.flashNotice(`Valore «${this.editDraft}» non valido per «${col.headerText ?? col.id}» (tipo ${col.type ?? 'testo'}).`);
7585
+ this.cdr.markForCheck();
7586
+ return;
7587
+ }
7588
+ const change = this.applyCoerced(item, col, res.value);
7589
+ if (change) {
7590
+ this.onModelChange.emit([change]);
7591
+ this.changed();
7592
+ }
7593
+ this.cdr.markForCheck();
7594
+ }
7595
+ cancelEdit() {
7596
+ if (!this.editing())
7597
+ return;
7598
+ this.editing.set(null);
7599
+ this.cdr.markForCheck();
7600
+ }
7601
+ // --- Valori: lettura/scrittura tipizzata + testo di copia ------------------
7602
+ /** Valore grezzo su cui operano editing/paste (gestisce gli oggetti `properties`) */
7603
+ rawCellValue(item, col) {
7604
+ if (!col.property)
7605
+ return null;
7606
+ return item?.properties ? item.properties[col.property] : item?.[col.property];
7607
+ }
7608
+ /** Scrive un valore GIÀ convertito e restituisce l'eventuale EsTableModelChange */
7609
+ applyCoerced(item, col, niu) {
7610
+ if (!col.property)
7611
+ return null;
7612
+ const old = this.rawCellValue(item, col);
7613
+ if (old === niu)
7614
+ return null;
7615
+ if (item?.properties)
7616
+ item.properties[col.property] = niu;
7617
+ else
7618
+ item[col.property] = niu;
7619
+ return new EsTableModelChange(item, String(col.headerText ?? col.id), col.property, old, niu);
7620
+ }
7621
+ /**
7622
+ * Converte una stringa (digitata/incollata) nel tipo della colonna, segnalando
7623
+ * se è compatibile. `ok:false` = valore incompatibile col tipo (es. testo in una
7624
+ * colonna numerica) → chi chiama avvisa e non scrive. Una stringa vuota è sempre
7625
+ * valida e azzera il valore (null).
7626
+ */
7627
+ coerceValue(raw, col) {
7628
+ const type = col.type;
7629
+ const v = raw == null ? '' : String(raw).trim();
7630
+ switch (type) {
7631
+ case 'int':
7632
+ case 'number':
7633
+ case 'float':
7634
+ case 'currency': {
7635
+ if (v === '')
7636
+ return { ok: true, value: null };
7637
+ const n = this.toNumber(v);
7638
+ if (n == null || isNaN(n))
7639
+ return { ok: false, value: null };
7640
+ return { ok: true, value: (type === 'int' || type === 'number') ? Math.round(n) : n };
7641
+ }
7642
+ case 'boolean': {
7643
+ if (v === '')
7644
+ return { ok: true, value: null };
7645
+ if (/^(true|1|✓|s[iì]|si|yes|y)$/i.test(v))
7646
+ return { ok: true, value: true };
7647
+ if (/^(false|0|—|no|n)$/i.test(v))
7648
+ return { ok: true, value: false };
7649
+ return { ok: false, value: null };
7650
+ }
7651
+ case 'enum':
7652
+ case 'autocomplete': {
7653
+ if (!col.source || col.source.length === 0)
7654
+ return { ok: true, value: v };
7655
+ const byId = col.source.find(o => String(o.id) === v);
7656
+ if (byId)
7657
+ return { ok: true, value: byId.id };
7658
+ const byDesc = col.source.find(o => o.description === v);
7659
+ if (byDesc)
7660
+ return { ok: true, value: byDesc.id };
7661
+ return { ok: false, value: null };
7662
+ }
7663
+ case 'date':
7664
+ case 'datetime':
7665
+ case 'time': {
7666
+ if (v === '')
7667
+ return { ok: true, value: null };
7668
+ const d = this.parseDateLoose(v);
7669
+ return d ? { ok: true, value: d } : { ok: false, value: null };
7670
+ }
7671
+ default:
7672
+ return { ok: true, value: raw };
7673
+ }
7674
+ }
7675
+ /** Parsing numerico tollerante (separatori di migliaia + virgola/punto decimale). `NaN` = non numerico */
7676
+ toNumber(v) {
7677
+ let s = v.replace(/\s/g, '').replace(/[€$%]/g, '');
7678
+ if (s === '')
7679
+ return NaN;
7680
+ const hasComma = s.includes(','), hasDot = s.includes('.');
7681
+ if (hasComma && hasDot) {
7682
+ // l'ultimo separatore è quello decimale
7683
+ if (s.lastIndexOf(',') > s.lastIndexOf('.'))
7684
+ s = s.replace(/\./g, '').replace(',', '.');
7685
+ else
7686
+ s = s.replace(/,/g, '');
7687
+ }
7688
+ else if (hasComma) {
7689
+ s = s.replace(',', '.');
7690
+ }
7691
+ return /^-?\d*\.?\d+$/.test(s) ? parseFloat(s) : NaN;
7692
+ }
7693
+ /** Parsing date tollerante: `dd/MM/yyyy [HH:mm[:ss]]` o formati nativi. `null` = non valida */
7694
+ parseDateLoose(v) {
7695
+ const m = v.match(/^(\d{1,2})[\/\-.](\d{1,2})[\/\-.](\d{2,4})(?:[ T](\d{1,2}):(\d{2})(?::(\d{2}))?)?$/);
7696
+ if (m) {
7697
+ const y = m[3].length === 2 ? 2000 + +m[3] : +m[3];
7698
+ const d = new Date(y, +m[2] - 1, +m[1], +(m[4] || 0), +(m[5] || 0), +(m[6] || 0));
7699
+ return isNaN(d.getTime()) ? null : d;
7700
+ }
7701
+ const d = new Date(v);
7702
+ return isNaN(d.getTime()) ? null : d;
7703
+ }
7704
+ /** Testo visualizzato di una cella (usato dalla copia) */
7705
+ cellDisplayText(item, col) {
7706
+ if (col.propAccessor != null)
7707
+ return this.reportCellDisplay(item, col);
7708
+ const val = this.cellValue(item, col);
7709
+ if (col.type === 'enum')
7710
+ return this.lk.transform(val, col.source).description;
7711
+ return this.fmt.transform(val, col.type, col.format, this.locale);
7712
+ }
7713
+ // ===========================================================================
7714
+ // Raggruppamento (array mode) — espansione/collasso e resa delle celle
7715
+ // ===========================================================================
7716
+ /** true se `childKey` è `ancestorKey` stesso o un suo discendente (senza collisioni di prefisso) */
7717
+ isDescendantKey(childKey, ancestorKey) {
7718
+ return !!childKey && (childKey === ancestorKey || childKey.startsWith(ancestorKey + '¦'));
7719
+ }
7720
+ /** Espande/collassa un gruppo agendo solo sui flag `_visible` (nessuna ricostruzione) */
7721
+ toggleGroup(group) {
7722
+ group._expanded = !group._expanded;
7723
+ const open = group._expanded;
7724
+ const key = group._thisKey;
7725
+ for (const r of this.boundSource()) {
7726
+ if (r === group)
7727
+ continue;
7728
+ // espandi: solo i figli immediati; collassa: l'intero sottoalbero
7729
+ const childGroup = r._group && (open ? r._parent === key : this.isDescendantKey(r._parent, key));
7730
+ const childItem = r._item && (open ? r._thisKey === key : this.isDescendantKey(r._thisKey, key));
7731
+ if (childGroup || childItem) {
7732
+ r._visible = open;
7733
+ if (r._group && !open)
7734
+ r._expanded = false; // ri-collassa i discendenti
7735
+ }
7736
+ }
7737
+ this.cdr.markForCheck();
7738
+ }
7739
+ /** Item (foglia) appartenenti a un gruppo, a qualsiasi profondità */
7740
+ groupItems(group) {
7741
+ return this.boundSource().filter(r => r._item && this.isDescendantKey(r._thisKey, group._thisKey));
7742
+ }
7743
+ /** Stato di selezione aggregato di un gruppo, per la sua checkbox */
7744
+ groupSelectionState(group) {
7745
+ const items = this.groupItems(group);
7746
+ if (items.length === 0)
7747
+ return 'none';
7748
+ const n = items.filter(it => it._selected).length;
7749
+ return n === 0 ? 'none' : n === items.length ? 'all' : 'some';
7750
+ }
7751
+ /** Seleziona/deseleziona tutti gli item di un gruppo */
7752
+ toggleGroupSelection(group) {
7753
+ if (this.SelectionDisabled() || this.SingleSelection())
7754
+ return;
7755
+ const items = this.groupItems(group);
7756
+ const select = this.groupSelectionState(group) !== 'all';
7757
+ const sel = this.currentSelection();
7758
+ for (const it of items) {
7759
+ it._selected = select;
7760
+ if (sel.all) {
7761
+ if (select)
7762
+ sel.exclusions = sel.exclusions.filter(i => i !== it);
7763
+ else if (!sel.exclusions.includes(it))
7764
+ sel.exclusions.push(it);
7765
+ }
7766
+ else {
7767
+ if (select) {
7768
+ if (!sel.items.includes(it))
7769
+ sel.items.push(it);
7770
+ }
7771
+ else
7772
+ sel.items = sel.items.filter(i => i !== it);
7773
+ }
7774
+ }
7775
+ this.refreshSelectionCount(sel);
7776
+ this.syncGlobalCheck();
7777
+ this.emitSelection(sel);
7778
+ }
7779
+ /** Testo da mostrare nella cella `col` per una riga-gruppo */
7780
+ groupCellDisplay(group, col) {
7781
+ if (group.column === col.id) {
7782
+ const gp = group._groupscount
7783
+ ? ` - ${group._groupscount} ${group._groupscount === 1 ? 'gruppo' : 'gruppi'}`
7784
+ : '';
7785
+ return `${group.value} (${group.count}${gp})`;
7786
+ }
7787
+ const colMap = group._agg?.get(col.id);
7788
+ if (!colMap || !col.header?.thAggregation)
7789
+ return '';
7790
+ let display = col.header.thAggregation;
7791
+ for (const spec of col.aggSpecs)
7792
+ display = display.replace(spec.match, formatAggValue(colMap.get(spec.func), spec.format, this.locale));
7793
+ return display;
7794
+ }
7795
+ /** true se la cella di una riga-oggetto va lasciata vuota (colonna di gruppo nascosta) */
7796
+ itemCellHidden(col) {
7797
+ return this.grouped() && col.isGroup && !this.ShowItemGroupsColumns();
7798
+ }
7799
+ /** Indentazione (px) della cella-chiave in base al livello di gruppo */
7800
+ groupIndent(group) {
7801
+ return (group?._grouplevel || 0) * 18;
7802
+ }
7803
+ // ===========================================================================
7804
+ // Modalità gerarchica (albero padre/figlio)
7805
+ // ===========================================================================
7806
+ /** Auto-rileva ownKey/parentKey ('id'/'parentid') se non specificati */
7807
+ autoDetectHierarchyKeys(items) {
7808
+ if (!this.ParentKey() && !this.OwnKey() && items.length > 0) {
7809
+ if (items[0]['parentid'] !== undefined)
7810
+ this.ParentKey.set('parentid');
7811
+ if (items[0]['id'] !== undefined)
7812
+ this.OwnKey.set('id');
7813
+ }
7814
+ if (!this.OwnKey() || !this.ParentKey())
7815
+ console.error("[es-table2] La modalità gerarchica richiede sia OwnKey che ParentKey. Fallback a lista piatta.");
7816
+ }
7817
+ /** Costruisce l'albero e applica la selezione; se mancano le chiavi torna la lista invariata */
7818
+ applyHierarchy(items, sel) {
7819
+ this.autoDetectHierarchyKeys(items);
7820
+ if (!this.OwnKey() || !this.ParentKey()) {
7821
+ this.applySelectionFlags(items, sel);
7822
+ return items;
7823
+ }
7824
+ const ordered = assignHierarchyLevels(items, this.OwnKey(), this.ParentKey(), this.StartsExpanded(), this.AutoSortHierarchy());
7825
+ // mappa padre -> figli per lookup O(1) (cascade selection, indeterminate)
7826
+ this.hierChildren.clear();
7827
+ const pk = this.ParentKey();
7828
+ for (const r of ordered) {
7829
+ const pv = r[pk];
7830
+ if (pv === null || pv === undefined || pv === '')
7831
+ continue;
7832
+ const k = String(pv);
7833
+ const arr = this.hierChildren.get(k);
7834
+ if (arr)
7835
+ arr.push(r);
7836
+ else
7837
+ this.hierChildren.set(k, [r]);
7838
+ }
7839
+ this.applySelectionFlags(ordered, sel);
7840
+ return ordered;
7841
+ }
7842
+ directChildren(item) {
7843
+ return this.hierChildren.get(String(item[this.OwnKey()])) ?? [];
7844
+ }
7845
+ hierDescendants(item) {
7846
+ const out = [];
7847
+ const stack = [...this.directChildren(item)];
7848
+ while (stack.length) {
7849
+ const n = stack.pop();
7850
+ out.push(n);
7851
+ const kids = this.directChildren(n);
7852
+ if (kids.length)
7853
+ stack.push(...kids);
7854
+ }
7855
+ return out;
7856
+ }
7857
+ /** Selezione in cascata: propaga lo stato al sottoalbero e ricalcola gli antenati */
7858
+ cascadeSelection(item, selected) {
7859
+ item._selected = selected;
7860
+ for (const d of this.hierDescendants(item))
7861
+ d._selected = selected;
7862
+ // risali: un padre è selezionato sse tutti i figli lo sono
7863
+ const pk = this.ParentKey();
7864
+ let current = item;
7865
+ while (true) {
7866
+ const pv = current[pk];
7867
+ if (pv === null || pv === undefined || pv === '')
7868
+ break;
7869
+ const parent = this.boundSource().find(r => String(r[this.OwnKey()]) === String(pv));
7870
+ if (!parent)
7871
+ break;
7872
+ const kids = this.directChildren(parent);
7873
+ parent._selected = kids.length > 0 && kids.every(k => k._selected);
7874
+ current = parent;
7875
+ }
7876
+ }
7877
+ /** Ricostruisce ItemsSelection dai flag `_selected` correnti (usato dalla cascade) */
7878
+ rebuildSelectionFromFlags(sel) {
7879
+ sel.all = false;
7880
+ sel.exclusions = [];
7881
+ sel.items = this.boundSource().filter(r => r._selected);
7882
+ sel.count = sel.items.length;
7883
+ }
7884
+ /** Stato indeterminato di una riga-padre in cascade (alcuni discendenti selezionati) */
7885
+ hierarchyIndeterminate(item) {
7886
+ if (!this.CascadeSelection() || !item?.parent || item._selected)
7887
+ return false;
7888
+ return this.hierDescendants(item).some(d => d._selected);
7889
+ }
7890
+ /** Espande/collassa un nodo dell'albero */
7891
+ toggleHierarchyNode(item) {
7892
+ if (!item?.parent)
7893
+ return;
7894
+ const rows = this.boundSource();
7895
+ if (item._expanded)
7896
+ collapseNode(rows, this.OwnKey(), this.ParentKey(), item[this.OwnKey()]);
7897
+ else
7898
+ expandNode(rows, this.OwnKey(), this.ParentKey(), item[this.OwnKey()]);
7899
+ this.cdr.markForCheck();
7900
+ }
7901
+ /** Indentazione (px) di una riga dell'albero in base al livello */
7902
+ hierarchyIndent(item) {
7903
+ return ((item?._level || 1) - 1) * 20;
7904
+ }
7905
+ /** Apertura context menu: se la riga non è selezionata la seleziona (solo lei), poi notifica */
7906
+ onContextMenuOpen(item) {
7907
+ // Il click destro apre il menu sulla riga: azzero l'eventuale range di celle
7908
+ // (la selezione a range e quella di riga sono meccanismi distinti)
7909
+ if (this.rangeSel()) {
7910
+ this.rangeSel.set(null);
7911
+ this.cdr.markForCheck();
7912
+ }
7913
+ if (item && !item._sep && !item._group && !item._selected) {
7914
+ const sel = this.currentSelection();
7915
+ for (const r of this.selectableRows())
7916
+ r._selected = false;
7917
+ sel.all = false;
7918
+ sel.exclusions = [];
7919
+ sel.items = [item];
7920
+ item._selected = true;
7921
+ this.refreshSelectionCount(sel);
7922
+ this.syncGlobalCheck();
7923
+ this.emitSelection(sel);
7924
+ }
7925
+ this.onOpenContextMenu.emit();
7926
+ }
7927
+ /** Indeterminato: alcune ma non tutte le righe selezionate (per la checkbox globale) */
7928
+ get selectionIndeterminate() {
7929
+ const sel = this.currentSelection();
7930
+ if (sel.all)
7931
+ return sel.exclusions.length > 0;
7932
+ const rows = this.selectableRows();
7933
+ const n = rows.filter(r => r._selected).length;
7934
+ return n > 0 && n < rows.length;
7935
+ }
7936
+ /** Numero di elementi selezionati (per la barra) */
7937
+ get selectedCount() { return this.currentSelection().count || 0; }
7938
+ /** true se c'è almeno un elemento selezionato (mostra la barra) */
7939
+ get hasSelection() {
7940
+ const sel = this.currentSelection();
7941
+ return sel.all || sel.items.length > 0;
7942
+ }
7943
+ /** true se sono selezionati TUTTI gli elementi (all senza esclusioni) */
7944
+ get allSelected() {
7945
+ const sel = this.currentSelection();
7946
+ return sel.all && sel.exclusions.length === 0;
7947
+ }
7948
+ /** true se ha senso proporre "seleziona tutti": pagina piena, non in all, e ci sono altre pagine */
7949
+ get canSelectEverything() {
7950
+ const sel = this.currentSelection();
7951
+ const rows = this.selectableRows();
7952
+ return !sel.all && rows.length > 0 && rows.every(r => r._selected) && this.totalCount() > rows.length;
7953
+ }
7954
+ // ===========================================================================
7955
+ // Rimozione
7956
+ // ===========================================================================
7957
+ canRemove(item) {
7958
+ const cond = this.RemovalCondition();
7959
+ return cond ? !!item[cond] : true;
7960
+ }
7961
+ removeItem(item) { this.onRemoval.emit(item); }
7962
+ abortRemoval(item) { this.onAbortRemoval.emit(item); }
7963
+ /** Applica ordine + visibilità + pin delle preferenze a una lista di colonne appena costruita */
7964
+ setColumns(cols) {
7965
+ const p = this.columnPrefs;
7966
+ if (!p) {
7967
+ this.columns.set(cols);
7968
+ this.ensurePrefsLoaded();
7969
+ return;
7970
+ }
7971
+ const byId = new Map(cols.map(c => [c.id, c]));
7972
+ const ordered = [];
7973
+ for (const id of p.order) {
7974
+ const c = byId.get(id);
7975
+ if (c) {
7976
+ ordered.push(c);
7977
+ byId.delete(id);
7978
+ }
7979
+ }
7980
+ for (const c of cols)
7981
+ if (byId.has(c.id))
7982
+ ordered.push(c); // colonne nuove (non in prefs) in coda
7983
+ for (const c of ordered) {
7984
+ c.visible = !p.hidden.includes(c.id);
7985
+ // il pin salvato sovrascrive il default della direttiva (solo se presente nelle prefs)
7986
+ if (p.pinned)
7987
+ c.pinned = p.pinned.includes(c.id);
7988
+ }
7989
+ this.columns.set(ordered);
7990
+ this.ensurePrefsLoaded();
7991
+ }
7992
+ /**
7993
+ * Chiave di persistenza delle preferenze. Identica all'originale: il `name`
7994
+ * del controllo (ngModel/formControlName). In fallback usa `[Name]`.
7995
+ */
7996
+ prefsKey() {
7997
+ const key = this.ngControl?.name || this.Name() || null;
7998
+ if (!key && this.SavePreferences())
7999
+ console.error("[es-table2] Non posso usare le preferenze senza un 'name' (ngModel) o un [Name] per la tabella");
8000
+ return key;
8001
+ }
8002
+ /**
8003
+ * Carica le preferenze dal `PreferencesService` (stesso formato dell'es-table
8004
+ * originale: `{ columns, column_ordering, orders }`). Chiamata una volta sola,
8005
+ * quando esistono già le colonne correnti per poter validare la coerenza.
8006
+ */
8007
+ ensurePrefsLoaded() {
8008
+ if (this.prefsLoaded || !this.SavePreferences())
8009
+ return;
8010
+ if (this.columns().length === 0)
8011
+ return; // attendo che le colonne esistano
8012
+ const key = this.prefsKey();
8013
+ if (!key) {
8014
+ this.prefsLoaded = true;
8015
+ return;
8016
+ }
8017
+ if (!this.prefService) {
8018
+ console.error('[es-table2] SavePreferences attivo ma @esfaenza/preferences non è disponibile');
8019
+ this.prefsLoaded = true;
8020
+ return;
8021
+ }
8022
+ this.prefsLoaded = true;
8023
+ this.prefService.getItem(key, true)
8024
+ .subscribe(prefs => this.applyLoadedPrefs(prefs));
8025
+ }
8026
+ applyLoadedPrefs(prefs) {
8027
+ if (!prefs)
8028
+ return;
8029
+ const currentIds = new Set(this.columns().map(c => c.id));
8030
+ const order = (prefs.column_ordering?.length ? prefs.column_ordering : prefs.columns) || [];
8031
+ const visible = prefs.columns || [];
8032
+ // Coerenza: se le preferenze citano colonne non più presenti, l'es-table è
8033
+ // cambiata → ignoro (come l'originale) per non nascondere/riordinare a vuoto.
8034
+ for (const id of order)
8035
+ if (id && !currentIds.has(id))
8036
+ return;
8037
+ for (const id of visible)
8038
+ if (id && !currentIds.has(id))
8039
+ return;
8040
+ if (order.length === 0 && visible.length === 0)
8041
+ return;
8042
+ // `pinned` è un'estensione retro-compatibile: se assente (prefs vecchie/originali)
8043
+ // resta il pin di default delle direttive.
8044
+ const pinned = Array.isArray(prefs.pinned) ? prefs.pinned.filter(id => currentIds.has(id)) : undefined;
8045
+ this.columnPrefs = { order, hidden: [...currentIds].filter(id => !visible.includes(id)), pinned };
8046
+ this.setColumns(this.columns());
8047
+ if (prefs.orders?.length) {
8048
+ this.orders.set([...prefs.orders]);
8049
+ if (this.viewMode() && this.view())
8050
+ this.view().orders = [...prefs.orders];
8051
+ }
8052
+ this.cdr.markForCheck();
8053
+ }
8054
+ /**
8055
+ * Persiste le preferenze correnti nel formato originale
8056
+ * `{ columns (visibili), column_ordering (ordine), orders (ordinamenti) }`.
8057
+ */
8058
+ saveColumnPrefs() {
8059
+ if (!this.SavePreferences() || !this.prefService)
8060
+ return;
8061
+ const key = this.prefsKey();
8062
+ if (!key)
8063
+ return;
8064
+ // Escludo le sotto-colonne Multi: sono auto-generate dai dati, non vanno
8065
+ // persistite (altrimenti il guard di coerenza le rifiuterebbe al reload,
8066
+ // quando ancora non esistono).
8067
+ const cols = this.columns().filter(c => c.multiProp == null);
8068
+ const payload = {
8069
+ columns: cols.filter(c => c.visible).map(c => c.id),
8070
+ column_ordering: cols.map(c => c.id),
8071
+ orders: this.orders(),
8072
+ // estensione es-table2 (retro-compatibile: l'originale ignora i campi extra)
8073
+ pinned: cols.filter(c => c.pinned).map(c => c.id)
8074
+ };
8075
+ // allineo anche i campi wire dell'AppSearch (parità col server-side)
8076
+ if (this.viewMode() && this.view()) {
8077
+ this.view().columns = payload.columns;
8078
+ this.view().column_ordering = payload.column_ordering;
8079
+ }
8080
+ this.prefService.setItem(key, payload).subscribe();
8081
+ }
8082
+ // --- Pin/unpin dall'header --------------------------------------------------
8083
+ /** Attiva/disattiva il pin di una colonna e persiste (dall'indicatore in testata) */
8084
+ togglePin(col, ev) {
8085
+ ev?.stopPropagation();
8086
+ col.pinned = !col.pinned;
8087
+ // aggiorno le prefs correnti (ordine/visibilità invariati) — escludo le Multi
8088
+ const nonMulti = this.columns().filter(c => c.multiProp == null);
8089
+ this.columnPrefs = {
8090
+ order: nonMulti.map(c => c.id),
8091
+ hidden: nonMulti.filter(c => !c.visible).map(c => c.id),
8092
+ pinned: nonMulti.filter(c => c.pinned).map(c => c.id)
8093
+ };
8094
+ this.columns.set([...this.columns()]); // forza il ricalcolo di visibleColumns/headerGroupRows
8095
+ this.saveColumnPrefs();
8096
+ this.cdr.markForCheck();
8097
+ }
8098
+ /** Etichetta leggibile di una colonna (header statico o id) */
8099
+ columnLabel(col) {
8100
+ return (col.headerText && String(col.headerText).trim()) || col.id;
8101
+ }
8102
+ openColumnsDialog() {
8103
+ // Le sotto-colonne Multi sono auto-generate: non le espongo nella dialog
8104
+ // (riordinarle/pinnarle singolarmente romperebbe il raggruppamento).
8105
+ this.dialogCols.set(this.columns().filter(c => c.multiProp == null)
8106
+ .map(c => ({ id: c.id, label: this.columnLabel(c), visible: c.visible, pinned: !!c.pinned })));
8107
+ this.columnsDialogOpen.set(true);
8108
+ this.cdr.markForCheck();
8109
+ }
8110
+ closeColumnsDialog() { this.columnsDialogOpen.set(false); this.cdr.markForCheck(); }
8111
+ dialogToggle(i) {
8112
+ const list = [...this.dialogCols()];
8113
+ list[i] = { ...list[i], visible: !list[i].visible };
8114
+ this.dialogCols.set(list);
8115
+ }
8116
+ dialogTogglePin(i) {
8117
+ const list = [...this.dialogCols()];
8118
+ list[i] = { ...list[i], pinned: !list[i].pinned };
8119
+ this.dialogCols.set(list);
8120
+ }
8121
+ dialogSetAll(visible) {
8122
+ this.dialogCols.set(this.dialogCols().map(c => ({ ...c, visible })));
8123
+ }
8124
+ dialogMove(i, dir) {
8125
+ const list = [...this.dialogCols()];
8126
+ const j = i + dir;
8127
+ if (j < 0 || j >= list.length)
8128
+ return;
8129
+ [list[i], list[j]] = [list[j], list[i]];
8130
+ this.dialogCols.set(list);
8131
+ }
8132
+ applyColumnsDialog() {
8133
+ const list = this.dialogCols();
8134
+ this.columnPrefs = {
8135
+ order: list.map(c => c.id),
8136
+ hidden: list.filter(c => !c.visible).map(c => c.id),
8137
+ pinned: list.filter(c => c.pinned).map(c => c.id)
8138
+ };
8139
+ // riapplico alla lista corrente conservando gli oggetti-colonna...
8140
+ this.setColumns(this.columns());
8141
+ // ...e SOLO DOPO persisto (saveColumnPrefs deriva il payload da columns())
8142
+ this.saveColumnPrefs();
8143
+ this.columnsDialogOpen.set(false);
8144
+ this.cdr.markForCheck();
8145
+ }
8146
+ resetColumnsDialog() {
8147
+ this.columnPrefs = null;
8148
+ this.buildColumns(); // ricostruisce dalle direttive nell'ordine originale
8149
+ if (this.viewMode() || this.arrayMode())
8150
+ this.refresh();
8151
+ this.saveColumnPrefs(); // persisto lo stato di default (dopo la ricostruzione)
8152
+ this.columnsDialogOpen.set(false);
8153
+ this.cdr.markForCheck();
8154
+ }
8155
+ toggleExportMenu() { this.cornerMenuOpen.set(false); this.exportMenuOpen.set(!this.exportMenuOpen()); this.cdr.markForCheck(); }
8156
+ toggleCornerMenu() { this.exportMenuOpen.set(false); this.cornerMenuOpen.set(!this.cornerMenuOpen()); this.cdr.markForCheck(); }
8157
+ /** Chiude i menu a tendina del chrome quando si clicca fuori */
8158
+ onDocClick() {
8159
+ if (this.exportMenuOpen() || this.cornerMenuOpen()) {
8160
+ this.exportMenuOpen.set(false);
8161
+ this.cornerMenuOpen.set(false);
8162
+ this.cdr.markForCheck();
8163
+ }
8164
+ }
8165
+ /** Colonne da esportare (tutte o solo visibili) e con dato testuale */
8166
+ exportColumns() {
8167
+ const cols = this.ExportOnlyVisibleColumns() ? this.visibleColumns() : this.columns();
8168
+ return cols.filter(c => !c.isGroup);
8169
+ }
8170
+ /** Righe-dato correnti (esclude righe-gruppo) */
8171
+ exportRows() {
8172
+ return this.boundSource().filter(r => !r._group);
8173
+ }
8174
+ /** Matrice [header, ...righe] con i valori visualizzati */
8175
+ buildExportMatrix() {
8176
+ const cols = this.exportColumns();
8177
+ const header = cols.map(c => this.columnLabel(c));
8178
+ const rows = this.exportRows().map(item => cols.map(c => this.cellDisplayText(item, c)));
8179
+ return [header, ...rows];
8180
+ }
8181
+ export(format = 'CSV') {
8182
+ this.exportMenuOpen.set(false);
8183
+ // Parità con l'originale: se il consumer fornisce ExportFunction gliela lascio gestire
8184
+ if (this.ExportFunction) {
8185
+ this.ExportFunction((data, _type) => this.exportData(data, format), format);
8186
+ return;
8187
+ }
8188
+ const matrix = this.buildExportMatrix();
8189
+ if (matrix.length <= 1) {
8190
+ this.flashNotice('Nessun dato da esportare.');
8191
+ return;
8192
+ }
8193
+ if (format === 'XLSX')
8194
+ this.exportXlsx(matrix);
8195
+ else
8196
+ this.exportCsv(matrix);
8197
+ }
8198
+ /** Esporta dati forniti dal consumer (via ExportFunction): li mappa sulle colonne correnti */
8199
+ exportData(data, format) {
8200
+ if (!data?.length) {
8201
+ this.flashNotice('Nessun dato da esportare.');
8202
+ return;
8203
+ }
8204
+ const cols = this.exportColumns();
8205
+ const header = cols.map(c => this.columnLabel(c));
8206
+ const rows = data.filter(r => !r._group).map(item => cols.map(c => this.cellDisplayText(item, c)));
8207
+ const matrix = [header, ...rows];
8208
+ if (format === 'XLSX')
8209
+ this.exportXlsx(matrix);
8210
+ else
8211
+ this.exportCsv(matrix);
8212
+ }
8213
+ exportCsv(matrix) {
8214
+ const esc = (v) => {
8215
+ const s = v == null ? '' : String(v);
8216
+ return /[",\n;]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
8217
+ };
8218
+ const csv = matrix.map(row => row.map(esc).join(';')).join('\r\n');
8219
+ // BOM per la corretta apertura in Excel con caratteri accentati
8220
+ this.downloadBlob('' + csv, this.exportFileName('csv'), 'text/csv;charset=utf-8;');
8221
+ }
8222
+ async exportXlsx(matrix) {
8223
+ try {
8224
+ const XLSX = await import('xlsx');
8225
+ const ws = XLSX.utils.aoa_to_sheet(matrix);
8226
+ const wb = XLSX.utils.book_new();
8227
+ XLSX.utils.book_append_sheet(wb, ws, 'Export');
8228
+ XLSX.writeFile(wb, this.exportFileName('xlsx'));
8229
+ }
8230
+ catch {
8231
+ this.flashNotice('Export XLSX non disponibile (SheetJS assente): esporto in CSV.');
8232
+ this.exportCsv(matrix);
8233
+ }
8234
+ }
8235
+ /** Nome file rispettando l'estensione richiesta */
8236
+ exportFileName(ext) {
8237
+ const name = this.ExportFileName() || 'Export';
8238
+ return /\.(csv|xlsx)$/i.test(name) ? name.replace(/\.(csv|xlsx)$/i, '.' + ext) : `${name}.${ext}`;
8239
+ }
8240
+ downloadBlob(content, filename, mime) {
8241
+ if (typeof document === 'undefined')
8242
+ return;
8243
+ const blob = new Blob([content], { type: mime });
8244
+ const url = URL.createObjectURL(blob);
8245
+ const a = document.createElement('a');
8246
+ a.href = url;
8247
+ a.download = filename;
8248
+ document.body.appendChild(a);
8249
+ a.click();
8250
+ document.body.removeChild(a);
8251
+ setTimeout(() => URL.revokeObjectURL(url), 0);
8252
+ }
8253
+ // ===========================================================================
8254
+ // Corner menu / helpers template
8255
+ // ===========================================================================
8256
+ cornerAction(id) { this.onCornerAction.emit({ id, estable: this }); }
8257
+ rowClass(item) {
8258
+ return this.RowClassAssigner ? (this.RowClassAssigner(item) || '') : '';
8259
+ }
8260
+ hasChrome() {
8261
+ return this.Export() || this.HiddenColumns() || this.ColumnsOrdering() || this.CornerMenuOptions().length > 0;
8262
+ }
8263
+ /** Colspan totale per righe a piena larghezza (stato vuoto) */
8264
+ totalColspan() {
8265
+ let n = this.usesColumns() ? this.visibleColumns().length : 1;
8266
+ n += this.DynamicOperations().length;
8267
+ if (this.Selection())
8268
+ n++;
8269
+ if (this.Removal())
8270
+ n++;
8271
+ if (this.hasChrome())
8272
+ n++;
8273
+ return Math.max(1, n);
8274
+ }
8275
+ /** Valore grezzo di una proprietà, gestendo gli oggetti "generic" con `properties` */
8276
+ cellValue(item, col) {
8277
+ if (!col.property)
8278
+ return null;
8279
+ return item?.properties ? item.properties[col.property] : item?.[col.property];
8280
+ }
8281
+ /** Entry Multi corrispondente a una cella (`item[multiProp][multiIndex]`) */
8282
+ multiCell(item, col) {
8283
+ if (col.multiProp == null || col.multiIndex == null)
8284
+ return {};
8285
+ const arr = item?.[col.multiProp];
8286
+ return (Array.isArray(arr) ? arr[col.multiIndex] : null) ?? {};
8287
+ }
8288
+ 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: EST2_DEFAULTS, optional: true }, { token: EST2_DEBUG, optional: true }, { token: EST2_EXPORT_GLOBAL_ACL, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
8289
+ 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 }, 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 }], 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 <!-- 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\" [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 [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\"\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 [class]=\"col.cssClass\"\n [class.est2-th--orderable]=\"OrderByColumn() && col.orderable\"\n [class.est2-col-min]=\"col.header?.thShrink\"\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\"\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-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 [title]=\"c.pinned ? 'Sblocca' : 'Blocca a sinistra'\" (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: 10px;--est-cell-px: 14px;--est-row-h: 44px;--est-font: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;--est-fs: 13.5px;--est-fs-sm: 12px;--est-fw-head: 600;--est-transition: .14s cubic-bezier(.4, 0, .2, 1);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: 5px;--est-cell-px: 10px;--est-row-h: 32px;--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-muted);font-weight:var(--est-fw-head);font-size:var(--est-fs-sm);letter-spacing:.02em;text-transform:uppercase;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:600;letter-spacing:.02em;text-transform:uppercase;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);border-bottom-color:transparent}.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 th.est2-pinned,.est2 td.est2-pinned{position:sticky;background:var(--est-bg);box-shadow:1px 0 0 var(--est-border)}.est2 td.est2-pinned{z-index:3}.est2 thead th.est2-pinned{z-index:6;background:var(--est-bg-subtle)}.est2 thead tr.est2-hgroup-row th.est2-pinned{z-index:6;background:var(--est-bg)}.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-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: 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 }); }
8290
+ }
8291
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: EsTable2Component, decorators: [{
8292
+ type: Component,
8293
+ 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 <!-- 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\" [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 [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\"\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 [class]=\"col.cssClass\"\n [class.est2-th--orderable]=\"OrderByColumn() && col.orderable\"\n [class.est2-col-min]=\"col.header?.thShrink\"\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\"\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-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 [title]=\"c.pinned ? 'Sblocca' : 'Blocca a sinistra'\" (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: 10px;--est-cell-px: 14px;--est-row-h: 44px;--est-font: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;--est-fs: 13.5px;--est-fs-sm: 12px;--est-fw-head: 600;--est-transition: .14s cubic-bezier(.4, 0, .2, 1);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: 5px;--est-cell-px: 10px;--est-row-h: 32px;--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-muted);font-weight:var(--est-fw-head);font-size:var(--est-fs-sm);letter-spacing:.02em;text-transform:uppercase;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:600;letter-spacing:.02em;text-transform:uppercase;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);border-bottom-color:transparent}.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 th.est2-pinned,.est2 td.est2-pinned{position:sticky;background:var(--est-bg);box-shadow:1px 0 0 var(--est-border)}.est2 td.est2-pinned{z-index:3}.est2 thead th.est2-pinned{z-index:6;background:var(--est-bg-subtle)}.est2 thead tr.est2-hgroup-row th.est2-pinned{z-index:6;background:var(--est-bg)}.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-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"] }]
8294
+ }], ctorParameters: () => [{ type: i1.NgControl, decorators: [{
8295
+ type: Self
8296
+ }, {
8297
+ type: Optional
8298
+ }] }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i2$1.PreferencesService, decorators: [{
8299
+ type: Optional
8300
+ }] }, { type: EsTableDefaultable, decorators: [{
8301
+ type: Optional
8302
+ }, {
8303
+ type: Inject,
8304
+ args: [EST2_DEFAULTS]
8305
+ }] }, { type: undefined, decorators: [{
8306
+ type: Optional
8307
+ }, {
8308
+ type: Inject,
8309
+ args: [EST2_DEBUG]
8310
+ }] }, { type: undefined, decorators: [{
8311
+ type: Optional
8312
+ }, {
8313
+ type: Inject,
8314
+ args: [EST2_EXPORT_GLOBAL_ACL]
8315
+ }] }], propDecorators: { headerRef: [{
8316
+ type: ContentChild,
8317
+ args: ['header', { static: false }]
8318
+ }], bodyRef: [{
8319
+ type: ContentChild,
8320
+ args: ['body', { static: false }]
8321
+ }], thDirectives: [{
8322
+ type: ContentChildren,
8323
+ args: [EsThDirective]
8324
+ }], tdDirectives: [{
8325
+ type: ContentChildren,
8326
+ args: [EsTdDirective]
8327
+ }], editorDirectives: [{
8328
+ type: ContentChildren,
8329
+ args: [EsTdEditorDirective]
8330
+ }], ContextMenu: [{
8331
+ type: Input
8332
+ }], tableEmptyMenu: [{
8333
+ type: ViewChild,
8334
+ args: ['emptyMenu', { static: true }]
8335
+ }], theadRef: [{
8336
+ type: ViewChild,
8337
+ args: ['theadRef']
8338
+ }], _Selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "Selection", required: false }] }], SingleSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "SingleSelection", required: false }] }], SelectionDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "SelectionDisabled", required: false }] }], _SelectAll: [{ type: i0.Input, args: [{ isSignal: true, alias: "SelectAll", required: false }] }], _UseSelectionCache: [{ type: i0.Input, args: [{ isSignal: true, alias: "UseSelectionCache", required: false }] }], _ShiftClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "ShiftClick", required: false }] }], _OrderByColumn: [{ type: i0.Input, args: [{ isSignal: true, alias: "OrderByColumn", required: false }] }], MultipleOrderingDirectives: [{ type: i0.Input, args: [{ isSignal: true, alias: "MultipleOrderingDirectives", required: false }] }], Removal: [{ type: i0.Input, args: [{ isSignal: true, alias: "Removal", required: false }] }], RemovalCondition: [{ type: i0.Input, args: [{ isSignal: true, alias: "RemovalCondition", required: false }] }], RowClassAssigner: [{
8339
+ type: Input
8340
+ }], _HidePaging: [{ type: i0.Input, args: [{ isSignal: true, alias: "HidePaging", required: false }] }], _HidePagingCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "HidePagingCount", required: false }] }], _HidePagingButtons: [{ type: i0.Input, args: [{ isSignal: true, alias: "HidePagingButtons", required: false }] }], _AllSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "AllSearch", required: false }] }], _PagingStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "PagingStyle", required: false }] }], _ArraymodeItemsPerPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "ArraymodeItemsPerPage", required: false }] }], _UseArrayModePaging: [{ type: i0.Input, args: [{ isSignal: true, alias: "UseArrayModePaging", required: false }] }], CountLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "CountLabel", required: false }] }], Height: [{ type: i0.Input, args: [{ isSignal: true, alias: "Height", required: false }] }], MaxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "MaxHeight", required: false }] }], EmptySpaceBackgroundColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "EmptySpaceBackgroundColor", required: false }] }], HighCellDensity: [{ type: i0.Input, args: [{ isSignal: true, alias: "HighCellDensity", required: false }] }], HeaderHidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "HeaderHidden", required: false }] }], BodyHidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "BodyHidden", required: false }] }], ShowLoadingOnBootstrap: [{ type: i0.Input, args: [{ isSignal: true, alias: "ShowLoadingOnBootstrap", required: false }] }], _DefaultAlignment: [{ type: i0.Input, args: [{ isSignal: true, alias: "DefaultAlignment", required: false }] }], _TableClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "TableClass", required: false }] }], _ContainerClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "ContainerClass", required: false }] }], EsTableHandledSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "EsTableHandledSearch", required: false }] }], SearchThrottle: [{ type: i0.Input, args: [{ isSignal: true, alias: "SearchThrottle", required: false }] }], _ColumnsResizable: [{ type: i0.Input, args: [{ isSignal: true, alias: "ColumnsResizable", required: false }] }], _ColumnsPinnable: [{ type: i0.Input, args: [{ isSignal: true, alias: "ColumnsPinnable", required: false }] }], _HiddenColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "HiddenColumns", required: false }] }], _ColumnsOrdering: [{ type: i0.Input, args: [{ isSignal: true, alias: "ColumnsOrdering", required: false }] }], _Export: [{ type: i0.Input, args: [{ isSignal: true, alias: "Export", required: false }] }], XLSXExport: [{ type: i0.Input, args: [{ isSignal: true, alias: "XLSXExport", required: false }] }], CSVExport: [{ type: i0.Input, args: [{ isSignal: true, alias: "CSVExport", required: false }] }], ExportFileName: [{ type: i0.Input, args: [{ isSignal: true, alias: "ExportFileName", required: false }] }], ExportOnlyVisibleColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "ExportOnlyVisibleColumns", required: false }] }], ExportFunction: [{
8341
+ type: Input
8342
+ }], CornerMenuOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "CornerMenuOptions", required: false }] }], DynamicOperations: [{ type: i0.Input, args: [{ isSignal: true, alias: "DynamicOperations", required: false }] }], _DynamicRowColumnsDefinition: [{ type: i0.Input, args: [{ isSignal: true, alias: "DynamicRowColumnsDefinition", required: false }] }], Hierarchy: [{ type: i0.Input, args: [{ isSignal: true, alias: "Hierarchy", required: false }] }], _ParentKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "ParentKey", required: false }] }], _OwnKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "OwnKey", required: false }] }], _AutoSortHierarchy: [{ type: i0.Input, args: [{ isSignal: true, alias: "AutoSortHierarchy", required: false }] }], StartsExpanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "StartsExpanded", required: false }] }], CascadeSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "CascadeSelection", required: false }] }], _SavePreferences: [{ type: i0.Input, args: [{ isSignal: true, alias: "SavePreferences", required: false }] }], Name: [{ type: i0.Input, args: [{ isSignal: true, alias: "Name", required: false }] }], _RowGroupingPagingStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "RowGroupingPagingStyle", required: false }] }], _ShowItemGroupsColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "ShowItemGroupsColumns", required: false }] }], Editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "Editable", required: false }] }], RangeSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "RangeSelection", required: false }] }], ItemSourceProperty: [{ type: i0.Input, args: [{ isSignal: true, alias: "ItemSourceProperty", required: false }] }], HasHeaderGroup: [{ type: i0.Input, args: [{ isSignal: true, alias: "HasHeaderGroup", required: false }] }], HasSecondaryHeaderGroup: [{ type: i0.Input, args: [{ isSignal: true, alias: "HasSecondaryHeaderGroup", required: false }] }], SearchView: [{ type: i0.Input, args: [{ isSignal: true, alias: "SearchView", required: false }] }], _AutoUpdate: [{ type: i0.Input, args: [{ isSignal: true, alias: "AutoUpdate", required: false }] }], onOrderChanged: [{
8343
+ type: Output
8344
+ }], onSearchRequest: [{
8345
+ type: Output
8346
+ }], onSelectionChanged: [{
8347
+ type: Output
8348
+ }], onRemoval: [{
8349
+ type: Output
8350
+ }], onAbortRemoval: [{
8351
+ type: Output
8352
+ }], onModelChange: [{
8353
+ type: Output
8354
+ }], onOpenContextMenu: [{
8355
+ type: Output
8356
+ }], onCornerAction: [{
8357
+ type: Output
8358
+ }], onDynamicOperation: [{
8359
+ type: Output
8360
+ }], globalCheck: [{ type: i0.Input, args: [{ isSignal: true, alias: "globalCheck", required: false }] }, { type: i0.Output, args: ["globalCheckChange"] }], autoUpdate: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoUpdate", required: false }] }, { type: i0.Output, args: ["autoUpdateChange"] }], seconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "seconds", required: false }] }, { type: i0.Output, args: ["secondsChange"] }], researchInProgress: [{ type: i0.Input, args: [{ isSignal: true, alias: "researchInProgress", required: false }] }, { type: i0.Output, args: ["researchInProgressChange"] }], locale: [{
8361
+ type: Input
8362
+ }], hostClass: [{
8363
+ type: HostBinding,
8364
+ args: ['class.est2']
8365
+ }], denseClass: [{
8366
+ type: HostBinding,
8367
+ args: ['class.est2--dense']
8368
+ }], onDocMouseUp: [{
8369
+ type: HostListener,
8370
+ args: ['document:mouseup']
8371
+ }], onDocCopy: [{
8372
+ type: HostListener,
8373
+ args: ['document:copy', ['$event']]
8374
+ }], onDocPaste: [{
8375
+ type: HostListener,
8376
+ args: ['document:paste', ['$event']]
8377
+ }], onDocClick: [{
8378
+ type: HostListener,
8379
+ args: ['document:click']
8380
+ }] } });
8381
+
5771
8382
  const MODULES = [
5772
8383
  CommonModule,
5773
8384
  FormsModule,
@@ -5783,14 +8394,16 @@ const MODULES = [
5783
8394
  AccessControlModule
5784
8395
  ];
5785
8396
  const PUBLIC_ELEMENTS = [
5786
- // Direttive
8397
+ // Direttive (condivise da <es-table> e <es-table2>)
5787
8398
  EsTdDirective,
5788
8399
  EsThDirective,
5789
8400
  EsTdEditorDirective,
5790
8401
  // Componenti
5791
8402
  EsTableComponent,
5792
8403
  EsTablePagerComponent,
5793
- ThTdProvider
8404
+ ThTdProvider,
8405
+ // es-table2
8406
+ EsTable2Component
5794
8407
  ];
5795
8408
  const PRIVATE_ELEMENTS = [
5796
8409
  // Componenti
@@ -5802,7 +8415,11 @@ const PRIVATE_ELEMENTS = [
5802
8415
  EstRouterLinkPipe,
5803
8416
  EstRendererPipe,
5804
8417
  EstEditorPipe,
5805
- EstPropInsensitivePipe
8418
+ EstPropInsensitivePipe,
8419
+ // es-table2
8420
+ EsTable2PagerComponent,
8421
+ Est2FormatPipe,
8422
+ Est2LookupPipe
5806
8423
  ];
5807
8424
  class EsTableModule {
5808
8425
  static forRoot(config) {
@@ -5812,19 +8429,25 @@ class EsTableModule {
5812
8429
  { provide: EST_DEBUG, useValue: config?.debugMode || false },
5813
8430
  { provide: EST_DEFAULTS, useValue: config?.defaults || null },
5814
8431
  { provide: EST_EXPORT_GLOBAL_ACL, useValue: config?.globalExportAclExpression || 'true' },
8432
+ // es-table2 condivide la stessa configurazione
8433
+ { provide: EST2_DEBUG, useValue: config?.debugMode || false },
8434
+ { provide: EST2_DEFAULTS, useValue: config?.defaults || null },
8435
+ { provide: EST2_EXPORT_GLOBAL_ACL, useValue: config?.globalExportAclExpression || 'true' },
5815
8436
  ]
5816
8437
  };
5817
8438
  }
5818
8439
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: EsTableModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
5819
8440
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.28", ngImport: i0, type: EsTableModule, declarations: [
5820
- // Direttive
8441
+ // Direttive (condivise da <es-table> e <es-table2>)
5821
8442
  EsTdDirective,
5822
8443
  EsThDirective,
5823
8444
  EsTdEditorDirective,
5824
8445
  // Componenti
5825
8446
  EsTableComponent,
5826
8447
  EsTablePagerComponent,
5827
- ThTdProvider,
8448
+ ThTdProvider,
8449
+ // es-table2
8450
+ EsTable2Component,
5828
8451
  // Componenti
5829
8452
  EsTh,
5830
8453
  EsTd,
@@ -5834,7 +8457,11 @@ class EsTableModule {
5834
8457
  EstRouterLinkPipe,
5835
8458
  EstRendererPipe,
5836
8459
  EstEditorPipe,
5837
- EstPropInsensitivePipe], imports: [CommonModule,
8460
+ EstPropInsensitivePipe,
8461
+ // es-table2
8462
+ EsTable2PagerComponent,
8463
+ Est2FormatPipe,
8464
+ Est2LookupPipe], imports: [CommonModule,
5838
8465
  FormsModule,
5839
8466
  RouterModule,
5840
8467
  LocalizationModule,
@@ -5846,14 +8473,16 @@ class EsTableModule {
5846
8473
  FormsAndValidationsModule,
5847
8474
  TooltipModule,
5848
8475
  AccessControlModule, SelectModule], exports: [
5849
- // Direttive
8476
+ // Direttive (condivise da <es-table> e <es-table2>)
5850
8477
  EsTdDirective,
5851
8478
  EsThDirective,
5852
8479
  EsTdEditorDirective,
5853
8480
  // Componenti
5854
8481
  EsTableComponent,
5855
8482
  EsTablePagerComponent,
5856
- ThTdProvider] }); }
8483
+ ThTdProvider,
8484
+ // es-table2
8485
+ EsTable2Component] }); }
5857
8486
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: EsTableModule, imports: [MODULES, SelectModule] }); }
5858
8487
  }
5859
8488
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: EsTableModule, decorators: [{
@@ -5947,5 +8576,5 @@ class ItemPropEditableReference {
5947
8576
  * Generated bundle index. Do not edit.
5948
8577
  */
5949
8578
 
5950
- export { AppOrdering, AppSearch, CornerMenuOption, EST_DEBUG, EST_DEFAULTS, EST_EXPORT_GLOBAL_ACL, EsTableColumnsDefinition, EsTableComponent, EsTableDefaultable, EsTableModelChange, EsTableModule, EsTableModuleConfig, EsTableMultiValue, EsTableOperationDefinition, EsTablePagerComponent, EsTdDirective, EsTdEditorDirective, EsThDirective, GenericItem, Group, GroupAggregation, GroupFilter, GroupOrAggregation, ItemPropEditableReference, ItemsSelection, ReportColumn, ReportParam, ReportRow, ThTdProvider };
8579
+ export { AppOrdering, AppSearch, CornerMenuOption, EST2_DEBUG, EST2_DEFAULTS, EST2_EXPORT_GLOBAL_ACL, EST_DEBUG, EST_DEFAULTS, EST_EXPORT_GLOBAL_ACL, EsTable2Component, EsTableColumnsDefinition, EsTableComponent, EsTableDefaultable, EsTableModelChange, EsTableModule, EsTableModuleConfig, EsTableMultiValue, EsTableOperationDefinition, EsTablePagerComponent, EsTdDirective, EsTdEditorDirective, EsThDirective, GenericItem, Group, GroupAggregation, GroupFilter, GroupOrAggregation, ItemPropEditableReference, ItemsSelection, ReportColumn, ReportParam, ReportRow, ThTdProvider };
5951
8580
  //# sourceMappingURL=esfaenza-es-table.mjs.map