wuic-framework-lib 1.0.20 → 1.2.0

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.
Files changed (16) hide show
  1. package/fesm2022/{wuic-framework-lib-chart-list.component-B44k_wZe.mjs → wuic-framework-lib-chart-list.component-B1V8Q-Qq.mjs} +2 -2
  2. package/fesm2022/{wuic-framework-lib-code-editor.component-D1dy4Dl9.mjs → wuic-framework-lib-code-editor.component-4N95qt3D.mjs} +26 -5
  3. package/fesm2022/{wuic-framework-lib-designer.component-CnOQbvEH.mjs → wuic-framework-lib-designer.component-pW1YK-oY.mjs} +4 -4
  4. package/fesm2022/{wuic-framework-lib-first-run-wizard.component-C9lswoxG.mjs → wuic-framework-lib-first-run-wizard.component-CaGePYLi.mjs} +14 -4
  5. package/fesm2022/{wuic-framework-lib-framework-docs.component-B6qNTs5H.mjs → wuic-framework-lib-framework-docs.component-m0IlS0Sl.mjs} +406 -10
  6. package/fesm2022/{wuic-framework-lib-pivot-builder.component-DMk26H0M.mjs → wuic-framework-lib-pivot-builder.component-VIs0CIH_.mjs} +19 -16
  7. package/fesm2022/{wuic-framework-lib-prompt-dialog-fallback-host.component-y3shST6g.mjs → wuic-framework-lib-prompt-dialog-fallback-host.component-Dm9GTZ07.mjs} +3 -3
  8. package/fesm2022/{wuic-framework-lib-report-designer.component-BnngR2eL.mjs → wuic-framework-lib-report-designer.component-FiAwNkPM.mjs} +2 -2
  9. package/fesm2022/{wuic-framework-lib-report-viewer.component-LGNRIQh2.mjs → wuic-framework-lib-report-viewer.component-DY0dGMrt.mjs} +2 -2
  10. package/fesm2022/{wuic-framework-lib-scheduler-list.component-DbcFMwx5.mjs → wuic-framework-lib-scheduler-list.component-3E5m-9Qd.mjs} +31 -5
  11. package/fesm2022/{wuic-framework-lib-spreadsheet-list-sf.component-DXyRX0YK.mjs → wuic-framework-lib-spreadsheet-list-sf.component-D9P9WbDl.mjs} +483 -24
  12. package/fesm2022/{wuic-framework-lib-workflow-designer.component-Bm719AWV.mjs → wuic-framework-lib-workflow-designer.component-4RC2EkGA.mjs} +25 -5
  13. package/fesm2022/{wuic-framework-lib-wuic-framework-lib-CGCZvtKK.mjs → wuic-framework-lib-wuic-framework-lib-BSyGhpM1.mjs} +5082 -3802
  14. package/fesm2022/wuic-framework-lib.mjs +1 -1
  15. package/package.json +1 -1
  16. package/types/wuic-framework-lib.d.ts +1271 -646
@@ -7,7 +7,7 @@ import * as i8 from '@ngx-translate/core';
7
7
  import { TranslateModule } from '@ngx-translate/core';
8
8
  import * as i7 from '@syncfusion/ej2-angular-spreadsheet';
9
9
  import { SpreadsheetModule } from '@syncfusion/ej2-angular-spreadsheet';
10
- import { W as WtoolboxService, M as MetadataProviderService, U as UserInfoService, b as DataSourceComponent, I as ImportExportButtonComponent } from './wuic-framework-lib-wuic-framework-lib-CGCZvtKK.mjs';
10
+ import { W as WtoolboxService, M as MetadataProviderService, U as UserInfoService, b as DataSourceComponent, I as ImportExportButtonComponent } from './wuic-framework-lib-wuic-framework-lib-BSyGhpM1.mjs';
11
11
  import { registerLicense } from '@syncfusion/ej2-base';
12
12
  import * as i1 from '@angular/platform-browser';
13
13
  import * as i1$1 from '@angular/router';
@@ -360,6 +360,78 @@ class SpreadsheetListSfComponent {
360
360
  datasource;
361
361
  hardcodedDatasource;
362
362
  hideToolbar = false;
363
+ // ============================================================
364
+ // Lock-awareness API (opt-in, additive — non rompe API legacy)
365
+ // ------------------------------------------------------------
366
+ // GENERIC + METADATA-DRIVEN (no app-specific naming hardcoded).
367
+ //
368
+ // Configurazione PER ROUTE in `_metadati__tabelle.md_props_bag`:
369
+ // {
370
+ // "archetypes": {
371
+ // "spreadsheet": {
372
+ // "lock": {
373
+ // "endpointBase": "/api/spreadsheet",
374
+ // "primaryScopeField": "program_id", // field usato come URL path segment
375
+ // "requiredScopeFields": ["program_id"], // field obbligatori per acquire-lock
376
+ // "autoEnableOnScope": true, // se tutti i required presenti → auto-acquire
377
+ // "heartbeatIntervalSec": 60 // override (default 60)
378
+ // },
379
+ // "formulas": { ... }
380
+ // }
381
+ // }
382
+ // }
383
+ //
384
+ // Lo `lockScope` e' un dictionary generico chiave→valore (es. `{program_id:1, year:2026}`),
385
+ // tipicamente popolato dalla lazy-wrapper leggendo i query param della URL.
386
+ // Il component:
387
+ // - In ngAfterViewInit (o post-dataBound se config in mdpropsbag) chiama
388
+ // POST {endpointBase}/lock-range/{lockScope[primaryScopeField]} con body = scope.
389
+ // - Avvia heartbeat timer (default 60s) verso {endpointBase}/heartbeat.
390
+ // - Espone metodi pubblici saveCellsBatch() + releaseLock() + acquireLock().
391
+ // - In ngOnDestroy chiama POST {endpointBase}/release-lock con lockToken.
392
+ //
393
+ // Vedi `SpreadsheetController.cs` di una app concreta (es. CostCnh) per il
394
+ // contratto endpoint. La forma della request: path /:primaryScope, body = scope.
395
+ // ============================================================
396
+ /**
397
+ * Forza attivazione lock-awareness ANCHE se `mdpropsbag.archetypes.spreadsheet.lock.autoEnableOnScope`
398
+ * e' false o assente. Default: false (la decisione viene presa dalla mdpropsbag).
399
+ * Util per scenari programmatici dove un parent override-a la config.
400
+ */
401
+ enableLockAwareness = false;
402
+ /**
403
+ * Scope di lock generico (record key→value). Tipicamente popolato dalla
404
+ * lazy-wrapper a partire dai query param della URL (es. `?program_id=1&year=2026`),
405
+ * oppure passato esplicitamente da un parent. La mdpropsbag dichiara quale
406
+ * field e' il "primary" (usato come path segment) e quali sono required.
407
+ */
408
+ lockScope = {};
409
+ /**
410
+ * Override `heartbeatIntervalSec` se settato (>0). Altrimenti viene letto da
411
+ * `mdpropsbag.archetypes.spreadsheet.lock.heartbeatIntervalSec` (default 60).
412
+ */
413
+ heartbeatIntervalSec;
414
+ // ============================================================
415
+ // Formula / Ribbon UX (additive, default-off per backward compat)
416
+ // ------------------------------------------------------------
417
+ // Syncfusion EJ2 Spreadsheet supporta 350+ funzioni (SUM, AVG, IF, VLOOKUP,
418
+ // INDEX/MATCH, dates, math, financial, logical, lookup) e references inter-cell
419
+ // (=A1+B1, =SUM(C5:C10)). Per attivarle:
420
+ // - [enableCalculation]="true" → engine formula attivo (default era già true Syncfusion)
421
+ // - [showFormulaBar]="true" → barra formula visibile (modifica formula da UI)
422
+ // - [showRibbon]="true" → ribbon completo (funzioni, format, charts)
423
+ //
424
+ // Quando user inserisce '=A1+B1' in una cella:
425
+ // - Syncfusion CALCOLA il valore numerico per display
426
+ // - cellSave event riceve { value: computed, formula: '=A1+B1' }
427
+ // - Component invia entrambi al backend via saveCellsBatch (campo formulaText opzionale)
428
+ // - Backend persiste value in cp.facts.<field> + formula_text in cp.facts_formula (1:1)
429
+ // - Su reload, snapshot re-injection formula in cell → user vede formula in formula bar
430
+ // ============================================================
431
+ /** Mostra formula bar Syncfusion (default false per backward compat). NB: Syncfusion engine formule sempre attivo. */
432
+ showFormulaBar = false;
433
+ /** Mostra ribbon Syncfusion completo (default false). */
434
+ showRibbon = false;
363
435
  /**
364
436
  * Provider letto da `<wuic-import-export-button>` al click su Export XLS.
365
437
  * Arrow function per preservare il binding di `this` quando passata come Input.
@@ -378,6 +450,14 @@ class SpreadsheetListSfComponent {
378
450
  onSpreadsheetRowInserted = new EventEmitter();
379
451
  onSpreadsheetRowsDeleted = new EventEmitter();
380
452
  onSpreadsheetBatchSaved = new EventEmitter();
453
+ /** Emesso dopo successful acquire/refresh lock (vedi SpreadsheetController.lock-range). */
454
+ onLockAcquired = new EventEmitter();
455
+ /** Emesso se acquire-lock ritorna outcome='conflict' (altro utente sta editando). */
456
+ onLockConflict = new EventEmitter();
457
+ /** Emesso al successful release (ngOnDestroy o release esplicito). */
458
+ onLockReleased = new EventEmitter();
459
+ /** Emesso quando heartbeat fallisce (lock scaduto o released da altro). */
460
+ onLockExpired = new EventEmitter();
381
461
  // ============================================================
382
462
  // Public state (API PUBBLICA COMPATIBILE COL VECCHIO)
383
463
  // ============================================================
@@ -485,6 +565,14 @@ class SpreadsheetListSfComponent {
485
565
  * enableVirtualization: true è il default Syncfusion, lasciato per performance.
486
566
  */
487
567
  sfScrollSettings = { isFinite: true, enableVirtualization: true };
568
+ // ─── Lock-awareness state (Sprint 9.4 framework integration) ─────────────
569
+ /** Token UUID lock corrente. Settato da acquireLock(), letto da save/heartbeat/release. */
570
+ lockToken;
571
+ /** Lock id (numerico) per audit join con cp.spreadsheet_change_log. */
572
+ lockId;
573
+ /** Lock expiry UTC. UI puo' mostrare countdown. */
574
+ lockExpiresUtc;
575
+ heartbeatTimer;
488
576
  // ============================================================
489
577
  // Constructor
490
578
  // ============================================================
@@ -533,6 +621,13 @@ class SpreadsheetListSfComponent {
533
621
  }
534
622
  });
535
623
  }
624
+ // Sprint 9.4 — auto-acquire lock se opt-in @Input.
625
+ // Quando l'opt-in viene da `mdpropsbag.archetypes.spreadsheet.lock.autoEnableOnScope`,
626
+ // l'acquire viene rinviato a `tryAutoAcquireLockFromMetadata()` chiamato post-dataBound
627
+ // (quando metaInfo e' loaded dal datasource).
628
+ if (this.enableLockAwareness) {
629
+ void this.acquireLock();
630
+ }
536
631
  }
