@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.
- package/fesm2022/esfaenza-es-table.mjs +2631 -10
- package/fesm2022/esfaenza-es-table.mjs.map +1 -1
- package/index.d.ts +674 -17
- package/package.json +8 -2
|
@@ -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
|
|
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
|