@esfaenza/es-table 20.3.14 → 20.3.16

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';
@@ -5794,6 +5794,2609 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
5794
5794
  class EsTableModuleConfig {
5795
5795
  }
5796
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
+ /** true se il mousedown corrente è un click sulla singola cella già selezionata
6634
+ * (→ un click senza trascinamento la deseleziona) */
6635
+ this.rangeClickToggle = false;
6636
+ /** Avviso transitorio mostrato in un piccolo toast (es. incolla incompatibile) */
6637
+ this.notice = signal(null, ...(ngDevMode ? [{ debugName: "notice" }] : []));
6638
+ this.noticeTimer = null;
6639
+ // ===========================================================================
6640
+ // Fase 6 — Colonne (visibilità/ordine), preferenze, export
6641
+ // ===========================================================================
6642
+ /** Preferenze colonne in memoria (ordine + nascoste). Persistite se `SavePreferences`. */
6643
+ this.columnPrefs = null;
6644
+ /** true una volta caricate le preferenze dal server (evita retrieve multipli) */
6645
+ this.prefsLoaded = false;
6646
+ // --- Dialog visibilità/ordine/pin colonne ----------------------------------
6647
+ this.columnsDialogOpen = signal(false, ...(ngDevMode ? [{ debugName: "columnsDialogOpen" }] : []));
6648
+ /** Lista di lavoro della dialog (copia, per poter annullare) */
6649
+ this.dialogCols = signal([], ...(ngDevMode ? [{ debugName: "dialogCols" }] : []));
6650
+ // --- Export (CSV self-contained / XLSX via SheetJS opzionale) --------------
6651
+ this.exportMenuOpen = signal(false, ...(ngDevMode ? [{ debugName: "exportMenuOpen" }] : []));
6652
+ this.cornerMenuOpen = signal(false, ...(ngDevMode ? [{ debugName: "cornerMenuOpen" }] : []));
6653
+ this.trackByIndex = (i) => i;
6654
+ this.trackRow = (i, item) => item?._hash ?? item?.id ?? item;
6655
+ this.trackCol = (_, col) => col.id;
6656
+ if (ngControl)
6657
+ ngControl.valueAccessor = this;
6658
+ }
6659
+ ngOnInit() {
6660
+ // Tutti gli input risolti sono ora `computed()`/`linkedSignal()` reattivi
6661
+ // (vedi dichiarazioni): niente più risoluzione one-shot qui.
6662
+ this.inited = true;
6663
+ // Click destro: alla apertura del menu seleziono l'elemento (se non già selezionato)
6664
+ const menu = this.ContextMenu || this.tableEmptyMenu;
6665
+ if (menu)
6666
+ this.ctxMenuSub = menu.open.subscribe((e) => this.onContextMenuOpen(e?.item));
6667
+ }
6668
+ ngOnDestroy() {
6669
+ this.ctxMenuSub?.unsubscribe();
6670
+ this.headerRO?.disconnect();
6671
+ if (this.noticeTimer)
6672
+ clearTimeout(this.noticeTimer);
6673
+ }
6674
+ /** Risoluzione input: valore esplicito || default di progetto || hard default */
6675
+ io(v, d, hard) {
6676
+ return (v !== null && v !== undefined) ? v : (d !== null && d !== undefined) ? d : hard;
6677
+ }
6678
+ ngAfterContentInit() {
6679
+ this.buildColumns();
6680
+ this.thDirectives?.changes.subscribe(() => { this.buildColumns(); this.refresh(); });
6681
+ this.tdDirectives?.changes.subscribe(() => { this.buildColumns(); this.refresh(); });
6682
+ }
6683
+ // ===========================================================================
6684
+ // ControlValueAccessor
6685
+ // ===========================================================================
6686
+ writeValue(obj) {
6687
+ this.view.set(obj ?? null);
6688
+ this.detectMode(obj);
6689
+ this.syncOrdersFromView();
6690
+ this.refresh();
6691
+ if (obj)
6692
+ this.firstBind.set(false);
6693
+ this.cdr.markForCheck();
6694
+ }
6695
+ registerOnChange(fn) { this.onChange = fn; }
6696
+ registerOnTouched(fn) { this.onTouched = fn; }
6697
+ setDisabledState() { }
6698
+ changed() { this.onChange(this.view()); }
6699
+ // ===========================================================================
6700
+ // Mode & column model
6701
+ // ===========================================================================
6702
+ detectMode(obj) {
6703
+ const isSearch = obj instanceof AppSearch ||
6704
+ (obj && typeof obj === 'object' && typeof obj.itemsperpageoverride !== 'undefined' && !Array.isArray(obj));
6705
+ this.viewMode.set(!!isSearch);
6706
+ this.arrayMode.set(!isSearch && Array.isArray(obj));
6707
+ }
6708
+ buildColumns() {
6709
+ const ths = this.thDirectives?.toArray() ?? [];
6710
+ this.directivesBased.set(ths.length > 0);
6711
+ if (ths.length === 0) {
6712
+ this.columns.set([]);
6713
+ this.directiveColumns = [];
6714
+ this.multiDefs = [];
6715
+ this.hgThById.set(new Map());
6716
+ this.hgParentIds.set(new Set());
6717
+ this.baseThById = new Map();
6718
+ this.baseParentIds = new Set();
6719
+ return;
6720
+ }
6721
+ const tdByCol = new Map();
6722
+ for (const td of this.tdDirectives?.toArray() ?? [])
6723
+ if (!tdByCol.has(td.td))
6724
+ tdByCol.set(td.td, td);
6725
+ // Colonne Multi (`*th Multi:true`): srotolate sui dati in resolveColumns()
6726
+ this.multiDefs = ths.filter(t => t.thMulti && t.thIf).map(t => ({ prop: t.th, th: t, td: tdByCol.get(t.th) }));
6727
+ // Header groups multi-livello: un `*th` è un GRUPPO (non una colonna-dato) se
6728
+ // il suo id è referenziato da un altro `*th` come `Parent` (`thParent`).
6729
+ const parentIds = new Set();
6730
+ for (const th of ths)
6731
+ if (th.thParent)
6732
+ parentIds.add(th.thParent);
6733
+ const thById = new Map();
6734
+ for (const th of ths)
6735
+ thById.set(th.th, th);
6736
+ this.baseThById = thById;
6737
+ this.baseParentIds = parentIds;
6738
+ this.hgThById.set(new Map(thById));
6739
+ this.hgParentIds.set(new Set(parentIds));
6740
+ const def = this.DefaultAlignment().toLowerCase();
6741
+ const cols = [];
6742
+ for (const th of ths) {
6743
+ if (!th.thIf)
6744
+ continue;
6745
+ if (th.thMulti)
6746
+ continue; // srotolata separatamente
6747
+ if (parentIds.has(th.th))
6748
+ continue; // è un header di gruppo, non una colonna-dato
6749
+ const td = tdByCol.get(th.th);
6750
+ cols.push({
6751
+ id: th.th,
6752
+ header: th,
6753
+ cell: td,
6754
+ visible: th.thVisible,
6755
+ orderable: th.thOrderable,
6756
+ alignment: (td?.tdAlignment || th.thAlignment)?.toLowerCase() || def,
6757
+ property: th.thProperty,
6758
+ type: th.thType ?? null,
6759
+ source: th.thSource,
6760
+ format: td?.tdFormat || th.thFormat || null,
6761
+ cssClass: td?.tdClass || th.thClass || '',
6762
+ wrap: th.thWrap,
6763
+ isGroup: th.thGroup,
6764
+ aggSpecs: parseAggregationSpecs(th.th, th.thAggregation),
6765
+ headerText: th.thStaticContent,
6766
+ headerBg: th.thBackgroundColor,
6767
+ routePath: th.thRoutePath,
6768
+ routeParams: th.thRouteParameterProperties,
6769
+ rendererName: th.thRendererName,
6770
+ pinned: !!th.thPinned,
6771
+ pinnedWidth: th.thPinnedWidth ?? null
6772
+ });
6773
+ }
6774
+ this.directiveColumns = cols;
6775
+ this.setColumns(cols);
6776
+ }
6777
+ /**
6778
+ * Srotola le colonne `*th Multi:true` sui dati: ogni entry unica
6779
+ * `(header_group, header)` della proprietà-array diventa una colonna sintetica,
6780
+ * e i livelli di `header_group` (separati da ">") alimentano gli header-group.
6781
+ */
6782
+ buildMultiColumns(items) {
6783
+ const cols = [];
6784
+ const thById = new Map(this.baseThById);
6785
+ const parentIds = new Set(this.baseParentIds);
6786
+ const seen = new Set();
6787
+ for (const def of this.multiDefs) {
6788
+ const prop = def.prop;
6789
+ for (const item of (items || [])) {
6790
+ const arr = item?.[prop];
6791
+ if (!Array.isArray(arr))
6792
+ continue;
6793
+ for (let ii = 0; ii < arr.length; ii++) {
6794
+ const mv = arr[ii] || {};
6795
+ const header = mv.header ?? '';
6796
+ const group = mv.header_group ?? null;
6797
+ const key = `${group}__${header}`;
6798
+ if (seen.has(key))
6799
+ continue;
6800
+ seen.add(key);
6801
+ const hier = group ? String(group).split('>').map((s) => s.trim()).filter(Boolean) : [];
6802
+ const immediateParent = hier.length ? hier[hier.length - 1] : null;
6803
+ const colId = `${prop}_${ii}`;
6804
+ // th sintetico foglia: fornisce la catena thParent + il template header
6805
+ const leafTh = new EsThDirective(def.th.Template);
6806
+ leafTh.th = colId;
6807
+ leafTh.thStaticContent = header;
6808
+ leafTh.thParent = immediateParent;
6809
+ leafTh.thOrderable = false;
6810
+ thById.set(colId, leafTh);
6811
+ // th sintetici dei gruppi padre (una volta ciascuno)
6812
+ for (let h = 0; h < hier.length; h++) {
6813
+ const pid = hier[h];
6814
+ parentIds.add(pid);
6815
+ if (!thById.has(pid)) {
6816
+ const pth = new EsThDirective(null);
6817
+ pth.th = pid;
6818
+ pth.thStaticContent = pid;
6819
+ pth.thParent = h > 0 ? hier[h - 1] : null;
6820
+ thById.set(pid, pth);
6821
+ }
6822
+ }
6823
+ cols.push({
6824
+ id: colId,
6825
+ header: leafTh,
6826
+ cell: def.td,
6827
+ visible: true,
6828
+ orderable: false,
6829
+ alignment: 'right',
6830
+ property: null,
6831
+ type: null, source: null, format: null,
6832
+ cssClass: def.td?.tdClass || def.th.thClass || '',
6833
+ wrap: false, isGroup: false, aggSpecs: [],
6834
+ headerText: header,
6835
+ headerBg: null,
6836
+ multiProp: prop, multiIndex: ii,
6837
+ pinned: false, pinnedWidth: null
6838
+ });
6839
+ }
6840
+ }
6841
+ }
6842
+ this.hgThById.set(thById);
6843
+ this.hgParentIds.set(parentIds);
6844
+ return cols;
6845
+ }
6846
+ /** Costruisce le colonne da una `EsTableColumnsDefinition[]` (modalità dinamica) */
6847
+ buildDynamicColumns(defs) {
6848
+ // renderer custom forniti dal consumer come `*td tdForProperty="..."`
6849
+ const tds = this.tdDirectives?.toArray() ?? [];
6850
+ const cols = [];
6851
+ for (let i = 0; i < defs.length; i++) {
6852
+ const d = defs[i];
6853
+ if (!d.Visible)
6854
+ continue;
6855
+ const renderer = tds.find(t => (d.RendererName && t.td === d.RendererName) || t.tdForProperty === d.PropertyName);
6856
+ cols.push({
6857
+ id: d.PropertyName,
6858
+ cell: renderer,
6859
+ visible: true,
6860
+ orderable: d.Orderable !== false,
6861
+ alignment: 'left',
6862
+ property: d.PropertyName,
6863
+ type: d.PropertyType ?? null,
6864
+ source: d.PropertySource ?? null,
6865
+ format: d.Format ?? null,
6866
+ cssClass: d.Clss || '',
6867
+ wrap: !!d.HeaderWraps,
6868
+ isGroup: false,
6869
+ aggSpecs: [],
6870
+ headerText: d.Description,
6871
+ routePath: d.RoutePath,
6872
+ routeParams: d.RouteParameterProperties,
6873
+ rendererName: d.RendererName,
6874
+ // colori per-cella opzionali (forecolors/backcolors allineati all'ordine delle colonne)
6875
+ colorIndex: i
6876
+ });
6877
+ }
6878
+ this.setColumns(cols);
6879
+ }
6880
+ /** Costruisce le colonne da `report_columns` (modalità report): il valore è preso per indice */
6881
+ buildReportColumns(reportColumns) {
6882
+ const cols = [];
6883
+ for (let i = 0; i < reportColumns.length; i++) {
6884
+ const c = reportColumns[i];
6885
+ cols.push({
6886
+ id: c.id,
6887
+ visible: true,
6888
+ orderable: false,
6889
+ alignment: c.alignment?.toLowerCase() || 'left',
6890
+ property: null,
6891
+ type: null,
6892
+ source: null,
6893
+ format: c.format ?? null,
6894
+ cssClass: '',
6895
+ wrap: false,
6896
+ isGroup: false,
6897
+ aggSpecs: [],
6898
+ headerText: c.description,
6899
+ headerBg: c.color || null,
6900
+ propAccessor: String(i),
6901
+ colorIndex: i
6902
+ });
6903
+ }
6904
+ this.setColumns(cols);
6905
+ }
6906
+ /** Sceglie la sorgente delle colonne in base al modello: report → dinamica → direttive */
6907
+ resolveColumns(items) {
6908
+ const v = this.view();
6909
+ if (this.viewMode() && v?.report_columns?.length) {
6910
+ this.reportMode.set(true);
6911
+ this.dynamicMode.set(false);
6912
+ this.buildReportColumns(v.report_columns);
6913
+ return;
6914
+ }
6915
+ this.reportMode.set(false);
6916
+ let defs = this.DynamicRowColumnsDefinition();
6917
+ const hasTh = (this.thDirectives?.length || 0) > 0;
6918
+ // Auto-derivazione da "generic items" (oggetti con bag `.properties`) se non ci sono direttive né definizioni
6919
+ if ((!defs || defs.length === 0) && !hasTh && items.length && items[0]?.properties) {
6920
+ defs = Object.keys(items[0].properties).map(k => new EsTableColumnsDefinition(k, k));
6921
+ this.DynamicRowColumnsDefinition.set(defs);
6922
+ }
6923
+ if (defs && defs.length > 0) {
6924
+ this.dynamicMode.set(true);
6925
+ this.buildDynamicColumns(defs);
6926
+ return;
6927
+ }
6928
+ this.dynamicMode.set(false);
6929
+ // Colonne da direttive (costruite in ngAfterContentInit). Se ci sono `*th Multi`
6930
+ // le srotolo sui dati correnti e le accodo alle colonne-direttiva.
6931
+ if (this.multiDefs.length && this.directiveColumns.length) {
6932
+ const multiCols = this.buildMultiColumns(items);
6933
+ this.setColumns([...this.directiveColumns, ...multiCols]);
6934
+ }
6935
+ }
6936
+ // ===========================================================================
6937
+ // Operazioni dinamiche (colonne-icona a sinistra)
6938
+ // ===========================================================================
6939
+ /** Visibilità di un'operazione su una riga (gate conditionField/conditionValue, case-insensitive) */
6940
+ operationVisible(op, item) {
6941
+ if (!op.conditionField)
6942
+ return true;
6943
+ const src = item?.properties ?? item;
6944
+ const key = Object.keys(src ?? {}).find(k => k.toLowerCase() === op.conditionField.toLowerCase());
6945
+ return key != null && String(src[key]) === String(op.conditionValue);
6946
+ }
6947
+ dynamicOperation(item, id) {
6948
+ this.onDynamicOperation.emit({ id, item });
6949
+ }
6950
+ // ===========================================================================
6951
+ // Valore/renderer di cella per modalità dinamica/report
6952
+ // ===========================================================================
6953
+ /** Valore di una cella report: `values[indice]` (fallback su accesso diretto) */
6954
+ reportCellValue(item, col) {
6955
+ const idx = col.propAccessor;
6956
+ return item?.values ? item.values[idx] : item?.[idx];
6957
+ }
6958
+ /** Colore per-cella (fore/back) da `forecolors[i]`/`backcolors[i]`; vale sia report che dinamica */
6959
+ cellColor(item, col, kind) {
6960
+ if (col.colorIndex == null)
6961
+ return null;
6962
+ const arr = kind === 'fore' ? item?.forecolors : item?.backcolors;
6963
+ return arr ? (arr[col.colorIndex] || null) : null;
6964
+ }
6965
+ /** Valore report formattato (usa il formatter delle aggregazioni: F0-9 / small-long-verylong) */
6966
+ reportCellDisplay(item, col) {
6967
+ const raw = this.reportCellValue(item, col);
6968
+ return formatAggValue(raw == null ? '' : String(raw), col.format || '', this.locale);
6969
+ }
6970
+ /** Costruisce il routerLink di una cella dinamica da `RoutePath` + `RouteParameterProperties` */
6971
+ routerLinkFor(item, col) {
6972
+ let path = col.routePath || '';
6973
+ const pars = col.routeParams || [];
6974
+ const src = item?.properties ?? item;
6975
+ for (let i = 0; i < pars.length; i++)
6976
+ path = path.replace('{' + i + '}', src?.[pars[i]]);
6977
+ path = path.replace(/'/g, '"').replace(/ /g, '');
6978
+ try {
6979
+ return JSON.parse(path);
6980
+ }
6981
+ catch {
6982
+ return path;
6983
+ }
6984
+ }
6985
+ // ===========================================================================
6986
+ // Refresh: ricostruisce boundSource in base a modalità/ordinamento/paginazione
6987
+ // ===========================================================================
6988
+ refresh() {
6989
+ const v = this.view();
6990
+ if (!v) {
6991
+ this.boundSource.set([]);
6992
+ return;
6993
+ }
6994
+ // Determina la sorgente delle colonne (report / dinamica / direttive)
6995
+ const rawItems = this.viewMode()
6996
+ ? ((this.ItemSourceProperty() ? v[this.ItemSourceProperty()] : v.items) ?? [])
6997
+ : (Array.isArray(v) ? v : []);
6998
+ this.resolveColumns(rawItems);
6999
+ if (this.viewMode()) {
7000
+ // Il backend (consumer) fornisce già la pagina ordinata in v.items
7001
+ const items = rawItems;
7002
+ if (this.Hierarchy()) {
7003
+ this.grouped.set(false);
7004
+ this.boundSource.set(this.applyHierarchy(items, v.selection));
7005
+ return;
7006
+ }
7007
+ this.applySelectionFlags(items, v.selection);
7008
+ this.boundSource.set(items);
7009
+ return;
7010
+ }
7011
+ if (this.arrayMode()) {
7012
+ let items = [...this.view()];
7013
+ // Modalità gerarchica: albero padre/figlio (ignora paginazione)
7014
+ if (this.Hierarchy()) {
7015
+ this.grouped.set(false);
7016
+ this.boundSource.set(this.applyHierarchy(items, this.arraySelection));
7017
+ return;
7018
+ }
7019
+ // Raggruppamento client-side: se ci sono colonne `thGroup`, ignora la paginazione
7020
+ const groupCols = this.groupColumns();
7021
+ if (groupCols.length > 0) {
7022
+ this.grouped.set(true);
7023
+ const rows = buildGroupedRows(this.clientSort(items), groupCols, this.aggregationSpecs());
7024
+ this.applySelectionFlags(rows.filter(r => r._item), this.arraySelection);
7025
+ this.boundSource.set(rows);
7026
+ return;
7027
+ }
7028
+ this.grouped.set(false);
7029
+ items = this.clientSort(items);
7030
+ const ipp = this.ArraymodeItemsPerPage();
7031
+ if (this.UseArrayModePaging() && ipp && ipp < this.effectiveAllValue) {
7032
+ const page = Math.min(this.arrayPage(), Math.max(1, Math.ceil(items.length / ipp)));
7033
+ this.arrayPage.set(page);
7034
+ items = items.slice((page - 1) * ipp, page * ipp);
7035
+ }
7036
+ this.applySelectionFlags(items, this.arraySelection);
7037
+ this.boundSource.set(items);
7038
+ return;
7039
+ }
7040
+ this.boundSource.set([]);
7041
+ }
7042
+ /** Colonne chiave di raggruppamento, nell'ordine di dichiarazione */
7043
+ groupColumns() {
7044
+ return this.columns().filter(c => c.isGroup).map(c => c.id);
7045
+ }
7046
+ /** Tutte le specifiche di aggregazione, appiattite su tutte le colonne */
7047
+ aggregationSpecs() {
7048
+ const specs = [];
7049
+ for (const c of this.columns())
7050
+ specs.push(...c.aggSpecs);
7051
+ return specs;
7052
+ }
7053
+ clientSort(items) {
7054
+ const ord = this.orders();
7055
+ if (ord.length === 0)
7056
+ return items;
7057
+ return items.sort((a, b) => {
7058
+ for (const o of ord) {
7059
+ const dir = o.order === 'DESC' ? -1 : 1;
7060
+ const av = a?.[o.id], bv = b?.[o.id];
7061
+ if (av == null && bv == null)
7062
+ continue;
7063
+ if (av == null)
7064
+ return -dir;
7065
+ if (bv == null)
7066
+ return dir;
7067
+ if (av < bv)
7068
+ return -dir;
7069
+ if (av > bv)
7070
+ return dir;
7071
+ }
7072
+ return 0;
7073
+ });
7074
+ }
7075
+ // ===========================================================================
7076
+ // Ordinamento
7077
+ // ===========================================================================
7078
+ orderOf(id) {
7079
+ return this.orders().find(o => o.id === id)?.order ?? null;
7080
+ }
7081
+ orderIndex(id) {
7082
+ const idx = this.orders().findIndex(o => o.id === id);
7083
+ return idx < 0 ? -1 : idx + 1;
7084
+ }
7085
+ toggleSort(col) {
7086
+ if (!this.OrderByColumn() || !col.orderable)
7087
+ return;
7088
+ const current = this.orderOf(col.id);
7089
+ const next = current === 'ASC' ? 'DESC' : current === 'DESC' ? null : 'ASC';
7090
+ let list = this.MultipleOrderingDirectives()
7091
+ ? this.orders().filter(o => o.id !== col.id)
7092
+ : [];
7093
+ if (next)
7094
+ list = [...list, new AppOrdering(col.id, next)];
7095
+ this.orders.set(list);
7096
+ if (this.viewMode()) {
7097
+ const v = this.view();
7098
+ v.orders = list;
7099
+ v.internalsearchrequest = true;
7100
+ this.onOrderChanged.emit(new AppOrdering(col.id, next ?? undefined));
7101
+ this.onSearchRequest.emit(false);
7102
+ }
7103
+ else {
7104
+ this.refresh();
7105
+ }
7106
+ if (this.SavePreferences())
7107
+ this.saveColumnPrefs();
7108
+ this.cdr.markForCheck();
7109
+ }
7110
+ syncOrdersFromView() {
7111
+ if (this.viewMode() && this.view()?.orders)
7112
+ this.orders.set([...this.view().orders]);
7113
+ }
7114
+ // ===========================================================================
7115
+ // Paginazione
7116
+ // ===========================================================================
7117
+ goToPage(p) {
7118
+ if (this.viewMode()) {
7119
+ const v = this.view();
7120
+ v.page = p;
7121
+ v.internalsearchrequest = true;
7122
+ this.onSearchRequest.emit(false);
7123
+ }
7124
+ else {
7125
+ this.arrayPage.set(p);
7126
+ this.refresh();
7127
+ }
7128
+ this.cdr.markForCheck();
7129
+ }
7130
+ changeItemsPerPage(n) {
7131
+ if (this.viewMode()) {
7132
+ const v = this.view();
7133
+ v.itemsperpageoverride = n;
7134
+ v.page = 1;
7135
+ v.internalsearchrequest = true;
7136
+ this.onSearchRequest.emit(false);
7137
+ }
7138
+ else {
7139
+ this.ArraymodeItemsPerPage.set(n);
7140
+ this.arrayPage.set(1);
7141
+ this.refresh();
7142
+ }
7143
+ this.cdr.markForCheck();
7144
+ }
7145
+ currentSelection() {
7146
+ if (this.viewMode()) {
7147
+ const v = this.view();
7148
+ if (!v.selection)
7149
+ v.selection = new ItemsSelection([]);
7150
+ return v.selection;
7151
+ }
7152
+ if (!this.arraySelection)
7153
+ this.arraySelection = new ItemsSelection([]);
7154
+ return this.arraySelection;
7155
+ }
7156
+ /** Righe realmente selezionabili: esclude le righe-gruppo quando il raggruppamento è attivo */
7157
+ selectableRows() {
7158
+ const rows = this.boundSource();
7159
+ return this.grouped() ? rows.filter(r => r._item) : rows;
7160
+ }
7161
+ /**
7162
+ * Applica il flag _selected alle righe correnti in base allo stato di selezione.
7163
+ * In modalità **all** una riga è selezionata se NON è tra le esclusioni (vale anche
7164
+ * per le pagine non ancora visitate, dove tutte le righe risultano selezionate).
7165
+ */
7166
+ applySelectionFlags(items, sel) {
7167
+ if (!sel) {
7168
+ for (const it of items)
7169
+ it._selected = false;
7170
+ return;
7171
+ }
7172
+ for (const it of items)
7173
+ it._selected = sel.all ? !sel.exclusions.includes(it) : sel.items.includes(it);
7174
+ }
7175
+ /** Ricalcola il conteggio: in modalità **all** è (totale - esclusioni) */
7176
+ refreshSelectionCount(sel) {
7177
+ sel.count = sel.all ? Math.max(0, this.totalCount() - sel.exclusions.length) : sel.items.length;
7178
+ }
7179
+ /** Toggle della selezione di una singola riga (checkbox, click su riga o click destro) */
7180
+ toggleRow(item) {
7181
+ if (this.SelectionDisabled() || item?._sep)
7182
+ return;
7183
+ const sel = this.currentSelection();
7184
+ // Selezione in cascata (albero): propaga a discendenti e antenati
7185
+ if (this.Hierarchy() && this.CascadeSelection() && !this.SingleSelection()) {
7186
+ this.cascadeSelection(item, !item._selected);
7187
+ this.rebuildSelectionFromFlags(sel);
7188
+ this.syncGlobalCheck();
7189
+ this.emitSelection(sel);
7190
+ return;
7191
+ }
7192
+ if (this.SingleSelection()) {
7193
+ const wasSelected = item._selected;
7194
+ for (const it of this.selectableRows())
7195
+ it._selected = false;
7196
+ sel.all = false;
7197
+ sel.exclusions = [];
7198
+ sel.items = wasSelected ? [] : [item];
7199
+ item._selected = !wasSelected;
7200
+ }
7201
+ else if (sel.all) {
7202
+ // "Tutto selezionato": deselezionare = aggiungere alle esclusioni
7203
+ item._selected = !item._selected;
7204
+ if (item._selected)
7205
+ sel.exclusions = sel.exclusions.filter(i => i !== item);
7206
+ else if (!sel.exclusions.includes(item))
7207
+ sel.exclusions.push(item);
7208
+ }
7209
+ else {
7210
+ item._selected = !item._selected;
7211
+ if (item._selected) {
7212
+ if (!sel.items.includes(item))
7213
+ sel.items.push(item);
7214
+ }
7215
+ else
7216
+ sel.items = sel.items.filter(i => i !== item);
7217
+ this.selectionAnchor = item;
7218
+ }
7219
+ this.refreshSelectionCount(sel);
7220
+ this.syncGlobalCheck();
7221
+ this.emitSelection(sel);
7222
+ }
7223
+ /**
7224
+ * Checkbox globale in testata: seleziona/azzera la **pagina corrente**.
7225
+ * Per estendere la selezione a tutte le pagine si usa `selectEverything()`
7226
+ * (link mostrato nella barra di selezione).
7227
+ */
7228
+ toggleAll() {
7229
+ if (this.SelectionDisabled() || this.SingleSelection())
7230
+ return;
7231
+ const sel = this.currentSelection();
7232
+ const rows = this.selectableRows();
7233
+ const pageFully = !sel.all && rows.length > 0 && rows.every(r => r._selected);
7234
+ if (sel.all || pageFully) {
7235
+ this.clearSelectionState(sel);
7236
+ }
7237
+ else {
7238
+ sel.all = false;
7239
+ sel.exclusions = [];
7240
+ sel.items = [...rows];
7241
+ for (const r of rows)
7242
+ r._selected = true;
7243
+ }
7244
+ this.refreshSelectionCount(sel);
7245
+ this.syncGlobalCheck();
7246
+ this.emitSelection(sel);
7247
+ }
7248
+ /** Estende la selezione a TUTTI gli elementi, anche quelli delle altre pagine (`selection.all`) */
7249
+ selectEverything() {
7250
+ if (this.SelectionDisabled() || this.SingleSelection())
7251
+ return;
7252
+ const sel = this.currentSelection();
7253
+ sel.all = true;
7254
+ sel.items = [];
7255
+ sel.exclusions = [];
7256
+ for (const r of this.selectableRows())
7257
+ r._selected = true;
7258
+ this.refreshSelectionCount(sel);
7259
+ this.globalCheck.set(true);
7260
+ this.emitSelection(sel);
7261
+ }
7262
+ /** Azzera completamente la selezione */
7263
+ clearSelection() {
7264
+ const sel = this.currentSelection();
7265
+ this.clearSelectionState(sel);
7266
+ this.refreshSelectionCount(sel);
7267
+ this.globalCheck.set(false);
7268
+ this.emitSelection(sel);
7269
+ }
7270
+ clearSelectionState(sel) {
7271
+ sel.all = false;
7272
+ sel.items = [];
7273
+ sel.exclusions = [];
7274
+ for (const r of this.selectableRows())
7275
+ r._selected = false;
7276
+ }
7277
+ syncGlobalCheck() {
7278
+ const sel = this.currentSelection();
7279
+ const rows = this.selectableRows();
7280
+ this.globalCheck.set(sel.all ? sel.exclusions.length === 0 : (rows.length > 0 && rows.every(r => r._selected)));
7281
+ }
7282
+ emitSelection(sel) {
7283
+ // Una sola selezione alla volta: qualunque selezione di riga azzera il range di celle
7284
+ if (this.rangeSel())
7285
+ this.rangeSel.set(null);
7286
+ this.onSelectionChanged.emit(sel);
7287
+ this.changed();
7288
+ this.cdr.markForCheck();
7289
+ }
7290
+ /** Click su una riga: espande/collassa un gruppo, oppure gestisce la selezione. */
7291
+ handleRowClick(item, event) {
7292
+ if (item?._group) {
7293
+ this.toggleGroup(item);
7294
+ return;
7295
+ }
7296
+ // In modalità range la selezione di riga avviene solo tramite le checkbox
7297
+ // (il click sulla cella pilota il range), così i due meccanismi non collidono.
7298
+ if (this.rangeActive())
7299
+ return;
7300
+ if (!this.Selection())
7301
+ return;
7302
+ if (this.SingleSelection()) {
7303
+ this.toggleRow(item);
7304
+ return;
7305
+ }
7306
+ // Selezione stile Windows (plain / Ctrl / Shift) quando ShiftClick è attivo
7307
+ if (this.ShiftClick() && !this.Hierarchy()) {
7308
+ this.windowsSelect(item, event);
7309
+ return;
7310
+ }
7311
+ // Comportamento classico: Ctrl+click lascia il default del browser (link, ecc.)
7312
+ if (event?.ctrlKey || event?.metaKey)
7313
+ return;
7314
+ this.toggleRow(item);
7315
+ }
7316
+ /** Selezione a click stile Esplora risorse: plain=solo questo, Ctrl=toggle, Shift=range dall'ancora */
7317
+ windowsSelect(item, event) {
7318
+ if (this.SelectionDisabled() || item?._sep)
7319
+ return;
7320
+ const rows = this.selectableRows();
7321
+ const sel = this.currentSelection();
7322
+ const shift = !!event?.shiftKey;
7323
+ const ctrl = !!(event?.ctrlKey || event?.metaKey);
7324
+ if (shift && this.selectionAnchor != null && rows.indexOf(this.selectionAnchor) >= 0) {
7325
+ const a = rows.indexOf(this.selectionAnchor);
7326
+ const b = rows.indexOf(item);
7327
+ if (b >= 0) {
7328
+ const [lo, hi] = a <= b ? [a, b] : [b, a];
7329
+ if (!ctrl) {
7330
+ for (const r of rows)
7331
+ r._selected = false;
7332
+ sel.items = [];
7333
+ }
7334
+ for (let i = lo; i <= hi; i++) {
7335
+ rows[i]._selected = true;
7336
+ if (!sel.items.includes(rows[i]))
7337
+ sel.items.push(rows[i]);
7338
+ }
7339
+ // l'ancora resta invariata per estensioni successive
7340
+ }
7341
+ }
7342
+ else if (ctrl) {
7343
+ item._selected = !item._selected;
7344
+ if (item._selected) {
7345
+ if (!sel.items.includes(item))
7346
+ sel.items.push(item);
7347
+ }
7348
+ else
7349
+ sel.items = sel.items.filter(i => i !== item);
7350
+ this.selectionAnchor = item;
7351
+ }
7352
+ else {
7353
+ // Re-click sull'unica riga selezionata => deseleziona; altrimenti seleziona solo questa
7354
+ const onlyThisSelected = item._selected && !sel.all && sel.items.length === 1 && sel.items[0] === item;
7355
+ for (const r of rows)
7356
+ r._selected = false;
7357
+ if (onlyThisSelected) {
7358
+ sel.items = [];
7359
+ this.selectionAnchor = null;
7360
+ }
7361
+ else {
7362
+ sel.items = [item];
7363
+ item._selected = true;
7364
+ this.selectionAnchor = item;
7365
+ }
7366
+ }
7367
+ sel.all = false;
7368
+ sel.exclusions = [];
7369
+ this.refreshSelectionCount(sel);
7370
+ this.syncGlobalCheck();
7371
+ this.emitSelection(sel);
7372
+ }
7373
+ /** Owner globale del range attivo: solo la tabella "attiva" reagisce a Ctrl+C/V */
7374
+ static { this.activeRangeOwner = null; }
7375
+ /** true se la cella (r,c) è dentro il range */
7376
+ inRange(r, c) {
7377
+ const rr = this.rangeRect();
7378
+ return !!rr && r >= rr.top && r <= rr.bottom && c >= rr.left && c <= rr.right;
7379
+ }
7380
+ /** Estrae gli indici r/c dal `<td data-r data-c>` più vicino al target */
7381
+ cellCoords(target) {
7382
+ const td = target?.closest?.('td[data-r]');
7383
+ if (!td)
7384
+ return null;
7385
+ const r = parseInt(td.getAttribute('data-r'), 10);
7386
+ const c = parseInt(td.getAttribute('data-c'), 10);
7387
+ return isNaN(r) || isNaN(c) ? null : { r, c };
7388
+ }
7389
+ /** mousedown sulla griglia: apre un nuovo range sulla cella-ancora */
7390
+ onGridMouseDown(event) {
7391
+ if (!this.rangeActive() || event.button !== 0 || this.editing())
7392
+ return;
7393
+ const co = this.cellCoords(event.target);
7394
+ if (!co)
7395
+ return;
7396
+ EsTable2Component.activeRangeOwner = this;
7397
+ // Una sola selezione alla volta: avviare un range azzera l'eventuale selezione di riga
7398
+ if (this.hasSelection)
7399
+ this.clearSelection();
7400
+ // Se ri-clicco l'unica cella già selezionata (range 1×1 su questa cella), un
7401
+ // click senza drag la deseleziona (gestito nel mouseup).
7402
+ const prev = this.rangeSel();
7403
+ this.rangeClickToggle = !!prev &&
7404
+ prev.a.r === co.r && prev.a.c === co.c && prev.f.r === co.r && prev.f.c === co.c;
7405
+ this.rangeDragging.set(true);
7406
+ this.rangeSel.set({ a: co, f: co });
7407
+ this.clearTextSelection();
7408
+ this.cdr.markForCheck();
7409
+ }
7410
+ /** mouseover durante il drag: estende il range (solo se la cella cambia davvero) */
7411
+ onGridMouseOver(event) {
7412
+ if (!this.rangeDragging() || !this.rangeActive())
7413
+ return;
7414
+ if ((event.buttons & 1) === 0) {
7415
+ this.rangeDragging.set(false);
7416
+ return;
7417
+ } // tasto già rilasciato
7418
+ const s = this.rangeSel();
7419
+ const co = this.cellCoords(event.target);
7420
+ if (!s || !co)
7421
+ return;
7422
+ if (s.f.r === co.r && s.f.c === co.c)
7423
+ return; // nessun cambiamento → niente ricalcolo (no debounce necessario)
7424
+ this.rangeClickToggle = false; // è un drag, non un click di toggle
7425
+ this.rangeSel.set({ a: s.a, f: co });
7426
+ this.clearTextSelection();
7427
+ this.cdr.markForCheck();
7428
+ }
7429
+ onDocMouseUp() {
7430
+ if (!this.rangeDragging())
7431
+ return;
7432
+ this.rangeDragging.set(false);
7433
+ // click (nessun drag) sull'unica cella già selezionata → deseleziona
7434
+ if (this.rangeClickToggle) {
7435
+ this.rangeClickToggle = false;
7436
+ this.rangeSel.set(null);
7437
+ this.cdr.markForCheck();
7438
+ }
7439
+ }
7440
+ flashNotice(msg) {
7441
+ this.notice.set(msg);
7442
+ this.cdr.markForCheck();
7443
+ if (this.noticeTimer)
7444
+ clearTimeout(this.noticeTimer);
7445
+ this.noticeTimer = setTimeout(() => { this.notice.set(null); this.cdr.markForCheck(); }, 4500);
7446
+ }
7447
+ clearTextSelection() {
7448
+ const sel = typeof window !== 'undefined' ? window.getSelection?.() : null;
7449
+ if (sel && !sel.isCollapsed)
7450
+ sel.removeAllRanges();
7451
+ }
7452
+ // --- Copia / Incolla -------------------------------------------------------
7453
+ // Uso gli eventi nativi `copy`/`paste` (sincroni, dentro il gesto utente):
7454
+ // niente prompt di permessi né rifiuti silenziosi come con
7455
+ // navigator.clipboard.readText(), che in molti browser è bloccata.
7456
+ onDocCopy(event) {
7457
+ if (EsTable2Component.activeRangeOwner !== this || this.editing())
7458
+ return;
7459
+ if (this.isFieldTarget(event.target))
7460
+ return; // lascio la copia nativa nei campi
7461
+ const cells = this.rangeCells();
7462
+ if (!cells.length)
7463
+ return;
7464
+ const tsv = cells.map(row => row.map(({ item, col }) => this.cellDisplayText(item, col)).join('\t')).join('\n');
7465
+ event.clipboardData?.setData('text/plain', tsv);
7466
+ event.preventDefault();
7467
+ }
7468
+ onDocPaste(event) {
7469
+ if (EsTable2Component.activeRangeOwner !== this || this.editing() || !this.Editable())
7470
+ return;
7471
+ if (this.isFieldTarget(event.target))
7472
+ return; // lascio l'incolla nativo nei campi
7473
+ if (!this.rangeRect())
7474
+ return;
7475
+ const text = event.clipboardData?.getData('text/plain') ?? '';
7476
+ event.preventDefault();
7477
+ this.applyPaste(text);
7478
+ }
7479
+ /** true se il target dell'evento è un campo di input (copia/incolla nativi) */
7480
+ isFieldTarget(t) {
7481
+ const tag = t?.tagName;
7482
+ return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';
7483
+ }
7484
+ /** Celle (item+colonna) comprese nel rettangolo di selezione */
7485
+ rangeCells() {
7486
+ const rr = this.rangeRect();
7487
+ if (!rr)
7488
+ return [];
7489
+ const rows = this.boundSource();
7490
+ const cols = this.visibleColumns();
7491
+ const out = [];
7492
+ for (let r = rr.top; r <= rr.bottom; r++) {
7493
+ const item = rows[r];
7494
+ if (!item)
7495
+ continue;
7496
+ const line = [];
7497
+ for (let c = rr.left; c <= rr.right; c++)
7498
+ if (cols[c])
7499
+ line.push({ item, col: cols[c] });
7500
+ if (line.length)
7501
+ out.push(line);
7502
+ }
7503
+ return out;
7504
+ }
7505
+ applyPaste(text) {
7506
+ if (text == null)
7507
+ return;
7508
+ const matrix = text.replace(/\r/g, '').replace(/\n$/, '').split('\n').map(l => l.split('\t'));
7509
+ const cells = this.rangeCells();
7510
+ if (!cells.length || !matrix.length)
7511
+ return;
7512
+ // Un singolo valore si applica all'intera selezione; altrimenti le dimensioni devono coincidere
7513
+ const single = matrix.length === 1 && matrix[0].length === 1;
7514
+ if (!single && (matrix.length !== cells.length || matrix[0].length !== cells[0].length)) {
7515
+ this.flashNotice(`Impossibile incollare: i dati (${matrix.length}×${matrix[0].length}) non corrispondono alla selezione (${cells.length}×${cells[0].length}).`);
7516
+ return;
7517
+ }
7518
+ // Pass 1 — convalido TUTTE le celle per tipo di colonna. Se anche una sola
7519
+ // è incompatibile, avviso e non applico NULLA (operazione atomica): meglio
7520
+ // un avviso chiaro che svuotare silenziosamente le celle.
7521
+ const coerced = [];
7522
+ for (let r = 0; r < cells.length; r++) {
7523
+ coerced[r] = [];
7524
+ for (let c = 0; c < cells[r].length; c++) {
7525
+ const { col } = cells[r][c];
7526
+ const raw = single ? matrix[0][0] : matrix[r][c];
7527
+ const res = this.coerceValue(raw, col);
7528
+ if (!res.ok) {
7529
+ this.flashNotice(`Impossibile incollare: «${raw}» non è compatibile con la colonna «${col.headerText ?? col.id}» (tipo ${col.type ?? 'testo'}).`);
7530
+ return;
7531
+ }
7532
+ coerced[r][c] = res.value;
7533
+ }
7534
+ }
7535
+ // Pass 2 — applico i valori già convertiti
7536
+ const changes = [];
7537
+ for (let r = 0; r < cells.length; r++)
7538
+ for (let c = 0; c < cells[r].length; c++) {
7539
+ const { item, col } = cells[r][c];
7540
+ const change = this.applyCoerced(item, col, coerced[r][c]);
7541
+ if (change)
7542
+ changes.push(change);
7543
+ }
7544
+ if (changes.length) {
7545
+ this.onModelChange.emit(changes);
7546
+ this.changed();
7547
+ }
7548
+ this.cdr.markForCheck();
7549
+ }
7550
+ // --- Editing in cella ------------------------------------------------------
7551
+ /** Template di editor custom (`*editor`) per una colonna, se fornito dal consumer */
7552
+ editorFor(col) {
7553
+ const eds = this.editorDirectives?.toArray() ?? [];
7554
+ const e = eds.find(x => (x.editor && x.editor === col.id) || (!!col.property && x.editorForProperty === col.property));
7555
+ return e?.Template ?? null;
7556
+ }
7557
+ /** Contesto passato ai template `*editor` custom */
7558
+ editorContext(item, col) {
7559
+ return {
7560
+ $implicit: item,
7561
+ item, column: col,
7562
+ value: this.editDraft,
7563
+ setValue: (v) => { this.editDraft = v; },
7564
+ commit: () => this.commitEdit(item, col),
7565
+ cancel: () => this.cancelEdit()
7566
+ };
7567
+ }
7568
+ isEditing(r, c) {
7569
+ const e = this.editing();
7570
+ return !!e && e.r === r && e.c === c;
7571
+ }
7572
+ /** Tipo dell'`<input>` di default in base al tipo di colonna */
7573
+ editorInputType(type) {
7574
+ switch (type) {
7575
+ case 'number':
7576
+ case 'int':
7577
+ case 'float':
7578
+ case 'currency': return 'number';
7579
+ default: return 'text';
7580
+ }
7581
+ }
7582
+ onCellDblClick(item, col, r, c) {
7583
+ if (!this.Editable() || !col.property || item?._group)
7584
+ return;
7585
+ this.rangeSel.set(null);
7586
+ this.editDraft = this.rawCellValue(item, col) ?? '';
7587
+ this.editing.set({ r, c });
7588
+ this.cdr.markForCheck();
7589
+ requestAnimationFrame(() => {
7590
+ const el = this.hostEl?.nativeElement?.querySelector('.est2-editor-input');
7591
+ el?.focus();
7592
+ el?.select?.();
7593
+ });
7594
+ }
7595
+ commitEdit(item, col) {
7596
+ if (!this.editing())
7597
+ return; // già chiusa (es. blur dopo Enter/Escape)
7598
+ const res = this.coerceValue(this.editDraft, col);
7599
+ this.editing.set(null);
7600
+ if (!res.ok) {
7601
+ // valore incompatibile col tipo: avviso e NON modifico (nessun svuotamento)
7602
+ this.flashNotice(`Valore «${this.editDraft}» non valido per «${col.headerText ?? col.id}» (tipo ${col.type ?? 'testo'}).`);
7603
+ this.cdr.markForCheck();
7604
+ return;
7605
+ }
7606
+ const change = this.applyCoerced(item, col, res.value);
7607
+ if (change) {
7608
+ this.onModelChange.emit([change]);
7609
+ this.changed();
7610
+ }
7611
+ this.cdr.markForCheck();
7612
+ }
7613
+ cancelEdit() {
7614
+ if (!this.editing())
7615
+ return;
7616
+ this.editing.set(null);
7617
+ this.cdr.markForCheck();
7618
+ }
7619
+ // --- Valori: lettura/scrittura tipizzata + testo di copia ------------------
7620
+ /** Valore grezzo su cui operano editing/paste (gestisce gli oggetti `properties`) */
7621
+ rawCellValue(item, col) {
7622
+ if (!col.property)
7623
+ return null;
7624
+ return item?.properties ? item.properties[col.property] : item?.[col.property];
7625
+ }
7626
+ /** Scrive un valore GIÀ convertito e restituisce l'eventuale EsTableModelChange */
7627
+ applyCoerced(item, col, niu) {
7628
+ if (!col.property)
7629
+ return null;
7630
+ const old = this.rawCellValue(item, col);
7631
+ if (old === niu)
7632
+ return null;
7633
+ if (item?.properties)
7634
+ item.properties[col.property] = niu;
7635
+ else
7636
+ item[col.property] = niu;
7637
+ return new EsTableModelChange(item, String(col.headerText ?? col.id), col.property, old, niu);
7638
+ }
7639
+ /**
7640
+ * Converte una stringa (digitata/incollata) nel tipo della colonna, segnalando
7641
+ * se è compatibile. `ok:false` = valore incompatibile col tipo (es. testo in una
7642
+ * colonna numerica) → chi chiama avvisa e non scrive. Una stringa vuota è sempre
7643
+ * valida e azzera il valore (null).
7644
+ */
7645
+ coerceValue(raw, col) {
7646
+ const type = col.type;
7647
+ const v = raw == null ? '' : String(raw).trim();
7648
+ switch (type) {
7649
+ case 'int':
7650
+ case 'number':
7651
+ case 'float':
7652
+ case 'currency': {
7653
+ if (v === '')
7654
+ return { ok: true, value: null };
7655
+ const n = this.toNumber(v);
7656
+ if (n == null || isNaN(n))
7657
+ return { ok: false, value: null };
7658
+ return { ok: true, value: (type === 'int' || type === 'number') ? Math.round(n) : n };
7659
+ }
7660
+ case 'boolean': {
7661
+ if (v === '')
7662
+ return { ok: true, value: null };
7663
+ if (/^(true|1|✓|s[iì]|si|yes|y)$/i.test(v))
7664
+ return { ok: true, value: true };
7665
+ if (/^(false|0|—|no|n)$/i.test(v))
7666
+ return { ok: true, value: false };
7667
+ return { ok: false, value: null };
7668
+ }
7669
+ case 'enum':
7670
+ case 'autocomplete': {
7671
+ if (!col.source || col.source.length === 0)
7672
+ return { ok: true, value: v };
7673
+ const byId = col.source.find(o => String(o.id) === v);
7674
+ if (byId)
7675
+ return { ok: true, value: byId.id };
7676
+ const byDesc = col.source.find(o => o.description === v);
7677
+ if (byDesc)
7678
+ return { ok: true, value: byDesc.id };
7679
+ return { ok: false, value: null };
7680
+ }
7681
+ case 'date':
7682
+ case 'datetime':
7683
+ case 'time': {
7684
+ if (v === '')
7685
+ return { ok: true, value: null };
7686
+ const d = this.parseDateLoose(v);
7687
+ return d ? { ok: true, value: d } : { ok: false, value: null };
7688
+ }
7689
+ default:
7690
+ return { ok: true, value: raw };
7691
+ }
7692
+ }
7693
+ /** Parsing numerico tollerante (separatori di migliaia + virgola/punto decimale). `NaN` = non numerico */
7694
+ toNumber(v) {
7695
+ let s = v.replace(/\s/g, '').replace(/[€$%]/g, '');
7696
+ if (s === '')
7697
+ return NaN;
7698
+ const hasComma = s.includes(','), hasDot = s.includes('.');
7699
+ if (hasComma && hasDot) {
7700
+ // l'ultimo separatore è quello decimale
7701
+ if (s.lastIndexOf(',') > s.lastIndexOf('.'))
7702
+ s = s.replace(/\./g, '').replace(',', '.');
7703
+ else
7704
+ s = s.replace(/,/g, '');
7705
+ }
7706
+ else if (hasComma) {
7707
+ s = s.replace(',', '.');
7708
+ }
7709
+ return /^-?\d*\.?\d+$/.test(s) ? parseFloat(s) : NaN;
7710
+ }
7711
+ /** Parsing date tollerante: `dd/MM/yyyy [HH:mm[:ss]]` o formati nativi. `null` = non valida */
7712
+ parseDateLoose(v) {
7713
+ const m = v.match(/^(\d{1,2})[\/\-.](\d{1,2})[\/\-.](\d{2,4})(?:[ T](\d{1,2}):(\d{2})(?::(\d{2}))?)?$/);
7714
+ if (m) {
7715
+ const y = m[3].length === 2 ? 2000 + +m[3] : +m[3];
7716
+ const d = new Date(y, +m[2] - 1, +m[1], +(m[4] || 0), +(m[5] || 0), +(m[6] || 0));
7717
+ return isNaN(d.getTime()) ? null : d;
7718
+ }
7719
+ const d = new Date(v);
7720
+ return isNaN(d.getTime()) ? null : d;
7721
+ }
7722
+ /** Testo visualizzato di una cella (usato dalla copia) */
7723
+ cellDisplayText(item, col) {
7724
+ if (col.propAccessor != null)
7725
+ return this.reportCellDisplay(item, col);
7726
+ const val = this.cellValue(item, col);
7727
+ if (col.type === 'enum')
7728
+ return this.lk.transform(val, col.source).description;
7729
+ return this.fmt.transform(val, col.type, col.format, this.locale);
7730
+ }
7731
+ // ===========================================================================
7732
+ // Raggruppamento (array mode) — espansione/collasso e resa delle celle
7733
+ // ===========================================================================
7734
+ /** true se `childKey` è `ancestorKey` stesso o un suo discendente (senza collisioni di prefisso) */
7735
+ isDescendantKey(childKey, ancestorKey) {
7736
+ return !!childKey && (childKey === ancestorKey || childKey.startsWith(ancestorKey + '¦'));
7737
+ }
7738
+ /** Espande/collassa un gruppo agendo solo sui flag `_visible` (nessuna ricostruzione) */
7739
+ toggleGroup(group) {
7740
+ group._expanded = !group._expanded;
7741
+ const open = group._expanded;
7742
+ const key = group._thisKey;
7743
+ for (const r of this.boundSource()) {
7744
+ if (r === group)
7745
+ continue;
7746
+ // espandi: solo i figli immediati; collassa: l'intero sottoalbero
7747
+ const childGroup = r._group && (open ? r._parent === key : this.isDescendantKey(r._parent, key));
7748
+ const childItem = r._item && (open ? r._thisKey === key : this.isDescendantKey(r._thisKey, key));
7749
+ if (childGroup || childItem) {
7750
+ r._visible = open;
7751
+ if (r._group && !open)
7752
+ r._expanded = false; // ri-collassa i discendenti
7753
+ }
7754
+ }
7755
+ this.cdr.markForCheck();
7756
+ }
7757
+ /** Item (foglia) appartenenti a un gruppo, a qualsiasi profondità */
7758
+ groupItems(group) {
7759
+ return this.boundSource().filter(r => r._item && this.isDescendantKey(r._thisKey, group._thisKey));
7760
+ }
7761
+ /** Stato di selezione aggregato di un gruppo, per la sua checkbox */
7762
+ groupSelectionState(group) {
7763
+ const items = this.groupItems(group);
7764
+ if (items.length === 0)
7765
+ return 'none';
7766
+ const n = items.filter(it => it._selected).length;
7767
+ return n === 0 ? 'none' : n === items.length ? 'all' : 'some';
7768
+ }
7769
+ /** Seleziona/deseleziona tutti gli item di un gruppo */
7770
+ toggleGroupSelection(group) {
7771
+ if (this.SelectionDisabled() || this.SingleSelection())
7772
+ return;
7773
+ const items = this.groupItems(group);
7774
+ const select = this.groupSelectionState(group) !== 'all';
7775
+ const sel = this.currentSelection();
7776
+ for (const it of items) {
7777
+ it._selected = select;
7778
+ if (sel.all) {
7779
+ if (select)
7780
+ sel.exclusions = sel.exclusions.filter(i => i !== it);
7781
+ else if (!sel.exclusions.includes(it))
7782
+ sel.exclusions.push(it);
7783
+ }
7784
+ else {
7785
+ if (select) {
7786
+ if (!sel.items.includes(it))
7787
+ sel.items.push(it);
7788
+ }
7789
+ else
7790
+ sel.items = sel.items.filter(i => i !== it);
7791
+ }
7792
+ }
7793
+ this.refreshSelectionCount(sel);
7794
+ this.syncGlobalCheck();
7795
+ this.emitSelection(sel);
7796
+ }
7797
+ /** Testo da mostrare nella cella `col` per una riga-gruppo */
7798
+ groupCellDisplay(group, col) {
7799
+ if (group.column === col.id) {
7800
+ const gp = group._groupscount
7801
+ ? ` - ${group._groupscount} ${group._groupscount === 1 ? 'gruppo' : 'gruppi'}`
7802
+ : '';
7803
+ return `${group.value} (${group.count}${gp})`;
7804
+ }
7805
+ const colMap = group._agg?.get(col.id);
7806
+ if (!colMap || !col.header?.thAggregation)
7807
+ return '';
7808
+ let display = col.header.thAggregation;
7809
+ for (const spec of col.aggSpecs)
7810
+ display = display.replace(spec.match, formatAggValue(colMap.get(spec.func), spec.format, this.locale));
7811
+ return display;
7812
+ }
7813
+ /** true se la cella di una riga-oggetto va lasciata vuota (colonna di gruppo nascosta) */
7814
+ itemCellHidden(col) {
7815
+ return this.grouped() && col.isGroup && !this.ShowItemGroupsColumns();
7816
+ }
7817
+ /** Indentazione (px) della cella-chiave in base al livello di gruppo */
7818
+ groupIndent(group) {
7819
+ return (group?._grouplevel || 0) * 18;
7820
+ }
7821
+ // ===========================================================================
7822
+ // Modalità gerarchica (albero padre/figlio)
7823
+ // ===========================================================================
7824
+ /** Auto-rileva ownKey/parentKey ('id'/'parentid') se non specificati */
7825
+ autoDetectHierarchyKeys(items) {
7826
+ if (!this.ParentKey() && !this.OwnKey() && items.length > 0) {
7827
+ if (items[0]['parentid'] !== undefined)
7828
+ this.ParentKey.set('parentid');
7829
+ if (items[0]['id'] !== undefined)
7830
+ this.OwnKey.set('id');
7831
+ }
7832
+ if (!this.OwnKey() || !this.ParentKey())
7833
+ console.error("[es-table2] La modalità gerarchica richiede sia OwnKey che ParentKey. Fallback a lista piatta.");
7834
+ }
7835
+ /** Costruisce l'albero e applica la selezione; se mancano le chiavi torna la lista invariata */
7836
+ applyHierarchy(items, sel) {
7837
+ this.autoDetectHierarchyKeys(items);
7838
+ if (!this.OwnKey() || !this.ParentKey()) {
7839
+ this.applySelectionFlags(items, sel);
7840
+ return items;
7841
+ }
7842
+ const ordered = assignHierarchyLevels(items, this.OwnKey(), this.ParentKey(), this.StartsExpanded(), this.AutoSortHierarchy());
7843
+ // mappa padre -> figli per lookup O(1) (cascade selection, indeterminate)
7844
+ this.hierChildren.clear();
7845
+ const pk = this.ParentKey();
7846
+ for (const r of ordered) {
7847
+ const pv = r[pk];
7848
+ if (pv === null || pv === undefined || pv === '')
7849
+ continue;
7850
+ const k = String(pv);
7851
+ const arr = this.hierChildren.get(k);
7852
+ if (arr)
7853
+ arr.push(r);
7854
+ else
7855
+ this.hierChildren.set(k, [r]);
7856
+ }
7857
+ this.applySelectionFlags(ordered, sel);
7858
+ return ordered;
7859
+ }
7860
+ directChildren(item) {
7861
+ return this.hierChildren.get(String(item[this.OwnKey()])) ?? [];
7862
+ }
7863
+ hierDescendants(item) {
7864
+ const out = [];
7865
+ const stack = [...this.directChildren(item)];
7866
+ while (stack.length) {
7867
+ const n = stack.pop();
7868
+ out.push(n);
7869
+ const kids = this.directChildren(n);
7870
+ if (kids.length)
7871
+ stack.push(...kids);
7872
+ }
7873
+ return out;
7874
+ }
7875
+ /** Selezione in cascata: propaga lo stato al sottoalbero e ricalcola gli antenati */
7876
+ cascadeSelection(item, selected) {
7877
+ item._selected = selected;
7878
+ for (const d of this.hierDescendants(item))
7879
+ d._selected = selected;
7880
+ // risali: un padre è selezionato sse tutti i figli lo sono
7881
+ const pk = this.ParentKey();
7882
+ let current = item;
7883
+ while (true) {
7884
+ const pv = current[pk];
7885
+ if (pv === null || pv === undefined || pv === '')
7886
+ break;
7887
+ const parent = this.boundSource().find(r => String(r[this.OwnKey()]) === String(pv));
7888
+ if (!parent)
7889
+ break;
7890
+ const kids = this.directChildren(parent);
7891
+ parent._selected = kids.length > 0 && kids.every(k => k._selected);
7892
+ current = parent;
7893
+ }
7894
+ }
7895
+ /** Ricostruisce ItemsSelection dai flag `_selected` correnti (usato dalla cascade) */
7896
+ rebuildSelectionFromFlags(sel) {
7897
+ sel.all = false;
7898
+ sel.exclusions = [];
7899
+ sel.items = this.boundSource().filter(r => r._selected);
7900
+ sel.count = sel.items.length;
7901
+ }
7902
+ /** Stato indeterminato di una riga-padre in cascade (alcuni discendenti selezionati) */
7903
+ hierarchyIndeterminate(item) {
7904
+ if (!this.CascadeSelection() || !item?.parent || item._selected)
7905
+ return false;
7906
+ return this.hierDescendants(item).some(d => d._selected);
7907
+ }
7908
+ /** Espande/collassa un nodo dell'albero */
7909
+ toggleHierarchyNode(item) {
7910
+ if (!item?.parent)
7911
+ return;
7912
+ const rows = this.boundSource();
7913
+ if (item._expanded)
7914
+ collapseNode(rows, this.OwnKey(), this.ParentKey(), item[this.OwnKey()]);
7915
+ else
7916
+ expandNode(rows, this.OwnKey(), this.ParentKey(), item[this.OwnKey()]);
7917
+ this.cdr.markForCheck();
7918
+ }
7919
+ /** Indentazione (px) di una riga dell'albero in base al livello */
7920
+ hierarchyIndent(item) {
7921
+ return ((item?._level || 1) - 1) * 20;
7922
+ }
7923
+ /** Apertura context menu: se la riga non è selezionata la seleziona (solo lei), poi notifica */
7924
+ onContextMenuOpen(item) {
7925
+ // Il click destro apre il menu sulla riga: azzero l'eventuale range di celle
7926
+ // (la selezione a range e quella di riga sono meccanismi distinti)
7927
+ if (this.rangeSel()) {
7928
+ this.rangeSel.set(null);
7929
+ this.cdr.markForCheck();
7930
+ }
7931
+ if (item && !item._sep && !item._group && !item._selected) {
7932
+ const sel = this.currentSelection();
7933
+ for (const r of this.selectableRows())
7934
+ r._selected = false;
7935
+ sel.all = false;
7936
+ sel.exclusions = [];
7937
+ sel.items = [item];
7938
+ item._selected = true;
7939
+ this.refreshSelectionCount(sel);
7940
+ this.syncGlobalCheck();
7941
+ this.emitSelection(sel);
7942
+ }
7943
+ this.onOpenContextMenu.emit();
7944
+ }
7945
+ /** Indeterminato: alcune ma non tutte le righe selezionate (per la checkbox globale) */
7946
+ get selectionIndeterminate() {
7947
+ const sel = this.currentSelection();
7948
+ if (sel.all)
7949
+ return sel.exclusions.length > 0;
7950
+ const rows = this.selectableRows();
7951
+ const n = rows.filter(r => r._selected).length;
7952
+ return n > 0 && n < rows.length;
7953
+ }
7954
+ /** Numero di elementi selezionati (per la barra) */
7955
+ get selectedCount() { return this.currentSelection().count || 0; }
7956
+ /** true se c'è almeno un elemento selezionato (mostra la barra) */
7957
+ get hasSelection() {
7958
+ const sel = this.currentSelection();
7959
+ return sel.all || sel.items.length > 0;
7960
+ }
7961
+ /** true se sono selezionati TUTTI gli elementi (all senza esclusioni) */
7962
+ get allSelected() {
7963
+ const sel = this.currentSelection();
7964
+ return sel.all && sel.exclusions.length === 0;
7965
+ }
7966
+ /** true se ha senso proporre "seleziona tutti": pagina piena, non in all, e ci sono altre pagine */
7967
+ get canSelectEverything() {
7968
+ const sel = this.currentSelection();
7969
+ const rows = this.selectableRows();
7970
+ return !sel.all && rows.length > 0 && rows.every(r => r._selected) && this.totalCount() > rows.length;
7971
+ }
7972
+ // ===========================================================================
7973
+ // Rimozione
7974
+ // ===========================================================================
7975
+ canRemove(item) {
7976
+ const cond = this.RemovalCondition();
7977
+ return cond ? !!item[cond] : true;
7978
+ }
7979
+ removeItem(item) { this.onRemoval.emit(item); }
7980
+ abortRemoval(item) { this.onAbortRemoval.emit(item); }
7981
+ /** Applica ordine + visibilità + pin delle preferenze a una lista di colonne appena costruita */
7982
+ setColumns(cols) {
7983
+ const p = this.columnPrefs;
7984
+ if (!p) {
7985
+ this.columns.set(cols);
7986
+ this.ensurePrefsLoaded();
7987
+ return;
7988
+ }
7989
+ const byId = new Map(cols.map(c => [c.id, c]));
7990
+ const ordered = [];
7991
+ for (const id of p.order) {
7992
+ const c = byId.get(id);
7993
+ if (c) {
7994
+ ordered.push(c);
7995
+ byId.delete(id);
7996
+ }
7997
+ }
7998
+ for (const c of cols)
7999
+ if (byId.has(c.id))
8000
+ ordered.push(c); // colonne nuove (non in prefs) in coda
8001
+ for (const c of ordered) {
8002
+ c.visible = !p.hidden.includes(c.id);
8003
+ // il pin salvato sovrascrive il default della direttiva (solo se presente nelle prefs)
8004
+ if (p.pinned)
8005
+ c.pinned = p.pinned.includes(c.id);
8006
+ }
8007
+ this.columns.set(ordered);
8008
+ this.ensurePrefsLoaded();
8009
+ }
8010
+ /**
8011
+ * Chiave di persistenza delle preferenze. Identica all'originale: il `name`
8012
+ * del controllo (ngModel/formControlName). In fallback usa `[Name]`.
8013
+ */
8014
+ prefsKey() {
8015
+ const key = this.ngControl?.name || this.Name() || null;
8016
+ if (!key && this.SavePreferences())
8017
+ console.error("[es-table2] Non posso usare le preferenze senza un 'name' (ngModel) o un [Name] per la tabella");
8018
+ return key;
8019
+ }
8020
+ /**
8021
+ * Carica le preferenze dal `PreferencesService` (stesso formato dell'es-table
8022
+ * originale: `{ columns, column_ordering, orders }`). Chiamata una volta sola,
8023
+ * quando esistono già le colonne correnti per poter validare la coerenza.
8024
+ */
8025
+ ensurePrefsLoaded() {
8026
+ if (this.prefsLoaded || !this.SavePreferences())
8027
+ return;
8028
+ if (this.columns().length === 0)
8029
+ return; // attendo che le colonne esistano
8030
+ const key = this.prefsKey();
8031
+ if (!key) {
8032
+ this.prefsLoaded = true;
8033
+ return;
8034
+ }
8035
+ if (!this.prefService) {
8036
+ console.error('[es-table2] SavePreferences attivo ma @esfaenza/preferences non è disponibile');
8037
+ this.prefsLoaded = true;
8038
+ return;
8039
+ }
8040
+ this.prefsLoaded = true;
8041
+ this.prefService.getItem(key, true)
8042
+ .subscribe(prefs => this.applyLoadedPrefs(prefs));
8043
+ }
8044
+ applyLoadedPrefs(prefs) {
8045
+ if (!prefs)
8046
+ return;
8047
+ const currentIds = new Set(this.columns().map(c => c.id));
8048
+ const order = (prefs.column_ordering?.length ? prefs.column_ordering : prefs.columns) || [];
8049
+ const visible = prefs.columns || [];
8050
+ // Coerenza: se le preferenze citano colonne non più presenti, l'es-table è
8051
+ // cambiata → ignoro (come l'originale) per non nascondere/riordinare a vuoto.
8052
+ for (const id of order)
8053
+ if (id && !currentIds.has(id))
8054
+ return;
8055
+ for (const id of visible)
8056
+ if (id && !currentIds.has(id))
8057
+ return;
8058
+ if (order.length === 0 && visible.length === 0)
8059
+ return;
8060
+ // `pinned` è un'estensione retro-compatibile: se assente (prefs vecchie/originali)
8061
+ // resta il pin di default delle direttive.
8062
+ const pinned = Array.isArray(prefs.pinned) ? prefs.pinned.filter(id => currentIds.has(id)) : undefined;
8063
+ this.columnPrefs = { order, hidden: [...currentIds].filter(id => !visible.includes(id)), pinned };
8064
+ this.setColumns(this.columns());
8065
+ if (prefs.orders?.length) {
8066
+ this.orders.set([...prefs.orders]);
8067
+ if (this.viewMode() && this.view())
8068
+ this.view().orders = [...prefs.orders];
8069
+ }
8070
+ this.cdr.markForCheck();
8071
+ }
8072
+ /**
8073
+ * Persiste le preferenze correnti nel formato originale
8074
+ * `{ columns (visibili), column_ordering (ordine), orders (ordinamenti) }`.
8075
+ */
8076
+ saveColumnPrefs() {
8077
+ if (!this.SavePreferences() || !this.prefService)
8078
+ return;
8079
+ const key = this.prefsKey();
8080
+ if (!key)
8081
+ return;
8082
+ // Escludo le sotto-colonne Multi: sono auto-generate dai dati, non vanno
8083
+ // persistite (altrimenti il guard di coerenza le rifiuterebbe al reload,
8084
+ // quando ancora non esistono).
8085
+ const cols = this.columns().filter(c => c.multiProp == null);
8086
+ const payload = {
8087
+ columns: cols.filter(c => c.visible).map(c => c.id),
8088
+ column_ordering: cols.map(c => c.id),
8089
+ orders: this.orders(),
8090
+ // estensione es-table2 (retro-compatibile: l'originale ignora i campi extra)
8091
+ pinned: cols.filter(c => c.pinned).map(c => c.id)
8092
+ };
8093
+ // allineo anche i campi wire dell'AppSearch (parità col server-side)
8094
+ if (this.viewMode() && this.view()) {
8095
+ this.view().columns = payload.columns;
8096
+ this.view().column_ordering = payload.column_ordering;
8097
+ }
8098
+ this.prefService.setItem(key, payload).subscribe();
8099
+ }
8100
+ // --- Pin/unpin dall'header --------------------------------------------------
8101
+ /** Attiva/disattiva il pin di una colonna e persiste (dall'indicatore in testata) */
8102
+ togglePin(col, ev) {
8103
+ ev?.stopPropagation();
8104
+ col.pinned = !col.pinned;
8105
+ // aggiorno le prefs correnti (ordine/visibilità invariati) — escludo le Multi
8106
+ const nonMulti = this.columns().filter(c => c.multiProp == null);
8107
+ this.columnPrefs = {
8108
+ order: nonMulti.map(c => c.id),
8109
+ hidden: nonMulti.filter(c => !c.visible).map(c => c.id),
8110
+ pinned: nonMulti.filter(c => c.pinned).map(c => c.id)
8111
+ };
8112
+ this.columns.set([...this.columns()]); // forza il ricalcolo di visibleColumns/headerGroupRows
8113
+ this.saveColumnPrefs();
8114
+ this.cdr.markForCheck();
8115
+ }
8116
+ /** Etichetta leggibile di una colonna (header statico o id) */
8117
+ columnLabel(col) {
8118
+ return (col.headerText && String(col.headerText).trim()) || col.id;
8119
+ }
8120
+ openColumnsDialog() {
8121
+ // Le sotto-colonne Multi sono auto-generate: non le espongo nella dialog
8122
+ // (riordinarle/pinnarle singolarmente romperebbe il raggruppamento).
8123
+ this.dialogCols.set(this.columns().filter(c => c.multiProp == null)
8124
+ .map(c => ({ id: c.id, label: this.columnLabel(c), visible: c.visible, pinned: !!c.pinned })));
8125
+ this.columnsDialogOpen.set(true);
8126
+ this.cdr.markForCheck();
8127
+ }
8128
+ closeColumnsDialog() { this.columnsDialogOpen.set(false); this.cdr.markForCheck(); }
8129
+ dialogToggle(i) {
8130
+ const list = [...this.dialogCols()];
8131
+ list[i] = { ...list[i], visible: !list[i].visible };
8132
+ this.dialogCols.set(list);
8133
+ }
8134
+ dialogTogglePin(i) {
8135
+ const list = [...this.dialogCols()];
8136
+ list[i] = { ...list[i], pinned: !list[i].pinned };
8137
+ this.dialogCols.set(list);
8138
+ }
8139
+ dialogSetAll(visible) {
8140
+ this.dialogCols.set(this.dialogCols().map(c => ({ ...c, visible })));
8141
+ }
8142
+ dialogMove(i, dir) {
8143
+ const list = [...this.dialogCols()];
8144
+ const j = i + dir;
8145
+ if (j < 0 || j >= list.length)
8146
+ return;
8147
+ [list[i], list[j]] = [list[j], list[i]];
8148
+ this.dialogCols.set(list);
8149
+ }
8150
+ applyColumnsDialog() {
8151
+ const list = this.dialogCols();
8152
+ this.columnPrefs = {
8153
+ order: list.map(c => c.id),
8154
+ hidden: list.filter(c => !c.visible).map(c => c.id),
8155
+ pinned: list.filter(c => c.pinned).map(c => c.id)
8156
+ };
8157
+ // riapplico alla lista corrente conservando gli oggetti-colonna...
8158
+ this.setColumns(this.columns());
8159
+ // ...e SOLO DOPO persisto (saveColumnPrefs deriva il payload da columns())
8160
+ this.saveColumnPrefs();
8161
+ this.columnsDialogOpen.set(false);
8162
+ this.cdr.markForCheck();
8163
+ }
8164
+ resetColumnsDialog() {
8165
+ this.columnPrefs = null;
8166
+ this.buildColumns(); // ricostruisce dalle direttive nell'ordine originale
8167
+ if (this.viewMode() || this.arrayMode())
8168
+ this.refresh();
8169
+ this.saveColumnPrefs(); // persisto lo stato di default (dopo la ricostruzione)
8170
+ this.columnsDialogOpen.set(false);
8171
+ this.cdr.markForCheck();
8172
+ }
8173
+ toggleExportMenu() { this.cornerMenuOpen.set(false); this.exportMenuOpen.set(!this.exportMenuOpen()); this.cdr.markForCheck(); }
8174
+ toggleCornerMenu() { this.exportMenuOpen.set(false); this.cornerMenuOpen.set(!this.cornerMenuOpen()); this.cdr.markForCheck(); }
8175
+ /** Chiude i menu a tendina del chrome quando si clicca fuori */
8176
+ onDocClick() {
8177
+ if (this.exportMenuOpen() || this.cornerMenuOpen()) {
8178
+ this.exportMenuOpen.set(false);
8179
+ this.cornerMenuOpen.set(false);
8180
+ this.cdr.markForCheck();
8181
+ }
8182
+ }
8183
+ /** Colonne da esportare (tutte o solo visibili) e con dato testuale */
8184
+ exportColumns() {
8185
+ const cols = this.ExportOnlyVisibleColumns() ? this.visibleColumns() : this.columns();
8186
+ return cols.filter(c => !c.isGroup);
8187
+ }
8188
+ /** Righe-dato correnti (esclude righe-gruppo) */
8189
+ exportRows() {
8190
+ return this.boundSource().filter(r => !r._group);
8191
+ }
8192
+ /** Matrice [header, ...righe] con i valori visualizzati */
8193
+ buildExportMatrix() {
8194
+ const cols = this.exportColumns();
8195
+ const header = cols.map(c => this.columnLabel(c));
8196
+ const rows = this.exportRows().map(item => cols.map(c => this.cellDisplayText(item, c)));
8197
+ return [header, ...rows];
8198
+ }
8199
+ export(format = 'CSV') {
8200
+ this.exportMenuOpen.set(false);
8201
+ // Parità con l'originale: se il consumer fornisce ExportFunction gliela lascio gestire
8202
+ if (this.ExportFunction) {
8203
+ this.ExportFunction((data, _type) => this.exportData(data, format), format);
8204
+ return;
8205
+ }
8206
+ const matrix = this.buildExportMatrix();
8207
+ if (matrix.length <= 1) {
8208
+ this.flashNotice('Nessun dato da esportare.');
8209
+ return;
8210
+ }
8211
+ if (format === 'XLSX')
8212
+ this.exportXlsx(matrix);
8213
+ else
8214
+ this.exportCsv(matrix);
8215
+ }
8216
+ /** Esporta dati forniti dal consumer (via ExportFunction): li mappa sulle colonne correnti */
8217
+ exportData(data, format) {
8218
+ if (!data?.length) {
8219
+ this.flashNotice('Nessun dato da esportare.');
8220
+ return;
8221
+ }
8222
+ const cols = this.exportColumns();
8223
+ const header = cols.map(c => this.columnLabel(c));
8224
+ const rows = data.filter(r => !r._group).map(item => cols.map(c => this.cellDisplayText(item, c)));
8225
+ const matrix = [header, ...rows];
8226
+ if (format === 'XLSX')
8227
+ this.exportXlsx(matrix);
8228
+ else
8229
+ this.exportCsv(matrix);
8230
+ }
8231
+ exportCsv(matrix) {
8232
+ const esc = (v) => {
8233
+ const s = v == null ? '' : String(v);
8234
+ return /[",\n;]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
8235
+ };
8236
+ const csv = matrix.map(row => row.map(esc).join(';')).join('\r\n');
8237
+ // BOM per la corretta apertura in Excel con caratteri accentati
8238
+ this.downloadBlob('' + csv, this.exportFileName('csv'), 'text/csv;charset=utf-8;');
8239
+ }
8240
+ async exportXlsx(matrix) {
8241
+ try {
8242
+ const XLSX = await import('xlsx');
8243
+ const ws = XLSX.utils.aoa_to_sheet(matrix);
8244
+ const wb = XLSX.utils.book_new();
8245
+ XLSX.utils.book_append_sheet(wb, ws, 'Export');
8246
+ XLSX.writeFile(wb, this.exportFileName('xlsx'));
8247
+ }
8248
+ catch {
8249
+ this.flashNotice('Export XLSX non disponibile (SheetJS assente): esporto in CSV.');
8250
+ this.exportCsv(matrix);
8251
+ }
8252
+ }
8253
+ /** Nome file rispettando l'estensione richiesta */
8254
+ exportFileName(ext) {
8255
+ const name = this.ExportFileName() || 'Export';
8256
+ return /\.(csv|xlsx)$/i.test(name) ? name.replace(/\.(csv|xlsx)$/i, '.' + ext) : `${name}.${ext}`;
8257
+ }
8258
+ downloadBlob(content, filename, mime) {
8259
+ if (typeof document === 'undefined')
8260
+ return;
8261
+ const blob = new Blob([content], { type: mime });
8262
+ const url = URL.createObjectURL(blob);
8263
+ const a = document.createElement('a');
8264
+ a.href = url;
8265
+ a.download = filename;
8266
+ document.body.appendChild(a);
8267
+ a.click();
8268
+ document.body.removeChild(a);
8269
+ setTimeout(() => URL.revokeObjectURL(url), 0);
8270
+ }
8271
+ // ===========================================================================
8272
+ // Corner menu / helpers template
8273
+ // ===========================================================================
8274
+ cornerAction(id) { this.onCornerAction.emit({ id, estable: this }); }
8275
+ rowClass(item) {
8276
+ return this.RowClassAssigner ? (this.RowClassAssigner(item) || '') : '';
8277
+ }
8278
+ hasChrome() {
8279
+ return this.Export() || this.HiddenColumns() || this.ColumnsOrdering() || this.CornerMenuOptions().length > 0;
8280
+ }
8281
+ /** Colspan totale per righe a piena larghezza (stato vuoto) */
8282
+ totalColspan() {
8283
+ let n = this.usesColumns() ? this.visibleColumns().length : 1;
8284
+ n += this.DynamicOperations().length;
8285
+ if (this.Selection())
8286
+ n++;
8287
+ if (this.Removal())
8288
+ n++;
8289
+ if (this.hasChrome())
8290
+ n++;
8291
+ return Math.max(1, n);
8292
+ }
8293
+ /** Valore grezzo di una proprietà, gestendo gli oggetti "generic" con `properties` */
8294
+ cellValue(item, col) {
8295
+ if (!col.property)
8296
+ return null;
8297
+ return item?.properties ? item.properties[col.property] : item?.[col.property];
8298
+ }
8299
+ /** Entry Multi corrispondente a una cella (`item[multiProp][multiIndex]`) */
8300
+ multiCell(item, col) {
8301
+ if (col.multiProp == null || col.multiIndex == null)
8302
+ return {};
8303
+ const arr = item?.[col.multiProp];
8304
+ return (Array.isArray(arr) ? arr[col.multiIndex] : null) ?? {};
8305
+ }
8306
+ 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 }); }
8307
+ 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 }); }
8308
+ }
8309
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: EsTable2Component, decorators: [{
8310
+ type: Component,
8311
+ 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"] }]
8312
+ }], ctorParameters: () => [{ type: i1.NgControl, decorators: [{
8313
+ type: Self
8314
+ }, {
8315
+ type: Optional
8316
+ }] }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i2$1.PreferencesService, decorators: [{
8317
+ type: Optional
8318
+ }] }, { type: EsTableDefaultable, decorators: [{
8319
+ type: Optional
8320
+ }, {
8321
+ type: Inject,
8322
+ args: [EST2_DEFAULTS]
8323
+ }] }, { type: undefined, decorators: [{
8324
+ type: Optional
8325
+ }, {
8326
+ type: Inject,
8327
+ args: [EST2_DEBUG]
8328
+ }] }, { type: undefined, decorators: [{
8329
+ type: Optional
8330
+ }, {
8331
+ type: Inject,
8332
+ args: [EST2_EXPORT_GLOBAL_ACL]
8333
+ }] }], propDecorators: { headerRef: [{
8334
+ type: ContentChild,
8335
+ args: ['header', { static: false }]
8336
+ }], bodyRef: [{
8337
+ type: ContentChild,
8338
+ args: ['body', { static: false }]
8339
+ }], thDirectives: [{
8340
+ type: ContentChildren,
8341
+ args: [EsThDirective]
8342
+ }], tdDirectives: [{
8343
+ type: ContentChildren,
8344
+ args: [EsTdDirective]
8345
+ }], editorDirectives: [{
8346
+ type: ContentChildren,
8347
+ args: [EsTdEditorDirective]
8348
+ }], ContextMenu: [{
8349
+ type: Input
8350
+ }], tableEmptyMenu: [{
8351
+ type: ViewChild,
8352
+ args: ['emptyMenu', { static: true }]
8353
+ }], theadRef: [{
8354
+ type: ViewChild,
8355
+ args: ['theadRef']
8356
+ }], _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: [{
8357
+ type: Input
8358
+ }], _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: [{
8359
+ type: Input
8360
+ }], 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: [{
8361
+ type: Output
8362
+ }], onSearchRequest: [{
8363
+ type: Output
8364
+ }], onSelectionChanged: [{
8365
+ type: Output
8366
+ }], onRemoval: [{
8367
+ type: Output
8368
+ }], onAbortRemoval: [{
8369
+ type: Output
8370
+ }], onModelChange: [{
8371
+ type: Output
8372
+ }], onOpenContextMenu: [{
8373
+ type: Output
8374
+ }], onCornerAction: [{
8375
+ type: Output
8376
+ }], onDynamicOperation: [{
8377
+ type: Output
8378
+ }], 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: [{
8379
+ type: Input
8380
+ }], hostClass: [{
8381
+ type: HostBinding,
8382
+ args: ['class.est2']
8383
+ }], denseClass: [{
8384
+ type: HostBinding,
8385
+ args: ['class.est2--dense']
8386
+ }], onDocMouseUp: [{
8387
+ type: HostListener,
8388
+ args: ['document:mouseup']
8389
+ }], onDocCopy: [{
8390
+ type: HostListener,
8391
+ args: ['document:copy', ['$event']]
8392
+ }], onDocPaste: [{
8393
+ type: HostListener,
8394
+ args: ['document:paste', ['$event']]
8395
+ }], onDocClick: [{
8396
+ type: HostListener,
8397
+ args: ['document:click']
8398
+ }] } });
8399
+
5797
8400
  const MODULES = [
5798
8401
  CommonModule,
5799
8402
  FormsModule,
@@ -5809,14 +8412,16 @@ const MODULES = [
5809
8412
  AccessControlModule
5810
8413
  ];
5811
8414
  const PUBLIC_ELEMENTS = [
5812
- // Direttive
8415
+ // Direttive (condivise da <es-table> e <es-table2>)
5813
8416
  EsTdDirective,
5814
8417
  EsThDirective,
5815
8418
  EsTdEditorDirective,
5816
8419
  // Componenti
5817
8420
  EsTableComponent,
5818
8421
  EsTablePagerComponent,
5819
- ThTdProvider
8422
+ ThTdProvider,
8423
+ // es-table2
8424
+ EsTable2Component
5820
8425
  ];
5821
8426
  const PRIVATE_ELEMENTS = [
5822
8427
  // Componenti
@@ -5828,7 +8433,11 @@ const PRIVATE_ELEMENTS = [
5828
8433
  EstRouterLinkPipe,
5829
8434
  EstRendererPipe,
5830
8435
  EstEditorPipe,
5831
- EstPropInsensitivePipe
8436
+ EstPropInsensitivePipe,
8437
+ // es-table2
8438
+ EsTable2PagerComponent,
8439
+ Est2FormatPipe,
8440
+ Est2LookupPipe
5832
8441
  ];
5833
8442
  class EsTableModule {
5834
8443
  static forRoot(config) {
@@ -5838,19 +8447,25 @@ class EsTableModule {
5838
8447
  { provide: EST_DEBUG, useValue: config?.debugMode || false },
5839
8448
  { provide: EST_DEFAULTS, useValue: config?.defaults || null },
5840
8449
  { provide: EST_EXPORT_GLOBAL_ACL, useValue: config?.globalExportAclExpression || 'true' },
8450
+ // es-table2 condivide la stessa configurazione
8451
+ { provide: EST2_DEBUG, useValue: config?.debugMode || false },
8452
+ { provide: EST2_DEFAULTS, useValue: config?.defaults || null },
8453
+ { provide: EST2_EXPORT_GLOBAL_ACL, useValue: config?.globalExportAclExpression || 'true' },
5841
8454
  ]
5842
8455
  };
5843
8456
  }
5844
8457
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: EsTableModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
5845
8458
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.28", ngImport: i0, type: EsTableModule, declarations: [
5846
- // Direttive
8459
+ // Direttive (condivise da <es-table> e <es-table2>)
5847
8460
  EsTdDirective,
5848
8461
  EsThDirective,
5849
8462
  EsTdEditorDirective,
5850
8463
  // Componenti
5851
8464
  EsTableComponent,
5852
8465
  EsTablePagerComponent,
5853
- ThTdProvider,
8466
+ ThTdProvider,
8467
+ // es-table2
8468
+ EsTable2Component,
5854
8469
  // Componenti
5855
8470
  EsTh,
5856
8471
  EsTd,
@@ -5860,7 +8475,11 @@ class EsTableModule {
5860
8475
  EstRouterLinkPipe,
5861
8476
  EstRendererPipe,
5862
8477
  EstEditorPipe,
5863
- EstPropInsensitivePipe], imports: [CommonModule,
8478
+ EstPropInsensitivePipe,
8479
+ // es-table2
8480
+ EsTable2PagerComponent,
8481
+ Est2FormatPipe,
8482
+ Est2LookupPipe], imports: [CommonModule,
5864
8483
  FormsModule,
5865
8484
  RouterModule,
5866
8485
  LocalizationModule,
@@ -5872,14 +8491,16 @@ class EsTableModule {
5872
8491
  FormsAndValidationsModule,
5873
8492
  TooltipModule,
5874
8493
  AccessControlModule, SelectModule], exports: [
5875
- // Direttive
8494
+ // Direttive (condivise da <es-table> e <es-table2>)
5876
8495
  EsTdDirective,
5877
8496
  EsThDirective,
5878
8497
  EsTdEditorDirective,
5879
8498
  // Componenti
5880
8499
  EsTableComponent,
5881
8500
  EsTablePagerComponent,
5882
- ThTdProvider] }); }
8501
+ ThTdProvider,
8502
+ // es-table2
8503
+ EsTable2Component] }); }
5883
8504
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: EsTableModule, imports: [MODULES, SelectModule] }); }
5884
8505
  }
5885
8506
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: EsTableModule, decorators: [{
@@ -5973,5 +8594,5 @@ class ItemPropEditableReference {
5973
8594
  * Generated bundle index. Do not edit.
5974
8595
  */
5975
8596
 
5976
- 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 };
8597
+ 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 };
5977
8598
  //# sourceMappingURL=esfaenza-es-table.mjs.map