537
632
  ngOnDestroy() {
538
633
  this.datasourceReadySubscription?.unsubscribe();
@@ -540,6 +635,15 @@ class SpreadsheetListSfComponent {
540
635
  this.themeObserver?.disconnect();
541
636
  this.stopColHeaderObserver();
542
637
  this.clearPendingLayoutPassTimeouts();
638
+ // Sprint 9.4 — cleanup lock
639
+ if (this.heartbeatTimer) {
640
+ clearInterval(this.heartbeatTimer);
641
+ this.heartbeatTimer = undefined;
642
+ }
643
+ if (this.lockToken) {
644
+ // best-effort release (fire-and-forget — non bloccare destroy)
645
+ void this.releaseLock().catch(() => { });
646
+ }
543
647
  try {
544
648
  this.sfSpreadsheet?.destroy?.();
545
649
  }
@@ -1487,6 +1591,11 @@ class SpreadsheetListSfComponent {
1487
1591
  for (const d of delays) {
1488
1592
  setTimeout(() => this.syncColumnHeadersWithMetadata(), d);
1489
1593
  }
1594
+ // Apply metadata-driven formulas (from mdpropsbag.archetypes.spreadsheet.formulas)
1595
+ this.applyMetadataFormulas();
1596
+ // Auto-acquire lock se mdpropsbag.archetypes.spreadsheet.lock.autoEnableOnScope=true
1597
+ // e tutti i requiredScopeFields sono presenti in lockScope. Idempotente.
1598
+ this.tryAutoAcquireLockFromMetadata();
1490
1599
  }
1491
1600
  /**
1492
1601
  * Sostituisce il testo delle celle nell'header column-letter nativo di
@@ -1605,10 +1714,13 @@ class SpreadsheetListSfComponent {
1605
1714
  const distinctList = await this.computeDistinctValuesForPopup(col, fieldName);
1606
1715
  // Selezione corrente: se esiste un filtro sulla colonna, quei valori
1607
1716
  // sono pre-checked; altrimenti tutti checked.
1717
+ // selectedSet contiene `d.value` (raw FK ID per lookupByID, raw value altrimenti)
1718
+ // — NON `d.display` — cosi' il filter server-side puo' usarli direttamente
1719
+ // come predicati `WHERE col = <id>` (cross-DBMS safe).
1608
1720
  const currentFilter = this.columnFilters.get(fieldName) || null;
1609
1721
  const selectedSet = currentFilter
1610
1722
  ? new Set(currentFilter)
1611
- : new Set(distinctList.map(d => d.display));
1723
+ : new Set(distinctList.map(d => d.value));
1612
1724
  // ==== Costruisci DOM popup ====
1613
1725
  const popup = document.createElement('div');
1614
1726
  popup.className = 'wuic-sf-filter-popup';
@@ -1670,16 +1782,20 @@ class SpreadsheetListSfComponent {
1670
1782
  return;
1671
1783
  const label = document.createElement('label');
1672
1784
  label.className = 'wuic-sf-filter-popup-item';
1673
- label.setAttribute('data-value', d.display);
1785
+ // data-value contiene il VALUE (FK ID per lookup) per leggibilita' DOM/testing.
1786
+ label.setAttribute('data-value', d.value);
1674
1787
  const cb = document.createElement('input');
1675
1788
  cb.type = 'checkbox';
1676
1789
  cb.className = 'wuic-sf-filter-popup-cb';
1677
- cb.checked = selectedSet.has(d.display);
1790
+ // Memorizziamo `d.value` (raw FK ID) nel selectedSet, ma renderizziamo
1791
+ // `d.display` (descrittivo) nello span — cosi' UI = descrittivo e
1792
+ // filter value emesso al server = raw ID (cross-DBMS safe).
1793
+ cb.checked = selectedSet.has(d.value);
1678
1794
  cb.addEventListener('change', () => {
1679
1795
  if (cb.checked)
1680
- selectedSet.add(d.display);
1796
+ selectedSet.add(d.value);
1681
1797
  else
1682
- selectedSet.delete(d.display);
1798
+ selectedSet.delete(d.value);
1683
1799
  updateSelectAllState();
1684
1800
  });
1685
1801
  const span = document.createElement('span');
@@ -1698,7 +1814,7 @@ class SpreadsheetListSfComponent {
1698
1814
  };
1699
1815
  selectAllCb.addEventListener('change', () => {
1700
1816
  if (selectAllCb.checked) {
1701
- distinctList.forEach(d => selectedSet.add(d.display));
1817
+ distinctList.forEach(d => selectedSet.add(d.value));
1702
1818
  }
1703
1819
  else {
1704
1820
  selectedSet.clear();
@@ -1798,23 +1914,33 @@ class SpreadsheetListSfComponent {
1798
1914
  return this.fetchDistinctValuesFromServer(col, fieldName);
1799
1915
  }
1800
1916
  const baseRecords = this.records.filter(rec => this.recordMatchesColumnFilters(rec, fieldName));
1917
+ // Per colonne lookupByID il `value` deve essere il FK ID raw (rec[fieldName]),
1918
+ // mentre il `display` è il descrittivo risolto via lookup alias. Senza questa
1919
+ // separazione il filter server-side invierebbe descrittivi (stringa) contro
1920
+ // colonna FK numerica → 42601 PG / ORA-00904 Oracle.
1801
1921
  const distinctMap = new Map();
1802
1922
  for (const rec of baseRecords) {
1803
1923
  const raw = rec?.[fieldName];
1804
1924
  const display = this.formatValueForDisplay(raw, col, rec);
1805
- const key = display == null ? '' : String(display);
1806
- const entry = distinctMap.get(key);
1925
+ const displayKey = display == null ? '' : String(display);
1926
+ const valueKey = raw == null ? '' : String(raw);
1927
+ const entry = distinctMap.get(valueKey);
1807
1928
  if (entry)
1808
1929
  entry.count++;
1809
1930
  else
1810
- distinctMap.set(key, { display: key, count: 1 });
1931
+ distinctMap.set(valueKey, { display: displayKey, value: valueKey, count: 1 });
1811
1932
  }
1812
1933
  return this.sortDistinctList(distinctMap);
1813
1934
  }
1814
1935
  /**
1815
1936
  * Chiama `MetaService.getDistinctValues(column_id, text, filter_type,
1816
1937
  * max_results, user_id)` (endpoint AsmxProxy) e trasforma `rawPagedResult`
1817
- * in `Array<{display, count}>` compatibile col popup.
1938
+ * in `Array<{display, value, count}>` compatibile col popup.
1939
+ *
1940
+ * Per lookupByID il server (PG provider — vedi metaQueryPostgreSql.cs distinct
1941
+ * branch) emette ANCHE l'alias `<entity>___<dataTextField>__<colName>` con il
1942
+ * descrittivo. `formatValueForDisplay` lo legge da `row` e ritorna la stringa
1943
+ * human-readable. Il filter usa invece il FK ID raw (`row[fieldName]`).
1818
1944
  *
1819
1945
  * In caso di errore ritorna array vuoto (il popup mostrerà nessuna opzione
1820
1946
  * — un notification error è comunque inviata).
@@ -1847,13 +1973,16 @@ class SpreadsheetListSfComponent {
1847
1973
  // FastExpando conserva il case originale.
1848
1974
  const raw = row?.[fieldName] ?? row?.[fieldName?.toLowerCase?.()]
1849
1975
  ?? row?.[fieldName?.toUpperCase?.()] ?? Object.values(row || {})[0];
1976
+ // display: descrittivo se lookup (via alias in row), altrimenti formatted raw.
1850
1977
  const display = this.formatValueForDisplay(raw, col, row);
1851
- const key = display == null ? '' : String(display);
1852
- const entry = distinctMap.get(key);
1978
+ const displayKey = display == null ? '' : String(display);
1979
+ // value: FK ID raw (sempre, anche per lookup). Usato come filter value.
1980
+ const valueKey = raw == null ? '' : String(raw);
1981
+ const entry = distinctMap.get(valueKey);
1853
1982
  if (entry)
1854
1983
  entry.count++;
1855
1984
  else
1856
- distinctMap.set(key, { display: key, count: 1 });
1985
+ distinctMap.set(valueKey, { display: displayKey, value: valueKey, count: 1 });
1857
1986
  }
1858
1987
  return this.sortDistinctList(distinctMap);
1859
1988
  }
@@ -1867,7 +1996,8 @@ class SpreadsheetListSfComponent {
1867
1996
  return [];
1868
1997
  }
1869
1998
  }
1870
- /** Ordinamento comune della lista distinct (numerico se possibile, poi locale). */
1999
+ /** Ordinamento comune della lista distinct (numerico se possibile, poi locale).
2000
+ * Ordina per `display` (descrittivo) cosi' il popup e' navigabile dall'utente. */
1871
2001
  sortDistinctList(distinctMap) {
1872
2002
  return Array.from(distinctMap.values()).sort((a, b) => {
1873
2003
  const na = Number(a.display);
@@ -1897,15 +2027,14 @@ class SpreadsheetListSfComponent {
1897
2027
  recordMatchesColumnFilters(rec, excludeField) {
1898
2028
  if (this.columnFilters.size === 0)
1899
2029
  return true;
1900
- const cols = this.metaInfo?.columnMetadata || [];
1901
2030
  for (const [field, selSet] of this.columnFilters.entries()) {
1902
2031
  if (excludeField && field === excludeField)
1903
2032
  continue;
1904
- const metaCol = cols.find(c => c.mc_nome_colonna === field);
1905
- if (!metaCol)
1906
- continue;
1907
- const display = this.formatValueForDisplay(rec?.[field], metaCol, rec);
1908
- const key = display == null ? '' : String(display);
2033
+ // selSet contiene raw values (FK ID per lookupByID, raw altrimenti) — vedi
2034
+ // computeDistinctValuesForPopup. Match contro raw rec[field], NON contro
2035
+ // display formattato (per parity con server-side WHERE col = <raw>).
2036
+ const raw = rec?.[field];
2037
+ const key = raw == null ? '' : String(raw);
1909
2038
  if (!selSet.has(key))
1910
2039
  return false;
1911
2040
  }
@@ -3845,8 +3974,320 @@ class SpreadsheetListSfComponent {
3845
3974
  onWindowResize() {
3846
3975
  this.scheduleLayoutPass();
3847
3976
  }
3977
+ // ============================================================
3978
+ // Metadata-driven formulas (Sprint 9.4)
3979
+ // ------------------------------------------------------------
3980
+ // Le formule sono definite dichiarativamente in metadata route:
3981
+ // _metadati__tabelle.mdpropsbag → archetypes.spreadsheet.formulas
3982
+ // Shape:
3983
+ // {
3984
+ // "<target_column_name>": "=<expression with {col_name} tokens>",
3985
+ // "balance": "={planned}-{actual}",
3986
+ // "variance_pct": "=IF({planned}=0,0,({actual}/{planned})*100)",
3987
+ // "remaining": "={planned}-{actual}-{committed}"
3988
+ // }
3989
+ // Pattern:
3990
+ // - {col_name} viene sostituito a runtime con l'indirizzo cella Syncfusion
3991
+ // della colonna corrispondente sulla riga corrente (es. row 2 → C2, D2)
3992
+ // - L'engine Syncfusion (enableCalculation=true) ricalcola i valori automaticamente
3993
+ // - Quando user modifica una cella source, le celle dipendenti si aggiornano
3994
+ // - On save (cellSave event), il VALUE COMPUTED viene persistito su DB,
3995
+ // la formula non viene memorizzata (resta nei metadati, ri-applicata al refresh)
3996
+ // ============================================================
3997
+ /**
3998
+ * Risolve la lista colonne visibili (idx → col name) dal metaInfo corrente.
3999
+ * Indice 0-based; il caller mappa a column letter A/B/C tramite indexToColumnLetter().
4000
+ */
4001
+ getVisibleColumnNames() {
4002
+ if (!this.metaInfo?.columnMetadata)
4003
+ return [];
4004
+ return this.metaInfo.columnMetadata
4005
+ .filter(c => !c.mc_hide_in_edit)
4006
+ .map(c => c.mc_nome_colonna || '');
4007
+ }
4008
+ /** Converte indice 0-based in letter A1-notation (0 → 'A', 25 → 'Z', 26 → 'AA'). */
4009
+ indexToColumnLetter(idx) {
4010
+ let s = '';
4011
+ let n = idx;
4012
+ while (n >= 0) {
4013
+ s = String.fromCharCode(65 + (n % 26)) + s;
4014
+ n = Math.floor(n / 26) - 1;
4015
+ }
4016
+ return s;
4017
+ }
4018
+ /**
4019
+ * Risolve il blocco formulas da `metaInfo.tableMetadata.md_props_bag.archetypes.spreadsheet.formulas`.
4020
+ * Ritorna `{}` se non configurato.
4021
+ */
4022
+ getMetadataFormulas() {
4023
+ const tm = this.metaInfo?.tableMetadata;
4024
+ if (!tm)
4025
+ return {};
4026
+ const pb = tm.md_props_bag || tm.mdpropsbag || tm.extraProps?.md_props_bag;
4027
+ const arch = pb?.archetypes?.spreadsheet;
4028
+ const formulas = arch?.formulas;
4029
+ if (!formulas || typeof formulas !== 'object')
4030
+ return {};
4031
+ return formulas;
4032
+ }
4033
+ /**
4034
+ * Applica le formule metadata-driven a tutte le righe correntemente caricate.
4035
+ * Chiamato post-dataBound. Usa Syncfusion `updateCell({formula: '=...'}, address)`.
4036
+ * Idempotente: ri-chiamabile (sovrascrive la formula con la stessa).
4037
+ */
4038
+ applyMetadataFormulas() {
4039
+ const formulas = this.getMetadataFormulas();
4040
+ if (!formulas || Object.keys(formulas).length === 0)
4041
+ return;
4042
+ if (!this.sfSpreadsheet)
4043
+ return;
4044
+ const visibleCols = this.getVisibleColumnNames();
4045
+ if (visibleCols.length === 0)
4046
+ return;
4047
+ // Map column name → A1 letter
4048
+ const colIndex = {};
4049
+ visibleCols.forEach((name, idx) => { colIndex[name] = this.indexToColumnLetter(idx); });
4050
+ const rowOffset = 2; // Syncfusion riga 1 = header (showFieldAsHeader), dati partono da riga 2
4051
+ const rowCount = (this.pagedRecords?.length || this.records?.length || 0);
4052
+ for (const [targetCol, formulaTemplate] of Object.entries(formulas)) {
4053
+ const targetLetter = colIndex[targetCol];
4054
+ if (!targetLetter) {
4055
+ console.warn(`[wuic-spreadsheet] formula target column '${targetCol}' not visible — skipped`);
4056
+ continue;
4057
+ }
4058
+ for (let r = 0; r < rowCount; r++) {
4059
+ const sheetRow = rowOffset + r;
4060
+ // Sostituisci tutti i token {colName} con <colLetter><rowNumber>
4061
+ const resolved = formulaTemplate.replace(/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g, (_m, colName) => {
4062
+ const letter = colIndex[colName];
4063
+ if (!letter) {
4064
+ console.warn(`[wuic-spreadsheet] formula token {${colName}} → column not visible, leaving as-is`);
4065
+ return `{${colName}}`;
4066
+ }
4067
+ return `${letter}${sheetRow}`;
4068
+ });
4069
+ const address = `${targetLetter}${sheetRow}`;
4070
+ try {
4071
+ this.sfSpreadsheet.updateCell?.({ formula: resolved }, address);
4072
+ }
4073
+ catch (e) {
4074
+ console.warn(`[wuic-spreadsheet] updateCell failed at ${address}:`, e);
4075
+ }
4076
+ }
4077
+ }
4078
+ }
4079
+ // ============================================================
4080
+ // Lock-awareness public API (Sprint 9.4 — generic, metadata-driven)
4081
+ // ------------------------------------------------------------
4082
+ // Tutta la config (endpoint base, primaryScopeField, required fields,
4083
+ // heartbeat interval, auto-enable) viene letta da
4084
+ // `metaInfo.tableMetadata.md_props_bag.archetypes.spreadsheet.lock`.
4085
+ // L'@Input `lockScope` (Record<string,any>) e' il dictionary generico
4086
+ // dei valori di lock (es. `{program_id:1, year:2026, scenario_id:7}`),
4087
+ // passato dalla lazy-wrapper a partire dai query param della URL.
4088
+ // ============================================================
4089
+ /**
4090
+ * Estrae la config lock da `mdpropsbag.archetypes.spreadsheet.lock`.
4091
+ * Defaults conservativi se la config manca.
4092
+ */
4093
+ getLockConfig() {
4094
+ const pb = this.metaInfo?.tableMetadata?.md_props_bag
4095
+ || this.metaInfo?.tableMetadata?.mdpropsbag
4096
+ || {};
4097
+ const arch = pb?.archetypes?.spreadsheet?.lock || {};
4098
+ return {
4099
+ endpointBase: typeof arch.endpointBase === 'string' && arch.endpointBase.length > 0
4100
+ ? arch.endpointBase
4101
+ : '/api/spreadsheet',
4102
+ primaryScopeField: typeof arch.primaryScopeField === 'string' && arch.primaryScopeField.length > 0
4103
+ ? arch.primaryScopeField
4104
+ : null,
4105
+ requiredScopeFields: Array.isArray(arch.requiredScopeFields)
4106
+ ? arch.requiredScopeFields.filter((s) => typeof s === 'string')
4107
+ : (arch.primaryScopeField ? [arch.primaryScopeField] : []),
4108
+ autoEnableOnScope: arch.autoEnableOnScope === true,
4109
+ heartbeatIntervalSec: typeof this.heartbeatIntervalSec === 'number' && this.heartbeatIntervalSec > 0
4110
+ ? this.heartbeatIntervalSec
4111
+ : (typeof arch.heartbeatIntervalSec === 'number' && arch.heartbeatIntervalSec > 0
4112
+ ? arch.heartbeatIntervalSec
4113
+ : 60),
4114
+ };
4115
+ }
4116
+ /**
4117
+ * Valida che lo scope contenga tutti i required fields.
4118
+ * Ritorna lista dei missing (vuota = scope valido).
4119
+ */
4120
+ validateLockScope(cfg) {
4121
+ return cfg.requiredScopeFields.filter((f) => this.lockScope?.[f] == null);
4122
+ }
4123
+ /**
4124
+ * Trigger auto-acquire post-dataBound (quando metaInfo e' loaded). Idempotente:
4125
+ * non riacquista se gia' c'e' un lockToken attivo. Chiamabile manualmente.
4126
+ * Condizioni per auto-acquire:
4127
+ * - lockToken non gia' attivo
4128
+ * - mdpropsbag.archetypes.spreadsheet.lock.autoEnableOnScope === true
4129
+ * - tutti i requiredScopeFields presenti in lockScope
4130
+ */
4131
+ tryAutoAcquireLockFromMetadata() {
4132
+ if (this.lockToken)
4133
+ return;
4134
+ const cfg = this.getLockConfig();
4135
+ if (!cfg.autoEnableOnScope)
4136
+ return;
4137
+ const missing = this.validateLockScope(cfg);
4138
+ if (missing.length > 0) {
4139
+ console.info(`[wuic-spreadsheet] auto-acquire skipped — missing scope: ${missing.join(', ')}`);
4140
+ return;
4141
+ }
4142
+ this.enableLockAwareness = true;
4143
+ void this.acquireLock();
4144
+ }
4145
+ /**
4146
+ * Acquisisce lock pessimistic su `lockScope`. Endpoint:
4147
+ * POST {endpointBase}/lock-range/{lockScope[primaryScopeField]} body = lockScope
4148
+ * Emette onLockAcquired (acquired|refreshed) o onLockConflict.
4149
+ * Auto-avvia heartbeat timer se outcome=acquired|refreshed.
4150
+ */
4151
+ async acquireLock() {
4152
+ const cfg = this.getLockConfig();
4153
+ if (!cfg.primaryScopeField) {
4154
+ console.warn('[wuic-spreadsheet] acquireLock skipped: primaryScopeField non configurato in mdpropsbag.archetypes.spreadsheet.lock');
4155
+ return { outcome: 'skipped' };
4156
+ }
4157
+ const primaryVal = this.lockScope?.[cfg.primaryScopeField];
4158
+ if (primaryVal == null) {
4159
+ console.warn(`[wuic-spreadsheet] acquireLock skipped: lockScope[${cfg.primaryScopeField}] non valorizzato`);
4160
+ return { outcome: 'skipped' };
4161
+ }
4162
+ const missing = this.validateLockScope(cfg);
4163
+ if (missing.length > 0) {
4164
+ console.warn(`[wuic-spreadsheet] acquireLock skipped: missing required scope fields: ${missing.join(', ')}`);
4165
+ return { outcome: 'skipped' };
4166
+ }
4167
+ const url = `${cfg.endpointBase}/lock-range/${encodeURIComponent(String(primaryVal))}`;
4168
+ // Body = lockScope MENO il primaryScopeField (gia' in URL path)
4169
+ const body = {};
4170
+ for (const [k, v] of Object.entries(this.lockScope || {})) {
4171
+ if (k === cfg.primaryScopeField)
4172
+ continue;
4173
+ if (v == null)
4174
+ continue;
4175
+ body[k] = v;
4176
+ }
4177
+ try {
4178
+ const resp = await this.http.post(url, body, { withCredentials: true }).toPromise();
4179
+ if (!resp?.ok) {
4180
+ if (resp?.outcome === 'conflict') {
4181
+ this.onLockConflict.emit({
4182
+ conflictUserId: resp.conflictUserId ?? null,
4183
+ lockExpiresUtc: resp.lockExpiresUtc ? new Date(resp.lockExpiresUtc) : null,
4184
+ });
4185
+ return { outcome: 'conflict', conflictUserId: resp.conflictUserId };
4186
+ }
4187
+ return { outcome: resp?.outcome ?? 'failed' };
4188
+ }
4189
+ this.lockToken = resp.lockToken;
4190
+ this.lockId = resp.lockId;
4191
+ this.lockExpiresUtc = new Date(resp.lockExpiresUtc);
4192
+ this.onLockAcquired.emit({
4193
+ lockToken: resp.lockToken,
4194
+ lockId: resp.lockId,
4195
+ lockExpiresUtc: this.lockExpiresUtc,
4196
+ outcome: resp.outcome,
4197
+ });
4198
+ this.startHeartbeat(cfg.heartbeatIntervalSec);
4199
+ return { outcome: resp.outcome, lockToken: resp.lockToken };
4200
+ }
4201
+ catch (e) {
4202
+ console.error('[wuic-spreadsheet] acquireLock failed', e);
4203
+ return { outcome: 'error' };
4204
+ }
4205
+ }
4206
+ /**
4207
+ * Single heartbeat (extends TTL). Auto-called dal timer ogni heartbeatIntervalSec.
4208
+ * Emette onLockExpired se backend ritorna 410 Gone.
4209
+ */
4210
+ async heartbeat() {
4211
+ if (!this.lockToken)
4212
+ return false;
4213
+ const cfg = this.getLockConfig();
4214
+ try {
4215
+ const resp = await this.http.post(`${cfg.endpointBase}/heartbeat`, { lockToken: this.lockToken }, { withCredentials: true }).toPromise();
4216
+ if (resp?.ok) {
4217
+ this.lockExpiresUtc = new Date(resp.lockExpiresUtc);
4218
+ return true;
4219
+ }
4220
+ this.onLockExpired.emit();
4221
+ this.stopHeartbeat();
4222
+ return false;
4223
+ }
4224
+ catch (e) {
4225
+ if (e?.status === 410) {
4226
+ this.onLockExpired.emit();
4227
+ this.stopHeartbeat();
4228
+ }
4229
+ console.warn('[wuic-spreadsheet] heartbeat failed', e?.status ?? e);
4230
+ return false;
4231
+ }
4232
+ }
4233
+ /**
4234
+ * Batch save delle modifiche (alternativa al CRUD-per-row standard).
4235
+ * Useful per applicare copy/paste range o bulk edit con singolo round-trip.
4236
+ * @param changes array di { factsId, field, newValue }
4237
+ */
4238
+ async saveCellsBatch(changes) {
4239
+ if (!this.lockToken) {
4240
+ console.warn('[wuic-spreadsheet] saveCellsBatch: nessun lockToken — call acquireLock() first');
4241
+ return null;
4242
+ }
4243
+ if (!changes || changes.length === 0)
4244
+ return { applied: 0, failed: 0 };
4245
+ const cfg = this.getLockConfig();
4246
+ try {
4247
+ const resp = await this.http.post(`${cfg.endpointBase}/save-cells`, { lockToken: this.lockToken, changes }, { withCredentials: true }).toPromise();
4248
+ return { applied: resp?.applied ?? 0, failed: resp?.failed ?? 0 };
4249
+ }
4250
+ catch (e) {
4251
+ console.error('[wuic-spreadsheet] saveCellsBatch failed', e);
4252
+ return null;
4253
+ }
4254
+ }
4255
+ /**
4256
+ * Rilascia lock esplicitamente. Idempotente. Stop heartbeat timer.
4257
+ * Auto-chiamato in ngOnDestroy.
4258
+ */
4259
+ async releaseLock() {
4260
+ if (!this.lockToken)
4261
+ return 0;
4262
+ this.stopHeartbeat();
4263
+ const tok = this.lockToken;
4264
+ this.lockToken = undefined;
4265
+ this.lockExpiresUtc = undefined;
4266
+ const cfg = this.getLockConfig();
4267
+ try {
4268
+ const resp = await this.http.post(`${cfg.endpointBase}/release-lock`, { lockToken: tok }, { withCredentials: true }).toPromise();
4269
+ const released = resp?.released ?? 0;
4270
+ this.onLockReleased.emit({ released });
4271
+ return released;
4272
+ }
4273
+ catch (e) {
4274
+ console.warn('[wuic-spreadsheet] releaseLock failed', e);
4275
+ return 0;
4276
+ }
4277
+ }
4278
+ startHeartbeat(intervalSec = 60) {
4279
+ this.stopHeartbeat();
4280
+ const intervalMs = Math.max(15, intervalSec) * 1000;
4281
+ this.heartbeatTimer = setInterval(() => { void this.heartbeat(); }, intervalMs);
4282
+ }
4283
+ stopHeartbeat() {
4284
+ if (this.heartbeatTimer) {
4285
+ clearInterval(this.heartbeatTimer);
4286
+ this.heartbeatTimer = undefined;
4287
+ }
4288
+ }
3848
4289
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SpreadsheetListSfComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i1.Title }, { token: i1$1.ActivatedRoute }, { token: i1$2.HttpClient }, { token: WtoolboxService }, { token: MetadataProviderService }, { token: UserInfoService }], target: i0.ɵɵFactoryTarget.Component });
3849
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: SpreadsheetListSfComponent, isStandalone: true, selector: "wuic-spreadsheet-list-sf", inputs: { hardcodedRoute: "hardcodedRoute", parentRecord: "parentRecord", parentMetaInfo: "parentMetaInfo", datasource: "datasource", hardcodedDatasource: "hardcodedDatasource", hideToolbar: "hideToolbar" }, outputs: { onSpreadsheetDataBound: "onSpreadsheetDataBound", onSpreadsheetBeforePageChange: "onSpreadsheetBeforePageChange", onSpreadsheetPageChange: "onSpreadsheetPageChange", onSpreadsheetRowInserted: "onSpreadsheetRowInserted", onSpreadsheetRowsDeleted: "onSpreadsheetRowsDeleted", onSpreadsheetBatchSaved: "onSpreadsheetBatchSaved" }, host: { listeners: { "window:resize": "onWindowResize()" } }, viewQueries: [{ propertyName: "sfSpreadsheet", first: true, predicate: ["sfSpreadsheet"], descendants: true }, { propertyName: "lookupSource", first: true, predicate: ["lookupSource"], descendants: true }], ngImport: i0, template: "<!--\n Layout root wrapper:\n 1. Toolbar WUIC custom (insert / save batch / export / refresh)\n 2. Syncfusion Spreadsheet host\n 3. Pagination custom (Syncfusion non ha pagination built-in, quindi la\n rendiamo sotto il foglio con nav + page-size dropdown)\n 4. Lookup datasource (invisibile, pattern identico al vecchio componente)\n-->\n\n<!-- Bootstrap CSS Syncfusion (~500 KiB): si monta solo quando questo\n componente lazy \u00E8 istanziato \u2192 CSS NON nel bundle iniziale globale,\n risparmia ~500 KiB su tutte le pagine che non aprono spreadsheet. -->\n<wuic-syncfusion-styles-bootstrap></wuic-syncfusion-styles-bootstrap>\n\n<div class=\"wuic-sf-spreadsheet-wrapper\">\n\n @if (!hideToolbar && metaInfo) {\n <!-- Stile allineato a <wuic-list-grid>: stessa caption-bar, stessi p-button\n PrimeNG con icona + label, stesso componente import/export. -->\n <div class=\"grid-caption-bar wuic-sf-caption-bar\" role=\"toolbar\" aria-label=\"Spreadsheet toolbar\">\n <div class=\"grid-caption-left\">\n @if (!metaInfo.tableMetadata.md_hide_refresh) {\n <p-button icon=\"pi pi-refresh\" label=\"{{'refresh_grid' | translate}}\"\n (onClick)=\"onToolbarRefresh()\" />\n }\n @if (canInsertRows()) {\n <p-button icon=\"pi pi-plus\" label=\"{{'add_grid' | translate}}\"\n (onClick)=\"onToolbarInsert()\" />\n }\n @if (!metaInfo.tableMetadata.md_hide_export_xls) {\n <!-- Include dialog con progress bar + bottoni \"Stoppa e salva\n parziale\" / \"Stoppa\" / \"Continua in background\". -->\n <wuic-import-export-button\n [routeName]=\"metaInfo.tableMetadata.md_route_name\"\n [tableMetadata]=\"metaInfo.tableMetadata\"\n [filterInfoProvider]=\"getCurrentFilterInfoForExport\"></wuic-import-export-button>\n }\n @if (isBatchSaveEnabled()) {\n <p-button icon=\"pi pi-save\" label=\"{{'list_grid.save_changes' | translate}}\"\n (onClick)=\"onToolbarSaveBatch()\" />\n }\n </div>\n </div>\n }\n\n <div class=\"wuic-sf-spreadsheet-host\">\n @if (metaInfo && sfSheets.length > 0) {\n <ejs-spreadsheet #sfSpreadsheet\n [sheets]=\"sfSheets\"\n [height]=\"sfHeight\"\n [width]=\"sfWidth\"\n [scrollSettings]=\"sfScrollSettings\"\n [showRibbon]=\"false\"\n [showFormulaBar]=\"false\"\n [showSheetTabs]=\"false\"\n [allowEditing]=\"true\"\n [allowSorting]=\"true\"\n [allowFiltering]=\"false\"\n [allowResizing]=\"true\"\n [allowInsert]=\"canInsertRows()\"\n [allowDelete]=\"canDeleteRows()\"\n [enableContextMenu]=\"true\"\n [enableKeyboardNavigation]=\"true\"\n [enableClipboard]=\"true\"\n (created)=\"onSyncfusionCreated()\"\n (dataBound)=\"onSyncfusionDataBound()\"\n (cellSave)=\"onSyncfusionCellSave($event)\"\n (beforeCellSave)=\"onSyncfusionBeforeCellSave($event)\"\n (beforeCellRender)=\"onSyncfusionBeforeCellRender($event)\"\n (cellEdit)=\"onSyncfusionCellEdit($event)\"\n (contextMenuBeforeOpen)=\"onSyncfusionContextMenuBeforeOpen($event)\"\n (contextMenuItemSelect)=\"onSyncfusionContextMenuItemSelect($event)\"\n (actionBegin)=\"onSyncfusionActionBegin($event)\"\n (actionComplete)=\"onSyncfusionActionComplete($event)\"\n (dialogBeforeOpen)=\"onSyncfusionDialogBeforeOpen($event)\">\n </ejs-spreadsheet>\n }\n </div>\n\n @if (totalPages > 1 || totalRecords > pageSize) {\n <div class=\"wuic-sf-pagination\" role=\"navigation\" aria-label=\"Paginazione\">\n <button type=\"button\" class=\"wuic-sf-page-btn\"\n [disabled]=\"pageIndex <= 1\"\n (click)=\"isServerSide ? handleServerPageChange(1, pageSize) : setClientPage(1)\"\n title=\"Prima pagina\">\n <i class=\"pi pi-angle-double-left\"></i>\n </button>\n <button type=\"button\" class=\"wuic-sf-page-btn\"\n [disabled]=\"pageIndex <= 1\"\n (click)=\"isServerSide ? handleServerPageChange(pageIndex - 1, pageSize) : setClientPage(pageIndex - 1)\"\n title=\"Pagina precedente\">\n <i class=\"pi pi-angle-left\"></i>\n </button>\n <span class=\"wuic-sf-page-info\">\n Pagina <strong>{{ pageIndex }}</strong> di <strong>{{ totalPages }}</strong>\n <span class=\"wuic-sf-page-total\">({{ totalRecords }} record)</span>\n </span>\n <button type=\"button\" class=\"wuic-sf-page-btn\"\n [disabled]=\"pageIndex >= totalPages\"\n (click)=\"isServerSide ? handleServerPageChange(pageIndex + 1, pageSize) : setClientPage(pageIndex + 1)\"\n title=\"Pagina successiva\">\n <i class=\"pi pi-angle-right\"></i>\n </button>\n <button type=\"button\" class=\"wuic-sf-page-btn\"\n [disabled]=\"pageIndex >= totalPages\"\n (click)=\"isServerSide ? handleServerPageChange(totalPages, pageSize) : setClientPage(totalPages)\"\n title=\"Ultima pagina\">\n <i class=\"pi pi-angle-double-right\"></i>\n </button>\n <select class=\"wuic-sf-page-size\" [value]=\"pageSize\"\n (change)=\"setPageSize($any($event.target).value)\"\n title=\"Record per pagina\">\n <option [value]=\"10\">10</option>\n <option [value]=\"25\">25</option>\n <option [value]=\"50\">50</option>\n <option [value]=\"100\">100</option>\n </select>\n </div>\n }\n\n</div>\n\n@if (!hardcodedRoute) {\n <wuic-data-source #lookupSource [autoload]=\"true\" [routeFromRouting]=\"false\"\n [route]=\"currentRoute\" [parentRecord]=\"currentRecord\"\n [parentMetaInfo]=\"metaInfo\"></wuic-data-source>\n}\n", styles: [":host{display:block;width:100%;height:100%;min-height:0}.wuic-sf-spreadsheet-wrapper{display:flex;flex-direction:column;width:100%;height:100%;min-height:0;overflow:hidden}.wuic-sf-caption-bar{flex:0 0 auto;padding:5px}.grid-caption-left{display:flex;align-items:stretch;gap:.5rem;flex-wrap:nowrap;min-height:2.25rem;height:2.25rem}.wuic-sf-spreadsheet-host{flex:1 1 auto;min-height:0;width:100%;position:relative;overflow:visible}::ng-deep .e-dialog.e-popup-open,::ng-deep .e-excelfilter,::ng-deep .e-filter-popup{z-index:10010!important;min-width:240px}::ng-deep .e-excelfilter .e-footer-content,::ng-deep .e-filter-popup .e-footer-content{display:flex!important;justify-content:flex-end;gap:6px;padding:8px 12px!important}.wuic-sf-spreadsheet-host ejs-spreadsheet{display:block;width:100%;height:100%}:host ::ng-deep .e-spreadsheet .e-column-header .e-header-cell.wuic-sf-colname-header .e-header-cell-div{font-weight:700!important;text-align:left!important;padding-left:6px!important;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wuic-sf-pagination{display:flex;gap:6px;align-items:center;justify-content:flex-end;padding:6px 10px;background-color:var(--wuic-surface-a, #ffffff);border-top:1px solid var(--wuic-surface-border, #e5e7eb);flex:0 0 auto;font-size:13px}.wuic-sf-page-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;border:1px solid var(--wuic-surface-border, #e5e7eb);border-radius:4px;background:transparent;cursor:pointer;color:var(--wuic-text-color, #1f2937);transition:background-color .12s ease,border-color .12s ease}.wuic-sf-page-btn:hover:not(:disabled){background-color:var(--wuic-hover-bg, #f3f4f6);border-color:var(--wuic-primary-color, #6366f1)}.wuic-sf-page-btn:disabled{opacity:.35;cursor:not-allowed}.wuic-sf-page-info{margin:0 8px;white-space:nowrap;color:var(--wuic-text-muted, #6b7280)}.wuic-sf-page-info strong{color:var(--wuic-text-color, #1f2937)}.wuic-sf-page-total{margin-left:6px;font-size:12px;color:var(--wuic-text-muted, #6b7280)}.wuic-sf-page-size{margin-left:8px;height:30px;padding:0 8px;border:1px solid var(--wuic-surface-border, #e5e7eb);border-radius:4px;background:transparent;color:var(--wuic-text-color, #1f2937);cursor:pointer}:host-context(.theme-dark) .wuic-sf-pagination,:host-context([data-theme=\"dark\"]) .wuic-sf-pagination{background-color:var(--wuic-surface-a-dark, #1f2937);border-color:var(--wuic-surface-border-dark, #374151)}:host-context(.theme-dark) .wuic-sf-page-btn,:host-context([data-theme=\"dark\"]) .wuic-sf-page-btn,:host-context(.theme-dark) .wuic-sf-page-size,:host-context([data-theme=\"dark\"]) .wuic-sf-page-size{color:#f3f4f6;border-color:var(--wuic-surface-border-dark, #374151)}:host-context(.theme-dark) .wuic-sf-page-btn:hover:not(:disabled),:host-context([data-theme=\"dark\"]) .wuic-sf-page-btn:hover:not(:disabled){background-color:#374151}:host-context(.theme-dark) .wuic-sf-page-info,:host-context([data-theme=\"dark\"]) .wuic-sf-page-info{color:#9ca3af}:host-context(.theme-dark) .wuic-sf-page-info strong,:host-context([data-theme=\"dark\"]) .wuic-sf-page-info strong{color:#f3f4f6}:host-context(.theme-dark) .wuic-sf-page-total,:host-context([data-theme=\"dark\"]) .wuic-sf-page-total{color:#9ca3af}:host ::ng-deep .e-spreadsheet{font-family:var(--wuic-font-family, inherit)}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-column-header .e-header-cell.wuic-sf-colname-header{background-color:#111827!important;color:#e5e7eb!important}:host ::ng-deep .e-spreadsheet .e-sheet-content .e-filter-btn{opacity:0!important;pointer-events:none!important;width:0!important;height:0!important;min-width:0!important;padding:0!important;margin:0!important;overflow:hidden!important;position:absolute!important;right:0;top:0}:host ::ng-deep .e-spreadsheet .e-column-header .e-header-cell.wuic-sf-colname-header{position:relative!important}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-header-filter-btn{position:absolute;right:2px;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;padding:0;background:transparent;border:none;border-radius:3px;cursor:pointer;color:#475569;flex-shrink:0;transition:background-color .12s,color .12s;z-index:2}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-header-filter-btn:hover{background-color:#e2e8f0;color:#1f2937}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-header-filter-btn svg{display:block;pointer-events:none}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-header-filter-btn svg *{pointer-events:none!important}:host ::ng-deep .e-spreadsheet svg,:host ::ng-deep .e-spreadsheet svg *{pointer-events:none!important}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-header-filter-btn.wuic-filtered{color:#2563eb}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-colname-header .e-header-cell-div{padding-right:22px!important}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-column-header .wuic-sf-header-filter-btn{color:#cbd5e1}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-column-header .wuic-sf-header-filter-btn:hover{background-color:#334155;color:#f3f4f6}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-column-header .wuic-sf-header-filter-btn.wuic-filtered{color:#60a5fa}:host ::ng-deep .e-spreadsheet .e-sheet-panel{background-color:var(--wuic-surface-a, #ffffff)}:host-context(.theme-dark) ::ng-deep .e-spreadsheet,:host-context([data-theme=\"dark\"]) ::ng-deep .e-spreadsheet,:host ::ng-deep ejs-spreadsheet.wuic-sf-dark{background-color:var(--wuic-surface-a-dark, #1f2937);color:#f3f4f6}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-sheet-panel,:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-main-panel{background-color:var(--wuic-surface-a-dark, #1f2937);color:#f3f4f6}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-cell,:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-header-cell{background-color:var(--wuic-surface-a-dark, #1f2937);border-color:var(--wuic-surface-border-dark, #374151);color:#f3f4f6}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-header-row .e-header-cell,:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-row-header .e-header-cell{background-color:#111827!important;color:#e5e7eb!important}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-cell.row-danger{background-color:#7f1d1d!important;color:#fecaca!important}:host ::ng-deep .e-cell.row-danger{background-color:#fee2e2!important}:host ::ng-deep .e-cell.row-warning{background-color:#fef3c7!important}:host ::ng-deep .e-cell.row-success{background-color:#d1fae5!important}::ng-deep .wuic-sf-filter-popup{position:fixed;z-index:99999;min-width:260px;max-width:340px;background:#fff;color:#111827;border:1px solid #d1d5db;border-radius:6px;box-shadow:0 8px 24px #0000002e;font-family:inherit;font-size:13px;display:flex;flex-direction:column;overflow:hidden;-webkit-user-select:none;user-select:none}::ng-deep .wuic-sf-filter-popup-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:#f3f4f6;border-bottom:1px solid #e5e7eb;font-weight:600}::ng-deep .wuic-sf-filter-popup-title{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}::ng-deep .wuic-sf-filter-popup-close{background:transparent;border:none;cursor:pointer;font-size:18px;line-height:1;padding:0 4px;color:#6b7280}::ng-deep .wuic-sf-filter-popup-close:hover{color:#111827}::ng-deep .wuic-sf-filter-popup-sort{display:flex;flex-direction:column;border-bottom:1px solid #e5e7eb}::ng-deep .wuic-sf-filter-popup-sort-btn{background:transparent;border:none;text-align:left;padding:6px 12px;cursor:pointer;font-size:13px;color:#111827;display:flex;align-items:center;gap:6px}::ng-deep .wuic-sf-filter-popup-sort-btn:hover{background:#f3f4f6}::ng-deep .wuic-sf-filter-popup-sort-icon{font-size:10px;color:#6b7280}::ng-deep .wuic-sf-filter-popup-clear{border-bottom:1px solid #e5e7eb}::ng-deep .wuic-sf-filter-popup-clear-btn{width:100%;background:transparent;border:none;text-align:left;padding:6px 12px;cursor:pointer;font-size:13px;color:#2563eb}::ng-deep .wuic-sf-filter-popup-clear-btn:hover:not(:disabled){background:#eff6ff}::ng-deep .wuic-sf-filter-popup-clear-btn:disabled{color:#9ca3af;cursor:not-allowed}::ng-deep .wuic-sf-filter-popup-search{padding:8px 12px;border-bottom:1px solid #e5e7eb}::ng-deep .wuic-sf-filter-popup-search-input{width:100%;padding:4px 8px;border:1px solid #d1d5db;border-radius:4px;font-size:13px;outline:none;box-sizing:border-box}::ng-deep .wuic-sf-filter-popup-search-input:focus{border-color:#2563eb;box-shadow:0 0 0 2px #2563eb33}::ng-deep .wuic-sf-filter-popup-list-wrap{display:flex;flex-direction:column;max-height:260px;overflow:hidden}::ng-deep .wuic-sf-filter-popup-selectall{padding:4px 12px;border-bottom:1px solid #e5e7eb;background:#f9fafb;font-weight:500}::ng-deep .wuic-sf-filter-popup-list{flex:1 1 auto;overflow-y:auto;max-height:220px}::ng-deep .wuic-sf-filter-popup-item{display:flex;align-items:center;gap:6px;padding:4px 12px;cursor:pointer}::ng-deep .wuic-sf-filter-popup-item:hover{background:#f3f4f6}::ng-deep .wuic-sf-filter-popup-item input[type=checkbox]{margin:0;flex:0 0 auto}::ng-deep .wuic-sf-filter-popup-item span{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}::ng-deep .wuic-sf-filter-popup-footer{display:flex;justify-content:flex-end;gap:8px;padding:8px 12px;border-top:1px solid #e5e7eb;background:#f9fafb}::ng-deep .wuic-sf-filter-popup-ok,::ng-deep .wuic-sf-filter-popup-cancel{padding:4px 14px;border:1px solid #d1d5db;border-radius:4px;background:#fff;cursor:pointer;font-size:13px}::ng-deep .wuic-sf-filter-popup-ok{background:#2563eb;color:#fff;border-color:#2563eb}::ng-deep .wuic-sf-filter-popup-ok:hover{background:#1d4ed8}::ng-deep .wuic-sf-filter-popup-cancel:hover{background:#f3f4f6}:host ::ng-deep .wuic-sf-header-filter-btn.wuic-sf-header-filter-btn--active{color:#2563eb}:host ::ng-deep .wuic-sf-header-filter-btn.wuic-sf-header-filter-btn--active svg{fill:#2563eb}::ng-deep .wuic-sf-filter-popup.wuic-sf-filter-popup--dark{background:#1f2937;color:#f3f4f6;border-color:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-header,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-footer,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-selectall{background:#111827;border-color:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-sort,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-clear,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-search{border-color:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-sort-btn,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-cancel{color:#f3f4f6;background:transparent;border-color:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-cancel{background:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-sort-btn:hover,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-item:hover,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-cancel:hover{background:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-search-input{background:#111827;color:#f3f4f6;border-color:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-close{color:#9ca3af}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-close:hover{color:#f3f4f6}::ng-deep .wuic-sf-cell-editor{box-sizing:border-box;padding:2px 6px;border:2px solid #2563eb;background:#fff;color:#111827;font-family:inherit;font-size:13px;outline:none;box-shadow:0 2px 8px #00000026;border-radius:2px}::ng-deep .wuic-sf-cell-editor:focus{border-color:#1d4ed8;box-shadow:0 0 0 2px #2563eb40}::ng-deep select.wuic-sf-cell-editor{cursor:pointer}::ng-deep .wuic-sf-cell-editor.wuic-sf-cell-editor--dark{background:#1f2937;color:#f3f4f6;border-color:#2563eb}::ng-deep .wuic-sf-cell-editor.wuic-sf-cell-editor--dark option{background:#1f2937;color:#f3f4f6}\n"], dependencies: [{ kind: "component", type: DataSourceComponent, selector: "wuic-data-source", inputs: ["route", "routeFromRouting", "hardcodedRoute", "autoload", "loading", "changeTracking", "parentRecord", "parentMetaInfo", "parentDatasource", "useComboEndpoint", "comboFormulaLookup", "comboMcId", "comboField", "comboExtraFields", "comboCurrentRecord", "componentRef"] }, { kind: "ngmodule", type: SpreadsheetModule }, { kind: "component", type: i7.SpreadsheetComponent, selector: "ejs-spreadsheet", inputs: ["activeSheetIndex", "allowAutoFill", "allowCellFormatting", "allowChart", "allowConditionalFormat", "allowDataValidation", "allowDelete", "allowEditing", "allowFiltering", "allowFindAndReplace", "allowFreezePane", "allowHyperlink", "allowImage", "allowInsert", "allowMerge", "allowNumberFormatting", "allowOpen", "allowPrint", "allowResizing", "allowSave", "allowScrolling", "allowSorting", "allowUndoRedo", "allowWrap", "author", "autoFillSettings", "calculationMode", "cellStyle", "cssClass", "currencyCode", "definedNames", "enableClipboard", "enableContextMenu", "enableKeyboardNavigation", "enableKeyboardShortcut", "enableNotes", "enablePersistence", "enableRtl", "height", "isProtected", "listSeparator", "locale", "openSettings", "openUrl", "password", "saveUrl", "scrollSettings", "selectionSettings", "sheets", "showAggregate", "showCommentsPane", "showFormulaBar", "showRibbon", "showSheetTabs", "width"], outputs: ["actionBegin", "actionComplete", "afterHyperlinkClick", "afterHyperlinkCreate", "beforeCellFormat", "beforeCellRender", "beforeCellSave", "beforeCellUpdate", "beforeConditionalFormat", "beforeDataBound", "beforeHyperlinkClick", "beforeHyperlinkCreate", "beforeOpen", "beforeSave", "beforeSelect", "beforeSort", "cellEdit", "cellEdited", "cellEditing", "cellSave", "contextMenuBeforeClose", "contextMenuBeforeOpen", "contextMenuItemSelect", "created", "dataBound", "dataSourceChanged", "dialogBeforeOpen", "fileMenuBeforeClose", "fileMenuBeforeOpen", "fileMenuItemSelect", "openComplete", "openFailure", "queryCellInfo", "saveComplete", "select", "sortComplete"] }, { kind: "component", type: ImportExportButtonComponent, selector: "wuic-import-export-button", inputs: ["routeName", "tableMetadata", "filterInfoProvider"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i4.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: SyncfusionStylesBootstrapComponent, selector: "wuic-syncfusion-styles-bootstrap" }, { kind: "pipe", type: i8.TranslatePipe, name: "translate" }] });
4290
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: SpreadsheetListSfComponent, isStandalone: true, selector: "wuic-spreadsheet-list-sf", inputs: { hardcodedRoute: "hardcodedRoute", parentRecord: "parentRecord", parentMetaInfo: "parentMetaInfo", datasource: "datasource", hardcodedDatasource: "hardcodedDatasource", hideToolbar: "hideToolbar", enableLockAwareness: "enableLockAwareness", lockScope: "lockScope", heartbeatIntervalSec: "heartbeatIntervalSec", showFormulaBar: "showFormulaBar", showRibbon: "showRibbon" }, outputs: { onSpreadsheetDataBound: "onSpreadsheetDataBound", onSpreadsheetBeforePageChange: "onSpreadsheetBeforePageChange", onSpreadsheetPageChange: "onSpreadsheetPageChange", onSpreadsheetRowInserted: "onSpreadsheetRowInserted", onSpreadsheetRowsDeleted: "onSpreadsheetRowsDeleted", onSpreadsheetBatchSaved: "onSpreadsheetBatchSaved", onLockAcquired: "onLockAcquired", onLockConflict: "onLockConflict", onLockReleased: "onLockReleased", onLockExpired: "onLockExpired" }, host: { listeners: { "window:resize": "onWindowResize()" } }, viewQueries: [{ propertyName: "sfSpreadsheet", first: true, predicate: ["sfSpreadsheet"], descendants: true }, { propertyName: "lookupSource", first: true, predicate: ["lookupSource"], descendants: true }], ngImport: i0, template: "<!--\n Layout root wrapper:\n 1. Toolbar WUIC custom (insert / save batch / export / refresh)\n 2. Syncfusion Spreadsheet host\n 3. Pagination custom (Syncfusion non ha pagination built-in, quindi la\n rendiamo sotto il foglio con nav + page-size dropdown)\n 4. Lookup datasource (invisibile, pattern identico al vecchio componente)\n-->\n\n<!-- Bootstrap CSS Syncfusion (~500 KiB): si monta solo quando questo\n componente lazy \u00E8 istanziato \u2192 CSS NON nel bundle iniziale globale,\n risparmia ~500 KiB su tutte le pagine che non aprono spreadsheet. -->\n<wuic-syncfusion-styles-bootstrap></wuic-syncfusion-styles-bootstrap>\n\n<div class=\"wuic-sf-spreadsheet-wrapper\">\n\n @if (!hideToolbar && metaInfo) {\n <!-- Stile allineato a <wuic-list-grid>: stessa caption-bar, stessi p-button\n PrimeNG con icona + label, stesso componente import/export. -->\n <div class=\"grid-caption-bar wuic-sf-caption-bar\" role=\"toolbar\" aria-label=\"Spreadsheet toolbar\">\n <div class=\"grid-caption-left\">\n @if (!metaInfo.tableMetadata.md_hide_refresh) {\n <p-button icon=\"pi pi-refresh\" label=\"{{'refresh_grid' | translate}}\"\n (onClick)=\"onToolbarRefresh()\" />\n }\n @if (canInsertRows()) {\n <p-button icon=\"pi pi-plus\" label=\"{{'add_grid' | translate}}\"\n (onClick)=\"onToolbarInsert()\" />\n }\n @if (!metaInfo.tableMetadata.md_hide_export_xls) {\n <!-- Include dialog con progress bar + bottoni \"Stoppa e salva\n parziale\" / \"Stoppa\" / \"Continua in background\". -->\n <wuic-import-export-button\n [routeName]=\"metaInfo.tableMetadata.md_route_name\"\n [tableMetadata]=\"metaInfo.tableMetadata\"\n [filterInfoProvider]=\"getCurrentFilterInfoForExport\"></wuic-import-export-button>\n }\n @if (isBatchSaveEnabled()) {\n <p-button icon=\"pi pi-save\" label=\"{{'list_grid.save_changes' | translate}}\"\n (onClick)=\"onToolbarSaveBatch()\" />\n }\n </div>\n </div>\n }\n\n <div class=\"wuic-sf-spreadsheet-host\">\n @if (metaInfo && sfSheets.length > 0) {\n <ejs-spreadsheet #sfSpreadsheet\n [sheets]=\"sfSheets\"\n [height]=\"sfHeight\"\n [width]=\"sfWidth\"\n [scrollSettings]=\"sfScrollSettings\"\n [showRibbon]=\"showRibbon\"\n [showFormulaBar]=\"showFormulaBar\"\n [showSheetTabs]=\"false\"\n [allowEditing]=\"true\"\n [allowSorting]=\"true\"\n [allowFiltering]=\"false\"\n [allowResizing]=\"true\"\n [allowInsert]=\"canInsertRows()\"\n [allowDelete]=\"canDeleteRows()\"\n [enableContextMenu]=\"true\"\n [enableKeyboardNavigation]=\"true\"\n [enableClipboard]=\"true\"\n (created)=\"onSyncfusionCreated()\"\n (dataBound)=\"onSyncfusionDataBound()\"\n (cellSave)=\"onSyncfusionCellSave($event)\"\n (beforeCellSave)=\"onSyncfusionBeforeCellSave($event)\"\n (beforeCellRender)=\"onSyncfusionBeforeCellRender($event)\"\n (cellEdit)=\"onSyncfusionCellEdit($event)\"\n (contextMenuBeforeOpen)=\"onSyncfusionContextMenuBeforeOpen($event)\"\n (contextMenuItemSelect)=\"onSyncfusionContextMenuItemSelect($event)\"\n (actionBegin)=\"onSyncfusionActionBegin($event)\"\n (actionComplete)=\"onSyncfusionActionComplete($event)\"\n (dialogBeforeOpen)=\"onSyncfusionDialogBeforeOpen($event)\">\n </ejs-spreadsheet>\n }\n </div>\n\n @if (totalPages > 1 || totalRecords > pageSize) {\n <div class=\"wuic-sf-pagination\" role=\"navigation\" aria-label=\"Paginazione\">\n <button type=\"button\" class=\"wuic-sf-page-btn\"\n [disabled]=\"pageIndex <= 1\"\n (click)=\"isServerSide ? handleServerPageChange(1, pageSize) : setClientPage(1)\"\n title=\"Prima pagina\">\n <i class=\"pi pi-angle-double-left\"></i>\n </button>\n <button type=\"button\" class=\"wuic-sf-page-btn\"\n [disabled]=\"pageIndex <= 1\"\n (click)=\"isServerSide ? handleServerPageChange(pageIndex - 1, pageSize) : setClientPage(pageIndex - 1)\"\n title=\"Pagina precedente\">\n <i class=\"pi pi-angle-left\"></i>\n </button>\n <span class=\"wuic-sf-page-info\">\n Pagina <strong>{{ pageIndex }}</strong> di <strong>{{ totalPages }}</strong>\n <span class=\"wuic-sf-page-total\">({{ totalRecords }} record)</span>\n </span>\n <button type=\"button\" class=\"wuic-sf-page-btn\"\n [disabled]=\"pageIndex >= totalPages\"\n (click)=\"isServerSide ? handleServerPageChange(pageIndex + 1, pageSize) : setClientPage(pageIndex + 1)\"\n title=\"Pagina successiva\">\n <i class=\"pi pi-angle-right\"></i>\n </button>\n <button type=\"button\" class=\"wuic-sf-page-btn\"\n [disabled]=\"pageIndex >= totalPages\"\n (click)=\"isServerSide ? handleServerPageChange(totalPages, pageSize) : setClientPage(totalPages)\"\n title=\"Ultima pagina\">\n <i class=\"pi pi-angle-double-right\"></i>\n </button>\n <select class=\"wuic-sf-page-size\" [value]=\"pageSize\"\n (change)=\"setPageSize($any($event.target).value)\"\n title=\"Record per pagina\">\n <option [value]=\"10\">10</option>\n <option [value]=\"25\">25</option>\n <option [value]=\"50\">50</option>\n <option [value]=\"100\">100</option>\n </select>\n </div>\n }\n\n</div>\n\n@if (!hardcodedRoute) {\n <wuic-data-source #lookupSource [autoload]=\"true\" [routeFromRouting]=\"false\"\n [route]=\"currentRoute\" [parentRecord]=\"currentRecord\"\n [parentMetaInfo]=\"metaInfo\"></wuic-data-source>\n}\n", styles: [":host{display:block;width:100%;height:100%;min-height:0}.wuic-sf-spreadsheet-wrapper{display:flex;flex-direction:column;width:100%;height:100%;min-height:0;overflow:hidden}.wuic-sf-caption-bar{flex:0 0 auto;padding:5px}.grid-caption-left{display:flex;align-items:stretch;gap:.5rem;flex-wrap:nowrap;min-height:2.25rem;height:2.25rem}.wuic-sf-spreadsheet-host{flex:1 1 auto;min-height:0;width:100%;position:relative;overflow:visible}::ng-deep .e-dialog.e-popup-open,::ng-deep .e-excelfilter,::ng-deep .e-filter-popup{z-index:10010!important;min-width:240px}::ng-deep .e-excelfilter .e-footer-content,::ng-deep .e-filter-popup .e-footer-content{display:flex!important;justify-content:flex-end;gap:6px;padding:8px 12px!important}.wuic-sf-spreadsheet-host ejs-spreadsheet{display:block;width:100%;height:100%}:host ::ng-deep .e-spreadsheet .e-column-header .e-header-cell.wuic-sf-colname-header .e-header-cell-div{font-weight:700!important;text-align:left!important;padding-left:6px!important;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wuic-sf-pagination{display:flex;gap:6px;align-items:center;justify-content:flex-end;padding:6px 10px;background-color:var(--wuic-surface-a, #ffffff);border-top:1px solid var(--wuic-surface-border, #e5e7eb);flex:0 0 auto;font-size:13px}.wuic-sf-page-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;border:1px solid var(--wuic-surface-border, #e5e7eb);border-radius:4px;background:transparent;cursor:pointer;color:var(--wuic-text-color, #1f2937);transition:background-color .12s ease,border-color .12s ease}.wuic-sf-page-btn:hover:not(:disabled){background-color:var(--wuic-hover-bg, #f3f4f6);border-color:var(--wuic-primary-color, #6366f1)}.wuic-sf-page-btn:disabled{opacity:.35;cursor:not-allowed}.wuic-sf-page-info{margin:0 8px;white-space:nowrap;color:var(--wuic-text-muted, #6b7280)}.wuic-sf-page-info strong{color:var(--wuic-text-color, #1f2937)}.wuic-sf-page-total{margin-left:6px;font-size:12px;color:var(--wuic-text-muted, #6b7280)}.wuic-sf-page-size{margin-left:8px;height:30px;padding:0 8px;border:1px solid var(--wuic-surface-border, #e5e7eb);border-radius:4px;background:transparent;color:var(--wuic-text-color, #1f2937);cursor:pointer}:host-context(.theme-dark) .wuic-sf-pagination,:host-context([data-theme=\"dark\"]) .wuic-sf-pagination{background-color:var(--wuic-surface-a-dark, #1f2937);border-color:var(--wuic-surface-border-dark, #374151)}:host-context(.theme-dark) .wuic-sf-page-btn,:host-context([data-theme=\"dark\"]) .wuic-sf-page-btn,:host-context(.theme-dark) .wuic-sf-page-size,:host-context([data-theme=\"dark\"]) .wuic-sf-page-size{color:#f3f4f6;border-color:var(--wuic-surface-border-dark, #374151)}:host-context(.theme-dark) .wuic-sf-page-btn:hover:not(:disabled),:host-context([data-theme=\"dark\"]) .wuic-sf-page-btn:hover:not(:disabled){background-color:#374151}:host-context(.theme-dark) .wuic-sf-page-info,:host-context([data-theme=\"dark\"]) .wuic-sf-page-info{color:#9ca3af}:host-context(.theme-dark) .wuic-sf-page-info strong,:host-context([data-theme=\"dark\"]) .wuic-sf-page-info strong{color:#f3f4f6}:host-context(.theme-dark) .wuic-sf-page-total,:host-context([data-theme=\"dark\"]) .wuic-sf-page-total{color:#9ca3af}:host ::ng-deep .e-spreadsheet{font-family:var(--wuic-font-family, inherit)}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-column-header .e-header-cell.wuic-sf-colname-header{background-color:#111827!important;color:#e5e7eb!important}:host ::ng-deep .e-spreadsheet .e-sheet-content .e-filter-btn{opacity:0!important;pointer-events:none!important;width:0!important;height:0!important;min-width:0!important;padding:0!important;margin:0!important;overflow:hidden!important;position:absolute!important;right:0;top:0}:host ::ng-deep .e-spreadsheet .e-column-header .e-header-cell.wuic-sf-colname-header{position:relative!important}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-header-filter-btn{position:absolute;right:2px;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;padding:0;background:transparent;border:none;border-radius:3px;cursor:pointer;color:#475569;flex-shrink:0;transition:background-color .12s,color .12s;z-index:2}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-header-filter-btn:hover{background-color:#e2e8f0;color:#1f2937}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-header-filter-btn svg{display:block;pointer-events:none}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-header-filter-btn svg *{pointer-events:none!important}:host ::ng-deep .e-spreadsheet svg,:host ::ng-deep .e-spreadsheet svg *{pointer-events:none!important}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-header-filter-btn.wuic-filtered{color:#2563eb}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-colname-header .e-header-cell-div{padding-right:22px!important}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-column-header .wuic-sf-header-filter-btn{color:#cbd5e1}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-column-header .wuic-sf-header-filter-btn:hover{background-color:#334155;color:#f3f4f6}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-column-header .wuic-sf-header-filter-btn.wuic-filtered{color:#60a5fa}:host ::ng-deep .e-spreadsheet .e-sheet-panel{background-color:var(--wuic-surface-a, #ffffff)}:host-context(.theme-dark) ::ng-deep .e-spreadsheet,:host-context([data-theme=\"dark\"]) ::ng-deep .e-spreadsheet,:host ::ng-deep ejs-spreadsheet.wuic-sf-dark{background-color:var(--wuic-surface-a-dark, #1f2937);color:#f3f4f6}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-sheet-panel,:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-main-panel{background-color:var(--wuic-surface-a-dark, #1f2937);color:#f3f4f6}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-cell,:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-header-cell{background-color:var(--wuic-surface-a-dark, #1f2937);border-color:var(--wuic-surface-border-dark, #374151);color:#f3f4f6}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-header-row .e-header-cell,:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-row-header .e-header-cell{background-color:#111827!important;color:#e5e7eb!important}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-cell.row-danger{background-color:#7f1d1d!important;color:#fecaca!important}:host ::ng-deep .e-cell.row-danger{background-color:#fee2e2!important}:host ::ng-deep .e-cell.row-warning{background-color:#fef3c7!important}:host ::ng-deep .e-cell.row-success{background-color:#d1fae5!important}::ng-deep .wuic-sf-filter-popup{position:fixed;z-index:99999;min-width:260px;max-width:340px;background:#fff;color:#111827;border:1px solid #d1d5db;border-radius:6px;box-shadow:0 8px 24px #0000002e;font-family:inherit;font-size:13px;display:flex;flex-direction:column;overflow:hidden;-webkit-user-select:none;user-select:none}::ng-deep .wuic-sf-filter-popup-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:#f3f4f6;border-bottom:1px solid #e5e7eb;font-weight:600}::ng-deep .wuic-sf-filter-popup-title{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}::ng-deep .wuic-sf-filter-popup-close{background:transparent;border:none;cursor:pointer;font-size:18px;line-height:1;padding:0 4px;color:#6b7280}::ng-deep .wuic-sf-filter-popup-close:hover{color:#111827}::ng-deep .wuic-sf-filter-popup-sort{display:flex;flex-direction:column;border-bottom:1px solid #e5e7eb}::ng-deep .wuic-sf-filter-popup-sort-btn{background:transparent;border:none;text-align:left;padding:6px 12px;cursor:pointer;font-size:13px;color:#111827;display:flex;align-items:center;gap:6px}::ng-deep .wuic-sf-filter-popup-sort-btn:hover{background:#f3f4f6}::ng-deep .wuic-sf-filter-popup-sort-icon{font-size:10px;color:#6b7280}::ng-deep .wuic-sf-filter-popup-clear{border-bottom:1px solid #e5e7eb}::ng-deep .wuic-sf-filter-popup-clear-btn{width:100%;background:transparent;border:none;text-align:left;padding:6px 12px;cursor:pointer;font-size:13px;color:#2563eb}::ng-deep .wuic-sf-filter-popup-clear-btn:hover:not(:disabled){background:#eff6ff}::ng-deep .wuic-sf-filter-popup-clear-btn:disabled{color:#9ca3af;cursor:not-allowed}::ng-deep .wuic-sf-filter-popup-search{padding:8px 12px;border-bottom:1px solid #e5e7eb}::ng-deep .wuic-sf-filter-popup-search-input{width:100%;padding:4px 8px;border:1px solid #d1d5db;border-radius:4px;font-size:13px;outline:none;box-sizing:border-box}::ng-deep .wuic-sf-filter-popup-search-input:focus{border-color:#2563eb;box-shadow:0 0 0 2px #2563eb33}::ng-deep .wuic-sf-filter-popup-list-wrap{display:flex;flex-direction:column;max-height:260px;overflow:hidden}::ng-deep .wuic-sf-filter-popup-selectall{padding:4px 12px;border-bottom:1px solid #e5e7eb;background:#f9fafb;font-weight:500}::ng-deep .wuic-sf-filter-popup-list{flex:1 1 auto;overflow-y:auto;max-height:220px}::ng-deep .wuic-sf-filter-popup-item{display:flex;align-items:center;gap:6px;padding:4px 12px;cursor:pointer}::ng-deep .wuic-sf-filter-popup-item:hover{background:#f3f4f6}::ng-deep .wuic-sf-filter-popup-item input[type=checkbox]{margin:0;flex:0 0 auto}::ng-deep .wuic-sf-filter-popup-item span{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}::ng-deep .wuic-sf-filter-popup-footer{display:flex;justify-content:flex-end;gap:8px;padding:8px 12px;border-top:1px solid #e5e7eb;background:#f9fafb}::ng-deep .wuic-sf-filter-popup-ok,::ng-deep .wuic-sf-filter-popup-cancel{padding:4px 14px;border:1px solid #d1d5db;border-radius:4px;background:#fff;cursor:pointer;font-size:13px}::ng-deep .wuic-sf-filter-popup-ok{background:#2563eb;color:#fff;border-color:#2563eb}::ng-deep .wuic-sf-filter-popup-ok:hover{background:#1d4ed8}::ng-deep .wuic-sf-filter-popup-cancel:hover{background:#f3f4f6}:host ::ng-deep .wuic-sf-header-filter-btn.wuic-sf-header-filter-btn--active{color:#2563eb}:host ::ng-deep .wuic-sf-header-filter-btn.wuic-sf-header-filter-btn--active svg{fill:#2563eb}::ng-deep .wuic-sf-filter-popup.wuic-sf-filter-popup--dark{background:#1f2937;color:#f3f4f6;border-color:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-header,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-footer,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-selectall{background:#111827;border-color:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-sort,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-clear,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-search{border-color:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-sort-btn,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-cancel{color:#f3f4f6;background:transparent;border-color:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-cancel{background:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-sort-btn:hover,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-item:hover,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-cancel:hover{background:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-search-input{background:#111827;color:#f3f4f6;border-color:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-close{color:#9ca3af}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-close:hover{color:#f3f4f6}::ng-deep .wuic-sf-cell-editor{box-sizing:border-box;padding:2px 6px;border:2px solid #2563eb;background:#fff;color:#111827;font-family:inherit;font-size:13px;outline:none;box-shadow:0 2px 8px #00000026;border-radius:2px}::ng-deep .wuic-sf-cell-editor:focus{border-color:#1d4ed8;box-shadow:0 0 0 2px #2563eb40}::ng-deep select.wuic-sf-cell-editor{cursor:pointer}::ng-deep .wuic-sf-cell-editor.wuic-sf-cell-editor--dark{background:#1f2937;color:#f3f4f6;border-color:#2563eb}::ng-deep .wuic-sf-cell-editor.wuic-sf-cell-editor--dark option{background:#1f2937;color:#f3f4f6}\n"], dependencies: [{ kind: "component", type: DataSourceComponent, selector: "wuic-data-source", inputs: ["route", "routeFromRouting", "hardcodedRoute", "autoload", "loading", "changeTracking", "parentRecord", "parentMetaInfo", "parentDatasource", "useComboEndpoint", "comboFormulaLookup", "comboMcId", "comboField", "comboExtraFields", "comboCurrentRecord", "componentRef"] }, { kind: "ngmodule", type: SpreadsheetModule }, { kind: "component", type: i7.SpreadsheetComponent, selector: "ejs-spreadsheet", inputs: ["activeSheetIndex", "allowAutoFill", "allowCellFormatting", "allowChart", "allowConditionalFormat", "allowDataValidation", "allowDelete", "allowEditing", "allowFiltering", "allowFindAndReplace", "allowFreezePane", "allowHyperlink", "allowImage", "allowInsert", "allowMerge", "allowNumberFormatting", "allowOpen", "allowPrint", "allowResizing", "allowSave", "allowScrolling", "allowSorting", "allowUndoRedo", "allowWrap", "author", "autoFillSettings", "calculationMode", "cellStyle", "cssClass", "currencyCode", "definedNames", "enableClipboard", "enableContextMenu", "enableKeyboardNavigation", "enableKeyboardShortcut", "enableNotes", "enablePersistence", "enableRtl", "height", "isProtected", "listSeparator", "locale", "openSettings", "openUrl", "password", "saveUrl", "scrollSettings", "selectionSettings", "sheets", "showAggregate", "showCommentsPane", "showFormulaBar", "showRibbon", "showSheetTabs", "width"], outputs: ["actionBegin", "actionComplete", "afterHyperlinkClick", "afterHyperlinkCreate", "beforeCellFormat", "beforeCellRender", "beforeCellSave", "beforeCellUpdate", "beforeConditionalFormat", "beforeDataBound", "beforeHyperlinkClick", "beforeHyperlinkCreate", "beforeOpen", "beforeSave", "beforeSelect", "beforeSort", "cellEdit", "cellEdited", "cellEditing", "cellSave", "contextMenuBeforeClose", "contextMenuBeforeOpen", "contextMenuItemSelect", "created", "dataBound", "dataSourceChanged", "dialogBeforeOpen", "fileMenuBeforeClose", "fileMenuBeforeOpen", "fileMenuItemSelect", "openComplete", "openFailure", "queryCellInfo", "saveComplete", "select", "sortComplete"] }, { kind: "component", type: ImportExportButtonComponent, selector: "wuic-import-export-button", inputs: ["routeName", "tableMetadata", "filterInfoProvider"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i4.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: SyncfusionStylesBootstrapComponent, selector: "wuic-syncfusion-styles-bootstrap" }, { kind: "pipe", type: i8.TranslatePipe, name: "translate" }] });
3850
4291
  }
3851
4292
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SpreadsheetListSfComponent, decorators: [{
3852
4293
  type: Component,
@@ -3857,7 +4298,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
3857
4298
  ButtonModule,
3858
4299
  TranslateModule,
3859
4300
  SyncfusionStylesBootstrapComponent
3860
- ], template: "<!--\n Layout root wrapper:\n 1. Toolbar WUIC custom (insert / save batch / export / refresh)\n 2. Syncfusion Spreadsheet host\n 3. Pagination custom (Syncfusion non ha pagination built-in, quindi la\n rendiamo sotto il foglio con nav + page-size dropdown)\n 4. Lookup datasource (invisibile, pattern identico al vecchio componente)\n-->\n\n<!-- Bootstrap CSS Syncfusion (~500 KiB): si monta solo quando questo\n componente lazy \u00E8 istanziato \u2192 CSS NON nel bundle iniziale globale,\n risparmia ~500 KiB su tutte le pagine che non aprono spreadsheet. -->\n<wuic-syncfusion-styles-bootstrap></wuic-syncfusion-styles-bootstrap>\n\n<div class=\"wuic-sf-spreadsheet-wrapper\">\n\n @if (!hideToolbar && metaInfo) {\n <!-- Stile allineato a <wuic-list-grid>: stessa caption-bar, stessi p-button\n PrimeNG con icona + label, stesso componente import/export. -->\n <div class=\"grid-caption-bar wuic-sf-caption-bar\" role=\"toolbar\" aria-label=\"Spreadsheet toolbar\">\n <div class=\"grid-caption-left\">\n @if (!metaInfo.tableMetadata.md_hide_refresh) {\n <p-button icon=\"pi pi-refresh\" label=\"{{'refresh_grid' | translate}}\"\n (onClick)=\"onToolbarRefresh()\" />\n }\n @if (canInsertRows()) {\n <p-button icon=\"pi pi-plus\" label=\"{{'add_grid' | translate}}\"\n (onClick)=\"onToolbarInsert()\" />\n }\n @if (!metaInfo.tableMetadata.md_hide_export_xls) {\n <!-- Include dialog con progress bar + bottoni \"Stoppa e salva\n parziale\" / \"Stoppa\" / \"Continua in background\". -->\n <wuic-import-export-button\n [routeName]=\"metaInfo.tableMetadata.md_route_name\"\n [tableMetadata]=\"metaInfo.tableMetadata\"\n [filterInfoProvider]=\"getCurrentFilterInfoForExport\"></wuic-import-export-button>\n }\n @if (isBatchSaveEnabled()) {\n <p-button icon=\"pi pi-save\" label=\"{{'list_grid.save_changes' | translate}}\"\n (onClick)=\"onToolbarSaveBatch()\" />\n }\n </div>\n </div>\n }\n\n <div class=\"wuic-sf-spreadsheet-host\">\n @if (metaInfo && sfSheets.length > 0) {\n <ejs-spreadsheet #sfSpreadsheet\n [sheets]=\"sfSheets\"\n [height]=\"sfHeight\"\n [width]=\"sfWidth\"\n [scrollSettings]=\"sfScrollSettings\"\n [showRibbon]=\"false\"\n [showFormulaBar]=\"false\"\n [showSheetTabs]=\"false\"\n [allowEditing]=\"true\"\n [allowSorting]=\"true\"\n [allowFiltering]=\"false\"\n [allowResizing]=\"true\"\n [allowInsert]=\"canInsertRows()\"\n [allowDelete]=\"canDeleteRows()\"\n [enableContextMenu]=\"true\"\n [enableKeyboardNavigation]=\"true\"\n [enableClipboard]=\"true\"\n (created)=\"onSyncfusionCreated()\"\n (dataBound)=\"onSyncfusionDataBound()\"\n (cellSave)=\"onSyncfusionCellSave($event)\"\n (beforeCellSave)=\"onSyncfusionBeforeCellSave($event)\"\n (beforeCellRender)=\"onSyncfusionBeforeCellRender($event)\"\n (cellEdit)=\"onSyncfusionCellEdit($event)\"\n (contextMenuBeforeOpen)=\"onSyncfusionContextMenuBeforeOpen($event)\"\n (contextMenuItemSelect)=\"onSyncfusionContextMenuItemSelect($event)\"\n (actionBegin)=\"onSyncfusionActionBegin($event)\"\n (actionComplete)=\"onSyncfusionActionComplete($event)\"\n (dialogBeforeOpen)=\"onSyncfusionDialogBeforeOpen($event)\">\n </ejs-spreadsheet>\n }\n </div>\n\n @if (totalPages > 1 || totalRecords > pageSize) {\n <div class=\"wuic-sf-pagination\" role=\"navigation\" aria-label=\"Paginazione\">\n <button type=\"button\" class=\"wuic-sf-page-btn\"\n [disabled]=\"pageIndex <= 1\"\n (click)=\"isServerSide ? handleServerPageChange(1, pageSize) : setClientPage(1)\"\n title=\"Prima pagina\">\n <i class=\"pi pi-angle-double-left\"></i>\n </button>\n <button type=\"button\" class=\"wuic-sf-page-btn\"\n [disabled]=\"pageIndex <= 1\"\n (click)=\"isServerSide ? handleServerPageChange(pageIndex - 1, pageSize) : setClientPage(pageIndex - 1)\"\n title=\"Pagina precedente\">\n <i class=\"pi pi-angle-left\"></i>\n </button>\n <span class=\"wuic-sf-page-info\">\n Pagina <strong>{{ pageIndex }}</strong> di <strong>{{ totalPages }}</strong>\n <span class=\"wuic-sf-page-total\">({{ totalRecords }} record)</span>\n </span>\n <button type=\"button\" class=\"wuic-sf-page-btn\"\n [disabled]=\"pageIndex >= totalPages\"\n (click)=\"isServerSide ? handleServerPageChange(pageIndex + 1, pageSize) : setClientPage(pageIndex + 1)\"\n title=\"Pagina successiva\">\n <i class=\"pi pi-angle-right\"></i>\n </button>\n <button type=\"button\" class=\"wuic-sf-page-btn\"\n [disabled]=\"pageIndex >= totalPages\"\n (click)=\"isServerSide ? handleServerPageChange(totalPages, pageSize) : setClientPage(totalPages)\"\n title=\"Ultima pagina\">\n <i class=\"pi pi-angle-double-right\"></i>\n </button>\n <select class=\"wuic-sf-page-size\" [value]=\"pageSize\"\n (change)=\"setPageSize($any($event.target).value)\"\n title=\"Record per pagina\">\n <option [value]=\"10\">10</option>\n <option [value]=\"25\">25</option>\n <option [value]=\"50\">50</option>\n <option [value]=\"100\">100</option>\n </select>\n </div>\n }\n\n</div>\n\n@if (!hardcodedRoute) {\n <wuic-data-source #lookupSource [autoload]=\"true\" [routeFromRouting]=\"false\"\n [route]=\"currentRoute\" [parentRecord]=\"currentRecord\"\n [parentMetaInfo]=\"metaInfo\"></wuic-data-source>\n}\n", styles: [":host{display:block;width:100%;height:100%;min-height:0}.wuic-sf-spreadsheet-wrapper{display:flex;flex-direction:column;width:100%;height:100%;min-height:0;overflow:hidden}.wuic-sf-caption-bar{flex:0 0 auto;padding:5px}.grid-caption-left{display:flex;align-items:stretch;gap:.5rem;flex-wrap:nowrap;min-height:2.25rem;height:2.25rem}.wuic-sf-spreadsheet-host{flex:1 1 auto;min-height:0;width:100%;position:relative;overflow:visible}::ng-deep .e-dialog.e-popup-open,::ng-deep .e-excelfilter,::ng-deep .e-filter-popup{z-index:10010!important;min-width:240px}::ng-deep .e-excelfilter .e-footer-content,::ng-deep .e-filter-popup .e-footer-content{display:flex!important;justify-content:flex-end;gap:6px;padding:8px 12px!important}.wuic-sf-spreadsheet-host ejs-spreadsheet{display:block;width:100%;height:100%}:host ::ng-deep .e-spreadsheet .e-column-header .e-header-cell.wuic-sf-colname-header .e-header-cell-div{font-weight:700!important;text-align:left!important;padding-left:6px!important;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wuic-sf-pagination{display:flex;gap:6px;align-items:center;justify-content:flex-end;padding:6px 10px;background-color:var(--wuic-surface-a, #ffffff);border-top:1px solid var(--wuic-surface-border, #e5e7eb);flex:0 0 auto;font-size:13px}.wuic-sf-page-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;border:1px solid var(--wuic-surface-border, #e5e7eb);border-radius:4px;background:transparent;cursor:pointer;color:var(--wuic-text-color, #1f2937);transition:background-color .12s ease,border-color .12s ease}.wuic-sf-page-btn:hover:not(:disabled){background-color:var(--wuic-hover-bg, #f3f4f6);border-color:var(--wuic-primary-color, #6366f1)}.wuic-sf-page-btn:disabled{opacity:.35;cursor:not-allowed}.wuic-sf-page-info{margin:0 8px;white-space:nowrap;color:var(--wuic-text-muted, #6b7280)}.wuic-sf-page-info strong{color:var(--wuic-text-color, #1f2937)}.wuic-sf-page-total{margin-left:6px;font-size:12px;color:var(--wuic-text-muted, #6b7280)}.wuic-sf-page-size{margin-left:8px;height:30px;padding:0 8px;border:1px solid var(--wuic-surface-border, #e5e7eb);border-radius:4px;background:transparent;color:var(--wuic-text-color, #1f2937);cursor:pointer}:host-context(.theme-dark) .wuic-sf-pagination,:host-context([data-theme=\"dark\"]) .wuic-sf-pagination{background-color:var(--wuic-surface-a-dark, #1f2937);border-color:var(--wuic-surface-border-dark, #374151)}:host-context(.theme-dark) .wuic-sf-page-btn,:host-context([data-theme=\"dark\"]) .wuic-sf-page-btn,:host-context(.theme-dark) .wuic-sf-page-size,:host-context([data-theme=\"dark\"]) .wuic-sf-page-size{color:#f3f4f6;border-color:var(--wuic-surface-border-dark, #374151)}:host-context(.theme-dark) .wuic-sf-page-btn:hover:not(:disabled),:host-context([data-theme=\"dark\"]) .wuic-sf-page-btn:hover:not(:disabled){background-color:#374151}:host-context(.theme-dark) .wuic-sf-page-info,:host-context([data-theme=\"dark\"]) .wuic-sf-page-info{color:#9ca3af}:host-context(.theme-dark) .wuic-sf-page-info strong,:host-context([data-theme=\"dark\"]) .wuic-sf-page-info strong{color:#f3f4f6}:host-context(.theme-dark) .wuic-sf-page-total,:host-context([data-theme=\"dark\"]) .wuic-sf-page-total{color:#9ca3af}:host ::ng-deep .e-spreadsheet{font-family:var(--wuic-font-family, inherit)}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-column-header .e-header-cell.wuic-sf-colname-header{background-color:#111827!important;color:#e5e7eb!important}:host ::ng-deep .e-spreadsheet .e-sheet-content .e-filter-btn{opacity:0!important;pointer-events:none!important;width:0!important;height:0!important;min-width:0!important;padding:0!important;margin:0!important;overflow:hidden!important;position:absolute!important;right:0;top:0}:host ::ng-deep .e-spreadsheet .e-column-header .e-header-cell.wuic-sf-colname-header{position:relative!important}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-header-filter-btn{position:absolute;right:2px;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;padding:0;background:transparent;border:none;border-radius:3px;cursor:pointer;color:#475569;flex-shrink:0;transition:background-color .12s,color .12s;z-index:2}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-header-filter-btn:hover{background-color:#e2e8f0;color:#1f2937}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-header-filter-btn svg{display:block;pointer-events:none}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-header-filter-btn svg *{pointer-events:none!important}:host ::ng-deep .e-spreadsheet svg,:host ::ng-deep .e-spreadsheet svg *{pointer-events:none!important}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-header-filter-btn.wuic-filtered{color:#2563eb}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-colname-header .e-header-cell-div{padding-right:22px!important}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-column-header .wuic-sf-header-filter-btn{color:#cbd5e1}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-column-header .wuic-sf-header-filter-btn:hover{background-color:#334155;color:#f3f4f6}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-column-header .wuic-sf-header-filter-btn.wuic-filtered{color:#60a5fa}:host ::ng-deep .e-spreadsheet .e-sheet-panel{background-color:var(--wuic-surface-a, #ffffff)}:host-context(.theme-dark) ::ng-deep .e-spreadsheet,:host-context([data-theme=\"dark\"]) ::ng-deep .e-spreadsheet,:host ::ng-deep ejs-spreadsheet.wuic-sf-dark{background-color:var(--wuic-surface-a-dark, #1f2937);color:#f3f4f6}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-sheet-panel,:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-main-panel{background-color:var(--wuic-surface-a-dark, #1f2937);color:#f3f4f6}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-cell,:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-header-cell{background-color:var(--wuic-surface-a-dark, #1f2937);border-color:var(--wuic-surface-border-dark, #374151);color:#f3f4f6}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-header-row .e-header-cell,:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-row-header .e-header-cell{background-color:#111827!important;color:#e5e7eb!important}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-cell.row-danger{background-color:#7f1d1d!important;color:#fecaca!important}:host ::ng-deep .e-cell.row-danger{background-color:#fee2e2!important}:host ::ng-deep .e-cell.row-warning{background-color:#fef3c7!important}:host ::ng-deep .e-cell.row-success{background-color:#d1fae5!important}::ng-deep .wuic-sf-filter-popup{position:fixed;z-index:99999;min-width:260px;max-width:340px;background:#fff;color:#111827;border:1px solid #d1d5db;border-radius:6px;box-shadow:0 8px 24px #0000002e;font-family:inherit;font-size:13px;display:flex;flex-direction:column;overflow:hidden;-webkit-user-select:none;user-select:none}::ng-deep .wuic-sf-filter-popup-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:#f3f4f6;border-bottom:1px solid #e5e7eb;font-weight:600}::ng-deep .wuic-sf-filter-popup-title{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}::ng-deep .wuic-sf-filter-popup-close{background:transparent;border:none;cursor:pointer;font-size:18px;line-height:1;padding:0 4px;color:#6b7280}::ng-deep .wuic-sf-filter-popup-close:hover{color:#111827}::ng-deep .wuic-sf-filter-popup-sort{display:flex;flex-direction:column;border-bottom:1px solid #e5e7eb}::ng-deep .wuic-sf-filter-popup-sort-btn{background:transparent;border:none;text-align:left;padding:6px 12px;cursor:pointer;font-size:13px;color:#111827;display:flex;align-items:center;gap:6px}::ng-deep .wuic-sf-filter-popup-sort-btn:hover{background:#f3f4f6}::ng-deep .wuic-sf-filter-popup-sort-icon{font-size:10px;color:#6b7280}::ng-deep .wuic-sf-filter-popup-clear{border-bottom:1px solid #e5e7eb}::ng-deep .wuic-sf-filter-popup-clear-btn{width:100%;background:transparent;border:none;text-align:left;padding:6px 12px;cursor:pointer;font-size:13px;color:#2563eb}::ng-deep .wuic-sf-filter-popup-clear-btn:hover:not(:disabled){background:#eff6ff}::ng-deep .wuic-sf-filter-popup-clear-btn:disabled{color:#9ca3af;cursor:not-allowed}::ng-deep .wuic-sf-filter-popup-search{padding:8px 12px;border-bottom:1px solid #e5e7eb}::ng-deep .wuic-sf-filter-popup-search-input{width:100%;padding:4px 8px;border:1px solid #d1d5db;border-radius:4px;font-size:13px;outline:none;box-sizing:border-box}::ng-deep .wuic-sf-filter-popup-search-input:focus{border-color:#2563eb;box-shadow:0 0 0 2px #2563eb33}::ng-deep .wuic-sf-filter-popup-list-wrap{display:flex;flex-direction:column;max-height:260px;overflow:hidden}::ng-deep .wuic-sf-filter-popup-selectall{padding:4px 12px;border-bottom:1px solid #e5e7eb;background:#f9fafb;font-weight:500}::ng-deep .wuic-sf-filter-popup-list{flex:1 1 auto;overflow-y:auto;max-height:220px}::ng-deep .wuic-sf-filter-popup-item{display:flex;align-items:center;gap:6px;padding:4px 12px;cursor:pointer}::ng-deep .wuic-sf-filter-popup-item:hover{background:#f3f4f6}::ng-deep .wuic-sf-filter-popup-item input[type=checkbox]{margin:0;flex:0 0 auto}::ng-deep .wuic-sf-filter-popup-item span{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}::ng-deep .wuic-sf-filter-popup-footer{display:flex;justify-content:flex-end;gap:8px;padding:8px 12px;border-top:1px solid #e5e7eb;background:#f9fafb}::ng-deep .wuic-sf-filter-popup-ok,::ng-deep .wuic-sf-filter-popup-cancel{padding:4px 14px;border:1px solid #d1d5db;border-radius:4px;background:#fff;cursor:pointer;font-size:13px}::ng-deep .wuic-sf-filter-popup-ok{background:#2563eb;color:#fff;border-color:#2563eb}::ng-deep .wuic-sf-filter-popup-ok:hover{background:#1d4ed8}::ng-deep .wuic-sf-filter-popup-cancel:hover{background:#f3f4f6}:host ::ng-deep .wuic-sf-header-filter-btn.wuic-sf-header-filter-btn--active{color:#2563eb}:host ::ng-deep .wuic-sf-header-filter-btn.wuic-sf-header-filter-btn--active svg{fill:#2563eb}::ng-deep .wuic-sf-filter-popup.wuic-sf-filter-popup--dark{background:#1f2937;color:#f3f4f6;border-color:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-header,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-footer,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-selectall{background:#111827;border-color:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-sort,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-clear,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-search{border-color:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-sort-btn,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-cancel{color:#f3f4f6;background:transparent;border-color:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-cancel{background:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-sort-btn:hover,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-item:hover,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-cancel:hover{background:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-search-input{background:#111827;color:#f3f4f6;border-color:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-close{color:#9ca3af}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-close:hover{color:#f3f4f6}::ng-deep .wuic-sf-cell-editor{box-sizing:border-box;padding:2px 6px;border:2px solid #2563eb;background:#fff;color:#111827;font-family:inherit;font-size:13px;outline:none;box-shadow:0 2px 8px #00000026;border-radius:2px}::ng-deep .wuic-sf-cell-editor:focus{border-color:#1d4ed8;box-shadow:0 0 0 2px #2563eb40}::ng-deep select.wuic-sf-cell-editor{cursor:pointer}::ng-deep .wuic-sf-cell-editor.wuic-sf-cell-editor--dark{background:#1f2937;color:#f3f4f6;border-color:#2563eb}::ng-deep .wuic-sf-cell-editor.wuic-sf-cell-editor--dark option{background:#1f2937;color:#f3f4f6}\n"] }]
4301
+ ], template: "<!--\n Layout root wrapper:\n 1. Toolbar WUIC custom (insert / save batch / export / refresh)\n 2. Syncfusion Spreadsheet host\n 3. Pagination custom (Syncfusion non ha pagination built-in, quindi la\n rendiamo sotto il foglio con nav + page-size dropdown)\n 4. Lookup datasource (invisibile, pattern identico al vecchio componente)\n-->\n\n<!-- Bootstrap CSS Syncfusion (~500 KiB): si monta solo quando questo\n componente lazy \u00E8 istanziato \u2192 CSS NON nel bundle iniziale globale,\n risparmia ~500 KiB su tutte le pagine che non aprono spreadsheet. -->\n<wuic-syncfusion-styles-bootstrap></wuic-syncfusion-styles-bootstrap>\n\n<div class=\"wuic-sf-spreadsheet-wrapper\">\n\n @if (!hideToolbar && metaInfo) {\n <!-- Stile allineato a <wuic-list-grid>: stessa caption-bar, stessi p-button\n PrimeNG con icona + label, stesso componente import/export. -->\n <div class=\"grid-caption-bar wuic-sf-caption-bar\" role=\"toolbar\" aria-label=\"Spreadsheet toolbar\">\n <div class=\"grid-caption-left\">\n @if (!metaInfo.tableMetadata.md_hide_refresh) {\n <p-button icon=\"pi pi-refresh\" label=\"{{'refresh_grid' | translate}}\"\n (onClick)=\"onToolbarRefresh()\" />\n }\n @if (canInsertRows()) {\n <p-button icon=\"pi pi-plus\" label=\"{{'add_grid' | translate}}\"\n (onClick)=\"onToolbarInsert()\" />\n }\n @if (!metaInfo.tableMetadata.md_hide_export_xls) {\n <!-- Include dialog con progress bar + bottoni \"Stoppa e salva\n parziale\" / \"Stoppa\" / \"Continua in background\". -->\n <wuic-import-export-button\n [routeName]=\"metaInfo.tableMetadata.md_route_name\"\n [tableMetadata]=\"metaInfo.tableMetadata\"\n [filterInfoProvider]=\"getCurrentFilterInfoForExport\"></wuic-import-export-button>\n }\n @if (isBatchSaveEnabled()) {\n <p-button icon=\"pi pi-save\" label=\"{{'list_grid.save_changes' | translate}}\"\n (onClick)=\"onToolbarSaveBatch()\" />\n }\n </div>\n </div>\n }\n\n <div class=\"wuic-sf-spreadsheet-host\">\n @if (metaInfo && sfSheets.length > 0) {\n <ejs-spreadsheet #sfSpreadsheet\n [sheets]=\"sfSheets\"\n [height]=\"sfHeight\"\n [width]=\"sfWidth\"\n [scrollSettings]=\"sfScrollSettings\"\n [showRibbon]=\"showRibbon\"\n [showFormulaBar]=\"showFormulaBar\"\n [showSheetTabs]=\"false\"\n [allowEditing]=\"true\"\n [allowSorting]=\"true\"\n [allowFiltering]=\"false\"\n [allowResizing]=\"true\"\n [allowInsert]=\"canInsertRows()\"\n [allowDelete]=\"canDeleteRows()\"\n [enableContextMenu]=\"true\"\n [enableKeyboardNavigation]=\"true\"\n [enableClipboard]=\"true\"\n (created)=\"onSyncfusionCreated()\"\n (dataBound)=\"onSyncfusionDataBound()\"\n (cellSave)=\"onSyncfusionCellSave($event)\"\n (beforeCellSave)=\"onSyncfusionBeforeCellSave($event)\"\n (beforeCellRender)=\"onSyncfusionBeforeCellRender($event)\"\n (cellEdit)=\"onSyncfusionCellEdit($event)\"\n (contextMenuBeforeOpen)=\"onSyncfusionContextMenuBeforeOpen($event)\"\n (contextMenuItemSelect)=\"onSyncfusionContextMenuItemSelect($event)\"\n (actionBegin)=\"onSyncfusionActionBegin($event)\"\n (actionComplete)=\"onSyncfusionActionComplete($event)\"\n (dialogBeforeOpen)=\"onSyncfusionDialogBeforeOpen($event)\">\n </ejs-spreadsheet>\n }\n </div>\n\n @if (totalPages > 1 || totalRecords > pageSize) {\n <div class=\"wuic-sf-pagination\" role=\"navigation\" aria-label=\"Paginazione\">\n <button type=\"button\" class=\"wuic-sf-page-btn\"\n [disabled]=\"pageIndex <= 1\"\n (click)=\"isServerSide ? handleServerPageChange(1, pageSize) : setClientPage(1)\"\n title=\"Prima pagina\">\n <i class=\"pi pi-angle-double-left\"></i>\n </button>\n <button type=\"button\" class=\"wuic-sf-page-btn\"\n [disabled]=\"pageIndex <= 1\"\n (click)=\"isServerSide ? handleServerPageChange(pageIndex - 1, pageSize) : setClientPage(pageIndex - 1)\"\n title=\"Pagina precedente\">\n <i class=\"pi pi-angle-left\"></i>\n </button>\n <span class=\"wuic-sf-page-info\">\n Pagina <strong>{{ pageIndex }}</strong> di <strong>{{ totalPages }}</strong>\n <span class=\"wuic-sf-page-total\">({{ totalRecords }} record)</span>\n </span>\n <button type=\"button\" class=\"wuic-sf-page-btn\"\n [disabled]=\"pageIndex >= totalPages\"\n (click)=\"isServerSide ? handleServerPageChange(pageIndex + 1, pageSize) : setClientPage(pageIndex + 1)\"\n title=\"Pagina successiva\">\n <i class=\"pi pi-angle-right\"></i>\n </button>\n <button type=\"button\" class=\"wuic-sf-page-btn\"\n [disabled]=\"pageIndex >= totalPages\"\n (click)=\"isServerSide ? handleServerPageChange(totalPages, pageSize) : setClientPage(totalPages)\"\n title=\"Ultima pagina\">\n <i class=\"pi pi-angle-double-right\"></i>\n </button>\n <select class=\"wuic-sf-page-size\" [value]=\"pageSize\"\n (change)=\"setPageSize($any($event.target).value)\"\n title=\"Record per pagina\">\n <option [value]=\"10\">10</option>\n <option [value]=\"25\">25</option>\n <option [value]=\"50\">50</option>\n <option [value]=\"100\">100</option>\n </select>\n </div>\n }\n\n</div>\n\n@if (!hardcodedRoute) {\n <wuic-data-source #lookupSource [autoload]=\"true\" [routeFromRouting]=\"false\"\n [route]=\"currentRoute\" [parentRecord]=\"currentRecord\"\n [parentMetaInfo]=\"metaInfo\"></wuic-data-source>\n}\n", styles: [":host{display:block;width:100%;height:100%;min-height:0}.wuic-sf-spreadsheet-wrapper{display:flex;flex-direction:column;width:100%;height:100%;min-height:0;overflow:hidden}.wuic-sf-caption-bar{flex:0 0 auto;padding:5px}.grid-caption-left{display:flex;align-items:stretch;gap:.5rem;flex-wrap:nowrap;min-height:2.25rem;height:2.25rem}.wuic-sf-spreadsheet-host{flex:1 1 auto;min-height:0;width:100%;position:relative;overflow:visible}::ng-deep .e-dialog.e-popup-open,::ng-deep .e-excelfilter,::ng-deep .e-filter-popup{z-index:10010!important;min-width:240px}::ng-deep .e-excelfilter .e-footer-content,::ng-deep .e-filter-popup .e-footer-content{display:flex!important;justify-content:flex-end;gap:6px;padding:8px 12px!important}.wuic-sf-spreadsheet-host ejs-spreadsheet{display:block;width:100%;height:100%}:host ::ng-deep .e-spreadsheet .e-column-header .e-header-cell.wuic-sf-colname-header .e-header-cell-div{font-weight:700!important;text-align:left!important;padding-left:6px!important;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wuic-sf-pagination{display:flex;gap:6px;align-items:center;justify-content:flex-end;padding:6px 10px;background-color:var(--wuic-surface-a, #ffffff);border-top:1px solid var(--wuic-surface-border, #e5e7eb);flex:0 0 auto;font-size:13px}.wuic-sf-page-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;border:1px solid var(--wuic-surface-border, #e5e7eb);border-radius:4px;background:transparent;cursor:pointer;color:var(--wuic-text-color, #1f2937);transition:background-color .12s ease,border-color .12s ease}.wuic-sf-page-btn:hover:not(:disabled){background-color:var(--wuic-hover-bg, #f3f4f6);border-color:var(--wuic-primary-color, #6366f1)}.wuic-sf-page-btn:disabled{opacity:.35;cursor:not-allowed}.wuic-sf-page-info{margin:0 8px;white-space:nowrap;color:var(--wuic-text-muted, #6b7280)}.wuic-sf-page-info strong{color:var(--wuic-text-color, #1f2937)}.wuic-sf-page-total{margin-left:6px;font-size:12px;color:var(--wuic-text-muted, #6b7280)}.wuic-sf-page-size{margin-left:8px;height:30px;padding:0 8px;border:1px solid var(--wuic-surface-border, #e5e7eb);border-radius:4px;background:transparent;color:var(--wuic-text-color, #1f2937);cursor:pointer}:host-context(.theme-dark) .wuic-sf-pagination,:host-context([data-theme=\"dark\"]) .wuic-sf-pagination{background-color:var(--wuic-surface-a-dark, #1f2937);border-color:var(--wuic-surface-border-dark, #374151)}:host-context(.theme-dark) .wuic-sf-page-btn,:host-context([data-theme=\"dark\"]) .wuic-sf-page-btn,:host-context(.theme-dark) .wuic-sf-page-size,:host-context([data-theme=\"dark\"]) .wuic-sf-page-size{color:#f3f4f6;border-color:var(--wuic-surface-border-dark, #374151)}:host-context(.theme-dark) .wuic-sf-page-btn:hover:not(:disabled),:host-context([data-theme=\"dark\"]) .wuic-sf-page-btn:hover:not(:disabled){background-color:#374151}:host-context(.theme-dark) .wuic-sf-page-info,:host-context([data-theme=\"dark\"]) .wuic-sf-page-info{color:#9ca3af}:host-context(.theme-dark) .wuic-sf-page-info strong,:host-context([data-theme=\"dark\"]) .wuic-sf-page-info strong{color:#f3f4f6}:host-context(.theme-dark) .wuic-sf-page-total,:host-context([data-theme=\"dark\"]) .wuic-sf-page-total{color:#9ca3af}:host ::ng-deep .e-spreadsheet{font-family:var(--wuic-font-family, inherit)}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-column-header .e-header-cell.wuic-sf-colname-header{background-color:#111827!important;color:#e5e7eb!important}:host ::ng-deep .e-spreadsheet .e-sheet-content .e-filter-btn{opacity:0!important;pointer-events:none!important;width:0!important;height:0!important;min-width:0!important;padding:0!important;margin:0!important;overflow:hidden!important;position:absolute!important;right:0;top:0}:host ::ng-deep .e-spreadsheet .e-column-header .e-header-cell.wuic-sf-colname-header{position:relative!important}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-header-filter-btn{position:absolute;right:2px;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;padding:0;background:transparent;border:none;border-radius:3px;cursor:pointer;color:#475569;flex-shrink:0;transition:background-color .12s,color .12s;z-index:2}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-header-filter-btn:hover{background-color:#e2e8f0;color:#1f2937}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-header-filter-btn svg{display:block;pointer-events:none}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-header-filter-btn svg *{pointer-events:none!important}:host ::ng-deep .e-spreadsheet svg,:host ::ng-deep .e-spreadsheet svg *{pointer-events:none!important}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-header-filter-btn.wuic-filtered{color:#2563eb}:host ::ng-deep .e-spreadsheet .e-column-header .wuic-sf-colname-header .e-header-cell-div{padding-right:22px!important}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-column-header .wuic-sf-header-filter-btn{color:#cbd5e1}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-column-header .wuic-sf-header-filter-btn:hover{background-color:#334155;color:#f3f4f6}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-column-header .wuic-sf-header-filter-btn.wuic-filtered{color:#60a5fa}:host ::ng-deep .e-spreadsheet .e-sheet-panel{background-color:var(--wuic-surface-a, #ffffff)}:host-context(.theme-dark) ::ng-deep .e-spreadsheet,:host-context([data-theme=\"dark\"]) ::ng-deep .e-spreadsheet,:host ::ng-deep ejs-spreadsheet.wuic-sf-dark{background-color:var(--wuic-surface-a-dark, #1f2937);color:#f3f4f6}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-sheet-panel,:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-main-panel{background-color:var(--wuic-surface-a-dark, #1f2937);color:#f3f4f6}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-cell,:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-header-cell{background-color:var(--wuic-surface-a-dark, #1f2937);border-color:var(--wuic-surface-border-dark, #374151);color:#f3f4f6}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-header-row .e-header-cell,:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-row-header .e-header-cell{background-color:#111827!important;color:#e5e7eb!important}:host ::ng-deep ejs-spreadsheet.wuic-sf-dark .e-cell.row-danger{background-color:#7f1d1d!important;color:#fecaca!important}:host ::ng-deep .e-cell.row-danger{background-color:#fee2e2!important}:host ::ng-deep .e-cell.row-warning{background-color:#fef3c7!important}:host ::ng-deep .e-cell.row-success{background-color:#d1fae5!important}::ng-deep .wuic-sf-filter-popup{position:fixed;z-index:99999;min-width:260px;max-width:340px;background:#fff;color:#111827;border:1px solid #d1d5db;border-radius:6px;box-shadow:0 8px 24px #0000002e;font-family:inherit;font-size:13px;display:flex;flex-direction:column;overflow:hidden;-webkit-user-select:none;user-select:none}::ng-deep .wuic-sf-filter-popup-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:#f3f4f6;border-bottom:1px solid #e5e7eb;font-weight:600}::ng-deep .wuic-sf-filter-popup-title{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}::ng-deep .wuic-sf-filter-popup-close{background:transparent;border:none;cursor:pointer;font-size:18px;line-height:1;padding:0 4px;color:#6b7280}::ng-deep .wuic-sf-filter-popup-close:hover{color:#111827}::ng-deep .wuic-sf-filter-popup-sort{display:flex;flex-direction:column;border-bottom:1px solid #e5e7eb}::ng-deep .wuic-sf-filter-popup-sort-btn{background:transparent;border:none;text-align:left;padding:6px 12px;cursor:pointer;font-size:13px;color:#111827;display:flex;align-items:center;gap:6px}::ng-deep .wuic-sf-filter-popup-sort-btn:hover{background:#f3f4f6}::ng-deep .wuic-sf-filter-popup-sort-icon{font-size:10px;color:#6b7280}::ng-deep .wuic-sf-filter-popup-clear{border-bottom:1px solid #e5e7eb}::ng-deep .wuic-sf-filter-popup-clear-btn{width:100%;background:transparent;border:none;text-align:left;padding:6px 12px;cursor:pointer;font-size:13px;color:#2563eb}::ng-deep .wuic-sf-filter-popup-clear-btn:hover:not(:disabled){background:#eff6ff}::ng-deep .wuic-sf-filter-popup-clear-btn:disabled{color:#9ca3af;cursor:not-allowed}::ng-deep .wuic-sf-filter-popup-search{padding:8px 12px;border-bottom:1px solid #e5e7eb}::ng-deep .wuic-sf-filter-popup-search-input{width:100%;padding:4px 8px;border:1px solid #d1d5db;border-radius:4px;font-size:13px;outline:none;box-sizing:border-box}::ng-deep .wuic-sf-filter-popup-search-input:focus{border-color:#2563eb;box-shadow:0 0 0 2px #2563eb33}::ng-deep .wuic-sf-filter-popup-list-wrap{display:flex;flex-direction:column;max-height:260px;overflow:hidden}::ng-deep .wuic-sf-filter-popup-selectall{padding:4px 12px;border-bottom:1px solid #e5e7eb;background:#f9fafb;font-weight:500}::ng-deep .wuic-sf-filter-popup-list{flex:1 1 auto;overflow-y:auto;max-height:220px}::ng-deep .wuic-sf-filter-popup-item{display:flex;align-items:center;gap:6px;padding:4px 12px;cursor:pointer}::ng-deep .wuic-sf-filter-popup-item:hover{background:#f3f4f6}::ng-deep .wuic-sf-filter-popup-item input[type=checkbox]{margin:0;flex:0 0 auto}::ng-deep .wuic-sf-filter-popup-item span{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}::ng-deep .wuic-sf-filter-popup-footer{display:flex;justify-content:flex-end;gap:8px;padding:8px 12px;border-top:1px solid #e5e7eb;background:#f9fafb}::ng-deep .wuic-sf-filter-popup-ok,::ng-deep .wuic-sf-filter-popup-cancel{padding:4px 14px;border:1px solid #d1d5db;border-radius:4px;background:#fff;cursor:pointer;font-size:13px}::ng-deep .wuic-sf-filter-popup-ok{background:#2563eb;color:#fff;border-color:#2563eb}::ng-deep .wuic-sf-filter-popup-ok:hover{background:#1d4ed8}::ng-deep .wuic-sf-filter-popup-cancel:hover{background:#f3f4f6}:host ::ng-deep .wuic-sf-header-filter-btn.wuic-sf-header-filter-btn--active{color:#2563eb}:host ::ng-deep .wuic-sf-header-filter-btn.wuic-sf-header-filter-btn--active svg{fill:#2563eb}::ng-deep .wuic-sf-filter-popup.wuic-sf-filter-popup--dark{background:#1f2937;color:#f3f4f6;border-color:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-header,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-footer,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-selectall{background:#111827;border-color:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-sort,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-clear,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-search{border-color:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-sort-btn,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-cancel{color:#f3f4f6;background:transparent;border-color:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-cancel{background:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-sort-btn:hover,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-item:hover,::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-cancel:hover{background:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-search-input{background:#111827;color:#f3f4f6;border-color:#374151}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-close{color:#9ca3af}::ng-deep .wuic-sf-filter-popup--dark .wuic-sf-filter-popup-close:hover{color:#f3f4f6}::ng-deep .wuic-sf-cell-editor{box-sizing:border-box;padding:2px 6px;border:2px solid #2563eb;background:#fff;color:#111827;font-family:inherit;font-size:13px;outline:none;box-shadow:0 2px 8px #00000026;border-radius:2px}::ng-deep .wuic-sf-cell-editor:focus{border-color:#1d4ed8;box-shadow:0 0 0 2px #2563eb40}::ng-deep select.wuic-sf-cell-editor{cursor:pointer}::ng-deep .wuic-sf-cell-editor.wuic-sf-cell-editor--dark{background:#1f2937;color:#f3f4f6;border-color:#2563eb}::ng-deep .wuic-sf-cell-editor.wuic-sf-cell-editor--dark option{background:#1f2937;color:#f3f4f6}\n"] }]
3861
4302
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i1.Title }, { type: i1$1.ActivatedRoute }, { type: i1$2.HttpClient }, { type: WtoolboxService }, { type: MetadataProviderService }, { type: UserInfoService }], propDecorators: { sfSpreadsheet: [{
3862
4303
  type: ViewChild,
3863
4304
  args: ['sfSpreadsheet']
@@ -3876,6 +4317,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
3876
4317
  type: Input
3877
4318
  }], hideToolbar: [{
3878
4319
  type: Input
4320
+ }], enableLockAwareness: [{
4321
+ type: Input
4322
+ }], lockScope: [{
4323
+ type: Input
4324
+ }], heartbeatIntervalSec: [{
4325
+ type: Input
4326
+ }], showFormulaBar: [{
4327
+ type: Input
4328
+ }], showRibbon: [{
4329
+ type: Input
3879
4330
  }], onSpreadsheetDataBound: [{
3880
4331
  type: Output
3881
4332
  }], onSpreadsheetBeforePageChange: [{
@@ -3888,10 +4339,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
3888
4339
  type: Output
3889
4340
  }], onSpreadsheetBatchSaved: [{
3890
4341
  type: Output
4342
+ }], onLockAcquired: [{
4343
+ type: Output
4344
+ }], onLockConflict: [{
4345
+ type: Output
4346
+ }], onLockReleased: [{
4347
+ type: Output
4348
+ }], onLockExpired: [{
4349
+ type: Output
3891
4350
  }], onWindowResize: [{
3892
4351
  type: HostListener,
3893
4352
  args: ['window:resize']
3894
4353
  }] } });
3895
4354
 
3896
4355
  export { SpreadsheetListSfComponent };
3897
- //# sourceMappingURL=wuic-framework-lib-spreadsheet-list-sf.component-DXyRX0YK.mjs.map
4356
+ //# sourceMappingURL=wuic-framework-lib-spreadsheet-list-sf.component-D9P9WbDl.mjs.map