wuic-framework-lib 1.0.11 → 1.0.20

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-vuzmQW9F.mjs → wuic-framework-lib-chart-list.component-B44k_wZe.mjs} +79 -19
  2. package/fesm2022/{wuic-framework-lib-code-editor.component-BjbIkm4g.mjs → wuic-framework-lib-code-editor.component-D1dy4Dl9.mjs} +261 -261
  3. package/fesm2022/{wuic-framework-lib-designer.component-BM3elB-a.mjs → wuic-framework-lib-designer.component-CnOQbvEH.mjs} +178 -151
  4. package/fesm2022/{wuic-framework-lib-first-run-wizard.component-5tcl67dK.mjs → wuic-framework-lib-first-run-wizard.component-C9lswoxG.mjs} +248 -42
  5. package/fesm2022/{wuic-framework-lib-framework-docs.component-BGcJ7k1X.mjs → wuic-framework-lib-framework-docs.component-B6qNTs5H.mjs} +830 -121
  6. package/fesm2022/{wuic-framework-lib-pivot-builder.component-ChNyMAfi.mjs → wuic-framework-lib-pivot-builder.component-DMk26H0M.mjs} +475 -100
  7. package/fesm2022/{wuic-framework-lib-prompt-dialog-fallback-host.component-DFLR_EmK.mjs → wuic-framework-lib-prompt-dialog-fallback-host.component-y3shST6g.mjs} +5 -5
  8. package/fesm2022/{wuic-framework-lib-report-designer.component-B_SC7VkW.mjs → wuic-framework-lib-report-designer.component-BnngR2eL.mjs} +15 -15
  9. package/fesm2022/{wuic-framework-lib-report-viewer.component-BTK-p8xF.mjs → wuic-framework-lib-report-viewer.component-LGNRIQh2.mjs} +82 -12
  10. package/fesm2022/{wuic-framework-lib-scheduler-list.component-CIoGVf4O.mjs → wuic-framework-lib-scheduler-list.component-DbcFMwx5.mjs} +42 -19
  11. package/fesm2022/{wuic-framework-lib-spreadsheet-list-sf.component-DQ5OxO4j.mjs → wuic-framework-lib-spreadsheet-list-sf.component-DXyRX0YK.mjs} +25 -11
  12. package/fesm2022/{wuic-framework-lib-workflow-designer.component-BHLT4MER.mjs → wuic-framework-lib-workflow-designer.component-Bm719AWV.mjs} +9 -9
  13. package/fesm2022/{wuic-framework-lib-wuic-framework-lib-DDokY6wY.mjs → wuic-framework-lib-wuic-framework-lib-CGCZvtKK.mjs} +3795 -1466
  14. package/fesm2022/wuic-framework-lib.mjs +1 -1
  15. package/package.json +1 -1
  16. package/types/wuic-framework-lib.d.ts +647 -26
@@ -14,14 +14,14 @@ import * as i3 from 'primeng/tooltip';
14
14
  import { TooltipModule } from 'primeng/tooltip';
15
15
  import * as i1$3 from 'primeng/image';
16
16
  import { ImageModule } from 'primeng/image';
17
- import * as i2 from '@ngx-translate/core';
17
+ import * as i8 from '@ngx-translate/core';
18
18
  import { TranslateService, TranslateModule } from '@ngx-translate/core';
19
- import { q as getThemeOptions, T as TranslationManagerService, U as UserInfoService } from './wuic-framework-lib-wuic-framework-lib-DDokY6wY.mjs';
19
+ import { r as getThemeOptions, T as TranslationManagerService, U as UserInfoService } from './wuic-framework-lib-wuic-framework-lib-CGCZvtKK.mjs';
20
20
  import * as i1 from '@angular/router';
21
21
 
22
22
  const frameworkDocsContent = {
23
23
  "version": "1.0.0",
24
- "generatedAt": "2026-04-27T15:49:14.823Z",
24
+ "generatedAt": "2026-05-09T18:45:03.961Z",
25
25
  "groups": [
26
26
  {
27
27
  "id": "primi-passi",
@@ -1791,26 +1791,34 @@ const frameworkDocsContent = {
1791
1791
  },
1792
1792
  {
1793
1793
  "id": "sec_6",
1794
- "title": "md_props_bag: archetypes.list"
1794
+ "title": "md_props_bag: toolbar"
1795
1795
  },
1796
1796
  {
1797
1797
  "id": "sec_7",
1798
- "title": "Paging: md_pagesize e md_page_size_choice"
1798
+ "title": "md_props_bag: archetypes.list"
1799
1799
  },
1800
1800
  {
1801
1801
  "id": "sec_8",
1802
- "title": "Forzatura virtualizzazione su page size alto"
1802
+ "title": "Paging: md_pagesize e md_page_size_choice"
1803
1803
  },
1804
1804
  {
1805
1805
  "id": "sec_9",
1806
+ "title": "Forzatura virtualizzazione su page size alto"
1807
+ },
1808
+ {
1809
+ "id": "sec_10",
1806
1810
  "title": "Eventi e subscriptions (host)"
1811
+ },
1812
+ {
1813
+ "id": "sec_11",
1814
+ "title": "`rowCustomSelect` — signature reale (gotcha)"
1807
1815
  }
1808
1816
  ],
1809
1817
  "sections": [
1810
1818
  {
1811
1819
  "id": "overview",
1812
1820
  "title": "Overview",
1813
- "html": "<h1>List Grid</h1><p>Componente principale per liste tabellari con filtri, sorting, paging server-side e azioni riga.</p><h2>Use cases</h2><ul><li>CRUD tabellari enterprise.</li><li>Report operativi con filtri multi-colonna.</li><li>Dataset molto grandi con <code>cursorMode</code>.</li></ul><h2>Screenshot reference (manuale utente)</h2><ul><li><code>manual__grid__01.png</code>: modalita <code>inline cell edit</code> in list-grid.</li><li><code>manual__grid__02.png</code>: modalita <code>inline edit</code> (row-level) in list-grid.</li></ul><h2>Metadati inline editing</h2><p>Configurazione nel <code>md_props_bag</code> della tabella metadata:</p><ul><li><code>md_inline_edit</code></li></ul><p> - abilita l&#39;editing inline della riga in list-grid (celle editabili in contesto riga, senza apertura popup).</p><p> - utile quando vuoi mantenere la UX di tabella con editing rapido per record.</p><ul><li><code>md_inline_cell_edit</code></li></ul><p> - abilita l&#39;editing inline &quot;cell-by-cell&quot; (focus sulla singola cella).</p><p> - nota: nelle configurazioni legacy puo comparire come <code>md_inline_cell_editing</code>; il comportamento runtime e lo stesso.</p><p> - <strong>promozione runtime</strong>: quando <code>md_inline_cell_edit</code> e <code>true</code>, il componente forza</p><p> a runtime anche <code>md_inline_edit = true</code> indipendentemente dal valore in DB.</p><p> Entrambe le UX hanno bisogno della action column visibile, quindi sono mutualmente</p><p> non-esclusive a livello di rendering.</p><ul><li><code>md_batch_save</code></li></ul><p> - abilita il salvataggio batch delle modifiche pendenti (<code>Save changes</code> / <code>Cancel changes</code>).</p><p> - se usato insieme a inline-cell, le modifiche restano pending fino al salvataggio esplicito.</p><p> - <strong>prerequisito</strong>: <code>md_batch_save</code> ha effetto <strong>solo</strong> se <code>md_inline_cell_edit</code> e <code>true</code>.</p><p> Con <code>md_inline_cell_edit:false</code> il flag viene ignorato a runtime e i bottoni</p><p> &quot;Salva modifiche / Annulla modifiche&quot; non vengono renderizzati.</p><h3>Combinazioni valide</h3><table><thead><tr><th><code>md_inline_edit</code></th><th><code>md_inline_cell_edit</code></th><th><code>md_batch_save</code></th><th>Risultato runtime</th></tr></thead><tbody><tr><td><code>true</code></td><td><code>false</code></td><td>qualsiasi</td><td>Row-level inline edit con pencil. <code>md_batch_save</code> ignorato.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>false</code></td><td>Cell-by-cell autosave on blur. <code>md_inline_edit</code> forzato a <code>true</code> a runtime.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>true</code></td><td>Cell-by-cell con buffer pending + toolbar <code>Salva / Annulla modifiche</code>.</td></tr><tr><td><code>false</code></td><td><code>false</code></td><td>qualsiasi</td><td>Nessun inline editing. <code>md_batch_save</code> ignorato.</td></tr></tbody></table><p>Esempio row-level inline edit:</p><p>Snippet 1:</p><p>Esempio cell-by-cell con batch save:</p><p>Snippet 2:</p><h2>Toolbar export/import</h2><ul><li>Export XLS:</li></ul><p> - mostra dialog di progress con percentuale realtime;</p><p> - azioni: <code>Continua in background</code>, <code>Annulla task</code>, <code>Interrompi e scarica parziale</code>;</p><p> - in background crea una notifica con progress; click riapre il dialog.</p><ul><li>Import XLS/XLSX (se <code>md_importable = true</code>):</li></ul><p> - dialog di progress dopo la conferma;</p><p> - azioni: <code>Continua in background</code>, <code>Annulla import (rollback)</code>, <code>Stop e commit parziale</code>;</p><p> - a fine import crea una notifica di riepilogo che porta alla route.</p><h2>Config metadata</h2><p>Impostazioni chiave in <code>md_props_bag</code> e metadati colonna.</p><p>Snippet 3:</p><h2>md_props_bag: archetypes.list</h2><p>Il componente legge <code>md_props_bag.archetypes.list</code>.</p><p>Snippet 4:</p><ul><li><code>proportionalColwidth</code>:</li><li>tipo: boolean (con parser tollerante)</li><li>valori accettati: <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>&quot;true&quot;</code>, <code>&quot;false&quot;</code>, <code>&quot;1&quot;</code>, <code>&quot;0&quot;</code></li><li>default runtime: <code>true</code> quando assente o vuoto</li><li>effetto: distribuisce le larghezze colonna in percentuale (anziche px) quando non ci sono larghezze utente persistite.</li></ul><ul><li><code>virtualize</code>:</li><li>tipo: <code>boolean</code> oppure <code>object</code></li><li>valori accettati:</li><li><code>true</code>/<code>false</code>, <code>1</code>/<code>0</code>, <code>&quot;true&quot;</code>/<code>&quot;false&quot;</code>, <code>&quot;1&quot;</code>/<code>&quot;0&quot;</code></li><li>object con <code>enabled</code> (opzionale) e <code>itemSize</code> (opzionale)</li><li>default runtime: disabilitato (<code>false</code>) quando assente</li><li><code>enabled</code>:</li><li>default: <code>true</code> se il nodo <code>virtualize</code> e object senza <code>enabled</code>, altrimenti parser tollerante</li><li>effetto: abilita <code>virtualScroll</code> su <code>p-table</code></li><li><code>itemSize</code>:</li><li>default: <code>44</code></li><li>effetto: imposta <code>virtualScrollItemSize</code> (altezza riga virtuale in px)</li></ul><ul><li><code>advancedFilter</code>:</li><li>tipo: boolean (con parser tollerante)</li><li>default runtime: <code>false</code></li><li>effetto:</li><li>quando <code>true</code>, la griglia nasconde le icone filtro colonna (<code>p-columnFilter</code>) e usa la <code>wuic-filter-bar</code> sopra la tabella (a livello <code>data-repeater</code>/<code>bounded-repeater</code>) per applicare i filtri.</li></ul><p>Nota operativa:</p><ul><li>il suggest <code>md_props_bag</code> nel metadata editor espone anche il nodo checkabile <code>archetypes.list.advancedFilter</code>.</li></ul><h2>Paging: md_pagesize e md_page_size_choice</h2><p>La griglia usa <code>md_pagesize</code> come dimensione pagina di default e <code>md_page_size_choice</code> come elenco valori selezionabili nel paginator.</p><p>Regole runtime:</p><ul><li>se <code>md_pagesize</code> e maggiore del valore massimo presente in <code>md_page_size_choice</code>, il framework aggiunge automaticamente <code>md_pagesize</code> alla lista opzioni;</li><li>la lista viene normalizzata (numeri validi, deduplica, ordinamento crescente).</li></ul><p>Esempio:</p><ul><li><code>md_pagesize = 200</code></li><li><code>md_page_size_choice = &quot;10,25,50,100&quot;</code></li><li>risultato runtime: <code>10,25,50,100,200</code></li></ul><h2>Forzatura virtualizzazione su page size alto</h2><p>Quando l&#39;utente seleziona nel paginator un valore <code>pageSize &gt;= 1000</code>:</p><ul><li>la virtualizzazione viene forzata automaticamente anche se <code>md_props_bag.archetypes.list.virtualize</code> e assente o disabilitato;</li><li><code>virtualScrollItemSize</code> viene forzato al valore predefinito <code>44</code>.</li></ul><p>Questo comportamento protegge la resa della tabella su pagine molto grandi.</p><h2>Eventi e subscriptions (host)</h2><p><code>wuic-list-grid</code> espone eventi runtime utili per intercettare ciclo render e callback p-table lato progetto host.</p><p>Eventi disponibili:</p><ul><li><code>onAfterRender</code>: emesso a fine binding dati della griglia (<code>rows</code>, <code>totalRecords</code>, <code>metaInfo</code>, <code>datasource</code>).</li><li><code>onBeforeRowRender</code>: emesso prima del rendering logico della singola riga; supporta cancel tramite <code>event.cancelRender()</code>.</li><li><code>onAfterRowRender</code>: emesso dopo il rendering logico della singola riga.</li><li><code>onPaging</code>: emesso sugli eventi paging (<code>p-table onPage</code>).</li><li><code>onSorting</code>: emesso sugli eventi sorting (<code>p-table onSort</code>).</li><li><code>onFiltering</code>: emesso sugli eventi filtering (<code>p-table onFilter</code>).</li><li><code>onPTableSelectionChange</code>: emesso su cambio selezione righe.</li><li><code>onPTableRowExpand</code>: emesso su expand riga.</li><li><code>onPTableRowCollapse</code>: emesso su collapse riga.</li><li><code>onPTableColumnResize</code>: emesso su resize colonna.</li><li><code>onPTableColumnReorder</code>: emesso su reorder colonne.</li></ul><h3>Esempio 1: binding diretto nel template</h3><p>Snippet 5:</p><h3>Esempio 2: subscribe via ViewChild</h3><p>Snippet 6:</p>",
1821
+ "html": "<h1>List Grid</h1><p>Componente principale per liste tabellari con filtri, sorting, paging server-side e azioni riga.</p><h2>Use cases</h2><ul><li>CRUD tabellari enterprise.</li><li>Report operativi con filtri multi-colonna.</li><li>Dataset molto grandi con <code>cursorMode</code>.</li></ul><h2>Screenshot reference (manuale utente)</h2><ul><li><code>manual__grid__01.png</code>: modalita <code>inline cell edit</code> in list-grid.</li><li><code>manual__grid__02.png</code>: modalita <code>inline edit</code> (row-level) in list-grid.</li></ul><h2>Metadati inline editing</h2><p>Configurazione nel <code>md_props_bag</code> della tabella metadata:</p><ul><li><code>md_inline_edit</code></li></ul><p> - abilita l&#39;editing inline della riga in list-grid (celle editabili in contesto riga, senza apertura popup).</p><p> - utile quando vuoi mantenere la UX di tabella con editing rapido per record.</p><ul><li><code>md_inline_cell_edit</code></li></ul><p> - abilita l&#39;editing inline &quot;cell-by-cell&quot; (focus sulla singola cella).</p><p> - nota: nelle configurazioni legacy puo comparire come <code>md_inline_cell_editing</code>; il comportamento runtime e lo stesso.</p><p> - <strong>promozione runtime</strong>: quando <code>md_inline_cell_edit</code> e <code>true</code>, il componente forza</p><p> a runtime anche <code>md_inline_edit = true</code> indipendentemente dal valore in DB.</p><p> Entrambe le UX hanno bisogno della action column visibile, quindi sono mutualmente</p><p> non-esclusive a livello di rendering.</p><ul><li><code>md_batch_save</code></li></ul><p> - abilita il salvataggio batch delle modifiche pendenti (<code>Save changes</code> / <code>Cancel changes</code>).</p><p> - se usato insieme a inline-cell, le modifiche restano pending fino al salvataggio esplicito.</p><p> - <strong>prerequisito</strong>: <code>md_batch_save</code> ha effetto <strong>solo</strong> se <code>md_inline_cell_edit</code> e <code>true</code>.</p><p> Con <code>md_inline_cell_edit:false</code> il flag viene ignorato a runtime e i bottoni</p><p> &quot;Salva modifiche / Annulla modifiche&quot; non vengono renderizzati.</p><h3>Combinazioni valide</h3><table><thead><tr><th><code>md_inline_edit</code></th><th><code>md_inline_cell_edit</code></th><th><code>md_batch_save</code></th><th>Risultato runtime</th></tr></thead><tbody><tr><td><code>true</code></td><td><code>false</code></td><td>qualsiasi</td><td>Row-level inline edit con pencil. <code>md_batch_save</code> ignorato.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>false</code></td><td>Cell-by-cell autosave on blur. <code>md_inline_edit</code> forzato a <code>true</code> a runtime.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>true</code></td><td>Cell-by-cell con buffer pending + toolbar <code>Salva / Annulla modifiche</code>.</td></tr><tr><td><code>false</code></td><td><code>false</code></td><td>qualsiasi</td><td>Nessun inline editing. <code>md_batch_save</code> ignorato.</td></tr></tbody></table><p>Esempio row-level inline edit:</p><p>Snippet 1:</p><p>Esempio cell-by-cell con batch save:</p><p>Snippet 2:</p><h2>Toolbar export/import</h2><ul><li>Export XLS:</li></ul><p> - mostra dialog di progress con percentuale realtime;</p><p> - azioni: <code>Continua in background</code>, <code>Annulla task</code>, <code>Interrompi e scarica parziale</code>;</p><p> - in background crea una notifica con progress; click riapre il dialog.</p><ul><li>Import XLS/XLSX (se <code>md_importable = true</code>):</li></ul><p> - dialog di progress dopo la conferma;</p><p> - azioni: <code>Continua in background</code>, <code>Annulla import (rollback)</code>, <code>Stop e commit parziale</code>;</p><p> - a fine import crea una notifica di riepilogo che porta alla route.</p><h2>Config metadata</h2><p>Impostazioni chiave in <code>md_props_bag</code> e metadati colonna.</p><p>Snippet 3:</p><h2>md_props_bag: toolbar</h2><p>Flag opt-in che nascondono blocchi della toolbar della list-grid (<code>caption-bar</code>). Tutti sotto <code>md_props_bag.toolbar.*</code> (parsato in runtime come <code>tableMetadata.extraProps.toolbar</code>).</p><p>Snippet 4:</p><ul><li><code>hideManageState</code> (boolean, default <code>false</code>): nasconde nella <strong>caption-right</strong> il bottone &quot;Gestione stato&quot; (icona bookmark) + la <code>&lt;select&gt;</code> degli stati salvati. Utile per route hardcoded / demo dove il saved-state feature (persistenza per <code>user_id</code> + route via <code>MetaService</code>) non ha senso — es. Pattern 3 puro OData senza route metadata registrata.</li><li><code>hideBatchActions</code> (boolean, default <code>false</code>): nasconde nella <strong>caption-left</strong> i pulsanti &quot;Salva modifiche&quot; (<code>pi-save</code>) + &quot;Annulla modifiche&quot; (<code>pi-times</code>) + l&#39;indicatore conteggio changes (<code>grid-changes-indicator</code>, badge pencil + count). Generati dal framework quando <code>md_inline_cell_editing</code> + <code>md_batch_save</code> sono attivi. Pensato per <strong>nested grid in parametric-dialog</strong>: il save/cancel del padre persiste master + righe in un colpo via batch save framework, e i pulsanti duplicati sulla nested grid confondono l&#39;UX.</li></ul><p>Esempio nested rows in custom edit-form (runtime patch):</p><p>Snippet 5:</p><p>Tipo TS in <a href=\"../api/wuic-framework-lib.metadatitabella.html\">`metadati_tabella.ts`</a> (proprieta <code>extraProps.toolbar</code>). I flag NON disabilitano la logica <code>md_batch_save</code> sottostante (changes restano tracciate); rimuovono solo l&#39;UI toolbar — il save effettivo passa dal flusso del padre.</p><h2>md_props_bag: archetypes.list</h2><p>Il componente legge <code>md_props_bag.archetypes.list</code>.</p><p>Snippet 6:</p><ul><li><code>proportionalColwidth</code>:</li><li>tipo: boolean (con parser tollerante)</li><li>valori accettati: <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>&quot;true&quot;</code>, <code>&quot;false&quot;</code>, <code>&quot;1&quot;</code>, <code>&quot;0&quot;</code></li><li>default runtime: <code>true</code> quando assente o vuoto</li><li>effetto: distribuisce le larghezze colonna in percentuale (anziche px) quando non ci sono larghezze utente persistite.</li></ul><ul><li><code>virtualize</code>:</li><li>tipo: <code>boolean</code> oppure <code>object</code></li><li>valori accettati:</li><li><code>true</code>/<code>false</code>, <code>1</code>/<code>0</code>, <code>&quot;true&quot;</code>/<code>&quot;false&quot;</code>, <code>&quot;1&quot;</code>/<code>&quot;0&quot;</code></li><li>object con <code>enabled</code> (opzionale) e <code>itemSize</code> (opzionale)</li><li>default runtime: disabilitato (<code>false</code>) quando assente</li><li><code>enabled</code>:</li><li>default: <code>true</code> se il nodo <code>virtualize</code> e object senza <code>enabled</code>, altrimenti parser tollerante</li><li>effetto: abilita <code>virtualScroll</code> su <code>p-table</code></li><li><code>itemSize</code>:</li><li>default: <code>44</code></li><li>effetto: imposta <code>virtualScrollItemSize</code> (altezza riga virtuale in px)</li></ul><ul><li><code>advancedFilter</code>:</li><li>tipo: boolean (con parser tollerante)</li><li>default runtime: <code>false</code></li><li>effetto:</li><li>quando <code>true</code>, la griglia nasconde le icone filtro colonna (<code>p-columnFilter</code>) e usa la <code>wuic-filter-bar</code> sopra la tabella (a livello <code>data-repeater</code>/<code>bounded-repeater</code>) per applicare i filtri.</li></ul><p>Nota operativa:</p><ul><li>il suggest <code>md_props_bag</code> nel metadata editor espone anche il nodo checkabile <code>archetypes.list.advancedFilter</code>.</li></ul><h2>Paging: md_pagesize e md_page_size_choice</h2><p>La griglia usa <code>md_pagesize</code> come dimensione pagina di default e <code>md_page_size_choice</code> come elenco valori selezionabili nel paginator.</p><p>Regole runtime:</p><ul><li>se <code>md_pagesize</code> e maggiore del valore massimo presente in <code>md_page_size_choice</code>, il framework aggiunge automaticamente <code>md_pagesize</code> alla lista opzioni;</li><li>la lista viene normalizzata (numeri validi, deduplica, ordinamento crescente).</li></ul><p>Esempio:</p><ul><li><code>md_pagesize = 200</code></li><li><code>md_page_size_choice = &quot;10,25,50,100&quot;</code></li><li>risultato runtime: <code>10,25,50,100,200</code></li></ul><h2>Forzatura virtualizzazione su page size alto</h2><p>Quando l&#39;utente seleziona nel paginator un valore <code>pageSize &gt;= 1000</code>:</p><ul><li>la virtualizzazione viene forzata automaticamente anche se <code>md_props_bag.archetypes.list.virtualize</code> e assente o disabilitato;</li><li><code>virtualScrollItemSize</code> viene forzato al valore predefinito <code>44</code>.</li></ul><p>Questo comportamento protegge la resa della tabella su pagine molto grandi.</p><h2>Eventi e subscriptions (host)</h2><p><code>wuic-list-grid</code> espone eventi runtime utili per intercettare ciclo render e callback p-table lato progetto host.</p><p>Eventi disponibili:</p><ul><li><code>onAfterRender</code>: emesso a fine binding dati della griglia (<code>rows</code>, <code>totalRecords</code>, <code>metaInfo</code>, <code>datasource</code>).</li><li><code>onBeforeRowRender</code>: emesso prima del rendering logico della singola riga; supporta cancel tramite <code>event.cancelRender()</code>.</li><li><code>onAfterRowRender</code>: emesso dopo il rendering logico della singola riga.</li><li><code>onPaging</code>: emesso sugli eventi paging (<code>p-table onPage</code>).</li><li><code>onSorting</code>: emesso sugli eventi sorting (<code>p-table onSort</code>).</li><li><code>onFiltering</code>: emesso sugli eventi filtering (<code>p-table onFilter</code>).</li><li><code>onPTableSelectionChange</code>: emesso su cambio selezione righe.</li><li><code>onPTableRowExpand</code>: emesso su expand riga.</li><li><code>onPTableRowCollapse</code>: emesso su collapse riga.</li><li><code>onPTableColumnResize</code>: emesso su resize colonna.</li><li><code>onPTableColumnReorder</code>: emesso su reorder colonne.</li></ul><h3>Esempio 1: binding diretto nel template</h3><p>Snippet 7:</p><h3>Esempio 2: subscribe via ViewChild</h3><p>Snippet 8:</p><h2><code>rowCustomSelect</code> — signature reale (gotcha)</h2><p><code>&lt;wuic-list-grid&gt;</code> accetta un input <code>[rowCustomSelect]</code> per intercettare la selezione di una riga (apertura dialog &quot;scegli documento&quot;, master-detail, ecc.). La firma TypeScript dichiarata in <a href=\"../../src/lib/component/list-grid/list-grid.component.ts#L140\">list-grid.component.ts:140</a> è:</p><p>Snippet 9:</p><p><strong>Pero&#39; a runtime la callback viene invocata con argomenti invertiti</strong> — il framework chiama <code>rowCustomSelect($event, rowData, dt)</code> (vedi <a href=\"../../src/lib/component/dynamic-template/dynamic-template.component.ts#L447\">dynamic-template.component.ts:447</a> e <a href=\"../../src/lib/component/dynamic-template/dynamic-card-template.component.ts#L205\">dynamic-card-template.component.ts:205</a>).</p><p>Sintomo se sbagli ordine: <code>rowData?.id</code> e&#39; undefined → guard precoce nel handler → la callback ritorna senza side-effect e la dialog/azione non parte. Nessun errore in console.</p><p>Forma corretta (allineata all&#39;invocazione runtime):</p><p>Snippet 10:</p><p>Esempio integrato nel template:</p><p>Snippet 11:</p><p>&gt; Note: i test in <code>designer.component.spec.ts</code> (es. riga 512) chiamano la callback con <code>({currentTarget: rowCell}, {id: 42}, null)</code> confermando l&#39;ordine <code>($event, rowData, dt)</code>. Se in futuro il framework dovesse uniformare la signature dichiarata, si aggiornera&#39; anche questa pagina.</p>",
1814
1822
  "codeSamples": [
1815
1823
  {
1816
1824
  "id": "code_1",
@@ -1834,19 +1842,49 @@ const frameworkDocsContent = {
1834
1842
  "id": "code_4",
1835
1843
  "title": "Snippet 4",
1836
1844
  "language": "json",
1837
- "code": "{\n \"archetypes\": {\n \"list\": {\n \"proportionalColwidth\": true,\n \"advancedFilter\": false,\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n }\n }\n }\n}"
1845
+ "code": "{\n \"toolbar\": {\n \"hideManageState\": true,\n \"hideBatchActions\": true\n }\n}"
1838
1846
  },
1839
1847
  {
1840
1848
  "id": "code_5",
1841
1849
  "title": "Snippet 5",
1842
- "language": "html",
1843
- "code": "<wuic-list-grid\n [hardcodedDatasource]=\"citiesDs\"\n (onAfterRender)=\"handleAfterRender($event)\"\n (onBeforeRowRender)=\"handleBeforeRowRender($event)\"\n (onAfterRowRender)=\"handleAfterRowRender($event)\"\n (onPaging)=\"handlePaging($event)\"\n (onSorting)=\"handleSorting($event)\"\n (onFiltering)=\"handleFiltering($event)\"\n (onPTableSelectionChange)=\"handleSelection($event)\"\n (onPTableRowExpand)=\"handleRowExpand($event)\"\n (onPTableRowCollapse)=\"handleRowCollapse($event)\"\n (onPTableColumnResize)=\"handleColumnResize($event)\"\n (onPTableColumnReorder)=\"handleColumnReorder($event)\">\n</wuic-list-grid>"
1850
+ "language": "ts",
1851
+ "code": "this.righeDs.metaInfo.tableMetadata.extraProps = Object.assign(\n this.righeDs.metaInfo.tableMetadata.extraProps || {},\n { toolbar: { hideManageState: true, hideBatchActions: true } }\n) as any;"
1844
1852
  },
1845
1853
  {
1846
1854
  "id": "code_6",
1847
1855
  "title": "Snippet 6",
1856
+ "language": "json",
1857
+ "code": "{\n \"archetypes\": {\n \"list\": {\n \"proportionalColwidth\": true,\n \"advancedFilter\": false,\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n }\n }\n }\n}"
1858
+ },
1859
+ {
1860
+ "id": "code_7",
1861
+ "title": "Snippet 7",
1862
+ "language": "html",
1863
+ "code": "<wuic-list-grid\n [hardcodedDatasource]=\"citiesDs\"\n (onAfterRender)=\"handleAfterRender($event)\"\n (onBeforeRowRender)=\"handleBeforeRowRender($event)\"\n (onAfterRowRender)=\"handleAfterRowRender($event)\"\n (onPaging)=\"handlePaging($event)\"\n (onSorting)=\"handleSorting($event)\"\n (onFiltering)=\"handleFiltering($event)\"\n (onPTableSelectionChange)=\"handleSelection($event)\"\n (onPTableRowExpand)=\"handleRowExpand($event)\"\n (onPTableRowCollapse)=\"handleRowCollapse($event)\"\n (onPTableColumnResize)=\"handleColumnResize($event)\"\n (onPTableColumnReorder)=\"handleColumnReorder($event)\">\n</wuic-list-grid>"
1864
+ },
1865
+ {
1866
+ "id": "code_8",
1867
+ "title": "Snippet 8",
1848
1868
  "language": "ts",
1849
1869
  "code": "import { AfterViewInit, OnDestroy, ViewChild } from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport {\n ListGridAfterRenderEvent,\n ListGridAfterRowRenderEvent,\n ListGridBeforeRowRenderEvent,\n ListGridComponent\n} from 'wuic-framework-lib';\n\nexport class HostComponent implements AfterViewInit, OnDestroy {\n @ViewChild(ListGridComponent) grid?: ListGridComponent;\n private readonly subs = new Subscription();\n\n ngAfterViewInit(): void {\n if (!this.grid) return;\n\n this.subs.add(this.grid.onAfterRender.subscribe((e: ListGridAfterRenderEvent) => console.log(e)));\n this.subs.add(this.grid.onBeforeRowRender.subscribe((e: ListGridBeforeRowRenderEvent) => {\n // esempio cancel riga\n if (e.row?.deleted === true) e.cancelRender();\n }));\n this.subs.add(this.grid.onAfterRowRender.subscribe((e: ListGridAfterRowRenderEvent) => console.log(e)));\n this.subs.add(this.grid.onPaging.subscribe((e) => console.log('paging', e)));\n this.subs.add(this.grid.onSorting.subscribe((e) => console.log('sorting', e)));\n this.subs.add(this.grid.onFiltering.subscribe((e) => console.log('filtering', e)));\n this.subs.add(this.grid.onPTableSelectionChange.subscribe((e) => console.log('selection', e)));\n this.subs.add(this.grid.onPTableRowExpand.subscribe((e) => console.log('expand', e)));\n this.subs.add(this.grid.onPTableRowCollapse.subscribe((e) => console.log('collapse', e)));\n this.subs.add(this.grid.onPTableColumnResize.subscribe((e) => console.log('resize', e)));\n this.subs.add(this.grid.onPTableColumnReorder.subscribe((e) => console.log('reorder', e)));\n }\n\n ngOnDestroy(): void {\n this.subs.unsubscribe();\n }\n}"
1870
+ },
1871
+ {
1872
+ "id": "code_9",
1873
+ "title": "Snippet 9",
1874
+ "language": "ts",
1875
+ "code": "@Input() rowCustomSelect: (rowData: any, $event: any, dt: Table) => void;"
1876
+ },
1877
+ {
1878
+ "id": "code_10",
1879
+ "title": "Snippet 10",
1880
+ "language": "ts",
1881
+ "code": "onRowSelected = (_event: any, rowData: any, _dt: any): void => {\n // rowData e' il record dato dalla list-grid\n this.handleSelection(rowData);\n};"
1882
+ },
1883
+ {
1884
+ "id": "code_11",
1885
+ "title": "Snippet 11",
1886
+ "language": "html",
1887
+ "code": "<wuic-data-source #ds [hardcodedRoute]=\"'preventivi'\" [autoload]=\"true\"></wuic-data-source>\n<wuic-list-grid [hardcodedDatasource]=\"ds\" [rowCustomSelect]=\"onRowSelected\"></wuic-list-grid>"
1850
1888
  }
1851
1889
  ]
1852
1890
  }
@@ -2005,6 +2043,14 @@ const frameworkDocsContent = {
2005
2043
  },
2006
2044
  {
2007
2045
  "id": "sec_6",
2046
+ "title": "Personalizzazione marker per-record (colore + immagine/SVG)"
2047
+ },
2048
+ {
2049
+ "id": "sec_7",
2050
+ "title": "Polyline overlay (tracking percorsi GPS)"
2051
+ },
2052
+ {
2053
+ "id": "sec_8",
2008
2054
  "title": "Eventi e subscriptions (host)"
2009
2055
  }
2010
2056
  ],
@@ -2012,29 +2058,59 @@ const frameworkDocsContent = {
2012
2058
  {
2013
2059
  "id": "overview",
2014
2060
  "title": "Overview",
2015
- "html": "<h1>Map List</h1><p>Visualizzazione geografica con marker, cluster, filtri su boundaries e template marker custom.</p><h2>Screenshot reference (manuale utente)</h2><ul><li><code>manual__map__01.png</code>: overview mappa route con toolbar, marker e clustering.</li><li><code>manual__map__02.png</code>: configurazione avanzata archetype map (center/zoom/campi/info/template).</li></ul><h2>Use cases</h2><ul><li>Geo-localizzazione asset.</li><li>Tracking eventi geolocalizzati.</li><li>Heatmap o aggregazioni territoriali.</li></ul><h2>Differenza comportamento: <code>point</code> vs <code>polygon</code></h2><p>La mappa adatta automaticamente rendering e interazioni in base ai campi metadata con <code>mc_ui_column_type</code>.</p><ul><li>Se e presente almeno una colonna <code>point</code>:</li></ul><p> - ogni record viene renderizzato come marker (<code>map-advanced-marker</code>);</p><p> - supporta clustering (<code>useClusterer</code>);</p><p> - supporta drag marker (quando abilitato dalla toolbar);</p><p> - click su marker apre <code>infoWindow</code> con <code>titleField</code>/<code>infoField</code> e template.</p><ul><li>Se e presente una colonna <code>polygon</code>:</li></ul><p> - ogni record viene renderizzato come poligono (<code>map-polygon</code>);</p><p> - non usa marker clusterer;</p><p> - il filtro boundaries puo usare anche la geometria poligonale;</p><p> - click su poligono apre la stessa <code>infoWindow</code> dei marker (posizionata sul punto cliccato o, in fallback, sul centro geometrico).</p><ul><li>Se sono presenti entrambi (<code>point</code> e <code>polygon</code>):</li></ul><p> - il componente puo mostrare entrambi i layer;</p><p> - la toolbar privilegia le azioni coerenti col tipo principale (drag marker su scenari marker, edit polygons su scenari polygon-only);</p><p> - il filtro <code>maparea</code> usa il primo campo geografico disponibile tra <code>point</code>/<code>polygon</code>.</p><h2>Note operative</h2><p>Assicurarsi che i campi coordinate siano correttamente mappati nei metadata colonna.</p><h2>md_props_bag: archetypes.map</h2><p>Snippet 1:</p><ul><li><code>advancedFilter</code>: quando <code>true</code> mostra la <code>wuic-filter-bar</code> per l&#39;archetype <code>map</code> (a livello data-repeater).</li><li><code>zoom</code>: zoom iniziale.</li><li><code>center.lat</code>/<code>center.lng</code>: centro iniziale mappa; se mancanti viene usato il comportamento default del componente.</li><li><code>minZoom</code>/<code>maxZoom</code>: limiti zoom.</li><li><code>useCurrentLocation</code>: usa geolocalizzazione browser come centro.</li><li><code>useClusterer</code>: abilita clustering marker.</li><li><code>filterByBoundaries</code>: filtra i record visibili in base al bounding box corrente.</li><li><code>titleField</code>: campo metadata usato come titolo marker.</li><li><code>infoField</code>: campo metadata usato come info base marker.</li><li><code>customMarkerImageSrc</code>: URL marker fisso.</li><li><code>customMarkerImageSrcField</code>: campo metadata con URL marker per-record.</li><li><code>markerContentCallback</code>: callback JS per contenuto marker.</li><li><code>infoFunction</code>: callback JS per render info window. (Alternativo a itemTemplateString)</li><li><code>itemTemplateString</code>: template custom del contenuto. (Alternativo a infoFunction)</li></ul><h3>Contesto Template (<code>itemTemplateString</code>)</h3><p>Nel template custom sono disponibili questi input:</p><ul><li><code>record</code>: record corrente (in map archetype coincide con <code>rowData.record</code> quando presente).</li><li><code>rowData</code>: payload evento marker/poligono corrente.</li><li><code>metaInfo</code>: metadati tabella/colonne della datasource.</li><li><code>datasource</code>: riferimento al componente datasource associato.</li></ul><p>Contesto runtime usato dal framework:</p><p>Snippet 2:</p><h2>Eventi e subscriptions (host)</h2><p>Eventi disponibili su <code>wuic-map-list</code>:</p><ul><li><code>onMapClick</code>: click su mappa (<code>google-map mapClick</code>).</li><li><code>onMarkerClick</code>: click su marker.</li><li><code>onMarkerDragStart</code>: inizio drag marker.</li><li><code>onMarkerDragEnd</code>: fine drag marker (con posizione finale).</li><li><code>onPolygonClick</code>: click su poligono.</li><li><code>onPolygonShapeChangedEvent</code>: shape changed su poligono in edit mode.</li><li><code>onMapDataBound</code>: emesso quando il wrapper completa il binding dati dal datasource.</li></ul><p>Esempio template:</p><p>Snippet 3:</p><p>Esempio subscribe (ViewChild):</p><p>Snippet 4:</p>",
2061
+ "html": "<h1>Map List</h1><p>Visualizzazione geografica con marker, cluster, filtri su boundaries e template marker custom.</p><h2>Screenshot reference (manuale utente)</h2><ul><li><code>manual__map__01.png</code>: overview mappa route con toolbar, marker e clustering.</li><li><code>manual__map__02.png</code>: configurazione avanzata archetype map (center/zoom/campi/info/template).</li></ul><h2>Use cases</h2><ul><li>Geo-localizzazione asset.</li><li>Tracking eventi geolocalizzati.</li><li>Heatmap o aggregazioni territoriali.</li></ul><h2>Differenza comportamento: <code>point</code> vs <code>polygon</code></h2><p>La mappa adatta automaticamente rendering e interazioni in base ai campi metadata con <code>mc_ui_column_type</code>.</p><ul><li>Se e presente almeno una colonna <code>point</code>:</li></ul><p> - ogni record viene renderizzato come marker (<code>map-advanced-marker</code>);</p><p> - supporta clustering (<code>useClusterer</code>);</p><p> - supporta drag marker (quando abilitato dalla toolbar);</p><p> - click su marker apre <code>infoWindow</code> con <code>titleField</code>/<code>infoField</code> e template.</p><ul><li>Se e presente una colonna <code>polygon</code>:</li></ul><p> - ogni record viene renderizzato come poligono (<code>map-polygon</code>);</p><p> - non usa marker clusterer;</p><p> - il filtro boundaries puo usare anche la geometria poligonale;</p><p> - click su poligono apre la stessa <code>infoWindow</code> dei marker (posizionata sul punto cliccato o, in fallback, sul centro geometrico).</p><ul><li>Se sono presenti entrambi (<code>point</code> e <code>polygon</code>):</li></ul><p> - il componente puo mostrare entrambi i layer;</p><p> - la toolbar privilegia le azioni coerenti col tipo principale (drag marker su scenari marker, edit polygons su scenari polygon-only);</p><p> - il filtro <code>maparea</code> usa il primo campo geografico disponibile tra <code>point</code>/<code>polygon</code>.</p><h2>Note operative</h2><p>Assicurarsi che i campi coordinate siano correttamente mappati nei metadata colonna.</p><h2>md_props_bag: archetypes.map</h2><p>Snippet 1:</p><ul><li><code>advancedFilter</code>: quando <code>true</code> mostra la <code>wuic-filter-bar</code> per l&#39;archetype <code>map</code> (a livello data-repeater).</li><li><code>zoom</code>: zoom iniziale.</li><li><code>center.lat</code>/<code>center.lng</code>: centro iniziale mappa; se mancanti viene usato il comportamento default del componente.</li><li><code>minZoom</code>/<code>maxZoom</code>: limiti zoom.</li><li><code>useCurrentLocation</code>: usa geolocalizzazione browser come centro.</li><li><code>useClusterer</code>: abilita clustering marker.</li><li><code>filterByBoundaries</code>: filtra i record visibili in base al bounding box corrente.</li><li><code>titleField</code>: campo metadata usato come titolo marker.</li><li><code>infoField</code>: campo metadata usato come info base marker.</li><li><code>customMarkerImageSrc</code>: URL marker fisso (immagine o SVG inline applicata a tutti i marker).</li><li><code>customMarkerImageSrcField</code>: nome del campo record che contiene URL immagine <strong>o SVG inline</strong> (<code>&lt;svg ...&gt;...&lt;/svg&gt;</code>); auto-detect del formato. Vince su <code>markerColorField</code> e <code>markerContentCallback</code>.</li><li><code>markerColorField</code>: nome del campo record che contiene un colore CSS (<code>#rrggbb</code>); il marker viene reso come <code>PinElement</code> colorato. Ignorato quando <code>customMarkerImageSrcField</code> e&#39; valorizzato per quel record (immagine ha precedenza).</li><li><code>markerContentCallback</code>: callback JS per contenuto marker.</li><li><code>infoFunction</code>: callback JS per render info window. (Alternativo a itemTemplateString)</li><li><code>itemTemplateString</code>: template custom del contenuto. (Alternativo a infoFunction)</li><li><code>polyline</code>: overlay polyline per tracking percorsi GPS — vedi sezione dedicata sotto.</li></ul><h2>Personalizzazione marker per-record (colore + immagine/SVG)</h2><p>L&#39;archetype <code>map</code> permette di **personalizzare ogni marker in base ai dati del</p><p>singolo record** senza scrivere callback JS, configurando i campi del record</p><p>direttamente dal <code>mdpropsbag</code>.</p><h3>Priorità di rendering del marker</h3><p>Il framework risolve il contenuto del marker in quest&#39;ordine (prima che matcha vince):</p><p>1. <strong>`customMarkerImageSrcField`</strong> — campo record con URL immagine o SVG inline.</p><p> Auto-detect: se la stringa inizia con <code>&lt;svg</code>, viene wrappata in <code>&lt;div&gt;</code> come</p><p> AdvancedMarker content; altrimenti viene messa in <code>&lt;img src&gt;</code>.</p><p>2. <strong>`customMarkerImageSrc`</strong> — immagine/SVG fisso applicato a tutti.</p><p>3. <strong>`markerColorField`</strong> — colore CSS per-record, reso come <code>PinElement</code></p><p> (pin Google Maps standard colorato).</p><p>4. <strong>`markerContentCallback`</strong> — callback JS user-supplied (riceve <code>record</code>).</p><h3>Esempio: campo <code>colore_marker</code> + <code>svg_marker</code> su tabella <code>mezzi</code></h3><p>Schema DB Dati (estratto):</p><p>Snippet 2:</p><p>Metadata colonna:</p><table><thead><tr><th>Colonna</th><th><code>mc_ui_column_type</code></th><th><code>voa_class</code></th><th>Note</th></tr></thead><tbody><tr><td><code>colore_marker</code></td><td><code>color</code></td><td>1</td><td>color picker nativo (<code>#rrggbb</code>)</td></tr><tr><td><code>svg_marker</code></td><td><code>txt_area</code></td><td>1</td><td>SVG inline o URL — opzionale, sovrascrive il colore</td></tr></tbody></table><p>Propsbag dell&#39;archetype map sulla route che renderizza i mezzi:</p><p>Snippet 3:</p><p>Comportamento runtime:</p><ul><li>record con <code>svg_marker = &#39;&lt;svg ...&gt;...&lt;/svg&gt;&#39;</code> → marker = SVG inline (override del colore).</li><li>record con <code>svg_marker = &#39;https://.../truck.png&#39;</code> → marker = <code>&lt;img&gt;</code> con quella URL.</li><li>record con <code>svg_marker</code> vuoto e <code>colore_marker = &#39;#16a34a&#39;</code> → marker = <code>PinElement</code> verde.</li><li>record con entrambi vuoti → marker default Google Maps.</li></ul><h2>Polyline overlay (tracking percorsi GPS)</h2><p>L&#39;archetype <code>map</code> puo&#39; renderizzare <strong>una polyline per gruppo di record</strong></p><p>(es. tracciato GPS storicizzato di un mezzo per una giornata) leggendo le</p><p>coordinate dalla colonna <code>point</code> esistente sulla route, raggruppando i record</p><p>per un campo (<code>groupByField</code>) e ordinandoli per un campo temporale</p><p>(<code>orderByField</code>).</p><p>Quando <code>polyline.enabled=true</code> la mappa NON renderizza un marker per ogni</p><p>record: solo <strong>un marker per gruppo</strong>, posizionato sull&#39;**ultimo punto</p><p>temporale** (il piu&#39; recente). Tutti gli altri record sono rappresentati come</p><p>nodi della polyline.</p><h3>Configurazione minima</h3><p>Snippet 4:</p><h3>Tutte le opzioni <code>polyline</code></h3><ul><li><code>enabled</code>: abilita l&#39;overlay polyline (default <code>false</code>).</li><li><code>groupByField</code>: campo record per raggruppare i record in polyline distinte</li></ul><p> (es. <code>mezzo_id</code>, <code>tracking_session_id</code>). Se omesso, tutti i record finiscono</p><p> in un&#39;unica polyline globale.</p><ul><li><code>orderByField</code>: campo record per ordinare i punti dentro ogni gruppo</li></ul><p> (es. <code>timestamp_pos</code>). Se omesso, viene usato l&#39;ordine di arrivo.</p><ul><li><code>pointField</code>: campo record da cui leggere <code>{lat,lng}</code>. <strong>Default</strong>:</li></ul><p> auto-detect della prima colonna con <code>mc_ui_column_type=&#39;point&#39;</code> nel</p><p> metaInfo (la stessa che il map-list usa per i marker normali).</p><ul><li><code>colorField</code>: campo record con colore polyline per-gruppo (CSS <code>#rrggbb</code>).</li></ul><p> Il colore viene letto dal <strong>primo record del gruppo</strong> (assume omogeneita&#39;).</p><p> Sovrascrive <code>strokeColorByGroup</code> e <code>strokeColor</code>. Tipico use-case: portare</p><p> in view il <code>colore_marker</code> configurato sull&#39;entita&#39; lookup (es. <code>mezzi</code>).</p><ul><li><code>strokeColor</code>: colore polyline fisso (default <code>#1d4ed8</code>). Usato solo se</li></ul><p> <code>colorField</code> non risolve e <code>strokeColorByGroup=false</code>.</p><ul><li><code>strokeColorByGroup</code>: se <code>true</code> e <code>colorField</code> non e&#39; settato, ogni gruppo</li></ul><p> riceve un colore distinto da una palette interna (8 colori).</p><ul><li><code>strokeWeight</code>: spessore tratto in px (default 4).</li><li><code>strokeOpacity</code>: opacita 0..1 (default 0.85).</li><li><code>snapToRoads</code>: se <code>true</code>, la polyline viene snappata alle strade reali via</li></ul><p> Google Maps Routes API (<code>Route.computeRoutes</code>, fallback automatico al</p><p> legacy <code>DirectionsService</code> se la libreria <code>routes</code> non e&#39; caricata).</p><p> <strong>Richiede</strong> &quot;Routes API&quot; abilitata sulla key in Google Cloud Console.</p><p> La chiamata viene chunkata a 25 waypoint per request (limite Routes API).</p><ul><li><code>showMarkers</code>: se <code>false</code>, nasconde anche il marker last-in-group (default</li></ul><p> <code>true</code> — un marker per gruppo).</p><ul><li><code>showWaypointDots</code>: se <code>true</code>, disegna un <code>&lt;map-circle&gt;</code> (cerchio</li></ul><p> geografico) su ogni waypoint originale del record. Utile per distinguere</p><p> visivamente i punti GPS storicizzati nel DB dal path</p><p> interpolato/snappato.</p><ul><li><code>waypointDotRadius</code>: raggio in metri del dot (default <code>8</code>).</li><li><code>travelMode</code>: modalita di viaggio per <code>snapToRoads</code> (<code>DRIVING</code>/<code>WALKING</code>/</li></ul><p> <code>BICYCLING</code>). Per giri cittadini con waypoint in ZTL/zone pedonali,</p><p> preferire <code>WALKING</code> (<code>DRIVING</code> da spesso <code>no results</code> in centro storico</p><p> italiano).</p><h3>Combinare colore polyline + colore marker dalla stessa colonna</h3><p>Se la tabella entita&#39; (es. <code>mezzi</code>) ha gia&#39; un campo <code>colore_marker</code>,</p><p>configura <strong>lo stesso campo</strong> sia per il marker che per la polyline portando</p><p>quel campo nella view sorgente:</p><p>Snippet 5:</p><p>Snippet 6:</p><p>Ogni mezzo avra&#39; marker, polyline e dot dello stesso colore configurato sul</p><p>record <code>mezzi</code> (l&#39;utente lo modifica dall&#39;anagrafica e si propaga ovunque).</p><p>Per il mezzo con <code>svg_marker</code> valorizzato, il marker sara&#39; l&#39;icona custom</p><p>(SVG inline o immagine URL) e il colore continuera&#39; ad essere usato per</p><p>polyline + dot.</p><h3>Contesto Template (<code>itemTemplateString</code>)</h3><p>Nel template custom sono disponibili questi input:</p><ul><li><code>record</code>: record corrente (in map archetype coincide con <code>rowData.record</code> quando presente).</li><li><code>rowData</code>: payload evento marker/poligono corrente.</li><li><code>metaInfo</code>: metadati tabella/colonne della datasource.</li><li><code>datasource</code>: riferimento al componente datasource associato.</li></ul><p>Contesto runtime usato dal framework:</p><p>Snippet 7:</p><h2>Eventi e subscriptions (host)</h2><p>Eventi disponibili su <code>wuic-map-list</code>:</p><ul><li><code>onMapClick</code>: click su mappa (<code>google-map mapClick</code>).</li><li><code>onMarkerClick</code>: click su marker.</li><li><code>onMarkerDragStart</code>: inizio drag marker.</li><li><code>onMarkerDragEnd</code>: fine drag marker (con posizione finale).</li><li><code>onPolygonClick</code>: click su poligono.</li><li><code>onPolygonShapeChangedEvent</code>: shape changed su poligono in edit mode.</li><li><code>onMapDataBound</code>: emesso quando il wrapper completa il binding dati dal datasource.</li></ul><p>Esempio template:</p><p>Snippet 8:</p><p>Esempio subscribe (ViewChild):</p><p>Snippet 9:</p>",
2016
2062
  "codeSamples": [
2017
2063
  {
2018
2064
  "id": "code_1",
2019
2065
  "title": "Snippet 1",
2020
2066
  "language": "json",
2021
- "code": "{\n \"archetypes\": {\n \"map\": {\n \"advancedFilter\": false,\n \"center\": {\n \"lat\": 39.8283,\n \"lng\": -98.5795\n },\n \"zoom\": 4,\n \"minZoom\": 3,\n \"maxZoom\": 14,\n \"useCurrentLocation\": false,\n \"useClusterer\": true,\n \"filterByBoundaries\": false,\n \"titleField\": \"CityName\",\n \"infoField\": \"StateProvinceID__dictionary_display_value\",\n \"customMarkerImageSrc\": \"\",\n \"customMarkerImageSrcField\": \"\",\n \"markerContentCallback\": \"const glyph = String(record?.CityName || 'C').trim().charAt(0).toUpperCase() || 'C'; const pop = Number(record?.LatestRecordedPopulation || 0); const bg = pop > 1000000 ? '#B91C1C' : (pop > 300000 ? '#EA580C' : '#2563EB'); const pin = new google.maps.marker.PinElement({ background: bg, borderColor: '#0F172A', glyphColor: '#FFFFFF', glyph: glyph, scale: 1.2 }); return pin.element;\",\n \"infoFunction\": \"\",\n \"itemTemplateString\": \"<div class=\\\"map-info\\\"> <div class=\\\"map-info-title\\\">{{ rowData.record?.CityName || rowData.title }}</div> <div>{{ rowData.record?.StateProvinceID__dictionary_display_value || rowData.record?.StateProvinceID || '' }}</div> <div>Popolazione: {{ rowData.record?.LatestRecordedPopulation | number:'1.0-0' }}</div> <div>{{ rowData.record?.Location || '' }}</div> <wuic-data-action-button-lazy [data]=\\\"rowData.record\\\" [metaInfo]=\\\"metaInfo\\\" [datasource]=\\\"datasource\\\" [simplified]=\\\"true\\\"></wuic-data-action-button-lazy> </div>\"\n }\n }\n}"
2067
+ "code": "{\n \"archetypes\": {\n \"map\": {\n \"advancedFilter\": false,\n \"center\": {\n \"lat\": 39.8283,\n \"lng\": -98.5795\n },\n \"zoom\": 4,\n \"minZoom\": 3,\n \"maxZoom\": 14,\n \"useCurrentLocation\": false,\n \"useClusterer\": true,\n \"filterByBoundaries\": false,\n \"titleField\": \"CityName\",\n \"infoField\": \"StateProvinceID__dictionary_display_value\",\n \"customMarkerImageSrc\": \"\",\n \"customMarkerImageSrcField\": \"\",\n \"markerColorField\": \"\",\n \"markerContentCallback\": \"const glyph = String(record?.CityName || 'C').trim().charAt(0).toUpperCase() || 'C'; const pop = Number(record?.LatestRecordedPopulation || 0); const bg = pop > 1000000 ? '#B91C1C' : (pop > 300000 ? '#EA580C' : '#2563EB'); const pin = new google.maps.marker.PinElement({ background: bg, borderColor: '#0F172A', glyphColor: '#FFFFFF', glyphText: glyph, scale: 1.2 }); return pin;\",\n \"infoFunction\": \"\",\n \"itemTemplateString\": \"<div class=\\\"map-info\\\"> <div class=\\\"map-info-title\\\">{{ rowData.record?.CityName || rowData.title }}</div> <div>{{ rowData.record?.StateProvinceID__dictionary_display_value || rowData.record?.StateProvinceID || '' }}</div> <div>Popolazione: {{ rowData.record?.LatestRecordedPopulation | number:'1.0-0' }}</div> <div>{{ rowData.record?.Location || '' }}</div> <wuic-data-action-button-lazy [data]=\\\"rowData.record\\\" [metaInfo]=\\\"metaInfo\\\" [datasource]=\\\"datasource\\\" [simplified]=\\\"true\\\"></wuic-data-action-button-lazy> </div>\",\n \"polyline\": {\n \"enabled\": false,\n \"groupByField\": \"\",\n \"orderByField\": \"\",\n \"pointField\": \"\",\n \"colorField\": \"\",\n \"strokeColor\": \"#1d4ed8\",\n \"strokeColorByGroup\": false,\n \"strokeWeight\": 4,\n \"strokeOpacity\": 0.85,\n \"snapToRoads\": false,\n \"showMarkers\": true,\n \"showWaypointDots\": false,\n \"waypointDotRadius\": 8,\n \"travelMode\": \"DRIVING\"\n }\n }\n }\n}"
2022
2068
  },
2023
2069
  {
2024
2070
  "id": "code_2",
2025
2071
  "title": "Snippet 2",
2026
- "language": "ts",
2027
- "code": "inputs: { record: item, rowData: item, metaInfo: metaInfo, datasource: datasource }"
2072
+ "language": "sql",
2073
+ "code": "ALTER TABLE dbo.mezzi ADD colore_marker NVARCHAR(7) NULL; -- es. '#dc2626'\nALTER TABLE dbo.mezzi ADD svg_marker NVARCHAR(MAX) NULL; -- SVG inline o URL"
2028
2074
  },
2029
2075
  {
2030
2076
  "id": "code_3",
2031
2077
  "title": "Snippet 3",
2032
- "language": "html",
2033
- "code": "<wuic-map-list\n [hardcodedDatasource]=\"citiesDs\"\n (onMapClick)=\"handleMapClick($event)\"\n (onMarkerClick)=\"handleMarkerClick($event)\"\n (onMarkerDragStart)=\"handleMarkerDragStart($event)\"\n (onMarkerDragEnd)=\"handleMarkerDragEnd($event)\"\n (onPolygonClick)=\"handlePolygonClick($event)\"\n (onPolygonShapeChangedEvent)=\"handlePolygonShapeChanged($event)\"\n (onMapDataBound)=\"handleMapDataBound($event)\">\n</wuic-map-list>"
2078
+ "language": "json",
2079
+ "code": "{\n \"archetypes\": {\n \"map\": {\n \"markerColorField\": \"colore_marker\",\n \"customMarkerImageSrcField\": \"svg_marker\"\n }\n }\n}"
2034
2080
  },
2035
2081
  {
2036
2082
  "id": "code_4",
2037
2083
  "title": "Snippet 4",
2084
+ "language": "json",
2085
+ "code": "{\n \"archetypes\": {\n \"map\": {\n \"advancedFilter\": true,\n \"polyline\": {\n \"enabled\": true,\n \"groupByField\": \"mezzo_id\",\n \"orderByField\": \"timestamp_pos\",\n \"snapToRoads\": true,\n \"travelMode\": \"WALKING\"\n }\n }\n }\n}"
2086
+ },
2087
+ {
2088
+ "id": "code_5",
2089
+ "title": "Snippet 5",
2090
+ "language": "sql",
2091
+ "code": "CREATE VIEW vw_mezzi_posizioni_giorno AS\nSELECT p.*, m.colore_marker, m.svg_marker, ...\nFROM dbo.mezzi_posizioni p JOIN dbo.mezzi m ON m.id = p.mezzo_id;"
2092
+ },
2093
+ {
2094
+ "id": "code_6",
2095
+ "title": "Snippet 6",
2096
+ "language": "json",
2097
+ "code": "{\n \"archetypes\": {\n \"map\": {\n \"markerColorField\": \"colore_marker\",\n \"customMarkerImageSrcField\": \"svg_marker\",\n \"polyline\": {\n \"enabled\": true,\n \"groupByField\": \"mezzo_id\",\n \"orderByField\": \"timestamp_pos\",\n \"colorField\": \"colore_marker\",\n \"showWaypointDots\": true,\n \"snapToRoads\": true,\n \"travelMode\": \"WALKING\"\n }\n }\n }\n}"
2098
+ },
2099
+ {
2100
+ "id": "code_7",
2101
+ "title": "Snippet 7",
2102
+ "language": "ts",
2103
+ "code": "inputs: { record: item, rowData: item, metaInfo: metaInfo, datasource: datasource }"
2104
+ },
2105
+ {
2106
+ "id": "code_8",
2107
+ "title": "Snippet 8",
2108
+ "language": "html",
2109
+ "code": "<wuic-map-list\n [hardcodedDatasource]=\"citiesDs\"\n (onMapClick)=\"handleMapClick($event)\"\n (onMarkerClick)=\"handleMarkerClick($event)\"\n (onMarkerDragStart)=\"handleMarkerDragStart($event)\"\n (onMarkerDragEnd)=\"handleMarkerDragEnd($event)\"\n (onPolygonClick)=\"handlePolygonClick($event)\"\n (onPolygonShapeChangedEvent)=\"handlePolygonShapeChanged($event)\"\n (onMapDataBound)=\"handleMapDataBound($event)\">\n</wuic-map-list>"
2110
+ },
2111
+ {
2112
+ "id": "code_9",
2113
+ "title": "Snippet 9",
2038
2114
  "language": "ts",
2039
2115
  "code": "@ViewChild(MapListComponent) mapList?: MapListComponent;\nprivate readonly subs = new Subscription();\n\nngAfterViewInit(): void {\n if (!this.mapList) return;\n this.subs.add(this.mapList.onMapClick.subscribe((e) => console.log(e)));\n this.subs.add(this.mapList.onMarkerClick.subscribe((e) => console.log(e)));\n this.subs.add(this.mapList.onMarkerDragStart.subscribe((e) => console.log(e)));\n this.subs.add(this.mapList.onMarkerDragEnd.subscribe((e) => console.log(e)));\n this.subs.add(this.mapList.onPolygonClick.subscribe((e) => console.log(e)));\n this.subs.add(this.mapList.onPolygonShapeChangedEvent.subscribe((e) => console.log(e)));\n this.subs.add(this.mapList.onMapDataBound.subscribe((e) => console.log(e)));\n}"
2040
2116
  }
@@ -2103,14 +2179,47 @@ const frameworkDocsContent = {
2103
2179
  {
2104
2180
  "id": "sec_3",
2105
2181
  "title": "Riferimento al designer"
2182
+ },
2183
+ {
2184
+ "id": "sec_4",
2185
+ "title": "Parametri report (variabili SQL filtrate)"
2186
+ },
2187
+ {
2188
+ "id": "sec_5",
2189
+ "title": "Setup backend obbligatorio"
2106
2190
  }
2107
2191
  ],
2108
2192
  "sections": [
2109
2193
  {
2110
2194
  "id": "overview",
2111
2195
  "title": "Overview",
2112
- "html": "<h1>Reporting</h1><p>Reportistica operativa su route <code>#/&lt;route&gt;/report-viewer</code>.</p><h2>Screenshot reference (manuale utente)</h2><ul><li><code>manual__reporting__01.png</code>: apertura report viewer con rendering report e toolbar operativa.</li><li><code>manual__reporting__02.png</code>: esempio runtime con parametri/filtro applicati al report.</li></ul><h2>Report viewer</h2><p>Il <code>report-viewer</code> esegue e mostra report Stimulsoft associati alla route metadata corrente.</p><p>Feature principali:</p><ul><li>apertura report da list/grid con nome report (<code>reportName</code>) e parametri opzionali.</li><li>rendering dati lato runtime con toolbar viewer (zoom, export, stampa).</li><li>supporto filtro/parametri propagati dalla pagina chiamante.</li></ul><h2>Riferimento al designer</h2><p>Per creazione o modifica del report <code>.mrt</code>, vedi la sottosezione</p><p><a href=\"#/framework-docs/report-designer\">Report designer</a> in <code>Designer &amp; Workflow</code>.</p>",
2113
- "codeSamples": []
2196
+ "html": "<h1>Reporting</h1><p>Reportistica operativa su route <code>#/&lt;route&gt;/report-viewer</code>.</p><h2>Screenshot reference (manuale utente)</h2><ul><li><code>manual__reporting__01.png</code>: apertura report viewer con rendering report e toolbar operativa.</li><li><code>manual__reporting__02.png</code>: esempio runtime con parametri/filtro applicati al report.</li></ul><h2>Report viewer</h2><p>Il <code>report-viewer</code> esegue e mostra report Stimulsoft associati alla route metadata corrente.</p><p>Feature principali:</p><ul><li>apertura report da list/grid con nome report (<code>reportName</code>) e parametri opzionali.</li><li>rendering dati lato runtime con toolbar viewer (zoom, export, stampa).</li><li>supporto filtro/parametri propagati dalla pagina chiamante.</li></ul><h2>Riferimento al designer</h2><p>Per creazione o modifica del report <code>.mrt</code>, vedi la sottosezione</p><p><a href=\"#/framework-docs/report-designer\">Report designer</a> in <code>Designer &amp; Workflow</code>.</p><h2>Parametri report (variabili SQL filtrate)</h2><p>Per filtrare un report su una specifica entita (es. stampa di una singola fattura), il pattern canonico e&#39;:</p><p>1. <strong>Nel `.mrt`</strong>: dichiara una <strong>Variable</strong> nel Dictionary con <code>AllowUseAsSqlParameter = True</code> (Stimulsoft 2026.1+). Usa la variabile come <code>@nomeParam</code> nelle SqlCommand:</p><p>Snippet 1:</p><p> &gt; <strong>Trappola</strong>: NON usare il pattern legacy <code>&lt;StiDataParameter&gt;</code> block nel DataSource — non e&#39; piu&#39; supportato dalla versione corrente di Stimulsoft e produce <code>ArgumentException: Int32 is not a valid value for Int32. (Parameter &#39;value&#39;)</code> durante <code>report.Load()</code>.</p><p>2. <strong>Nell&#39;URL del viewer</strong>: passa il valore via query param <code>parameters</code> (NON come query param diretto):</p><p>Snippet 2:</p><p> Sintassi <code>parameters</code> URL-encoded: <code>nome||eq||valore</code> (separati da <code>@</code> per piu&#39; parametri).</p><p>3. <strong>Da row action o link UI</strong>: usa il callback canonico per costruire l&#39;URL:</p><p>Snippet 3:</p><h2>Setup backend obbligatorio</h2><p>In <code>appsettings.json</code> dell&#39;app, deve esistere <code>AppSettings.reportQueryTimeout</code> (es. <code>&quot;120&quot;</code>). Senza, <code>ReportViewerController.prepareDataSql</code> lancia <code>int.Parse(null)</code> → 500. Esempio:</p><p>Snippet 4:</p>",
2197
+ "codeSamples": [
2198
+ {
2199
+ "id": "code_1",
2200
+ "title": "Snippet 1",
2201
+ "language": "sql",
2202
+ "code": " SELECT ... FROM fatture_inviate WHERE id = @fattura_id\n SELECT ... FROM fatture_inviate_righe WHERE fattura_id = @fattura_id"
2203
+ },
2204
+ {
2205
+ "id": "code_2",
2206
+ "title": "Snippet 2",
2207
+ "language": "text",
2208
+ "code": " ✅ #/<route>/report-viewer?reportName=Report.mrt&parameters=fattura_id%7C%7Ceq%7C%7C42\n ❌ #/<route>/report-viewer?reportName=Report.mrt&fattura_id=42 <-- ignorato"
2209
+ },
2210
+ {
2211
+ "id": "code_3",
2212
+ "title": "Snippet 3",
2213
+ "language": "js",
2214
+ "code": " const recordId = (record && record.id && record.id.value) ?? (record && record.id);\n const params = 'fattura_id||eq||' + recordId;\n window.location.hash = '#/<route>/report-viewer?reportName=Report.mrt&parameters='\n + encodeURIComponent(params);"
2215
+ },
2216
+ {
2217
+ "id": "code_4",
2218
+ "title": "Snippet 4",
2219
+ "language": "json",
2220
+ "code": "\"AppSettings\": {\n \"reportQueryTimeout\": \"120\"\n}"
2221
+ }
2222
+ ]
2114
2223
  }
2115
2224
  ],
2116
2225
  "images": [
@@ -2843,28 +2952,54 @@ const frameworkDocsContent = {
2843
2952
  },
2844
2953
  {
2845
2954
  "id": "sec_5",
2846
- "title": "Workflow Assistito: Clone Lookup + Filtro Default + Lookup Hierarchy"
2955
+ "title": "Combo slim — campi extra via `mc_props_bag.slimCombo`"
2847
2956
  },
2848
2957
  {
2849
2958
  "id": "sec_6",
2850
- "title": "Effetti Client/Server"
2959
+ "title": "Workflow Assistito: Clone Lookup + Filtro Default + Lookup Hierarchy"
2851
2960
  },
2852
2961
  {
2853
2962
  "id": "sec_7",
2963
+ "title": "Effetti Client/Server"
2964
+ },
2965
+ {
2966
+ "id": "sec_8",
2854
2967
  "title": "Note operative"
2968
+ },
2969
+ {
2970
+ "id": "sec_9",
2971
+ "title": "Auto-fetch su FK programmatici"
2855
2972
  }
2856
2973
  ],
2857
2974
  "sections": [
2858
2975
  {
2859
2976
  "id": "overview",
2860
2977
  "title": "Overview",
2861
- "html": "<h1>Widget Lookup</h1><p>Widget colonna per relazioni (<code>lookupByID</code>, <code>multiple_check</code>) con tab dedicato <strong>Lookup</strong> nel metadata-editor colonne.</p><h2>Esempi</h2><ul><li><a class=\"wuic-demo-link\" href=\"#/examples/3c-odata-cities-grid\" target=\"_blank\" rel=\"noopener noreferrer\">Pattern 3c — lookup puro-OData (State/Province da `/odata/Stateprovinces`)</a> — dimostra <code>mc_props_bag.lookup.endpoint</code> con provider <code>odata</code> e rendering inline via <code>$expand</code>.</li></ul><h2>Quando usarlo</h2><ul><li>selezione record correlati da tabella metadata esterna.</li><li>scenari con ricerca lato server e paging lookup.</li><li>supporto singola selezione (<code>lookupByID</code>) e multi selezione (<code>multiple_check</code>).</li></ul><h2>Proprieta metadata colonna (tab Lookup)</h2><ul><li><code>mc_ui_lookup_entity_name</code>: route metadata sorgente lookup.</li><li><code>mc_ui_lookup_dataValueField</code>: campo ID restituito.</li><li><code>mc_ui_lookup_dataTextField</code>: campo descrizione mostrata.</li><li><code>mc_ui_lookup_filter</code>: filtro base lookup.</li><li><code>mc_serverside_operations</code>: ricerca/paging server-side.</li><li><code>mc_ui_pagesize</code>: dimensione pagina risultati lookup.</li><li><code>mc_ui_lookup_edit_allow</code>: abilita edit record lookup da popup.</li><li><code>mc_ui_lookup_insert_allow</code>: abilita inserimento record lookup da popup.</li><li><code>mc_ui_lookup_search_grid</code>: abilita griglia di ricerca nel popup.</li><li><code>mc_logic_allow_navigation</code>: navigazione su route collegata.</li></ul><h2>mc_props_bag utile</h2><p>Snippet 1:</p><ul><li><code>lookup.filter</code>: vincolo aggiuntivo lato client/server nella query lookup.</li><li><code>lookup.virtualize</code>: abilita virtual scroll su <code>p-autocomplete</code> del widget lookup.</li><li><code>lookup.virtualize.enabled</code>: parser tollerante (<code>true/false</code>, <code>1/0</code>, stringhe equivalenti); default <code>false</code> quando assente.</li><li><code>lookup.virtualize.itemSize</code>: altezza riga virtuale in px; default <code>44</code>.</li><li><code>lookup.endpoint</code>: override provider dati del dropdown. Quando settato, il nested datasource del lookup-editor non passa piu&#39; dal combo endpoint metadata (<code>MetaService.getFlatRecordComboData</code>) ma direttamente dal provider dichiarato in <code>type</code> con la URL in <code>uri</code>.</li><li><code>lookup.endpoint.type</code>: <code>&#39;odata&#39;</code> (dispatcher instrada a <code>DataProviderOdataService.selectCombo</code> che emette GET con <code>$top</code>, <code>$count=true</code>, <code>$select=valueField,textField</code> e <code>$filter=contains(textField,&#39;&lt;query&gt;&#39;)</code> per la server-side search). Altri valori seguono la stessa logica del table-level <code>extraProps.endpoint.type</code>.</li><li><code>lookup.endpoint.uri</code>: URL base dell&#39;entity set (es. <code>/odata/&lt;EntitySet&gt;</code>), con o senza origine esplicita. Query params esistenti vengono ignorati — il provider ricostruisce i params ad ogni fetch.</li><li><code>form.columns</code>: larghezza campo nel form edit.</li><li><code>style.editCss</code>: stile custom editor.</li></ul><h2>Workflow Assistito: Clone Lookup + Filtro Default + Lookup Hierarchy</h2><ul><li>Flusso operativo inline: <code>suggestLookup2 -&gt; suggestLookupDefaultFilter -&gt; getLookupListByRoute -&gt; getSeletClauseByLookupHierarchy</code>.</li><li>Snippet inline campi principali: <code>mc_ui_lookup_entity_name</code>, <code>mc_ui_lookup_dataValueField</code>, <code>mc_ui_lookup_dataTextField</code>, <code>mc_ui_lookup_filter</code>.</li><li>Snippet inline filtro rapido: <code>lookup.filter: {&quot;logic&quot;:&quot;AND&quot;,&quot;filters&quot;:[{&quot;field&quot;:&quot;is_active&quot;,&quot;operatore&quot;:&quot;eq&quot;,&quot;value&quot;:true}]}</code>.</li><li>Effetto client: setup lookup accelerato nel metadata-editor e filtro coerente gia pronto nel widget.</li><li>Effetto server: query lookup e select clause restano allineate agli endpoint <code>MetaService.*</code> senza mapping manuale.</li></ul><p>Checklist operativa rapida:</p><p>1. clonare configurazione base con <code>suggestLookup2</code>;</p><p>2. generare filtro default con <code>suggestLookupDefaultFilter</code>;</p><p>3. rifinire gerarchia/select clause dal tree lookup;</p><p>4. validare in runtime che ricerca e value/text mapping siano coerenti.</p><h2>Effetti Client/Server</h2><ul><li>Client: rendering widget lookup, search, etichetta/value binding.</li><li>Client: con <code>lookup.virtualize</code> attivo, l&#39;autocomplete renderizza solo il subset visuale delle opzioni.</li><li>Server: query lookup paginata/filtrata tramite <code>MetaService.getFlatRecordData</code>.</li></ul><h2>Note operative</h2><ul><li>in <code>multiple_check</code> la parte lookup resta la stessa, cambia il renderer multi valore.</li><li>se manca <code>mc_ui_lookup_entity_name</code>, il widget non puo risolvere la sorgente.</li></ul>",
2978
+ "html": "<h1>Widget Lookup</h1><p>Widget colonna per relazioni (<code>lookupByID</code>, <code>multiple_check</code>) con tab dedicato <strong>Lookup</strong> nel metadata-editor colonne.</p><h2>Esempi</h2><ul><li><a class=\"wuic-demo-link\" href=\"#/examples/3c-odata-cities-grid\" target=\"_blank\" rel=\"noopener noreferrer\">Pattern 3c — lookup puro-OData (State/Province da `/odata/Stateprovinces`)</a> — dimostra <code>mc_props_bag.lookup.endpoint</code> con provider <code>odata</code> e rendering inline via <code>$expand</code>.</li></ul><h2>Quando usarlo</h2><ul><li>selezione record correlati da tabella metadata esterna.</li><li>scenari con ricerca lato server e paging lookup.</li><li>supporto singola selezione (<code>lookupByID</code>) e multi selezione (<code>multiple_check</code>).</li></ul><h2>Proprieta metadata colonna (tab Lookup)</h2><ul><li><code>mc_ui_lookup_entity_name</code>: route metadata sorgente lookup.</li><li><code>mc_ui_lookup_dataValueField</code>: campo ID restituito.</li><li><code>mc_ui_lookup_dataTextField</code>: campo descrizione mostrata.</li><li><code>mc_ui_lookup_filter</code>: filtro base lookup.</li><li><code>mc_serverside_operations</code>: ricerca/paging server-side.</li><li><code>mc_ui_pagesize</code>: dimensione pagina risultati lookup.</li><li><code>mc_ui_lookup_edit_allow</code>: abilita edit record lookup da popup.</li><li><code>mc_ui_lookup_insert_allow</code>: abilita inserimento record lookup da popup.</li><li><code>mc_ui_lookup_search_grid</code>: abilita griglia di ricerca nel popup.</li><li><code>mc_logic_allow_navigation</code>: navigazione su route collegata.</li></ul><h2>mc_props_bag utile</h2><p>Snippet 1:</p><ul><li><code>lookup.filter</code>: vincolo aggiuntivo lato client/server nella query lookup.</li><li><code>lookup.virtualize</code>: abilita virtual scroll su <code>p-autocomplete</code> del widget lookup.</li><li><code>lookup.virtualize.enabled</code>: parser tollerante (<code>true/false</code>, <code>1/0</code>, stringhe equivalenti); default <code>false</code> quando assente.</li><li><code>lookup.virtualize.itemSize</code>: altezza riga virtuale in px; default <code>44</code>.</li><li><code>lookup.endpoint</code>: override provider dati del dropdown. Quando settato, il nested datasource del lookup-editor non passa piu&#39; dal combo endpoint metadata (<code>MetaService.getFlatRecordComboData</code>) ma direttamente dal provider dichiarato in <code>type</code> con la URL in <code>uri</code>.</li><li><code>lookup.endpoint.type</code>: <code>&#39;odata&#39;</code> (dispatcher instrada a <code>DataProviderOdataService.selectCombo</code> che emette GET con <code>$top</code>, <code>$count=true</code>, <code>$select=valueField,textField</code> e <code>$filter=contains(textField,&#39;&lt;query&gt;&#39;)</code> per la server-side search). Altri valori seguono la stessa logica del table-level <code>extraProps.endpoint.type</code>.</li><li><code>lookup.endpoint.uri</code>: URL base dell&#39;entity set (es. <code>/odata/&lt;EntitySet&gt;</code>), con o senza origine esplicita. Query params esistenti vengono ignorati — il provider ricostruisce i params ad ogni fetch.</li><li><code>form.columns</code>: larghezza campo nel form edit.</li><li><code>style.editCss</code>: stile custom editor.</li></ul><h2>Combo slim — campi extra via <code>mc_props_bag.slimCombo</code></h2><p>Il dropdown lookup di default fa un SELECT minimale sulla tabella correlata: PK + <code>mc_ui_lookup_dataValueField</code> + <code>mc_ui_lookup_dataTextField</code>. Riduce il payload del combo. Se il client ha bisogno di altri campi della tabella correlata — tipicamente per pre-popolare colonne dipendenti via <code>mc_selection_changed_custom_function__fn</code> — si usa <code>mc_props_bag.slimCombo</code> come <strong>array di nomi colonna extra</strong> da includere nel SELECT del combo:</p><p>Snippet 2:</p><ul><li>Array: i nomi vengono aggiunti al <code>columnRestrictionList</code> del combo response (logica in <code>DataProviderMetaService.buildSlimComboRestriction</code>).</li><li><code>slimCombo: false</code>: disabilita la restrizione → il combo ritorna <strong>tutte</strong> le colonne. Utile in debug, payload piu&#39; grande.</li><li>Se la colonna ha <code>mc_ui_lookup_combo_text_edit_computed_dataTextField</code> valorizzato (formula display computata), <code>slimCombo</code> viene ignorato e il combo ritorna tutte le colonne (la formula puo&#39; referenziare campi arbitrari).</li></ul><p>I campi extra finiscono in <code>record[&#39;&lt;col&gt;__lookup_obj&#39;].value</code> (record pieno della tabella correlata, gia&#39; completo dei campi dichiarati in <code>slimCombo</code>) e sono accessibili dal callback <code>mc_selection_changed_custom_function__fn</code>:</p><p>Snippet 3:</p><p>Tradeoff: payload combo leggermente piu&#39; grande ma niente HTTP roundtrip aggiuntivo (<code>getFlatRecordData</code> per recuperare il record pieno per id) ad ogni selezione.</p><h2>Workflow Assistito: Clone Lookup + Filtro Default + Lookup Hierarchy</h2><ul><li>Flusso operativo inline: <code>suggestLookup2 -&gt; suggestLookupDefaultFilter -&gt; getLookupListByRoute -&gt; getSeletClauseByLookupHierarchy</code>.</li><li>Snippet inline campi principali: <code>mc_ui_lookup_entity_name</code>, <code>mc_ui_lookup_dataValueField</code>, <code>mc_ui_lookup_dataTextField</code>, <code>mc_ui_lookup_filter</code>.</li><li>Snippet inline filtro rapido: <code>lookup.filter: {&quot;logic&quot;:&quot;AND&quot;,&quot;filters&quot;:[{&quot;field&quot;:&quot;is_active&quot;,&quot;operatore&quot;:&quot;eq&quot;,&quot;value&quot;:true}]}</code>.</li><li>Effetto client: setup lookup accelerato nel metadata-editor e filtro coerente gia pronto nel widget.</li><li>Effetto server: query lookup e select clause restano allineate agli endpoint <code>MetaService.*</code> senza mapping manuale.</li></ul><p>Checklist operativa rapida:</p><p>1. clonare configurazione base con <code>suggestLookup2</code>;</p><p>2. generare filtro default con <code>suggestLookupDefaultFilter</code>;</p><p>3. rifinire gerarchia/select clause dal tree lookup;</p><p>4. validare in runtime che ricerca e value/text mapping siano coerenti.</p><h2>Effetti Client/Server</h2><ul><li>Client: rendering widget lookup, search, etichetta/value binding.</li><li>Client: con <code>lookup.virtualize</code> attivo, l&#39;autocomplete renderizza solo il subset visuale delle opzioni.</li><li>Server: query lookup paginata/filtrata tramite <code>MetaService.getFlatRecordData</code>.</li></ul><h2>Note operative</h2><ul><li>in <code>multiple_check</code> la parte lookup resta la stessa, cambia il renderer multi valore.</li><li>se manca <code>mc_ui_lookup_entity_name</code>, il widget non puo risolvere la sorgente.</li></ul><h2>Auto-fetch su FK programmatici</h2><p>Il <code>wuic-lookup-editor</code> sottoscrive automaticamente al <code>BehaviorSubject</code> di <code>record[mc_nome_colonna]</code> e gestisce i cambi di FK programmatici (es. import documento sorgente, callback custom che setta <code>record.cliente_id.next(99)</code>):</p><ul><li>Se il nuovo valore e&#39; <strong>gia&#39; presente in `items`</strong> (la pagina caricata della lookup): popola solo <code>__lookup_obj</code> per coerenza, niente HTTP.</li><li>Se il nuovo valore <strong>non e&#39; negli items</strong> (es. valore fuori dalla pagina corrente del combo, o non ancora fetchato): scatta una <strong>second-stage fetch</strong> filtrata <code>WHERE &lt;valueField&gt; eqor &lt;newValue&gt;</code> per recuperare il record correlato e popolare automaticamente il dropdown.</li></ul><p>Skip cases (no-op):</p><ul><li>Lookup multi-value (<code>isFilter</code> con <code>eqor</code>, <code>multiple_check</code>)</li><li>Prima emit del BS (gia&#39; coperto dal flow init)</li><li>Valore null/undefined (utente ha clearato)</li><li><code>hasActiveLookupQuery=true</code> (utente sta typing una query manuale)</li></ul><p><strong>Implicazione per i custom form</strong>: settare solo il FK basta — non servono piu&#39; workaround manuali per popolare <code>__lookup_obj</code> o l&#39;alias joined. Il display del dropdown si aggiorna da solo.</p><p>Snippet 4:</p><p>&gt; <strong>Nota</strong>: l&#39;auto-fetch fired solo quando il <code>&lt;wuic-lookup-editor&gt;</code> e&#39; <strong>montato</strong> (cella in edit mode). Per il display in <strong>lista</strong> (cella read-only), <code>formatGridViewValue</code> legge l&#39;alias joined <code>&lt;route&gt;___&lt;textField&gt;__&lt;col&gt;</code> direttamente dal record — quello va comunque popolato manualmente se non viene dal backend.</p>",
2862
2979
  "codeSamples": [
2863
2980
  {
2864
2981
  "id": "code_1",
2865
2982
  "title": "Snippet 1",
2866
2983
  "language": "json",
2867
2984
  "code": "{\n \"lookup\": {\n \"filter\": {\n \"logic\": \"AND\",\n \"filters\": [\n { \"field\": \"is_active\", \"operatore\": \"eq\", \"value\": true }\n ]\n },\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n },\n \"endpoint\": {\n \"type\": \"odata\",\n \"uri\": \"http://localhost:5000/odata/StateProvinces\"\n }\n },\n \"form\": {\n \"columns\": 1,\n \"disabled\": false\n },\n \"style\": {\n \"editCss\": \"max-width: 420px;\"\n }\n}"
2985
+ },
2986
+ {
2987
+ "id": "code_2",
2988
+ "title": "Snippet 2",
2989
+ "language": "json",
2990
+ "code": "{\n \"slimCombo\": [\"unita_misura_id\",\"codice_iva_id\",\"prezzo_vendita\",\"sconto_default\"]\n}"
2991
+ },
2992
+ {
2993
+ "id": "code_3",
2994
+ "title": "Snippet 3",
2995
+ "language": "ts",
2996
+ "code": "prodCol.mc_selection_changed_custom_function__fn = (record, _f, _m, newValue) => {\n const prod = record['prodotto_id__lookup_obj']?.value;\n if (!prod) return;\n record['descrizione']?.next(prod.descrizione);\n record['prezzo_unitario']?.next(prod.prezzo_vendita);\n record['unita_misura_id']?.next(prod.unita_misura_id);\n record['codice_iva_id']?.next(prod.codice_iva_id);\n};"
2997
+ },
2998
+ {
2999
+ "id": "code_4",
3000
+ "title": "Snippet 4",
3001
+ "language": "ts",
3002
+ "code": "// PRIMA (workaround manuale richiesto)\nrecord.cliente_id.next(99);\nrecord.cliente_id__lookup_obj.next({ id: 99, ragione_sociale: 'Acme S.r.l.' });\nrecord['clienti___ragione_sociale__cliente_id'].next('Acme S.r.l.');\n\n// ORA (auto-fetch del lookup-editor)\nrecord.cliente_id.next(99);\n// → lookup-editor monta? Subscribe rileva il change → fetch filtrata → __lookup_obj popolato → dropdown mostra \"Acme S.r.l.\""
2868
3003
  }
2869
3004
  ]
2870
3005
  }
@@ -3127,10 +3262,14 @@ const frameworkDocsContent = {
3127
3262
  },
3128
3263
  {
3129
3264
  "id": "sec_4",
3130
- "title": "Flusso operativo consigliato"
3265
+ "title": "Context menu sugli elementi"
3131
3266
  },
3132
3267
  {
3133
3268
  "id": "sec_5",
3269
+ "title": "Flusso operativo consigliato"
3270
+ },
3271
+ {
3272
+ "id": "sec_6",
3134
3273
  "title": "Note operative"
3135
3274
  }
3136
3275
  ],
@@ -3138,7 +3277,7 @@ const frameworkDocsContent = {
3138
3277
  {
3139
3278
  "id": "overview",
3140
3279
  "title": "Overview",
3141
- "html": "<h1>Designer</h1><p>Editor visuale per dashboard metadata-driven su route <code>#/cities/dashboard</code>.</p><h2>Scope</h2><ul><li>Composizione board via drag-and-drop di nodi e blocchi UI.</li><li>Layout e styling pilotati da <code>boardcontent</code> e metadata tabella/colonna.</li><li>Editing property panel senza hardcode nei componenti runtime.</li></ul><h2>Palette Designer</h2><ul><li><code>Datasource</code>: sorgente dati metadata (<code>route</code>, filtri, sort, paging).</li><li><code>DataRepeater</code>: visualizzazione lista/card collegata a datasource.</li><li><code>Dynamic Template</code>: rendering template dinamico con input metadata-driven.</li><li><code>Action/Button</code>: azioni utente agganciate a record e callback.</li><li><code>Container/Layout</code>: blocchi strutturali per colonne, righe e wrapper.</li></ul><h2>Feature principali</h2><ul><li>Drag-and-drop nodi sulla canvas con persistenza nel <code>boardcontent</code>.</li><li>Property panel contestuale con input coerenti ai componenti selezionati.</li><li>Binding tra nodi (<code>datasource -&gt; repeater/template</code>) con validazione minima.</li><li>Preview runtime integrata e riallineamento layout.</li><li>Gestione pending changes con guard prima dell&#39;uscita.</li></ul><h2>Flusso operativo consigliato</h2><ul><li>Aprire route dashboard target.</li><li>Inserire/collegare nodi dalla palette.</li><li>Configurare <code>inputs</code> essenziali (es. <code>route</code>, <code>action</code>, <code>datasource</code>).</li><li>Salvare e validare il risultato in runtime.</li></ul><h2>Note operative</h2><ul><li>Per fix specifici dashboard, privilegiare patch del <code>boardcontent</code> invece di modifiche globali.</li><li>Evitare proprietà duplicate tra <code>inputs</code> e stile inline non coerente.</li><li>Verificare sempre che i datasource abbiano <code>inputs.route</code> valorizzata.</li></ul>",
3280
+ "html": "<h1>Designer</h1><p>Editor visuale per dashboard metadata-driven su route <code>#/cities/dashboard</code>.</p><h2>Scope</h2><ul><li>Composizione board via drag-and-drop di nodi e blocchi UI.</li><li>Layout e styling pilotati da <code>boardcontent</code> e metadata tabella/colonna.</li><li>Editing property panel senza hardcode nei componenti runtime.</li></ul><h2>Palette Designer</h2><ul><li><code>Datasource</code>: sorgente dati metadata (<code>route</code>, filtri, sort, paging).</li><li><code>DataRepeater</code>: visualizzazione lista/card collegata a datasource.</li><li><code>Dynamic Template</code>: rendering template dinamico con input metadata-driven.</li><li><code>Action/Button</code>: azioni utente agganciate a record e callback.</li><li><code>Container/Layout</code>: blocchi strutturali per colonne, righe e wrapper.</li></ul><h2>Feature principali</h2><ul><li>Drag-and-drop nodi sulla canvas con persistenza nel <code>boardcontent</code>.</li><li>Property panel contestuale con input coerenti ai componenti selezionati.</li><li>Binding tra nodi (<code>datasource -&gt; repeater/template</code>) con validazione minima.</li><li>Preview runtime integrata e riallineamento layout.</li><li>Gestione pending changes con guard prima dell&#39;uscita.</li></ul><h2>Context menu sugli elementi</h2><p>Right-click su un elemento del canvas, una voce del breadcrumb in basso o un nodo dell&#39;albero gerarchia mostra un context menu con tre voci:</p><ul><li><strong>Modifica proprietà</strong> (pi-cog) — seleziona l&#39;elemento come &quot;current tool&quot; e ne mostra il property panel a destra; equivalente al click nell&#39;albero. Utile per editare proprietà di elementi annidati senza dover cercare la voce nella gerarchia.</li><li><strong>Rinomina</strong> (solo breadcrumb in basso) — apre dialog per rinominare il <code>displayName</code>.</li><li><strong>Rimuovi</strong> — elimina l&#39;elemento dalla board.</li></ul><h2>Flusso operativo consigliato</h2><ul><li>Aprire route dashboard target.</li><li>Inserire/collegare nodi dalla palette.</li><li>Configurare <code>inputs</code> essenziali (es. <code>route</code>, <code>action</code>, <code>datasource</code>).</li><li>Salvare e validare il risultato in runtime.</li></ul><h2>Note operative</h2><ul><li>Per fix specifici dashboard, privilegiare patch del <code>boardcontent</code> invece di modifiche globali.</li><li>Evitare proprietà duplicate tra <code>inputs</code> e stile inline non coerente.</li><li>Verificare sempre che i datasource abbiano <code>inputs.route</code> valorizzata.</li></ul>",
3142
3281
  "codeSamples": []
3143
3282
  }
3144
3283
  ],
@@ -3202,14 +3341,18 @@ const frameworkDocsContent = {
3202
3341
  },
3203
3342
  {
3204
3343
  "id": "sec_3",
3205
- "title": "Flusso operativo consigliato"
3344
+ "title": "View Builder Tab — Aggregazioni e GROUP BY (Tier 1, 2026-05-08)"
3206
3345
  },
3207
3346
  {
3208
3347
  "id": "sec_4",
3209
- "title": "Pulsanti azione"
3348
+ "title": "Flusso operativo consigliato"
3210
3349
  },
3211
3350
  {
3212
3351
  "id": "sec_5",
3352
+ "title": "Pulsanti azione"
3353
+ },
3354
+ {
3355
+ "id": "sec_6",
3213
3356
  "title": "Note di utilizzo"
3214
3357
  }
3215
3358
  ],
@@ -3217,7 +3360,7 @@ const frameworkDocsContent = {
3217
3360
  {
3218
3361
  "id": "overview",
3219
3362
  "title": "Overview",
3220
- "html": "<h1>View/Pivot builder</h1><p>Il Pivot Builder consente di costruire viste analitiche dinamiche partendo dalle route metadata, senza scrivere query manuali lato UI.</p><h2>Screenshot reference</h2><ul><li><code>manual__pivot_builder__01.png</code>: configurazione e risultato del builder in runtime.</li><li><code>manual__pivot_builder__02.png</code>: dettaglio dialog di creazione tabella materializzata e opzioni operative.</li></ul><h2>Funzionalità principali</h2><ul><li>Selezione campi in area <code>rows</code>, <code>columns</code> e <code>values</code>.</li><li>Aggregazioni disponibili sui valori (somma, conteggio, media, minimo, massimo).</li><li>Raggruppamento multidimensionale con ordinamento per dimensione.</li><li>Filtri interattivi per ridurre il dataset analizzato.</li><li>Aggiornamento vista pivot in tempo reale dopo cambio layout o metrica.</li><li>Esportazione/riuso della configurazione pivot nel contesto operativo.</li></ul><h2>Flusso operativo consigliato</h2><p>1. Apri la route pivot builder.</p><p>2. Seleziona datasource/route metadata di partenza.</p><p>3. Trascina i campi nelle zone <code>rows</code>, <code>columns</code>, <code>values</code>.</p><p>4. Configura l&#39;aggregazione per ogni metrica.</p><p>5. Applica filtri e verifica i totali.</p><p>6. Salva o riusa la configurazione secondo il flusso applicativo.</p><h2>Pulsanti azione</h2><p><code>Crea vista</code></p><ul><li>Genera una vista SQL a partire dalla configurazione pivot corrente.</li><li>Utile quando vuoi riusare il risultato come sorgente dati stabile in altre route/report.</li><li>Mantiene la logica centralizzata nel DB, evitando di ricostruire ogni volta il layout manualmente.</li></ul><p><code>Crea tabella materializzata</code></p><ul><li>Crea una tabella fisica con i dati aggregati della configurazione pivot.</li><li>Utile per dataset grandi o dashboard dove servono tempi di lettura rapidi.</li><li>Scaffolda automaticamente la route associata alla tabella fisica creata e genera una schedulazione per refresh/allineamento dati.</li><li>Premendo Aggiorna sulla grid nella route associata forza un run della schedulazione.</li></ul><h2>Note di utilizzo</h2><ul><li>Per analisi coerenti, usare colonne con tipologia dati corretta (numeriche per metriche, testuali/date per dimensioni).</li><li>In presenza di grandi volumi, preferire filtri e aggregazioni mirate prima di espandere il dettaglio.</li></ul>",
3363
+ "html": "<h1>View/Pivot builder</h1><p>Il Pivot Builder consente di costruire viste analitiche dinamiche partendo dalle route metadata, senza scrivere query manuali lato UI.</p><h2>Screenshot reference</h2><ul><li><code>manual__pivot_builder__01.png</code>: configurazione e risultato del builder in runtime.</li><li><code>manual__pivot_builder__02.png</code>: dettaglio dialog di creazione tabella materializzata e opzioni operative.</li></ul><h2>Funzionalità principali</h2><ul><li>Selezione campi in area <code>rows</code>, <code>columns</code> e <code>values</code>.</li><li>Aggregazioni disponibili sui valori (somma, conteggio, media, minimo, massimo).</li><li>Raggruppamento multidimensionale con ordinamento per dimensione.</li><li>Filtri interattivi per ridurre il dataset analizzato.</li><li>Aggiornamento vista pivot in tempo reale dopo cambio layout o metrica.</li><li>Esportazione/riuso della configurazione pivot nel contesto operativo.</li></ul><h2>View Builder Tab — Aggregazioni e GROUP BY (Tier 1, 2026-05-08)</h2><p>Il Tab 1 &quot;View Builder&quot; oltre a tabelle/JOIN ora permette di emettere <code>GROUP BY</code> con aggregazioni <code>SUM/COUNT/AVG/MIN/MAX/COUNT(DISTINCT)</code> direttamente dalla <code>ViewDefinition</code>, senza passare in modalità manualSql.</p><p>Pannello sotto il canvas:</p><ul><li><strong>Aggregazioni</strong>: form per aggiungere <code>funzione(colonna) AS alias</code>. La colonna source può essere qualsiasi colonna del canvas o <code>*</code> (per <code>COUNT(*)</code>); l&#39;alias output è auto-generato se vuoto.</li><li><strong>GROUP BY</strong>: checkbox per ogni colonna selezionata sui nodi del canvas. Se nessuna è marcata esplicitamente, il backend deduce le dimensioni dalle colonne <code>selected=true</code> non aggregate.</li><li><strong>CASE WHEN per pivoting</strong>: per pattern come <code>SUM(CASE WHEN bucket=&#39;X&#39; THEN importo ELSE 0 END)</code> usa una colonna formula (right-click → Edit formula) come source dell&#39;aggregazione. La formula viene emessa come argomento dell&#39;aggregato.</li></ul><p>Output backend (<a href=\"../../Services/MetaService.cs\">`MetaService.previewViewDefinition`</a>):</p><ul><li><code>viewDefinition.aggregations[]</code> → emit <code>&lt;FN&gt;(&lt;col|formula&gt;) AS &lt;alias&gt;</code> come colonne SELECT aggiuntive</li><li><code>viewDefinition.groupByColumns[]</code> → emit <code>GROUP BY</code> clause</li><li>Auto-inferenza GROUP BY se <code>aggregations</code> presenti senza <code>groupByColumns</code> esplicito</li></ul><h3>UNION blocks (Tier 2, 2026-05-08)</h3><p>Un&#39;estensione successiva permette di concatenare più SELECT con <code>UNION</code> o <code>UNION ALL</code> direttamente da una singola <code>ViewDefinition</code>:</p><ul><li><strong>Block 0</strong> = top-level (le tabelle/joins/aggregations sul canvas) → definisce il <strong>vocabolario canonico</strong> di colonne SELECT</li><li><strong>Block N (N≥1)</strong> = ogni <code>unionBlocks[]</code> aggiuntivo è una sub-definition single-table con un <code>columnMapping</code> obbligatorio: per ogni canonical alias del block 0, il block N specifica <code>sourceQualified</code> (colonna del block) <strong>o</strong> <code>constantValue</code> (literal SQL come <code>&#39;CLIENTE&#39;</code>) <strong>o</strong> <code>formula</code> (espressione SQL per cast cross-block)</li><li><strong>Toggle UNION ALL / UNION</strong>: default <code>unionAll: true</code> (no dedup, performance migliore); <code>false</code> per <code>UNION DISTINCT</code></li><li><strong>TOP/LIMIT</strong>: applicato all&#39;intera UNION, wrappata in subquery <code>SELECT TOP N * FROM (&lt;union&gt;) AS u</code></li></ul><p>UI sotto il canvas: select route + bottone &quot;Add UNION block&quot; → ogni block compare come accordion con header (caption + route) + matrix column-mapping (per canonical alias: dropdown source col del block, oppure input constant SQL).</p><p>Limiti v0: ogni block UNION è single-table (no JOIN within block). Per cross-block JOIN (es. categoria &#39;ENTRAMBI&#39; di <code>vw_anagrafica_unificata</code> che richiede <code>clienti INNER JOIN fornitori ON piva</code>) serve Tier 2.5.</p><h2>Flusso operativo consigliato</h2><p>1. Apri la route pivot builder.</p><p>2. Seleziona datasource/route metadata di partenza.</p><p>3. Trascina i campi nelle zone <code>rows</code>, <code>columns</code>, <code>values</code>.</p><p>4. Configura l&#39;aggregazione per ogni metrica.</p><p>5. Applica filtri e verifica i totali.</p><p>6. Salva o riusa la configurazione secondo il flusso applicativo.</p><h2>Pulsanti azione</h2><p><code>Crea vista</code></p><ul><li>Genera una vista SQL a partire dalla configurazione pivot corrente.</li><li>Utile quando vuoi riusare il risultato come sorgente dati stabile in altre route/report.</li><li>Mantiene la logica centralizzata nel DB, evitando di ricostruire ogni volta il layout manualmente.</li></ul><p><code>Crea tabella materializzata</code></p><ul><li>Crea una tabella fisica con i dati aggregati della configurazione pivot.</li><li>Utile per dataset grandi o dashboard dove servono tempi di lettura rapidi.</li><li>Scaffolda automaticamente la route associata alla tabella fisica creata e genera una schedulazione per refresh/allineamento dati.</li><li>Premendo Aggiorna sulla grid nella route associata forza un run della schedulazione.</li></ul><h2>Note di utilizzo</h2><ul><li>Per analisi coerenti, usare colonne con tipologia dati corretta (numeriche per metriche, testuali/date per dimensioni).</li><li>In presenza di grandi volumi, preferire filtri e aggregazioni mirate prima di espandere il dettaglio.</li></ul>",
3221
3364
  "codeSamples": []
3222
3365
  }
3223
3366
  ],
@@ -4172,7 +4315,7 @@ const frameworkDocsContent = {
4172
4315
  {
4173
4316
  "id": "overview",
4174
4317
  "title": "Overview",
4175
- "html": "<h1>Catalogo Componenti</h1><p>Totale cartelle componenti rilevate: 48.</p><h2>Componenti disponibili</h2><ul><li>app-settings-editor</li><li>archetype-configurator</li><li>bounded-repeater</li><li>carousel-list</li><li>chart-list</li><li>code-editor</li><li>css-sheet-editor</li><li>dashboard</li><li>data-action-button</li><li>data-repeater</li><li>data-source</li><li>designer</li><li>dynamic-dashboard-template</li><li>dynamic-field-template</li><li>dynamic-form-template</li><li>dynamic-generic-template</li><li>dynamic-repeater-template</li><li>dynamic-template</li><li>field</li><li>filter-bar</li><li>first-run-wizard</li><li>framework-docs</li><li>image-wrapper</li><li>import-export-button</li><li>kanban-list</li><li>list-grid</li><li>map-list</li><li>meta-menu</li><li>metadata-editor</li><li>notification-bell</li><li>pager</li><li>parametric-dialog</li><li>pivot-builder</li><li>prompt-dialog-fallback-host</li><li>rag-chatbot</li><li>rag-chatbot-fab</li><li>report-designer</li><li>report-viewer</li><li>scheduler-list</li><li>split</li><li>split-area</li><li>spreadsheet-list-sf</li><li>tab-panel-wrapper</li><li>tab-view-wrapper</li><li>tree-list</li><li>upload-dialog</li><li>workflow-designer</li><li>workflow-runner</li></ul><h2>Export da public-api</h2><ul><li>data-source/data-source.component</li><li>list-grid/list-grid.component</li><li>import-export-button/import-export-button.component</li><li>pager/pager.component</li><li>filter-bar/filter-bar.component</li><li>map-list/map-list.component</li><li>carousel-list/carousel-list.component</li><li>tree-list/tree-list.component</li><li>parametric-dialog/parametric-dialog.component</li><li>upload-dialog/upload-dialog.component</li><li>app-settings-editor/app-settings-editor.component</li><li>data-action-button/data-action-button.component</li><li>meta-menu/meta-menu.component</li><li>field/text-editor/text-editor.component</li><li>field/text-area-editor/text-area-editor.component</li><li>field/number-editor/number-editor.component</li><li>field/lookup-editor/lookup-editor.component</li><li>field/dictionary-editor/dictionary-editor.component</li><li>field/boolean-editor/boolean-editor.component</li><li>field/date-editor/date-editor.component</li><li>field/html-editor/html-editor.component</li><li>field/upload-editor/upload-editor.component</li><li>field/button-editor/button-editor.component</li><li>field/field-editor/field-editor.component</li><li>field/code-area-editor/code-area-editor.component</li><li>dynamic-template/dynamic-template.component</li><li>image-wrapper/image-wrapper.component</li><li>field/color-editor/color-editor.component</li><li>field/property-array-editor/property-array-editor.component</li><li>field/property-object-editor/property-object-editor.component</li><li>field/tree-view-selector/tree-view-selector.component</li><li>workflow-designer/workflow-designer.component</li><li>workflow-runner/workflow-runner.component</li><li>notification-bell/notification-bell.component</li><li>bounded-repeater/bounded-repeater.component</li><li>framework-docs/framework-docs.component</li><li>designer/designer.route.component</li><li>workflow-designer/workflow-designer.route.component</li><li>kanban-list/kanban-list.component</li><li>data-repeater/data-repeater.component</li><li>bounded-repeater/bounded-repeater-pending-changes.guard</li><li>rag-chatbot/rag-chatbot.component</li><li>rag-chatbot-fab/rag-chatbot-fab.component</li></ul>",
4318
+ "html": "<h1>Catalogo Componenti</h1><p>Totale cartelle componenti rilevate: 49.</p><h2>Componenti disponibili</h2><ul><li>app-settings-editor</li><li>archetype-configurator</li><li>bounded-repeater</li><li>carousel-list</li><li>chart-list</li><li>code-editor</li><li>css-sheet-editor</li><li>dashboard</li><li>data-action-button</li><li>data-repeater</li><li>data-source</li><li>designer</li><li>dynamic-dashboard-template</li><li>dynamic-field-template</li><li>dynamic-form-template</li><li>dynamic-generic-template</li><li>dynamic-repeater-template</li><li>dynamic-template</li><li>error-dialog</li><li>field</li><li>filter-bar</li><li>first-run-wizard</li><li>framework-docs</li><li>image-wrapper</li><li>import-export-button</li><li>kanban-list</li><li>list-grid</li><li>map-list</li><li>meta-menu</li><li>metadata-editor</li><li>notification-bell</li><li>pager</li><li>parametric-dialog</li><li>pivot-builder</li><li>prompt-dialog-fallback-host</li><li>rag-chatbot</li><li>rag-chatbot-fab</li><li>report-designer</li><li>report-viewer</li><li>scheduler-list</li><li>split</li><li>split-area</li><li>spreadsheet-list-sf</li><li>tab-panel-wrapper</li><li>tab-view-wrapper</li><li>tree-list</li><li>upload-dialog</li><li>workflow-designer</li><li>workflow-runner</li></ul><h2>Export da public-api</h2><ul><li>data-source/data-source.component</li><li>list-grid/list-grid.component</li><li>import-export-button/import-export-button.component</li><li>pager/pager.component</li><li>filter-bar/filter-bar.component</li><li>map-list/map-list.component</li><li>carousel-list/carousel-list.component</li><li>tree-list/tree-list.component</li><li>parametric-dialog/parametric-dialog.component</li><li>upload-dialog/upload-dialog.component</li><li>app-settings-editor/app-settings-editor.component</li><li>data-action-button/data-action-button.component</li><li>meta-menu/meta-menu.component</li><li>field/text-editor/text-editor.component</li><li>field/text-area-editor/text-area-editor.component</li><li>field/number-editor/number-editor.component</li><li>field/lookup-editor/lookup-editor.component</li><li>field/dictionary-editor/dictionary-editor.component</li><li>field/boolean-editor/boolean-editor.component</li><li>field/date-editor/date-editor.component</li><li>field/html-editor/html-editor.component</li><li>field/upload-editor/upload-editor.component</li><li>field/button-editor/button-editor.component</li><li>field/field-editor/field-editor.component</li><li>field/code-area-editor/code-area-editor.component</li><li>dynamic-template/dynamic-template.component</li><li>image-wrapper/image-wrapper.component</li><li>field/color-editor/color-editor.component</li><li>field/property-array-editor/property-array-editor.component</li><li>field/property-object-editor/property-object-editor.component</li><li>field/tree-view-selector/tree-view-selector.component</li><li>workflow-designer/workflow-designer.component</li><li>workflow-runner/workflow-runner.component</li><li>notification-bell/notification-bell.component</li><li>bounded-repeater/bounded-repeater.component</li><li>framework-docs/framework-docs.component</li><li>designer/designer.route.component</li><li>workflow-designer/workflow-designer.route.component</li><li>kanban-list/kanban-list.component</li><li>data-repeater/data-repeater.component</li><li>bounded-repeater/bounded-repeater-pending-changes.guard</li><li>rag-chatbot/rag-chatbot.component</li><li>rag-chatbot-fab/rag-chatbot-fab.component</li><li>error-dialog/error-dialog.component</li></ul>",
4176
4319
  "codeSamples": []
4177
4320
  }
4178
4321
  ],
@@ -4293,13 +4436,21 @@ const frameworkDocsContent = {
4293
4436
  {
4294
4437
  "id": "sec_3",
4295
4438
  "title": "Esempi"
4439
+ },
4440
+ {
4441
+ "id": "sec_4",
4442
+ "title": "Patch runtime di `metaInfo` via `fetchInfo$.subscribe`"
4443
+ },
4444
+ {
4445
+ "id": "sec_5",
4446
+ "title": "Multipli `<wuic-data-source>` nello stesso componente"
4296
4447
  }
4297
4448
  ],
4298
4449
  "sections": [
4299
4450
  {
4300
4451
  "id": "overview",
4301
4452
  "title": "Overview",
4302
- "html": "<h1>Datasource</h1><p><code>wuic-data-source</code> e il componente che gestisce metadata, fetch dati, stato corrente, tracking modifiche e sync CRUD.</p><h2>Descrizione</h2><ul><li>Risolve la route (<code>route</code>/<code>hardcodedRoute</code> o routing URL).</li><li>Carica metadata tabella/colonne e pubblica stato su <code>fetchInfo$</code>.</li><li>Gestisce <code>filterInfo</code>, <code>sortInfo</code>, <code>groupInfo</code>, paging (<code>pageSize</code>, <code>currentPage</code>) e cursor paging.</li><li>Espone metodi runtime per insert/update/delete, batch save, rollback e export.</li><li>E il punto centrale a cui si agganciano <code>wuic-data-repeater</code>, <code>wuic-filter-bar</code> e <code>wuic-pager</code>.</li></ul><h2>API</h2><p>Selector:</p><ul><li><code>wuic-data-source</code></li></ul><p>Input principali:</p><ul><li><code>route: BehaviorSubject&lt;string&gt;</code></li><li><code>routeFromRouting: boolean</code></li><li><code>hardcodedRoute: string</code></li><li><code>autoload?: boolean</code></li><li><code>loading: BehaviorSubject&lt;boolean&gt;</code></li><li><code>changeTracking?: boolean</code></li><li><code>parentRecord: any</code></li><li><code>parentMetaInfo: MetaInfo</code></li><li><code>parentDatasource: DataSourceComponent</code></li><li><code>componentRef: BehaviorSubject&lt;{ component, id, name, uniqueName }&gt;</code></li></ul><p>Stato/runtime principale:</p><ul><li><code>metaInfo: MetaInfo</code></li><li><code>resultInfo: ResultInfo</code></li><li><code>filterInfo?: FilterInfo</code></li><li><code>sortInfo: SortInfo[]</code></li><li><code>groupInfo: GroupInfo[]</code></li><li><code>aggregationInfo: AggregationInfo[]</code></li><li><code>fetchInfo$: BehaviorSubject&lt;{ resultInfo, metaInfo, filterDescriptor }&gt;</code></li><li><code>datasourceReady$: BehaviorSubject&lt;DataSourceComponent | null&gt;</code></li><li><code>afterFirstLoad$: BehaviorSubject&lt;any&gt;</code></li><li><code>beforeSync$: Subject&lt;DataSourceBeforeSyncEvent&gt;</code> (cancellabile)</li><li><code>afterSync$: Subject&lt;DataSourceAfterSyncEvent&gt;</code></li></ul><p>Metodi runtime piu usati:</p><ul><li><code>fetchData()</code></li><li><code>syncData(entita, original, deleting?, cloning?)</code></li><li><code>addNewRecord(record?)</code></li><li><code>setCurrent(data)</code></li><li><code>clearColumnFilter(col, fetch?)</code></li><li><code>getPendingChanges()</code></li><li><code>hasPendingChanges()</code></li><li><code>batchSave(targetChanges?)</code></li><li><code>rollbackChanges(targetChanges?)</code></li><li><code>exportXls()</code></li></ul><p>Subscriptions/eventi runtime:</p><ul><li><code>fetchInfo$</code>: stream principale stato dati/metadati.</li><li><code>datasourceReady$</code>: emesso quando l&#39;istanza datasource e pronta.</li><li><code>afterFirstLoad$</code>: emesso una sola volta al primo payload utile.</li><li><code>beforeSync$</code>: emesso prima di <code>insert/update/delete/clone/batch</code>; puoi annullare con <code>event.cancelSync(...)</code>.</li><li><code>afterSync$</code>: emesso dopo sync riuscita.</li></ul><p>Nota compatibilita:</p><ul><li>e disponibile anche alias <code>fetchInfo</code> (getter/setter), ma il naming consigliato e <code>fetchInfo$</code>.</li></ul><h2>Esempi</h2><h3>Esempio base (route hardcoded + autoload)</h3><p>Snippet 1:</p><h3>Esempio master-detail (nested datasource)</h3><p>Snippet 2:</p><h3>Esempio operativo (fetch manuale + sync)</h3><p>Snippet 3:</p><h3>Esempio subscriptions (host)</h3><p>Snippet 4:</p>",
4453
+ "html": "<h1>Datasource</h1><p><code>wuic-data-source</code> e il componente che gestisce metadata, fetch dati, stato corrente, tracking modifiche e sync CRUD.</p><h2>Descrizione</h2><ul><li>Risolve la route (<code>route</code>/<code>hardcodedRoute</code> o routing URL).</li><li>Carica metadata tabella/colonne e pubblica stato su <code>fetchInfo$</code>.</li><li>Gestisce <code>filterInfo</code>, <code>sortInfo</code>, <code>groupInfo</code>, paging (<code>pageSize</code>, <code>currentPage</code>) e cursor paging.</li><li>Espone metodi runtime per insert/update/delete, batch save, rollback e export.</li><li>E il punto centrale a cui si agganciano <code>wuic-data-repeater</code>, <code>wuic-filter-bar</code> e <code>wuic-pager</code>.</li></ul><h2>API</h2><p>Selector:</p><ul><li><code>wuic-data-source</code></li></ul><p>Input principali:</p><ul><li><code>route: BehaviorSubject&lt;string&gt;</code></li><li><code>routeFromRouting: boolean</code></li><li><code>hardcodedRoute: string</code></li><li><code>autoload?: boolean</code></li><li><code>loading: BehaviorSubject&lt;boolean&gt;</code></li><li><code>changeTracking?: boolean</code></li><li><code>parentRecord: any</code></li><li><code>parentMetaInfo: MetaInfo</code></li><li><code>parentDatasource: DataSourceComponent</code></li><li><code>componentRef: BehaviorSubject&lt;{ component, id, name, uniqueName }&gt;</code></li></ul><p>Stato/runtime principale:</p><ul><li><code>metaInfo: MetaInfo</code></li><li><code>resultInfo: ResultInfo</code></li><li><code>filterInfo?: FilterInfo</code></li><li><code>sortInfo: SortInfo[]</code></li><li><code>groupInfo: GroupInfo[]</code></li><li><code>aggregationInfo: AggregationInfo[]</code></li><li><code>fetchInfo$: BehaviorSubject&lt;{ resultInfo, metaInfo, filterDescriptor }&gt;</code></li><li><code>datasourceReady$: BehaviorSubject&lt;DataSourceComponent | null&gt;</code></li><li><code>afterFirstLoad$: BehaviorSubject&lt;any&gt;</code></li><li><code>beforeSync$: Subject&lt;DataSourceBeforeSyncEvent&gt;</code> (cancellabile)</li><li><code>afterSync$: Subject&lt;DataSourceAfterSyncEvent&gt;</code></li></ul><p>Metodi runtime piu usati:</p><ul><li><code>fetchData()</code></li><li><code>syncData(entita, original, deleting?, cloning?)</code></li><li><code>addNewRecord(record?)</code></li><li><code>setCurrent(data)</code></li><li><code>clearColumnFilter(col, fetch?)</code></li><li><code>getPendingChanges()</code></li><li><code>hasPendingChanges()</code></li><li><code>batchSave(targetChanges?)</code></li><li><code>rollbackChanges(targetChanges?)</code></li><li><code>exportXls()</code></li></ul><p>Subscriptions/eventi runtime:</p><ul><li><code>fetchInfo$</code>: stream principale stato dati/metadati.</li><li><code>datasourceReady$</code>: emesso quando l&#39;istanza datasource e pronta.</li><li><code>afterFirstLoad$</code>: emesso una sola volta al primo payload utile.</li><li><code>beforeSync$</code>: emesso prima di <code>insert/update/delete/clone/batch</code>; puoi annullare con <code>event.cancelSync(...)</code>.</li><li><code>afterSync$</code>: emesso dopo sync riuscita.</li></ul><p>Nota compatibilita:</p><ul><li>e disponibile anche alias <code>fetchInfo</code> (getter/setter), ma il naming consigliato e <code>fetchInfo$</code>.</li></ul><h2>Esempi</h2><h3>Esempio base (route hardcoded + autoload)</h3><p>Snippet 1:</p><h3>Esempio master-detail (nested datasource)</h3><p>Snippet 2:</p><h3>Esempio operativo (fetch manuale + sync)</h3><p>Snippet 3:</p><h3>Esempio subscriptions (host)</h3><p>Snippet 4:</p><h2>Patch runtime di <code>metaInfo</code> via <code>fetchInfo$.subscribe</code></h2><p>Per modificare runtime i metadati di un nested datasource (es. <code>mc_hide_in_list</code>, <code>md_inline_cell_editing</code>, <code>md_pageable=false</code>, <code>mc_logic_editable=false</code>) <strong>senza toccare il DB</strong> — patch locale al riferimento cached dal <code>MetadataProviderService</code>, latest-wins, single-dialog scenario — sottoscrivi a <code>fetchInfo$</code> del data-source:</p><p>Snippet 5:</p><p><strong>Scope</strong>:</p><ul><li><strong>session-only + istanza-only</strong>: la patch sopravvive solo finche&#39; il <code>MetadataProviderService</code> cache non viene invalidata o il componente non e&#39; distrutto.</li><li><strong>utile</strong> quando un custom-form vuole una rappresentazione diversa di una nested grid rispetto al rendering standalone (es. nascondere colonne ridondanti in dialog).</li></ul><p><strong>Quando NON usare patch runtime</strong>:</p><ul><li>Per modifiche destinate a tutte le istanze della route (anche standalone <code>&lt;route&gt;/list</code>) → patch SQL diretta sui metadati (vedi skill <code>metadata-tables-columns</code>).</li><li>Per modifiche persistenti tra restart → SQL diretto + <code>MetaService.invalidateMetadataRuntime</code>.</li></ul><h2>Multipli <code>&lt;wuic-data-source&gt;</code> nello stesso componente</h2><p>Quando un componente custom contiene piu&#39; di un <code>&lt;wuic-data-source&gt;</code> (es. master + nested righe + nested scadenze), il pattern <strong>`@ViewChild(DataSourceComponent)`</strong> by-type matcha <strong>solo il primo</strong> data-source nell&#39;ordine top-down del template. Per i successivi serve <strong>template reference variable</strong> + <code>@ViewChild(&#39;refName&#39;)</code>:</p><p>Snippet 6:</p><p>Snippet 7:</p><p><strong>Antipattern</strong>: <code>document.querySelectorAll(&#39;wuic-data-source&#39;)</code> + <code>window.ng.getComponent(el)</code> per filtrare per <code>hardcodedRoute</code>. Funziona ma e&#39; fragile, dipende dalla debug API <code>window.ng</code> (non garantita in produzione). Usa sempre template ref.</p>",
4303
4454
  "codeSamples": [
4304
4455
  {
4305
4456
  "id": "code_1",
@@ -4324,6 +4475,24 @@ const frameworkDocsContent = {
4324
4475
  "title": "Snippet 4",
4325
4476
  "language": "ts",
4326
4477
  "code": "const subs = new Subscription();\n\nsubs.add(\n datasource.fetchInfo$.subscribe((info) => {\n if (!info) return;\n console.log('records', info.resultInfo?.dato?.length || 0);\n })\n);\n\nsubs.add(\n datasource.beforeSync$.subscribe((event) => {\n if (event.operation === 'delete' && !canDelete()) {\n event.cancelSync('Delete non consentito dal contesto host');\n }\n })\n);\n\nsubs.add(\n datasource.afterSync$.subscribe((event) => {\n console.log('sync ok', event.operation, event.syncedData);\n })\n);"
4478
+ },
4479
+ {
4480
+ "id": "code_5",
4481
+ "title": "Snippet 5",
4482
+ "language": "ts",
4483
+ "code": "private bindNestedMetaPatches(): void {\n if (!this.scadenzeDs) return;\n const ESSENTIAL = new Set(['data_scadenza', 'importo', 'pagamento_id', 'stato', 'note']);\n const sub = this.scadenzeDs.fetchInfo$.subscribe((info) => {\n const route = this.scadenzeDs?.route?.value;\n if (!info || route !== info.metaInfo?.tableMetadata?.md_route_name) return;\n const cols: any[] = info.metaInfo?.columnMetadata || [];\n for (const c of cols) {\n const name = c?.mc_nome_colonna;\n if (!name || c.mc_is_primary_key) continue;\n c.mc_hide_in_list = !ESSENTIAL.has(name);\n }\n // Patch tableMetadata, mc_logic_editable, ecc. allo stesso modo\n });\n this.subs.add(sub); // cleanup in ngOnDestroy\n}"
4484
+ },
4485
+ {
4486
+ "id": "code_6",
4487
+ "title": "Snippet 6",
4488
+ "language": "html",
4489
+ "code": "<!-- TEMPLATE -->\n<wuic-data-source #righeDs [hardcodedRoute]=\"'fatture_inviate_righe'\" [parentRecord]=\"record\"></wuic-data-source>\n<wuic-data-source #scadenzeDs [hardcodedRoute]=\"'scadenze'\" [parentRecord]=\"record\"></wuic-data-source>"
4490
+ },
4491
+ {
4492
+ "id": "code_7",
4493
+ "title": "Snippet 7",
4494
+ "language": "ts",
4495
+ "code": "// COMPONENT\nimport { DataSourceComponent } from 'wuic-framework-lib';\n\n// Primo data-source (matcha by type) -> niente template ref necessario\n@ViewChild(DataSourceComponent) righeDs?: DataSourceComponent;\n\n// Secondo data-source -> serve `#scadenzeDs` nel template + by-name ViewChild\n@ViewChild('scadenzeDs') scadenzeDs?: DataSourceComponent;"
4327
4496
  }
4328
4497
  ]
4329
4498
  }
@@ -4512,10 +4681,14 @@ const frameworkDocsContent = {
4512
4681
  },
4513
4682
  {
4514
4683
  "id": "sec_13",
4515
- "title": "Note operative"
4684
+ "title": "Authentication:Ldap (LDAP / Active Directory)"
4516
4685
  },
4517
4686
  {
4518
4687
  "id": "sec_14",
4688
+ "title": "Note operative"
4689
+ },
4690
+ {
4691
+ "id": "sec_15",
4519
4692
  "title": "appsettings.json reale (snapshot)"
4520
4693
  }
4521
4694
  ],
@@ -4523,12 +4696,24 @@ const frameworkDocsContent = {
4523
4696
  {
4524
4697
  "id": "overview",
4525
4698
  "title": "Overview",
4526
- "html": "<h1>AppSettings</h1><p>Parametri applicativi, configurazioni ambiente e override runtime.</p><p>Questa pagina documenta le chiavi usate dal framework in <code>appsettings.json</code> e <code>appsettings.{Environment}.json</code>.</p><h2>Convenzioni generali</h2><ul><li>I valori sotto <code>AppSettings</code> sono letti come stringa e poi convertiti (<code>bool/int</code>) dal backend.</li><li>Per flag booleani usare <code>true|false</code>.</li><li>Per chiavi numeriche usare interi positivi (secondi/minuti/timeout).</li><li>Le chiavi sono case-sensitive lato JSON.</li></ul><h2>AppSettings - Core bootstrap</h2><ul><li><code>firstRun</code></li></ul><p> Significato: abilita il flusso di prima configurazione/provisioning.</p><p> Valori: <code>true | false</code>.</p><p> Default tipico: <code>false</code>.</p><ul><li><code>preScaffold</code></li></ul><p> Significato: abilita pre-scaffolding di oggetti metadata/app.</p><p> Valori: <code>true | false</code>.</p><p> Default tipico: <code>false</code>.</p><ul><li><code>projectDataFolder</code></li></ul><p> Significato: root progetto applicativo (es. CrmApp) usata per file, assembly e risorse runtime.</p><p> Valori: path assoluto filesystem.</p><ul><li><code>projectAssemblyName</code></li></ul><p> Significato: assembly applicativo caricato dinamicamente.</p><p> Valori: path assoluto <code>.dll</code>.</p><ul><li><code>defaultSiteRoute</code></li></ul><p> Significato: route iniziale client dopo bootstrap/login.</p><p> Valori: hash-route (es. <code>#/</code>).</p><ul><li><code>license-email</code></li></ul><p> Significato: email/licenza installazione.</p><p> Valori: email valida.</p><h2>AppSettings - Database e provider</h2><ul><li><code>dbms</code></li></ul><p> Significato: provider DB principale route dati.</p><p> Valori enum-like: <code>mssql | mysql | postgresql | oracle | xml</code>.</p><p> Default tipico: <code>mssql</code>.</p><ul><li><code>meta-dbms</code></li></ul><p> Significato: provider DB metadata (route di sistema).</p><p> Valori enum-like: <code>mssql | mysql | postgresql | oracle | xml</code>.</p><p> Default tipico: uguale a <code>dbms</code>.</p><ul><li><code>allowMultipleDBMS</code></li></ul><p> Significato: abilita convivenza provider multipli nel runtime.</p><p> Valori: <code>true | false</code>.</p><p> Default tipico: <code>false</code>.</p><ul><li><code>connection</code></li></ul><p> Significato: connection string base usata da servizi/scaffolding.</p><p> Valori: connection string provider-specific.</p><ul><li><code>DataDBName</code></li></ul><p> Significato: nome database dati di default.</p><p> Valori: nome DB.</p><ul><li><code>connectionByUser</code></li></ul><p> Significato: usa connessione/contesto legato all&#39;utente corrente in punti runtime specifici.</p><p> Valori: <code>true | false</code>.</p><p> Default tipico: <code>false</code>.</p><h2>AppSettings - CRUD, query, filtri</h2><ul><li><code>autoGeneratedQueryTimeout</code></li></ul><p> Significato: timeout (secondi) query SQL autogenerate.</p><p> Valori: intero positivo.</p><p> Default tipico: <code>120</code>.</p><ul><li><code>storedProcTimeout</code></li></ul><p> Significato: timeout (secondi) esecuzione stored procedure.</p><p> Valori: intero positivo.</p><ul><li><code>enableServerSideCrudChangeLog</code></li></ul><p> Significato: abilita logging lato server delle operazioni CRUD.</p><p> Valori: <code>true | false</code>.</p><p> Default tipico: <code>true</code>.</p><ul><li><code>logicDeleteField</code></li></ul><p> Significato: campo globale per cancellazione logica (se non definito in metadata tabella).</p><p> Valori: nome colonna.</p><ul><li><code>logicDeleteValue</code></li></ul><p> Significato: valore che rappresenta &quot;record cancellato logicamente&quot;.</p><p> Valori: string/numero compatibile col tipo del campo.</p><p> Default tipico: <code>1</code>.</p><ul><li><code>cacheDataMinutes</code></li></ul><p> Significato: durata cache dati in minuti (quando caching route attivo).</p><p> Valori: intero positivo.</p><ul><li><code>cacheDataIncludedRoutes</code></li></ul><p> Significato: whitelist route cacheabili.</p><p> Valori: lista route separata da virgole.</p><ul><li><code>cacheDataExcludedRoutes</code></li></ul><p> Significato: blacklist route da escludere dalla cache.</p><p> Valori: lista route separata da virgole.</p><ul><li><code>traceQuery</code></li></ul><p> Significato: abilita tracing query SQL con tempi esecuzione.</p><p> Valori: <code>true | false</code>.</p><p> Default tipico: <code>false</code>.</p><ul><li><code>sqlVerbose</code></li></ul><p> Significato: abilita output SQL verboso in endpoint/tools che lo supportano.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>logInvoke</code></li></ul><p> Significato: abilita log invocazioni funzioni dinamiche/callback.</p><p> Valori: <code>true | false</code>.</p><h2>AppSettings - Sicurezza, login, sessione</h2><ul><li><code>customAuthentication</code></li></ul><p> Significato: delega autenticazione a logica custom applicativa.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>enableCookieAuthentication</code></li></ul><p> Significato: controlla <strong>chi</strong> scrive il cookie di sessione <code>k-user</code> e con <strong>quale policy</strong>. Il backend espone in entrambe le modalita&#39; lo stesso wire format (<code>k-user=&lt;urlencoded-json&gt;</code>), cambia solo il mittente e gli attributi del cookie.</p><p> Valori: <code>true | false</code>. Default: <code>false</code>.</p><p> Confronto sintetico delle due modalita&#39;:</p><table><thead><tr><th>Aspetto</th><th><code>false</code> (legacy / client-managed)</th><th><code>true</code> (server-managed)</th></tr></thead><tbody><tr><td>Chi scrive il cookie</td><td>Frontend JavaScript dopo il login</td><td>Backend via <code>Set-Cookie</code></td></tr><tr><td><code>HttpOnly</code></td><td>No (leggibile da JS)</td><td>Si (protezione XSS)</td></tr><tr><td><code>SameSite</code></td><td>—</td><td><code>Lax</code> (protezione CSRF)</td></tr><tr><td><code>Secure</code></td><td>—</td><td>Si in HTTPS, off su <code>http://localhost</code></td></tr><tr><td><code>Expires</code></td><td>—</td><td><code>sessionTimeoutMinutes</code> (default 60)</td></tr><tr><td>Validazione sessione server</td><td>Nessuna (basta il cookie)</td><td>Token + IP + timeout su DB ad ogni request</td></tr><tr><td>Single-session per utente</td><td>No (sessioni concorrenti libere)</td><td>Si (ultimo login vince)</td></tr><tr><td>IP binding</td><td>No</td><td>Si (cambio IP forza re-login)</td></tr><tr><td>Schema DB richiesto</td><td>Standard</td><td>Colonne <code>token</code>, <code>ip</code>, <code>LastActivityDate</code></td></tr><tr><td>Logout</td><td><code>MetaService.logout</code></td><td><code>MetaService.logoutSession</code></td></tr><tr><td>Quando usarlo</td><td>Sviluppo, CI, proxy SSO, retrocompatibilita&#39;</td><td>Produzione, deploy esposti su internet</td></tr></tbody></table><p> ### Comportamento single-session (modalita&#39; <code>true</code>)</p><p> Ogni login genera un nuovo token che sovrascrive il precedente. Conseguenze pratiche:</p><p> - Se lo stesso utente si logga da <strong>due browser diversi</strong> (o due finestre anonime), l&#39;ultimo login vince: il primo browser viene disconnesso con il messaggio <em>&quot;Sessione terminata: e&#39; stato effettuato l&#39;accesso con lo stesso utente da un altro browser.&quot;</em> — sia navigando una pagina, sia facendo refresh.</p><p> - Il browser con l&#39;ultimo login <strong>non subisce impatti</strong>.</p><p> - Due tab nello <strong>stesso browser</strong> non hanno questo problema: condividono lo stesso cookie, quindi il token si aggiorna su entrambe.</p><p> ### Trappole note</p><p> - Se modifichi <code>appsettings.json</code> a mano mentre il backend e&#39; running, serve un restart (l&#39;editor AppSettings web fa il flush automaticamente).</p><p> - Dopo un flip da <code>false</code> a <code>true</code>, i cookie preesistenti non hanno il token → gli utenti devono rifare il login.</p><p> - I test E2E (<code>backend-api-client.mjs</code>) rilevano la modalita&#39; automaticamente e funzionano in entrambi i casi.</p><ul><li><code>enableODATAAuthentication</code></li></ul><p> Significato: abilita auth per endpoint OData.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>enableGuest</code></li></ul><p> Significato: abilita accesso ospite.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>applyGrantByDefaultToGuest</code></li></ul><p> Significato: applica grant di default anche all&#39;utente guest.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>grantDashSaveToGuest</code></li></ul><p> Significato: consente salvataggio dashboard al guest.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>default-role-id</code></li></ul><p> Significato: ruolo assegnato in fallback/default.</p><p> Valori: intero (ID ruolo).</p><ul><li><code>sessionTimeoutMinutes</code></li></ul><p> Significato: timeout sessione utente in minuti.</p><p> Valori: intero positivo.</p><p> Default tipico: <code>60</code>.</p><ul><li><code>notifySessionExpirationBefore</code></li></ul><p> Significato: anticipo (minuti) notifica scadenza sessione.</p><p> Valori: intero positivo.</p><p> Default tipico: <code>1</code> o <code>2</code> a seconda ambiente.</p><ul><li><code>enableUserLanguageSwitch</code></li></ul><p> Significato: abilita switch lingua utente in UI.</p><p> Valori: <code>true | false</code>.</p><h2>AppSettings - Password e policy credenziali</h2><ul><li><code>IsPwdEncripted</code></li></ul><p> Significato: indica se le password in store sono hashate.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>encriptionMethod</code></li></ul><p> Significato: algoritmo hash legacy usato per verifica password.</p><p> Valori enum-like: <code>SHA1 | MD5</code>.</p><p> Default tipico: <code>SHA1</code>.</p><ul><li><code>password_min_length</code> e <code>password_min_length </code></li></ul><p> Significato: lunghezza minima password (nota: in alcuni file esiste variante con spazio finale nel nome chiave).</p><p> Valori: intero positivo.</p><p> Default tipico: <code>8</code>.</p><h2>AppSettings - Captcha, doppio token, URL applicative</h2><ul><li><code>captcha_public_key</code></li></ul><p> Significato: chiave pubblica captcha.</p><p> Valori: string.</p><ul><li><code>captcha_private_key</code></li></ul><p> Significato: chiave privata captcha.</p><p> Valori: string.</p><ul><li><code>enableDoubleTokenAuthentication</code></li></ul><p> Significato: abilita step di verifica token aggiuntivo nel flusso login/attivazione previsto.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>site-url</code></li></ul><p> Significato: URL base sito per link assoluti email/workflow.</p><p> Valori: URL assoluto.</p><ul><li><code>content-url</code></li></ul><p> Significato: URL base contenuti/link di conferma (mailing, read-confirm, ecc.).</p><p> Valori: URL assoluto.</p><h2>AppSettings - Email e notifiche mail</h2><ul><li><code>email-host</code></li></ul><p> Significato: host SMTP.</p><p> Valori: hostname/IP.</p><ul><li><code>email-port</code></li></ul><p> Significato: porta SMTP.</p><p> Valori: intero (<code>25</code>, <code>465</code>, <code>587</code>, ...).</p><ul><li><code>email-ssl</code></li></ul><p> Significato: abilita TLS/SSL SMTP.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>email-user</code></li></ul><p> Significato: username SMTP.</p><p> Valori: string.</p><ul><li><code>email-pwd</code></li></ul><p> Significato: password SMTP.</p><p> Valori: string/segreto.</p><ul><li><code>email-sender-address-mailing</code></li></ul><p> Significato: mittente email di mailing/notifiche.</p><p> Valori: email valida.</p><ul><li><code>email-sender-name-mailing</code></li></ul><p> Significato: display name mittente mailing.</p><p> Valori: string.</p><ul><li><code>email-sender-address-registration</code></li></ul><p> Significato: mittente email registrazione/attivazione.</p><p> Valori: email valida.</p><ul><li><code>email-admin-registration</code></li></ul><p> Significato: destinatario amministrativo eventi registrazione.</p><p> Valori: email valida.</p><ul><li><code>email_confirm_token_subject</code></li></ul><p> Significato: subject template email conferma token.</p><p> Valori: string.</p><ul><li><code>email_confirm_token_body</code></li></ul><p> Significato: body template email conferma token.</p><p> Valori: string (anche HTML).</p><h2>AppSettings - Upload e media</h2><ul><li><code>uploadFolder</code></li></ul><p> Significato: cartella root upload file/immagini.</p><p> Valori: path assoluto o virtual path risolto dal server.</p><ul><li><code>base64Image</code></li></ul><p> Significato: forza gestione immagini in formato base64 dove previsto.</p><p> Valori: <code>true | false</code>.</p><h2>AppSettings - Report e runtime report viewer</h2><ul><li><code>ReportMode</code></li></ul><p> Significato: modalità rendering report nel viewer.</p><p> Valori enum-like osservati: <code>web</code> (default fallback) e modalità custom gestite lato controller.</p><p> Nota: se assente, il backend usa fallback <code>web</code>.</p><ul><li><code>reportQueryTimeout</code></li></ul><p> Significato: timeout query dataset report (secondi).</p><p> Valori: intero positivo.</p><h2>AppSettings - Record translations (chiavi legacy in AppSettings)</h2><ul><li><code>recordTranslationsEnabled</code></li></ul><p> Significato: abilita traduzione record per campo.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>recordTranslationsDefaultTableName</code></li></ul><p> Significato: tabella default traduzioni record.</p><p> Valori: nome tabella.</p><ul><li><code>recordTranslationsTranslationJsonFieldName</code></li></ul><p> Significato: nome campo JSON traduzioni.</p><p> Valori: nome colonna.</p><ul><li><code>recordTranslationsDefaultLanguage</code></li></ul><p> Significato: lingua fallback traduzioni record.</p><p> Valori: culture code (es. <code>it-IT</code>, <code>en-US</code>).</p><ul><li><code>recordTranslationsFieldNames</code></li></ul><p> Significato: elenco campi traducibili.</p><p> Valori: lista separata da virgole.</p><h2>Sezioni top-level correlate (fuori da AppSettings)</h2><ul><li><code>RecordTranslations:Enabled</code></li></ul><p> Significato: switch principale runtime traduzioni record.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>RecordTranslations:DefaultTableName</code></li></ul><p> Significato: tabella fisica traduzioni.</p><p> Valori: nome tabella.</p><ul><li><code>RecordTranslations:TranslationJsonFieldName</code></li></ul><p> Significato: campo JSON traduzioni.</p><p> Valori: nome colonna.</p><ul><li><code>RecordTranslations:DefaultLanguage</code></li></ul><p> Significato: lingua fallback.</p><p> Valori: culture code.</p><ul><li><code>RecordTranslations:FieldNames</code></li></ul><p> Significato: campi traducibili.</p><p> Valori: array di stringhe.</p><ul><li><code>Scheduler:Enabled</code></li></ul><p> Significato: abilita hosted service scheduling.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>Scheduler:PollSeconds</code></li></ul><p> Significato: polling scheduler in secondi.</p><p> Valori: intero positivo.</p><ul><li><code>Scheduler:MaxTasksPerCycle</code></li></ul><p> Significato: massimo task processati per ciclo.</p><p> Valori: intero positivo.</p><ul><li><code>Scheduler:RetryDelaySeconds</code></li></ul><p> Significato: ritardo retry task falliti.</p><p> Valori: intero positivo.</p><ul><li><code>Scheduler:SqlCommandTimeoutSeconds</code></li></ul><p> Significato: timeout SQL scheduler.</p><p> Valori: intero positivo.</p><ul><li><code>Notifications:Enabled</code></li></ul><p> Significato: abilita subsystem notifiche realtime.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>Notifications:Mode</code></li></ul><p> Significato: modalità watcher backend notifiche.</p><p> Valori enum-like: <code>SqlDependency | Polling</code>.</p><ul><li><code>Notifications:PollSeconds</code></li></ul><p> Significato: polling notifiche se <code>Mode = Polling</code>.</p><p> Valori: intero positivo.</p><ul><li><code>Authentication:OAuth:Enabled</code></li></ul><p> Significato: abilita login OAuth/OIDC.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>Authentication:OAuth:Provider</code></li></ul><p> Significato: provider OAuth visualizzato/gestito.</p><p> Valori enum-like usato in config: <code>Google</code> (estendibile a provider custom compatibili OIDC).</p><ul><li><code>Authentication:OAuth:Authority</code></li></ul><p> Significato: authority/issuer OIDC.</p><p> Valori: URL.</p><ul><li><code>Authentication:OAuth:ClientId</code></li></ul><p> Significato: client id applicazione OAuth.</p><p> Valori: string.</p><ul><li><code>Authentication:OAuth:ClientSecret</code></li></ul><p> Significato: client secret OAuth.</p><p> Valori: string/segreto.</p><ul><li><code>Authentication:OAuth:RequireHttpsMetadata</code></li></ul><p> Significato: richiede metadati OIDC via HTTPS.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>Authentication:OAuth:CallbackPath</code></li></ul><p> Significato: path callback signin.</p><p> Valori: path assoluto.</p><ul><li><code>Authentication:OAuth:SignedOutCallbackPath</code></li></ul><p> Significato: path callback signout.</p><p> Valori: path assoluto.</p><ul><li><code>Authentication:OAuth:AllowedReturnOrigins</code></li></ul><p> Significato: whitelist origin di ritorno consentite.</p><p> Valori: array URL/origin.</p><ul><li><code>Authentication:OAuth:Scopes</code></li></ul><p> Significato: scope richiesti al provider.</p><p> Valori: array stringhe (<code>openid</code>, <code>profile</code>, <code>email</code>, ...).</p><h2>Note operative</h2><ul><li>In produzione usare sempre secret manager/variabili ambiente per password/token (<code>email-pwd</code>, <code>ClientSecret</code>, captcha key, ecc.).</li><li>Se abiliti <code>allowMultipleDBMS</code>, validare che <code>dbms</code> e <code>meta-dbms</code> siano entrambi supportati dal deploy.</li><li>Per notifiche realtime, <code>Notifications:Mode=SqlDependency</code> richiede setup SQL compatibile; in alternativa usare <code>Polling</code>.</li></ul><h2>appsettings.json reale (snapshot)</h2><p>Snippet 1:</p>",
4699
+ "html": "<h1>AppSettings</h1><p>Parametri applicativi, configurazioni ambiente e override runtime.</p><p>Questa pagina documenta le chiavi usate dal framework in <code>appsettings.json</code> e <code>appsettings.{Environment}.json</code>.</p><h2>Convenzioni generali</h2><ul><li>I valori sotto <code>AppSettings</code> sono letti come stringa e poi convertiti (<code>bool/int</code>) dal backend.</li><li>Per flag booleani usare <code>true|false</code>.</li><li>Per chiavi numeriche usare interi positivi (secondi/minuti/timeout).</li><li>Le chiavi sono case-sensitive lato JSON.</li></ul><h2>AppSettings - Core bootstrap</h2><ul><li><code>firstRun</code></li></ul><p> Significato: abilita il flusso di prima configurazione/provisioning.</p><p> Valori: <code>true | false</code>.</p><p> Default tipico: <code>false</code>.</p><ul><li><code>preScaffold</code></li></ul><p> Significato: abilita pre-scaffolding di oggetti metadata/app.</p><p> Valori: <code>true | false</code>.</p><p> Default tipico: <code>false</code>.</p><ul><li><code>projectDataFolder</code></li></ul><p> Significato: root progetto applicativo (es. CrmApp) usata per file, assembly e risorse runtime.</p><p> Valori: path assoluto filesystem.</p><ul><li><code>projectAssemblyName</code></li></ul><p> Significato: assembly applicativo caricato dinamicamente.</p><p> Valori: path assoluto <code>.dll</code>.</p><ul><li><code>defaultSiteRoute</code></li></ul><p> Significato: route iniziale client dopo bootstrap/login.</p><p> Valori: hash-route (es. <code>#/</code>).</p><ul><li><code>license-email</code></li></ul><p> Significato: email/licenza installazione.</p><p> Valori: email valida.</p><h2>AppSettings - Database e provider</h2><ul><li><code>dbms</code></li></ul><p> Significato: provider DB principale route dati.</p><p> Valori enum-like: <code>mssql | mysql | postgresql | oracle | xml</code>.</p><p> Default tipico: <code>mssql</code>.</p><ul><li><code>meta-dbms</code></li></ul><p> Significato: provider DB metadata (route di sistema).</p><p> Valori enum-like: <code>mssql | mysql | postgresql | oracle | xml</code>.</p><p> Default tipico: uguale a <code>dbms</code>.</p><ul><li><code>allowMultipleDBMS</code></li></ul><p> Significato: abilita convivenza provider multipli nel runtime.</p><p> Valori: <code>true | false</code>.</p><p> Default tipico: <code>false</code>.</p><ul><li><code>connection</code></li></ul><p> Significato: connection string base usata da servizi/scaffolding.</p><p> Valori: connection string provider-specific.</p><ul><li><code>DataDBName</code></li></ul><p> Significato: nome database dati di default.</p><p> Valori: nome DB.</p><ul><li><code>connectionByUser</code></li></ul><p> Significato: usa connessione/contesto legato all&#39;utente corrente in punti runtime specifici.</p><p> Valori: <code>true | false</code>.</p><p> Default tipico: <code>false</code>.</p><h2>AppSettings - CRUD, query, filtri</h2><ul><li><code>autoGeneratedQueryTimeout</code></li></ul><p> Significato: timeout (secondi) query SQL autogenerate.</p><p> Valori: intero positivo.</p><p> Default tipico: <code>120</code>.</p><ul><li><code>storedProcTimeout</code></li></ul><p> Significato: timeout (secondi) esecuzione stored procedure.</p><p> Valori: intero positivo.</p><ul><li><code>enableServerSideCrudChangeLog</code></li></ul><p> Significato: abilita logging lato server delle operazioni CRUD.</p><p> Valori: <code>true | false</code>.</p><p> Default tipico: <code>true</code>.</p><ul><li><code>logicDeleteField</code></li></ul><p> Significato: campo globale per cancellazione logica (se non definito in metadata tabella).</p><p> Valori: nome colonna.</p><ul><li><code>logicDeleteValue</code></li></ul><p> Significato: valore che rappresenta &quot;record cancellato logicamente&quot;.</p><p> Valori: string/numero compatibile col tipo del campo.</p><p> Default tipico: <code>1</code>.</p><ul><li><code>cacheDataMinutes</code></li></ul><p> Significato: durata cache dati in minuti (quando caching route attivo).</p><p> Valori: intero positivo.</p><ul><li><code>cacheDataIncludedRoutes</code></li></ul><p> Significato: whitelist route cacheabili.</p><p> Valori: lista route separata da virgole.</p><ul><li><code>cacheDataExcludedRoutes</code></li></ul><p> Significato: blacklist route da escludere dalla cache.</p><p> Valori: lista route separata da virgole.</p><ul><li><code>traceQuery</code></li></ul><p> Significato: abilita tracing query SQL con tempi esecuzione.</p><p> Valori: <code>true | false</code>.</p><p> Default tipico: <code>false</code>.</p><ul><li><code>sqlVerbose</code></li></ul><p> Significato: abilita output SQL verboso in endpoint/tools che lo supportano.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>logInvoke</code></li></ul><p> Significato: abilita log invocazioni funzioni dinamiche/callback.</p><p> Valori: <code>true | false</code>.</p><h2>AppSettings - Sicurezza, login, sessione</h2><ul><li><code>customAuthentication</code></li></ul><p> Significato: delega autenticazione a logica custom applicativa.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>enableCookieAuthentication</code></li></ul><p> Significato: controlla <strong>chi</strong> scrive il cookie di sessione <code>k-user</code> e con <strong>quale policy</strong>. Il backend espone in entrambe le modalita&#39; lo stesso wire format (<code>k-user=&lt;urlencoded-json&gt;</code>), cambia solo il mittente e gli attributi del cookie.</p><p> Valori: <code>true | false</code>. Default: <code>false</code>.</p><p> Confronto sintetico delle due modalita&#39;:</p><table><thead><tr><th>Aspetto</th><th><code>false</code> (legacy / client-managed)</th><th><code>true</code> (server-managed)</th></tr></thead><tbody><tr><td>Chi scrive il cookie</td><td>Frontend JavaScript dopo il login</td><td>Backend via <code>Set-Cookie</code></td></tr><tr><td><code>HttpOnly</code></td><td>No (leggibile da JS)</td><td>Si (protezione XSS)</td></tr><tr><td><code>SameSite</code></td><td>—</td><td><code>Lax</code> (protezione CSRF)</td></tr><tr><td><code>Secure</code></td><td>—</td><td>Si in HTTPS, off su <code>http://localhost</code></td></tr><tr><td><code>Expires</code></td><td>—</td><td><code>sessionTimeoutMinutes</code> (default 60)</td></tr><tr><td>Validazione sessione server</td><td>Nessuna (basta il cookie)</td><td>Token + IP + timeout su DB ad ogni request</td></tr><tr><td>Single-session per utente</td><td>No (sessioni concorrenti libere)</td><td>Si (ultimo login vince)</td></tr><tr><td>IP binding</td><td>No</td><td>Si (cambio IP forza re-login)</td></tr><tr><td>Schema DB richiesto</td><td>Standard</td><td>Colonne <code>token</code>, <code>ip</code>, <code>LastActivityDate</code></td></tr><tr><td>Logout</td><td><code>MetaService.logout</code></td><td><code>MetaService.logoutSession</code></td></tr><tr><td>Quando usarlo</td><td>Sviluppo, CI, proxy SSO, retrocompatibilita&#39;</td><td>Produzione, deploy esposti su internet</td></tr></tbody></table><p> ### Comportamento single-session (modalita&#39; <code>true</code>)</p><p> Ogni login genera un nuovo token che sovrascrive il precedente. Conseguenze pratiche:</p><p> - Se lo stesso utente si logga da <strong>due browser diversi</strong> (o due finestre anonime), l&#39;ultimo login vince: il primo browser viene disconnesso con il messaggio <em>&quot;Sessione terminata: e&#39; stato effettuato l&#39;accesso con lo stesso utente da un altro browser.&quot;</em> — sia navigando una pagina, sia facendo refresh.</p><p> - Il browser con l&#39;ultimo login <strong>non subisce impatti</strong>.</p><p> - Due tab nello <strong>stesso browser</strong> non hanno questo problema: condividono lo stesso cookie, quindi il token si aggiorna su entrambe.</p><p> ### Trappole note</p><p> - Se modifichi <code>appsettings.json</code> a mano mentre il backend e&#39; running, serve un restart (l&#39;editor AppSettings web fa il flush automaticamente).</p><p> - Dopo un flip da <code>false</code> a <code>true</code>, i cookie preesistenti non hanno il token → gli utenti devono rifare il login.</p><p> - I test E2E (<code>backend-api-client.mjs</code>) rilevano la modalita&#39; automaticamente e funzionano in entrambi i casi.</p><ul><li><code>enableODATAAuthentication</code></li></ul><p> Significato: abilita auth per endpoint OData.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>enableGuest</code></li></ul><p> Significato: abilita accesso ospite.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>applyGrantByDefaultToGuest</code></li></ul><p> Significato: applica grant di default anche all&#39;utente guest.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>grantDashSaveToGuest</code></li></ul><p> Significato: consente salvataggio dashboard al guest.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>default-role-id</code></li></ul><p> Significato: ruolo assegnato in fallback/default.</p><p> Valori: intero (ID ruolo).</p><ul><li><code>sessionTimeoutMinutes</code></li></ul><p> Significato: timeout sessione utente in minuti.</p><p> Valori: intero positivo.</p><p> Default tipico: <code>60</code>.</p><ul><li><code>notifySessionExpirationBefore</code></li></ul><p> Significato: anticipo (minuti) notifica scadenza sessione.</p><p> Valori: intero positivo.</p><p> Default tipico: <code>1</code> o <code>2</code> a seconda ambiente.</p><ul><li><code>enableUserLanguageSwitch</code></li></ul><p> Significato: abilita switch lingua utente in UI.</p><p> Valori: <code>true | false</code>.</p><h2>AppSettings - Password e policy credenziali</h2><ul><li><code>IsPwdEncripted</code></li></ul><p> Significato: indica se le password in store sono hashate.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>encriptionMethod</code></li></ul><p> Significato: algoritmo hash legacy usato per verifica password.</p><p> Valori enum-like: <code>SHA1 | MD5</code>.</p><p> Default tipico: <code>SHA1</code>.</p><ul><li><code>password_min_length</code> e <code>password_min_length </code></li></ul><p> Significato: lunghezza minima password (nota: in alcuni file esiste variante con spazio finale nel nome chiave).</p><p> Valori: intero positivo.</p><p> Default tipico: <code>8</code>.</p><h2>AppSettings - Captcha, doppio token, URL applicative</h2><ul><li><code>captcha_public_key</code></li></ul><p> Significato: chiave pubblica captcha.</p><p> Valori: string.</p><ul><li><code>captcha_private_key</code></li></ul><p> Significato: chiave privata captcha.</p><p> Valori: string.</p><ul><li><code>enableDoubleTokenAuthentication</code></li></ul><p> Significato: abilita step di verifica token aggiuntivo nel flusso login/attivazione previsto.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>site-url</code></li></ul><p> Significato: URL base sito per link assoluti email/workflow.</p><p> Valori: URL assoluto.</p><ul><li><code>content-url</code></li></ul><p> Significato: URL base contenuti/link di conferma (mailing, read-confirm, ecc.).</p><p> Valori: URL assoluto.</p><h2>AppSettings - Email e notifiche mail</h2><ul><li><code>email-host</code></li></ul><p> Significato: host SMTP.</p><p> Valori: hostname/IP.</p><ul><li><code>email-port</code></li></ul><p> Significato: porta SMTP.</p><p> Valori: intero (<code>25</code>, <code>465</code>, <code>587</code>, ...).</p><ul><li><code>email-ssl</code></li></ul><p> Significato: abilita TLS/SSL SMTP.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>email-user</code></li></ul><p> Significato: username SMTP.</p><p> Valori: string.</p><ul><li><code>email-pwd</code></li></ul><p> Significato: password SMTP.</p><p> Valori: string/segreto.</p><ul><li><code>email-sender-address-mailing</code></li></ul><p> Significato: mittente email di mailing/notifiche.</p><p> Valori: email valida.</p><ul><li><code>email-sender-name-mailing</code></li></ul><p> Significato: display name mittente mailing.</p><p> Valori: string.</p><ul><li><code>email-sender-address-registration</code></li></ul><p> Significato: mittente email registrazione/attivazione.</p><p> Valori: email valida.</p><ul><li><code>email-admin-registration</code></li></ul><p> Significato: destinatario amministrativo eventi registrazione.</p><p> Valori: email valida.</p><ul><li><code>email_confirm_token_subject</code></li></ul><p> Significato: subject template email conferma token.</p><p> Valori: string.</p><ul><li><code>email_confirm_token_body</code></li></ul><p> Significato: body template email conferma token.</p><p> Valori: string (anche HTML).</p><h2>AppSettings - Upload e media</h2><ul><li><code>uploadFolder</code></li></ul><p> Significato: cartella root upload file/immagini.</p><p> Valori: path assoluto o virtual path risolto dal server.</p><ul><li><code>base64Image</code></li></ul><p> Significato: forza gestione immagini in formato base64 dove previsto.</p><p> Valori: <code>true | false</code>.</p><h2>AppSettings - Report e runtime report viewer</h2><ul><li><code>ReportMode</code></li></ul><p> Significato: modalità rendering report nel viewer.</p><p> Valori enum-like osservati: <code>web</code> (default fallback) e modalità custom gestite lato controller.</p><p> Nota: se assente, il backend usa fallback <code>web</code>.</p><ul><li><code>reportQueryTimeout</code></li></ul><p> Significato: timeout query dataset report (secondi).</p><p> Valori: intero positivo.</p><h2>AppSettings - Record translations (chiavi legacy in AppSettings)</h2><ul><li><code>recordTranslationsEnabled</code></li></ul><p> Significato: abilita traduzione record per campo.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>recordTranslationsDefaultTableName</code></li></ul><p> Significato: tabella default traduzioni record.</p><p> Valori: nome tabella.</p><ul><li><code>recordTranslationsTranslationJsonFieldName</code></li></ul><p> Significato: nome campo JSON traduzioni.</p><p> Valori: nome colonna.</p><ul><li><code>recordTranslationsDefaultLanguage</code></li></ul><p> Significato: lingua fallback traduzioni record.</p><p> Valori: culture code (es. <code>it-IT</code>, <code>en-US</code>).</p><ul><li><code>recordTranslationsFieldNames</code></li></ul><p> Significato: elenco campi traducibili.</p><p> Valori: lista separata da virgole.</p><h2>Sezioni top-level correlate (fuori da AppSettings)</h2><ul><li><code>RecordTranslations:Enabled</code></li></ul><p> Significato: switch principale runtime traduzioni record.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>RecordTranslations:DefaultTableName</code></li></ul><p> Significato: tabella fisica traduzioni.</p><p> Valori: nome tabella.</p><ul><li><code>RecordTranslations:TranslationJsonFieldName</code></li></ul><p> Significato: campo JSON traduzioni.</p><p> Valori: nome colonna.</p><ul><li><code>RecordTranslations:DefaultLanguage</code></li></ul><p> Significato: lingua fallback.</p><p> Valori: culture code.</p><ul><li><code>RecordTranslations:FieldNames</code></li></ul><p> Significato: campi traducibili.</p><p> Valori: array di stringhe.</p><ul><li><code>Scheduler:Enabled</code></li></ul><p> Significato: abilita hosted service scheduling.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>Scheduler:PollSeconds</code></li></ul><p> Significato: polling scheduler in secondi.</p><p> Valori: intero positivo.</p><ul><li><code>Scheduler:MaxTasksPerCycle</code></li></ul><p> Significato: massimo task processati per ciclo.</p><p> Valori: intero positivo.</p><ul><li><code>Scheduler:RetryDelaySeconds</code></li></ul><p> Significato: ritardo retry task falliti.</p><p> Valori: intero positivo.</p><ul><li><code>Scheduler:SqlCommandTimeoutSeconds</code></li></ul><p> Significato: timeout SQL scheduler.</p><p> Valori: intero positivo.</p><ul><li><code>Notifications:Enabled</code></li></ul><p> Significato: abilita subsystem notifiche realtime.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>Notifications:Mode</code></li></ul><p> Significato: modalità watcher backend notifiche.</p><p> Valori enum-like: <code>SqlDependency | Polling</code>.</p><ul><li><code>Notifications:PollSeconds</code></li></ul><p> Significato: polling notifiche se <code>Mode = Polling</code>.</p><p> Valori: intero positivo.</p><ul><li><code>Authentication:OAuth:Enabled</code></li></ul><p> Significato: abilita login OAuth/OIDC.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>Authentication:OAuth:Provider</code></li></ul><p> Significato: provider OAuth visualizzato/gestito.</p><p> Valori enum-like usato in config: <code>Google</code> (estendibile a provider custom compatibili OIDC).</p><ul><li><code>Authentication:OAuth:Authority</code></li></ul><p> Significato: authority/issuer OIDC.</p><p> Valori: URL.</p><ul><li><code>Authentication:OAuth:ClientId</code></li></ul><p> Significato: client id applicazione OAuth.</p><p> Valori: string.</p><ul><li><code>Authentication:OAuth:ClientSecret</code></li></ul><p> Significato: client secret OAuth.</p><p> Valori: string/segreto.</p><ul><li><code>Authentication:OAuth:RequireHttpsMetadata</code></li></ul><p> Significato: richiede metadati OIDC via HTTPS.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>Authentication:OAuth:CallbackPath</code></li></ul><p> Significato: path callback signin.</p><p> Valori: path assoluto.</p><ul><li><code>Authentication:OAuth:SignedOutCallbackPath</code></li></ul><p> Significato: path callback signout.</p><p> Valori: path assoluto.</p><ul><li><code>Authentication:OAuth:AllowedReturnOrigins</code></li></ul><p> Significato: whitelist origin di ritorno consentite.</p><p> Valori: array URL/origin.</p><ul><li><code>Authentication:OAuth:Scopes</code></li></ul><p> Significato: scope richiesti al provider.</p><p> Valori: array stringhe (<code>openid</code>, <code>profile</code>, <code>email</code>, ...).</p><h3>ADFS via OpenID Connect</h3><p>ADFS 4.0+ (Windows Server 2016+) parla OIDC nativamente: l&#39;integrazione con WUIC <strong>non richiede codice nuovo</strong>, basta la sezione <code>Authentication.OAuth</code> configurata sul tenant ADFS. La pipeline e&#39; la stessa di Google/Auth0/Keycloak — <code>Provider=&quot;OpenIdConnect&quot;</code> instrada il flusso su <code>services.AddOpenIdConnect(...)</code> in <a href=\"../../../../Startup.cs#L264\">Startup.cs:264</a>.</p><p><strong>Pre-requisiti lato ADFS:</strong></p><p>1. <strong>Federation Service Name</strong> raggiungibile in HTTPS dal backend WUIC (es. <code>https://adfs.contoso.com/adfs</code>).</p><p>2. Endpoint OIDC abilitato: verifica che <code>https://adfs.contoso.com/adfs/.well-known/openid-configuration</code> risponda 200 con il discovery document.</p><p>3. <strong>Application Group</strong> registrata in ADFS Management Console (<code>adfs.msc</code> → Application Groups → Add Application Group → &quot;Server application accessing a web API&quot;):</p><p> - Redirect URI: <code>https://&lt;host-wuic&gt;/signin-oidc</code></p><p> - Annotare <code>ClientId</code> (auto-generato) e generare un <code>ClientSecret</code>.</p><p>4. <strong>Issuance Transform Rules</strong> sul Web API: emettere almeno <code>email</code>, <code>name</code>, <code>upn</code> come claim (servono al frontend per popolare il profilo utente).</p><p><strong>Configurazione lato KonvergenceCore:</strong></p><p>Snippet 1:</p><p><strong>Trappole note:</strong></p><ul><li><strong>`RequireHttpsMetadata=true` obbligatorio in produzione</strong>: ADFS espone i metadati firmati su HTTPS; mai disabilitarlo se non in dev contro un ADFS con cert self-signed.</li><li><strong>`CallbackPath`</strong> deve corrispondere <strong>letteralmente</strong> al Redirect URI registrato in ADFS (case-sensitive sulla path). Se cambi uno, cambia anche l&#39;altro.</li><li><strong>Scopes</strong>: ADFS supporta <code>openid</code>, <code>profile</code>, <code>email</code>, e custom scope definiti nelle Application Permissions. Se richiedi uno scope non concesso al client, ADFS risponde con <code>invalid_scope</code> durante il consent.</li><li><strong>Single Logout (SLO)</strong>: <code>SignedOutCallbackPath</code> viene chiamato solo se ADFS e&#39; configurato per emettere il logout token. Se manca, dopo il logout WUIC il browser resta loggato in ADFS e un nuovo login WUIC e&#39; silent SSO.</li><li><strong>Coesistenza con LDAP</strong>: la sezione <code>Authentication.OAuth</code> (federazione browser-redirect) e <code>Authentication.Ldap</code> (bind diretto da username/password) sono indipendenti. Possono coesistere — il front-end mostra un pulsante &quot;Login con ADFS&quot; oltre al form locale.</li><li><strong>Provider distinto da Google</strong>: il branch <code>Provider==Google</code> in <a href=\"../../../../Startup.cs#L241\">Startup.cs:241</a> usa <code>AddGoogle()</code> con scope cablati. Per ADFS impostare <strong>`Provider=OpenIdConnect`</strong> (non <code>Google</code>) altrimenti la pipeline punta a <code>accounts.google.com</code>.</li><li><strong>ADFS 2.0 / 3.0 (Windows Server 2008R2-2012R2)</strong>: parlano solo WS-Federation o SAML 2.0, non OIDC. Per quei tenant servirebbe un PackageReference aggiuntivo (<code>Microsoft.AspNetCore.Authentication.WsFederation</code> o <code>Sustainsys.Saml2</code>) e un nuovo branch in <code>Startup.ConfigureServices</code> — fuori scope di questa sezione.</li></ul><h2>Authentication:Ldap (LDAP / Active Directory)</h2><p>WUIC supporta autenticazione LDAP/AD come provider opzionale <strong>prima</strong> del login DB tradizionale (LDAP-first). Quando <code>Authentication:Ldap:Enabled=true</code>, il backend tenta una bind verso il directory server configurato; se la bind riesce e l&#39;utente non esiste ancora nella tabella utenti locale, viene auto-provisionato con <code>Authentication:Ldap:DefaultRoleId</code> (o, in fallback, <code>AppSettings:default-role-id</code>). Se LDAP è offline e <code>FallbackToDbOnFailure=true</code>, il login ricade automaticamente sul provider DB — utile per garantire l&#39;accesso <code>admin/admin</code> anche in caso di outage del directory server.</p><p>Esempio minimo:</p><p>Snippet 2:</p><p>Chiavi disponibili:</p><ul><li><code>Authentication:Ldap:Enabled</code></li></ul><p> Significato: abilita il provider LDAP davanti al DB. Quando <code>false</code>, il flusso e&#39; identico a un deploy senza la sezione.</p><p> Valori: <code>true | false</code>.</p><ul><li><code>Authentication:Ldap:Host</code></li></ul><p> Significato: hostname del directory server (AD DC, OpenLDAP, ApacheDS).</p><p> Valori: string.</p><ul><li><code>Authentication:Ldap:Port</code></li></ul><p> Significato: porta del directory server.</p><p> Valori: intero. Tipicamente <code>389</code> (plain o StartTLS) o <code>636</code> (LDAPS).</p><ul><li><code>Authentication:Ldap:UseSsl</code></li></ul><p> Significato: apre la connessione direttamente in LDAPS (TLS at-connect).</p><p> Valori: <code>true | false</code>.</p><ul><li><code>Authentication:Ldap:UseStartTls</code></li></ul><p> Significato: apre in plain e poi promuove a TLS via StartTLS extended op (RFC 4511).</p><p> Valori: <code>true | false</code>. Mutuamente esclusivo con <code>UseSsl</code>.</p><ul><li><code>Authentication:Ldap:BaseDn</code></li></ul><p> Significato: DN base sotto cui cercare gli utenti.</p><p> Valori: string DN (es. <code>DC=example,DC=com</code>).</p><ul><li><code>Authentication:Ldap:UserSearchFilter</code></li></ul><p> Significato: filter LDAP per individuare la entry utente. Il placeholder <code>{username}</code> viene sostituito con lo username (RFC 4515 escaping applicato lato C# per prevenire injection).</p><p> Valori: string filter. Default: <code>(&amp;(objectClass=user)(sAMAccountName={username}))</code> (Active Directory). Per OpenLDAP usare tipicamente <code>(&amp;(objectClass=inetOrgPerson)(uid={username}))</code>.</p><ul><li><code>Authentication:Ldap:UsernameAttribute</code></li></ul><p> Significato: attributo della entry da cui leggere lo username canonico (usato come <code>username_column_name</code> nella tabella utenti locale).</p><p> Valori: string. Default: <code>sAMAccountName</code>.</p><ul><li><code>Authentication:Ldap:DisplayNameAttribute</code></li></ul><p> Significato: attributo da cui leggere il display name (usato come <code>user_description_column_name</code>).</p><p> Valori: string. Default: <code>displayName</code>.</p><ul><li><code>Authentication:Ldap:EmailAttribute</code></li></ul><p> Significato: attributo da cui leggere l&#39;email (usato come <code>email_column_name</code>).</p><p> Valori: string. Default: <code>mail</code>.</p><ul><li><code>Authentication:Ldap:BindDn</code></li></ul><p> Significato: DN del service account read-only usato per la search. Vuoto = anonymous bind (alcuni AD lo permettono per ricerche limitate, OpenLDAP di solito no).</p><p> Valori: string DN.</p><ul><li><code>Authentication:Ldap:BindPassword</code></li></ul><p> Significato: password del service account. <strong>Mai committare</strong>: usare secret manager/env var (<code>Authentication__Ldap__BindPassword</code>) o riempire post-install dall&#39;editor AppSettings.</p><p> Valori: string/segreto.</p><ul><li><code>Authentication:Ldap:ConnectTimeoutSeconds</code></li></ul><p> Significato: timeout di connessione (in secondi) verso il directory server. Allo scadere il login fa fallback al DB se <code>FallbackToDbOnFailure=true</code>.</p><p> Valori: intero positivo. Default: <code>5</code>.</p><ul><li><code>Authentication:Ldap:FallbackToDbOnFailure</code></li></ul><p> Significato: se LDAP e&#39; irraggiungibile o ritorna errore non legato alle credenziali, ricade sul login DB tradizionale. Garantisce l&#39;accesso <code>admin/admin</code> durante outage del directory.</p><p> Valori: <code>true | false</code>. Default: <code>true</code>.</p><ul><li><code>Authentication:Ldap:AutoProvision</code></li></ul><p> Significato: al primo login LDAP riuscito di un utente sconosciuto al DB locale, crea automaticamente la riga in <code>_metadati__tabelle.user_table_name</code> (default <code>utenti</code>) con il default role.</p><p> Valori: <code>true | false</code>. Default: <code>true</code>.</p><ul><li><code>Authentication:Ldap:DefaultRoleId</code></li></ul><p> Significato: id ruolo assegnato in auto-provisioning. Override di <code>AppSettings:default-role-id</code>. Se entrambi <code>null</code>, il default e&#39; <code>1</code>.</p><p> Valori: intero o <code>null</code>.</p><h3>Trappole note</h3><ul><li><strong>Bind password mai committata</strong>: il template <code>appsettings.linux.*.json</code> usa <code>__SET_LDAP_BIND_PASSWORD__</code> come placeholder; il deploy script la sostituisce. Per i template &quot;dev&quot; (<code>appsettings.json</code>, <code>appsettings.Development.json</code>) il valore di default e&#39; la stringa vuota — mai un segreto reale.</li><li><strong>AD vs OpenLDAP filter</strong>: AD usa <code>sAMAccountName</code>, OpenLDAP usa <code>uid</code>. Cambiare di conseguenza <code>UserSearchFilter</code> e <code>UsernameAttribute</code>.</li><li><strong>StartTLS su porta 636</strong>: errore comune. La 636 e&#39; LDAPS (<code>UseSsl=true</code>); StartTLS gira sulla 389.</li><li><strong>Nessun cambiamento al cookie path</strong>: utenti LDAP e DB ricevono lo stesso cookie <code>k-user</code>. La differenza e&#39; solo <em>come</em> vengono autenticate le credenziali.</li><li><strong>Sentinel password</strong>: il provisioner inserisce <code>&#39;__LDAP__&#39;</code> come valore della password column locale per gli utenti auto-provisionati. Non viene mai usata per autenticare (la bind LDAP ha gia&#39; validato), ma la colonna deve essere NOT NULL-compatibile.</li><li><strong>Toggle a runtime</strong>: tutte le chiavi <code>Authentication:Ldap:*</code> sono <code>RestartAndLogout</code>: editarle dall&#39;AppSettings editor richiede restart pulito.</li></ul><h2>Note operative</h2><ul><li>In produzione usare sempre secret manager/variabili ambiente per password/token (<code>email-pwd</code>, <code>ClientSecret</code>, captcha key, ecc.).</li><li>Se abiliti <code>allowMultipleDBMS</code>, validare che <code>dbms</code> e <code>meta-dbms</code> siano entrambi supportati dal deploy.</li><li>Per notifiche realtime, <code>Notifications:Mode=SqlDependency</code> richiede setup SQL compatibile; in alternativa usare <code>Polling</code>.</li></ul><h2>appsettings.json reale (snapshot)</h2><p>Snippet 3:</p>",
4527
4700
  "codeSamples": [
4528
4701
  {
4529
4702
  "id": "code_1",
4530
4703
  "title": "Snippet 1",
4531
4704
  "language": "json",
4705
+ "code": "\"Authentication\": {\n \"OAuth\": {\n \"Enabled\": true,\n \"Provider\": \"OpenIdConnect\",\n \"Authority\": \"https://adfs.contoso.com/adfs\",\n \"ClientId\": \"<application-group-client-id>\",\n \"ClientSecret\": \"__SET_VIA_SECRET_MANAGER__\",\n \"RequireHttpsMetadata\": true,\n \"CallbackPath\": \"/signin-oidc\",\n \"SignedOutCallbackPath\": \"/signout-callback-oidc\",\n \"AllowedReturnOrigins\": [\n \"https://<host-wuic>\"\n ],\n \"Scopes\": [\"openid\", \"profile\", \"email\"]\n }\n}"
4706
+ },
4707
+ {
4708
+ "id": "code_2",
4709
+ "title": "Snippet 2",
4710
+ "language": "json",
4711
+ "code": "\"Authentication\": {\n \"Ldap\": {\n \"Enabled\": true,\n \"Host\": \"ldap.corp.example.com\",\n \"Port\": 389,\n \"UseStartTls\": true,\n \"BaseDn\": \"OU=Users,DC=corp,DC=example,DC=com\",\n \"UserSearchFilter\": \"(&(objectClass=user)(sAMAccountName={username}))\",\n \"UsernameAttribute\": \"sAMAccountName\",\n \"DisplayNameAttribute\": \"displayName\",\n \"EmailAttribute\": \"mail\",\n \"BindDn\": \"CN=svc-wuic,OU=ServiceAccounts,DC=corp,DC=example,DC=com\",\n \"BindPassword\": \"__SET_VIA_SECRET_MANAGER__\",\n \"ConnectTimeoutSeconds\": 5,\n \"FallbackToDbOnFailure\": true,\n \"AutoProvision\": true,\n \"DefaultRoleId\": null\n }\n}"
4712
+ },
4713
+ {
4714
+ "id": "code_3",
4715
+ "title": "Snippet 3",
4716
+ "language": "json",
4532
4717
  "code": "{\n \"Logging\": {\n \"LogLevel\": {\n \"Default\": \"Warning\"\n }\n },\n \"Authentication\": {\n \"OAuth\": {\n \"Enabled\": false,\n \"Provider\": \"Google\",\n \"Authority\": \"\",\n \"ClientId\": \"__REDACTED__\",\n \"ClientSecret\": \"__REDACTED__\",\n \"RequireHttpsMetadata\": true,\n \"CallbackPath\": \"/signin-google\",\n \"SignedOutCallbackPath\": \"/signout-callback-oidc\",\n \"AllowedReturnOrigins\": [],\n \"Scopes\": [\n \"openid\",\n \"profile\",\n \"email\"\n ]\n }\n },\n \"AppSettings\": {\n \"firstRun\": \"false\",\n \"preScaffold\": \"false\",\n \"dbms\": \"mssql\",\n \"meta-dbms\": \"mssql\",\n \"allowMultipleDBMS\": \"false\",\n \"connectionByUser\": \"false\",\n \"logicDeleteValue\": \"1\",\n \"logicDeleteField\": \"Stato_Record\",\n \"enableServerSideCrudChangeLog\": \"true\",\n \"base64Image\": \"false\",\n \"customAuthentication\": \"false\",\n \"logInvoke\": \"false\",\n \"sqlVerbose\": \"true\",\n \"license-email\": \"__REDACTED__\",\n \"connection\": \"Data Source=localhost\\\\sqlexpress;Initial Catalog=WideWorldImporters;Integrated Security=False;Persist Security Info=True;User ID=sa;Password=***REDACTED***;Encrypt=False;TrustServerCertificate=True\",\n \"DataDBName\": \"WideWorldImporters\",\n \"projectDataFolder\": \"C:\\\\src\\\\Wuic\\\\CrmApp\",\n \"projectAssemblyName\": \"C:\\\\src\\\\Wuic\\\\KonvergenceCore\\\\bin\\\\Debug\\\\net10.0\\\\KonvergenceCore.dll\",\n \"sessionTimeoutMinutes\": \"60\",\n \"notifySessionExpirationBefore\": \"1\",\n \"enableUserLanguageSwitch\": \"true\",\n \"enableGuest\": \"false\",\n \"applyGrantByDefaultToGuest\": \"false\",\n \"defaultSiteRoute\": \"#/\",\n \"IsPwdEncripted\": \"false\",\n \"encriptionMethod\": \"SHA1\",\n \"enableCookieAuthentication\": \"false\",\n \"enableODATAAuthentication\": \"false\",\n \"password_min_length \": \"8\",\n \"email-host\": \"smtp.example.com\",\n \"email-port\": \"25\",\n \"email-ssl\": \"false\",\n \"email-user\": \"__REDACTED__\",\n \"email-pwd\": \"__REDACTED__\",\n \"default-role-id\": \"1\",\n \"email-sender-address-registration\": \"\",\n \"email-admin-registration\": \"\",\n \"email-sender-name-mailing\": \"Wuic Cloud\",\n \"email-sender-address-mailing\": \"__REDACTED__\",\n \"autoGeneratedQueryTimeout\": \"120\",\n \"uploadFolder\": \"C:\\\\src\\\\Wuic\\\\CrmApp\\\\Upload\"\n },\n \"RecordTranslations\": {\n \"Enabled\": false,\n \"DefaultTableName\": \"_record_field_translations\",\n \"TranslationJsonFieldName\": \"translation_json\",\n \"FieldNames\": []\n },\n \"Scheduler\": {\n \"Enabled\": true,\n \"PollSeconds\": 15,\n \"MaxTasksPerCycle\": 20,\n \"RetryDelaySeconds\": 60,\n \"SqlCommandTimeoutSeconds\": 300\n },\n \"Notifications\": {\n \"Enabled\": true,\n \"Mode\": \"SqlDependency\",\n \"PollSeconds\": 5\n },\n \"ConnectionStrings\": {\n \"MetaDataSQLConnection\": \"Data Source=localhost\\\\sqlexpress;Initial Catalog=MetadataCRM;Integrated Security=False;Persist Security Info=True;User ID=sa;Password=***REDACTED***;Encrypt=False;TrustServerCertificate=True\",\n \"DataSQLConnection\": \"Data Source=localhost\\\\sqlexpress;Initial Catalog=AdvancedCRM;Integrated Security=False;Persist Security Info=True;User ID=sa;Password=***REDACTED***;Encrypt=False;TrustServerCertificate=True\"\n },\n \"NugetHost\": {\n \"StaticAngularRoot\": \"..\\\\\\\\KonvergenceCore\"\n },\n \"AllowedHosts\": \"*\",\n \"CrmApp\": {\n \"StaticAngularRoot\": \"..\\\\\\\\KonvergenceCore\"\n }\n}"
4533
4718
  }
4534
4719
  ]
@@ -6784,26 +6969,34 @@ const frameworkDocsContent = {
6784
6969
  },
6785
6970
  {
6786
6971
  "id": "sec_6",
6787
- "title": "md_props_bag: archetypes.list"
6972
+ "title": "md_props_bag: toolbar"
6788
6973
  },
6789
6974
  {
6790
6975
  "id": "sec_7",
6791
- "title": "Paging: md_pagesize and md_page_size_choice"
6976
+ "title": "md_props_bag: archetypes.list"
6792
6977
  },
6793
6978
  {
6794
6979
  "id": "sec_8",
6795
- "title": "Forced Virtualization on High Page Size"
6980
+ "title": "Paging: md_pagesize and md_page_size_choice"
6796
6981
  },
6797
6982
  {
6798
6983
  "id": "sec_9",
6984
+ "title": "Forced Virtualization on High Page Size"
6985
+ },
6986
+ {
6987
+ "id": "sec_10",
6799
6988
  "title": "Events and Subscriptions (Host)"
6989
+ },
6990
+ {
6991
+ "id": "sec_11",
6992
+ "title": "`rowCustomSelect` — actual signature (gotcha)"
6800
6993
  }
6801
6994
  ],
6802
6995
  "sections": [
6803
6996
  {
6804
6997
  "id": "overview",
6805
6998
  "title": "Overview",
6806
- "html": "<h1>List Grid</h1><p>Main component for tabular lists with filters, sorting, server-side paging, and row actions.</p><h2>Use Cases</h2><ul><li>Enterprise tabular CRUD.</li><li>Operational reports with multi-column filters.</li><li>Very large datasets with <code>cursorMode</code>.</li></ul><h2>Screenshot Reference (User Manual)</h2><ul><li><code>manual__grid__01.png</code>: <code>inline cell edit</code> mode in list-grid.</li><li><code>manual__grid__02.png</code>: <code>inline edit</code> (row-level) mode in list-grid.</li></ul><h2>Inline Editing Metadata</h2><p>Configuration in the table metadata <code>md_props_bag</code>:</p><ul><li><code>md_inline_edit</code></li></ul><p> - Enables inline row editing in list-grid (editable cells in row context, without opening a popup).</p><p> - Useful when you want to maintain the table UX with quick editing per record.</p><ul><li><code>md_inline_cell_edit</code></li></ul><p> - Enables &quot;cell-by-cell&quot; inline editing (focus on the individual cell).</p><p> - Note: in legacy configurations it may appear as <code>md_inline_cell_editing</code>; the runtime behavior is the same.</p><p> - <strong>Runtime promotion</strong>: when <code>md_inline_cell_edit</code> is <code>true</code>, the component forces</p><p> <code>md_inline_edit = true</code> at runtime regardless of the DB value.</p><p> Both UX modes need the action column visible, so they are mutually</p><p> non-exclusive at the rendering level.</p><ul><li><code>md_batch_save</code></li></ul><p> - Enables batch saving of pending changes (<code>Save changes</code> / <code>Cancel changes</code>).</p><p> - When used with inline-cell, changes remain pending until explicit save.</p><p> - <strong>Prerequisite</strong>: <code>md_batch_save</code> takes effect <strong>only</strong> if <code>md_inline_cell_edit</code> is <code>true</code>.</p><p> With <code>md_inline_cell_edit:false</code> the flag is ignored at runtime and the</p><p> &quot;Save changes / Cancel changes&quot; buttons are not rendered.</p><h3>Valid Combinations</h3><table><thead><tr><th><code>md_inline_edit</code></th><th><code>md_inline_cell_edit</code></th><th><code>md_batch_save</code></th><th>Runtime Result</th></tr></thead><tbody><tr><td><code>true</code></td><td><code>false</code></td><td>any</td><td>Row-level inline edit with pencil. <code>md_batch_save</code> ignored.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>false</code></td><td>Cell-by-cell autosave on blur. <code>md_inline_edit</code> forced to <code>true</code> at runtime.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>true</code></td><td>Cell-by-cell with pending buffer + <code>Save / Cancel changes</code> toolbar.</td></tr><tr><td><code>false</code></td><td><code>false</code></td><td>any</td><td>No inline editing. <code>md_batch_save</code> ignored.</td></tr></tbody></table><p>Row-level inline edit example:</p><p>Snippet 1:</p><p>Cell-by-cell with batch save example:</p><p>Snippet 2:</p><h2>Toolbar Export/Import</h2><ul><li>Export XLS:</li></ul><p> - Shows a progress dialog with real-time percentage;</p><p> - Actions: <code>Continue in background</code>, <code>Cancel task</code>, <code>Stop and download partial</code>;</p><p> - In background creates a notification with progress; click reopens the dialog.</p><ul><li>Import XLS/XLSX (if <code>md_importable = true</code>):</li></ul><p> - Progress dialog after confirmation;</p><p> - Actions: <code>Continue in background</code>, <code>Cancel import (rollback)</code>, <code>Stop and partial commit</code>;</p><p> - At the end of import creates a summary notification that navigates to the route.</p><h2>Metadata Config</h2><p>Key settings in <code>md_props_bag</code> and column metadata.</p><p>Snippet 3:</p><h2>md_props_bag: archetypes.list</h2><p>The component reads <code>md_props_bag.archetypes.list</code>.</p><p>Snippet 4:</p><ul><li><code>proportionalColwidth</code>:</li><li>type: boolean (with tolerant parser)</li><li>accepted values: <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>&quot;true&quot;</code>, <code>&quot;false&quot;</code>, <code>&quot;1&quot;</code>, <code>&quot;0&quot;</code></li><li>runtime default: <code>true</code> when absent or empty</li><li>effect: distributes column widths proportionally (instead of px) when no user-persisted widths exist.</li></ul><ul><li><code>virtualize</code>:</li><li>type: <code>boolean</code> or <code>object</code></li><li>accepted values:</li><li><code>true</code>/<code>false</code>, <code>1</code>/<code>0</code>, <code>&quot;true&quot;</code>/<code>&quot;false&quot;</code>, <code>&quot;1&quot;</code>/<code>&quot;0&quot;</code></li><li>object with <code>enabled</code> (optional) and <code>itemSize</code> (optional)</li><li>runtime default: disabled (<code>false</code>) when absent</li><li><code>enabled</code>:</li><li>default: <code>true</code> if the <code>virtualize</code> node is an object without <code>enabled</code>, otherwise tolerant parser</li><li>effect: enables <code>virtualScroll</code> on <code>p-table</code></li><li><code>itemSize</code>:</li><li>default: <code>44</code></li><li>effect: sets <code>virtualScrollItemSize</code> (virtual row height in px)</li></ul><ul><li><code>advancedFilter</code>:</li><li>type: boolean (with tolerant parser)</li><li>runtime default: <code>false</code></li><li>effect:</li><li>when <code>true</code>, the grid hides column filter icons (<code>p-columnFilter</code>) and uses the <code>wuic-filter-bar</code> above the table (at <code>data-repeater</code>/<code>bounded-repeater</code> level) to apply filters.</li></ul><p>Operational note:</p><ul><li>The <code>md_props_bag</code> suggest in the metadata editor also exposes the checkable <code>archetypes.list.advancedFilter</code> node.</li></ul><h2>Paging: md_pagesize and md_page_size_choice</h2><p>The grid uses <code>md_pagesize</code> as the default page size and <code>md_page_size_choice</code> as the list of selectable values in the paginator.</p><p>Runtime rules:</p><ul><li>If <code>md_pagesize</code> is greater than the maximum value present in <code>md_page_size_choice</code>, the framework automatically adds <code>md_pagesize</code> to the options list;</li><li>The list is normalized (valid numbers, deduplication, ascending sort).</li></ul><p>Example:</p><ul><li><code>md_pagesize = 200</code></li><li><code>md_page_size_choice = &quot;10,25,50,100&quot;</code></li><li>runtime result: <code>10,25,50,100,200</code></li></ul><h2>Forced Virtualization on High Page Size</h2><p>When the user selects in the paginator a <code>pageSize &gt;= 1000</code>:</p><ul><li>virtualization is automatically forced even if <code>md_props_bag.archetypes.list.virtualize</code> is absent or disabled;</li><li><code>virtualScrollItemSize</code> is forced to the default value <code>44</code>.</li></ul><p>This behavior protects the table rendering on very large pages.</p><h2>Events and Subscriptions (Host)</h2><p><code>wuic-list-grid</code> exposes runtime events useful for intercepting the render cycle and p-table callbacks on the host project side.</p><p>Available events:</p><ul><li><code>onAfterRender</code>: emitted at the end of grid data binding (<code>rows</code>, <code>totalRecords</code>, <code>metaInfo</code>, <code>datasource</code>).</li><li><code>onBeforeRowRender</code>: emitted before the logical rendering of a single row; supports cancel via <code>event.cancelRender()</code>.</li><li><code>onAfterRowRender</code>: emitted after the logical rendering of a single row.</li><li><code>onPaging</code>: emitted on paging events (<code>p-table onPage</code>).</li><li><code>onSorting</code>: emitted on sorting events (<code>p-table onSort</code>).</li><li><code>onFiltering</code>: emitted on filtering events (<code>p-table onFilter</code>).</li><li><code>onPTableSelectionChange</code>: emitted on row selection change.</li><li><code>onPTableRowExpand</code>: emitted on row expand.</li><li><code>onPTableRowCollapse</code>: emitted on row collapse.</li><li><code>onPTableColumnResize</code>: emitted on column resize.</li><li><code>onPTableColumnReorder</code>: emitted on column reorder.</li></ul><h3>Example 1: Direct Binding in Template</h3><p>Snippet 5:</p><h3>Example 2: Subscribe via ViewChild</h3><p>Snippet 6:</p>",
6999
+ "html": "<h1>List Grid</h1><p>Main component for tabular lists with filters, sorting, server-side paging, and row actions.</p><h2>Use Cases</h2><ul><li>Enterprise tabular CRUD.</li><li>Operational reports with multi-column filters.</li><li>Very large datasets with <code>cursorMode</code>.</li></ul><h2>Screenshot Reference (User Manual)</h2><ul><li><code>manual__grid__01.png</code>: <code>inline cell edit</code> mode in list-grid.</li><li><code>manual__grid__02.png</code>: <code>inline edit</code> (row-level) mode in list-grid.</li></ul><h2>Inline Editing Metadata</h2><p>Configuration in the table metadata <code>md_props_bag</code>:</p><ul><li><code>md_inline_edit</code></li></ul><p> - Enables inline row editing in list-grid (editable cells in row context, without opening a popup).</p><p> - Useful when you want to maintain the table UX with quick editing per record.</p><ul><li><code>md_inline_cell_edit</code></li></ul><p> - Enables &quot;cell-by-cell&quot; inline editing (focus on the individual cell).</p><p> - Note: in legacy configurations it may appear as <code>md_inline_cell_editing</code>; the runtime behavior is the same.</p><p> - <strong>Runtime promotion</strong>: when <code>md_inline_cell_edit</code> is <code>true</code>, the component forces</p><p> <code>md_inline_edit = true</code> at runtime regardless of the DB value.</p><p> Both UX modes need the action column visible, so they are mutually</p><p> non-exclusive at the rendering level.</p><ul><li><code>md_batch_save</code></li></ul><p> - Enables batch saving of pending changes (<code>Save changes</code> / <code>Cancel changes</code>).</p><p> - When used with inline-cell, changes remain pending until explicit save.</p><p> - <strong>Prerequisite</strong>: <code>md_batch_save</code> takes effect <strong>only</strong> if <code>md_inline_cell_edit</code> is <code>true</code>.</p><p> With <code>md_inline_cell_edit:false</code> the flag is ignored at runtime and the</p><p> &quot;Save changes / Cancel changes&quot; buttons are not rendered.</p><h3>Valid Combinations</h3><table><thead><tr><th><code>md_inline_edit</code></th><th><code>md_inline_cell_edit</code></th><th><code>md_batch_save</code></th><th>Runtime Result</th></tr></thead><tbody><tr><td><code>true</code></td><td><code>false</code></td><td>any</td><td>Row-level inline edit with pencil. <code>md_batch_save</code> ignored.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>false</code></td><td>Cell-by-cell autosave on blur. <code>md_inline_edit</code> forced to <code>true</code> at runtime.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>true</code></td><td>Cell-by-cell with pending buffer + <code>Save / Cancel changes</code> toolbar.</td></tr><tr><td><code>false</code></td><td><code>false</code></td><td>any</td><td>No inline editing. <code>md_batch_save</code> ignored.</td></tr></tbody></table><p>Row-level inline edit example:</p><p>Snippet 1:</p><p>Cell-by-cell with batch save example:</p><p>Snippet 2:</p><h2>Toolbar Export/Import</h2><ul><li>Export XLS:</li></ul><p> - Shows a progress dialog with real-time percentage;</p><p> - Actions: <code>Continue in background</code>, <code>Cancel task</code>, <code>Stop and download partial</code>;</p><p> - In background creates a notification with progress; click reopens the dialog.</p><ul><li>Import XLS/XLSX (if <code>md_importable = true</code>):</li></ul><p> - Progress dialog after confirmation;</p><p> - Actions: <code>Continue in background</code>, <code>Cancel import (rollback)</code>, <code>Stop and partial commit</code>;</p><p> - At the end of import creates a summary notification that navigates to the route.</p><h2>Metadata Config</h2><p>Key settings in <code>md_props_bag</code> and column metadata.</p><p>Snippet 3:</p><h2>md_props_bag: toolbar</h2><p>Opt-in flags that hide blocks of the list-grid toolbar (<code>caption-bar</code>). All under <code>md_props_bag.toolbar.*</code> (parsed at runtime as <code>tableMetadata.extraProps.toolbar</code>).</p><p>Snippet 4:</p><ul><li><code>hideManageState</code> (boolean, default <code>false</code>): hides in the <strong>caption-right</strong> the &quot;Manage state&quot; button (bookmark icon) + the saved-states <code>&lt;select&gt;</code>. Useful for hardcoded / demo routes where the saved-state feature (persistence per <code>user_id</code> + route via <code>MetaService</code>) doesn&#39;t make sense — e.g. Pattern 3 pure OData without registered route metadata.</li><li><code>hideBatchActions</code> (boolean, default <code>false</code>): hides in the <strong>caption-left</strong> the &quot;Save changes&quot; (<code>pi-save</code>) + &quot;Discard changes&quot; (<code>pi-times</code>) buttons + the changes count indicator (<code>grid-changes-indicator</code>, pencil badge + count). Generated by the framework when <code>md_inline_cell_editing</code> + <code>md_batch_save</code> are active. Designed for <strong>nested grids inside a parametric-dialog</strong>: the parent&#39;s save/cancel persists master + rows in one shot via the framework batch save, and the duplicate buttons on the nested grid confuse the UX.</li></ul><p>Example nested rows in custom edit-form (runtime patch):</p><p>Snippet 5:</p><p>TS type in <a href=\"../api/wuic-framework-lib.metadatitabella.html\">`metadati_tabella.ts`</a> (<code>extraProps.toolbar</code> property). The flags do NOT disable the underlying <code>md_batch_save</code> logic (changes are still tracked); they only remove the toolbar UI — the actual save flows through the parent.</p><h2>md_props_bag: archetypes.list</h2><p>The component reads <code>md_props_bag.archetypes.list</code>.</p><p>Snippet 6:</p><ul><li><code>proportionalColwidth</code>:</li><li>type: boolean (with tolerant parser)</li><li>accepted values: <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>&quot;true&quot;</code>, <code>&quot;false&quot;</code>, <code>&quot;1&quot;</code>, <code>&quot;0&quot;</code></li><li>runtime default: <code>true</code> when absent or empty</li><li>effect: distributes column widths proportionally (instead of px) when no user-persisted widths exist.</li></ul><ul><li><code>virtualize</code>:</li><li>type: <code>boolean</code> or <code>object</code></li><li>accepted values:</li><li><code>true</code>/<code>false</code>, <code>1</code>/<code>0</code>, <code>&quot;true&quot;</code>/<code>&quot;false&quot;</code>, <code>&quot;1&quot;</code>/<code>&quot;0&quot;</code></li><li>object with <code>enabled</code> (optional) and <code>itemSize</code> (optional)</li><li>runtime default: disabled (<code>false</code>) when absent</li><li><code>enabled</code>:</li><li>default: <code>true</code> if the <code>virtualize</code> node is an object without <code>enabled</code>, otherwise tolerant parser</li><li>effect: enables <code>virtualScroll</code> on <code>p-table</code></li><li><code>itemSize</code>:</li><li>default: <code>44</code></li><li>effect: sets <code>virtualScrollItemSize</code> (virtual row height in px)</li></ul><ul><li><code>advancedFilter</code>:</li><li>type: boolean (with tolerant parser)</li><li>runtime default: <code>false</code></li><li>effect:</li><li>when <code>true</code>, the grid hides column filter icons (<code>p-columnFilter</code>) and uses the <code>wuic-filter-bar</code> above the table (at <code>data-repeater</code>/<code>bounded-repeater</code> level) to apply filters.</li></ul><p>Operational note:</p><ul><li>The <code>md_props_bag</code> suggest in the metadata editor also exposes the checkable <code>archetypes.list.advancedFilter</code> node.</li></ul><h2>Paging: md_pagesize and md_page_size_choice</h2><p>The grid uses <code>md_pagesize</code> as the default page size and <code>md_page_size_choice</code> as the list of selectable values in the paginator.</p><p>Runtime rules:</p><ul><li>If <code>md_pagesize</code> is greater than the maximum value present in <code>md_page_size_choice</code>, the framework automatically adds <code>md_pagesize</code> to the options list;</li><li>The list is normalized (valid numbers, deduplication, ascending sort).</li></ul><p>Example:</p><ul><li><code>md_pagesize = 200</code></li><li><code>md_page_size_choice = &quot;10,25,50,100&quot;</code></li><li>runtime result: <code>10,25,50,100,200</code></li></ul><h2>Forced Virtualization on High Page Size</h2><p>When the user selects in the paginator a <code>pageSize &gt;= 1000</code>:</p><ul><li>virtualization is automatically forced even if <code>md_props_bag.archetypes.list.virtualize</code> is absent or disabled;</li><li><code>virtualScrollItemSize</code> is forced to the default value <code>44</code>.</li></ul><p>This behavior protects the table rendering on very large pages.</p><h2>Events and Subscriptions (Host)</h2><p><code>wuic-list-grid</code> exposes runtime events useful for intercepting the render cycle and p-table callbacks on the host project side.</p><p>Available events:</p><ul><li><code>onAfterRender</code>: emitted at the end of grid data binding (<code>rows</code>, <code>totalRecords</code>, <code>metaInfo</code>, <code>datasource</code>).</li><li><code>onBeforeRowRender</code>: emitted before the logical rendering of a single row; supports cancel via <code>event.cancelRender()</code>.</li><li><code>onAfterRowRender</code>: emitted after the logical rendering of a single row.</li><li><code>onPaging</code>: emitted on paging events (<code>p-table onPage</code>).</li><li><code>onSorting</code>: emitted on sorting events (<code>p-table onSort</code>).</li><li><code>onFiltering</code>: emitted on filtering events (<code>p-table onFilter</code>).</li><li><code>onPTableSelectionChange</code>: emitted on row selection change.</li><li><code>onPTableRowExpand</code>: emitted on row expand.</li><li><code>onPTableRowCollapse</code>: emitted on row collapse.</li><li><code>onPTableColumnResize</code>: emitted on column resize.</li><li><code>onPTableColumnReorder</code>: emitted on column reorder.</li></ul><h3>Example 1: Direct Binding in Template</h3><p>Snippet 7:</p><h3>Example 2: Subscribe via ViewChild</h3><p>Snippet 8:</p><h2><code>rowCustomSelect</code> — actual signature (gotcha)</h2><p><code>&lt;wuic-list-grid&gt;</code> accepts a <code>[rowCustomSelect]</code> input to hook into row selection (opening a &quot;pick a document&quot; dialog, master-detail flow, etc.). The TypeScript signature declared in <a href=\"../../src/lib/component/list-grid/list-grid.component.ts#L140\">list-grid.component.ts:140</a> reads:</p><p>Snippet 9:</p><p><strong>However at runtime the callback is invoked with arguments reversed</strong> — the framework calls <code>rowCustomSelect($event, rowData, dt)</code> (see <a href=\"../../src/lib/component/dynamic-template/dynamic-template.component.ts#L447\">dynamic-template.component.ts:447</a> and <a href=\"../../src/lib/component/dynamic-template/dynamic-card-template.component.ts#L205\">dynamic-card-template.component.ts:205</a>).</p><p>Symptom of wrong arg order: <code>rowData?.id</code> is undefined → early-return guard in your handler → the callback returns silently and your dialog/action never fires. No console error.</p><p>Correct form (matches runtime invocation):</p><p>Snippet 10:</p><p>Template usage:</p><p>Snippet 11:</p><p>&gt; Note: tests in <code>designer.component.spec.ts</code> (e.g. line 512) invoke the callback with <code>({currentTarget: rowCell}, {id: 42}, null)</code>, confirming the <code>($event, rowData, dt)</code> order. If the framework eventually aligns the declared signature, this page will be updated as well.</p>",
6807
7000
  "codeSamples": [
6808
7001
  {
6809
7002
  "id": "code_1",
@@ -6827,19 +7020,49 @@ const frameworkDocsContent = {
6827
7020
  "id": "code_4",
6828
7021
  "title": "Snippet 4",
6829
7022
  "language": "json",
6830
- "code": "{\n \"archetypes\": {\n \"list\": {\n \"proportionalColwidth\": true,\n \"advancedFilter\": false,\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n }\n }\n }\n}"
7023
+ "code": "{\n \"toolbar\": {\n \"hideManageState\": true,\n \"hideBatchActions\": true\n }\n}"
6831
7024
  },
6832
7025
  {
6833
7026
  "id": "code_5",
6834
7027
  "title": "Snippet 5",
6835
- "language": "html",
6836
- "code": "<wuic-list-grid\n [hardcodedDatasource]=\"citiesDs\"\n (onAfterRender)=\"handleAfterRender($event)\"\n (onBeforeRowRender)=\"handleBeforeRowRender($event)\"\n (onAfterRowRender)=\"handleAfterRowRender($event)\"\n (onPaging)=\"handlePaging($event)\"\n (onSorting)=\"handleSorting($event)\"\n (onFiltering)=\"handleFiltering($event)\"\n (onPTableSelectionChange)=\"handleSelection($event)\"\n (onPTableRowExpand)=\"handleRowExpand($event)\"\n (onPTableRowCollapse)=\"handleRowCollapse($event)\"\n (onPTableColumnResize)=\"handleColumnResize($event)\"\n (onPTableColumnReorder)=\"handleColumnReorder($event)\">\n</wuic-list-grid>"
7028
+ "language": "ts",
7029
+ "code": "this.righeDs.metaInfo.tableMetadata.extraProps = Object.assign(\n this.righeDs.metaInfo.tableMetadata.extraProps || {},\n { toolbar: { hideManageState: true, hideBatchActions: true } }\n) as any;"
6837
7030
  },
6838
7031
  {
6839
7032
  "id": "code_6",
6840
7033
  "title": "Snippet 6",
7034
+ "language": "json",
7035
+ "code": "{\n \"archetypes\": {\n \"list\": {\n \"proportionalColwidth\": true,\n \"advancedFilter\": false,\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n }\n }\n }\n}"
7036
+ },
7037
+ {
7038
+ "id": "code_7",
7039
+ "title": "Snippet 7",
7040
+ "language": "html",
7041
+ "code": "<wuic-list-grid\n [hardcodedDatasource]=\"citiesDs\"\n (onAfterRender)=\"handleAfterRender($event)\"\n (onBeforeRowRender)=\"handleBeforeRowRender($event)\"\n (onAfterRowRender)=\"handleAfterRowRender($event)\"\n (onPaging)=\"handlePaging($event)\"\n (onSorting)=\"handleSorting($event)\"\n (onFiltering)=\"handleFiltering($event)\"\n (onPTableSelectionChange)=\"handleSelection($event)\"\n (onPTableRowExpand)=\"handleRowExpand($event)\"\n (onPTableRowCollapse)=\"handleRowCollapse($event)\"\n (onPTableColumnResize)=\"handleColumnResize($event)\"\n (onPTableColumnReorder)=\"handleColumnReorder($event)\">\n</wuic-list-grid>"
7042
+ },
7043
+ {
7044
+ "id": "code_8",
7045
+ "title": "Snippet 8",
6841
7046
  "language": "ts",
6842
7047
  "code": "import { AfterViewInit, OnDestroy, ViewChild } from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport {\n ListGridAfterRenderEvent,\n ListGridAfterRowRenderEvent,\n ListGridBeforeRowRenderEvent,\n ListGridComponent\n} from 'wuic-framework-lib';\n\nexport class HostComponent implements AfterViewInit, OnDestroy {\n @ViewChild(ListGridComponent) grid?: ListGridComponent;\n private readonly subs = new Subscription();\n\n ngAfterViewInit(): void {\n if (!this.grid) return;\n\n this.subs.add(this.grid.onAfterRender.subscribe((e: ListGridAfterRenderEvent) => console.log(e)));\n this.subs.add(this.grid.onBeforeRowRender.subscribe((e: ListGridBeforeRowRenderEvent) => {\n // example: cancel row\n if (e.row?.deleted === true) e.cancelRender();\n }));\n this.subs.add(this.grid.onAfterRowRender.subscribe((e: ListGridAfterRowRenderEvent) => console.log(e)));\n this.subs.add(this.grid.onPaging.subscribe((e) => console.log('paging', e)));\n this.subs.add(this.grid.onSorting.subscribe((e) => console.log('sorting', e)));\n this.subs.add(this.grid.onFiltering.subscribe((e) => console.log('filtering', e)));\n this.subs.add(this.grid.onPTableSelectionChange.subscribe((e) => console.log('selection', e)));\n this.subs.add(this.grid.onPTableRowExpand.subscribe((e) => console.log('expand', e)));\n this.subs.add(this.grid.onPTableRowCollapse.subscribe((e) => console.log('collapse', e)));\n this.subs.add(this.grid.onPTableColumnResize.subscribe((e) => console.log('resize', e)));\n this.subs.add(this.grid.onPTableColumnReorder.subscribe((e) => console.log('reorder', e)));\n }\n\n ngOnDestroy(): void {\n this.subs.unsubscribe();\n }\n}"
7048
+ },
7049
+ {
7050
+ "id": "code_9",
7051
+ "title": "Snippet 9",
7052
+ "language": "ts",
7053
+ "code": "@Input() rowCustomSelect: (rowData: any, $event: any, dt: Table) => void;"
7054
+ },
7055
+ {
7056
+ "id": "code_10",
7057
+ "title": "Snippet 10",
7058
+ "language": "ts",
7059
+ "code": "onRowSelected = (_event: any, rowData: any, _dt: any): void => {\n // rowData is the record provided by list-grid\n this.handleSelection(rowData);\n};"
7060
+ },
7061
+ {
7062
+ "id": "code_11",
7063
+ "title": "Snippet 11",
7064
+ "language": "html",
7065
+ "code": "<wuic-data-source #ds [hardcodedRoute]=\"'preventivi'\" [autoload]=\"true\"></wuic-data-source>\n<wuic-list-grid [hardcodedDatasource]=\"ds\" [rowCustomSelect]=\"onRowSelected\"></wuic-list-grid>"
6843
7066
  }
6844
7067
  ]
6845
7068
  }
@@ -7011,7 +7234,7 @@ const frameworkDocsContent = {
7011
7234
  "id": "code_1",
7012
7235
  "title": "Snippet 1",
7013
7236
  "language": "json",
7014
- "code": "{\n \"archetypes\": {\n \"map\": {\n \"advancedFilter\": false,\n \"center\": {\n \"lat\": 39.8283,\n \"lng\": -98.5795\n },\n \"zoom\": 4,\n \"minZoom\": 3,\n \"maxZoom\": 14,\n \"useCurrentLocation\": false,\n \"useClusterer\": true,\n \"filterByBoundaries\": false,\n \"titleField\": \"CityName\",\n \"infoField\": \"StateProvinceID__dictionary_display_value\",\n \"customMarkerImageSrc\": \"\",\n \"customMarkerImageSrcField\": \"\",\n \"markerContentCallback\": \"const glyph = String(record?.CityName || 'C').trim().charAt(0).toUpperCase() || 'C'; const pop = Number(record?.LatestRecordedPopulation || 0); const bg = pop > 1000000 ? '#B91C1C' : (pop > 300000 ? '#EA580C' : '#2563EB'); const pin = new google.maps.marker.PinElement({ background: bg, borderColor: '#0F172A', glyphColor: '#FFFFFF', glyph: glyph, scale: 1.2 }); return pin.element;\",\n \"infoFunction\": \"\",\n \"itemTemplateString\": \"<div class=\\\"map-info\\\"> <div class=\\\"map-info-title\\\">{{ rowData.record?.CityName || rowData.title }}</div> <div>{{ rowData.record?.StateProvinceID__dictionary_display_value || rowData.record?.StateProvinceID || '' }}</div> <div>Population: {{ rowData.record?.LatestRecordedPopulation | number:'1.0-0' }}</div> <div>{{ rowData.record?.Location || '' }}</div> <wuic-data-action-button-lazy [data]=\\\"rowData.record\\\" [metaInfo]=\\\"metaInfo\\\" [datasource]=\\\"datasource\\\" [simplified]=\\\"true\\\"></wuic-data-action-button-lazy> </div>\"\n }\n }\n}"
7237
+ "code": "{\n \"archetypes\": {\n \"map\": {\n \"advancedFilter\": false,\n \"center\": {\n \"lat\": 39.8283,\n \"lng\": -98.5795\n },\n \"zoom\": 4,\n \"minZoom\": 3,\n \"maxZoom\": 14,\n \"useCurrentLocation\": false,\n \"useClusterer\": true,\n \"filterByBoundaries\": false,\n \"titleField\": \"CityName\",\n \"infoField\": \"StateProvinceID__dictionary_display_value\",\n \"customMarkerImageSrc\": \"\",\n \"customMarkerImageSrcField\": \"\",\n \"markerContentCallback\": \"const glyph = String(record?.CityName || 'C').trim().charAt(0).toUpperCase() || 'C'; const pop = Number(record?.LatestRecordedPopulation || 0); const bg = pop > 1000000 ? '#B91C1C' : (pop > 300000 ? '#EA580C' : '#2563EB'); const pin = new google.maps.marker.PinElement({ background: bg, borderColor: '#0F172A', glyphColor: '#FFFFFF', glyphText: glyph, scale: 1.2 }); return pin;\",\n \"infoFunction\": \"\",\n \"itemTemplateString\": \"<div class=\\\"map-info\\\"> <div class=\\\"map-info-title\\\">{{ rowData.record?.CityName || rowData.title }}</div> <div>{{ rowData.record?.StateProvinceID__dictionary_display_value || rowData.record?.StateProvinceID || '' }}</div> <div>Population: {{ rowData.record?.LatestRecordedPopulation | number:'1.0-0' }}</div> <div>{{ rowData.record?.Location || '' }}</div> <wuic-data-action-button-lazy [data]=\\\"rowData.record\\\" [metaInfo]=\\\"metaInfo\\\" [datasource]=\\\"datasource\\\" [simplified]=\\\"true\\\"></wuic-data-action-button-lazy> </div>\"\n }\n }\n}"
7015
7238
  },
7016
7239
  {
7017
7240
  "id": "code_2",
@@ -7096,14 +7319,47 @@ const frameworkDocsContent = {
7096
7319
  {
7097
7320
  "id": "sec_3",
7098
7321
  "title": "Designer Reference"
7322
+ },
7323
+ {
7324
+ "id": "sec_4",
7325
+ "title": "Report parameters (filtered SQL variables)"
7326
+ },
7327
+ {
7328
+ "id": "sec_5",
7329
+ "title": "Required backend setup"
7099
7330
  }
7100
7331
  ],
7101
7332
  "sections": [
7102
7333
  {
7103
7334
  "id": "overview",
7104
7335
  "title": "Overview",
7105
- "html": "<h1>Reporting</h1><p>Operational reporting on route <code>#/&lt;route&gt;/report-viewer</code>.</p><h2>Screenshot Reference (User Manual)</h2><ul><li><code>manual__reporting__01.png</code>: report viewer opening with report rendering and operational toolbar.</li><li><code>manual__reporting__02.png</code>: runtime example with parameters/filter applied to the report.</li></ul><h2>Report Viewer</h2><p>The <code>report-viewer</code> executes and displays Stimulsoft reports associated with the current metadata route.</p><p>Main features:</p><ul><li>report opening from list/grid with report name (<code>reportName</code>) and optional parameters.</li><li>runtime data rendering with viewer toolbar (zoom, export, print).</li><li>filter/parameter support propagated from the calling page.</li></ul><h2>Designer Reference</h2><p>For report <code>.mrt</code> creation or modification, see the subsection</p><p><a href=\"#/framework-docs/report-designer\">Report designer</a> in <code>Designer &amp; Workflow</code>.</p>",
7106
- "codeSamples": []
7336
+ "html": "<h1>Reporting</h1><p>Operational reporting on route <code>#/&lt;route&gt;/report-viewer</code>.</p><h2>Screenshot Reference (User Manual)</h2><ul><li><code>manual__reporting__01.png</code>: report viewer opening with report rendering and operational toolbar.</li><li><code>manual__reporting__02.png</code>: runtime example with parameters/filter applied to the report.</li></ul><h2>Report Viewer</h2><p>The <code>report-viewer</code> executes and displays Stimulsoft reports associated with the current metadata route.</p><p>Main features:</p><ul><li>report opening from list/grid with report name (<code>reportName</code>) and optional parameters.</li><li>runtime data rendering with viewer toolbar (zoom, export, print).</li><li>filter/parameter support propagated from the calling page.</li></ul><h2>Designer Reference</h2><p>For report <code>.mrt</code> creation or modification, see the subsection</p><p><a href=\"#/framework-docs/report-designer\">Report designer</a> in <code>Designer &amp; Workflow</code>.</p><h2>Report parameters (filtered SQL variables)</h2><p>To filter a report on a specific entity (e.g. printing a single invoice), the canonical pattern is:</p><p>1. <strong>In the `.mrt`</strong>: declare a <strong>Variable</strong> in the Dictionary with <code>AllowUseAsSqlParameter = True</code> (Stimulsoft 2026.1+). Use the variable as <code>@paramName</code> in SqlCommands:</p><p>Snippet 1:</p><p> &gt; <strong>Pitfall</strong>: do NOT use the legacy <code>&lt;StiDataParameter&gt;</code> block in the DataSource — it is no longer supported by the current Stimulsoft version and produces <code>ArgumentException: Int32 is not a valid value for Int32. (Parameter &#39;value&#39;)</code> during <code>report.Load()</code>.</p><p>2. <strong>In the viewer URL</strong>: pass the value via the <code>parameters</code> query param (NOT as a direct query param):</p><p>Snippet 2:</p><p> <code>parameters</code> URL-encoded syntax: <code>name||eq||value</code> (separated by <code>@</code> for multiple parameters).</p><p>3. <strong>From a row action or UI link</strong>: use the canonical callback to build the URL:</p><p>Snippet 3:</p><h2>Required backend setup</h2><p>In the app&#39;s <code>appsettings.json</code>, the <code>AppSettings.reportQueryTimeout</code> setting must exist (e.g. <code>&quot;120&quot;</code>). Without it, <code>ReportViewerController.prepareDataSql</code> throws <code>int.Parse(null)</code> → 500. Example:</p><p>Snippet 4:</p>",
7337
+ "codeSamples": [
7338
+ {
7339
+ "id": "code_1",
7340
+ "title": "Snippet 1",
7341
+ "language": "sql",
7342
+ "code": " SELECT ... FROM fatture_inviate WHERE id = @fattura_id\n SELECT ... FROM fatture_inviate_righe WHERE fattura_id = @fattura_id"
7343
+ },
7344
+ {
7345
+ "id": "code_2",
7346
+ "title": "Snippet 2",
7347
+ "language": "text",
7348
+ "code": " ✅ #/<route>/report-viewer?reportName=Report.mrt&parameters=fattura_id%7C%7Ceq%7C%7C42\n ❌ #/<route>/report-viewer?reportName=Report.mrt&fattura_id=42 <-- ignored"
7349
+ },
7350
+ {
7351
+ "id": "code_3",
7352
+ "title": "Snippet 3",
7353
+ "language": "js",
7354
+ "code": " const recordId = (record && record.id && record.id.value) ?? (record && record.id);\n const params = 'fattura_id||eq||' + recordId;\n window.location.hash = '#/<route>/report-viewer?reportName=Report.mrt&parameters='\n + encodeURIComponent(params);"
7355
+ },
7356
+ {
7357
+ "id": "code_4",
7358
+ "title": "Snippet 4",
7359
+ "language": "json",
7360
+ "code": "\"AppSettings\": {\n \"reportQueryTimeout\": \"120\"\n}"
7361
+ }
7362
+ ]
7107
7363
  }
7108
7364
  ],
7109
7365
  "images": [
@@ -7836,28 +8092,54 @@ const frameworkDocsContent = {
7836
8092
  },
7837
8093
  {
7838
8094
  "id": "sec_5",
7839
- "title": "Assisted Workflow: Clone Lookup + Default Filter + Lookup Hierarchy"
8095
+ "title": "Slim combo — extra fields via `mc_props_bag.slimCombo`"
7840
8096
  },
7841
8097
  {
7842
8098
  "id": "sec_6",
7843
- "title": "Client/Server Effects"
8099
+ "title": "Assisted Workflow: Clone Lookup + Default Filter + Lookup Hierarchy"
7844
8100
  },
7845
8101
  {
7846
8102
  "id": "sec_7",
8103
+ "title": "Client/Server Effects"
8104
+ },
8105
+ {
8106
+ "id": "sec_8",
7847
8107
  "title": "Operational Notes"
8108
+ },
8109
+ {
8110
+ "id": "sec_9",
8111
+ "title": "Auto-fetch on programmatic FK changes"
7848
8112
  }
7849
8113
  ],
7850
8114
  "sections": [
7851
8115
  {
7852
8116
  "id": "overview",
7853
8117
  "title": "Overview",
7854
- "html": "<h1>Widget Lookup</h1><p>Column widget for relationships (<code>lookupByID</code>, <code>multiple_check</code>) with a dedicated <strong>Lookup</strong> tab in the column metadata-editor.</p><h2>Examples</h2><ul><li><a class=\"wuic-demo-link\" href=\"#/examples/3c-odata-cities-grid\" target=\"_blank\" rel=\"noopener noreferrer\">Pattern 3c — pure-OData lookup (State/Province from `/odata/Stateprovinces`)</a> — demonstrates <code>mc_props_bag.lookup.endpoint</code> with the <code>odata</code> provider and inline rendering via <code>$expand</code>.</li></ul><h2>When to Use It</h2><ul><li>Selecting related records from an external metadata table.</li><li>Scenarios with server-side lookup search and paging.</li><li>Support for single selection (<code>lookupByID</code>) and multi-selection (<code>multiple_check</code>).</li></ul><h2>Column Metadata Properties (Lookup tab)</h2><ul><li><code>mc_ui_lookup_entity_name</code>: lookup data source metadata route.</li><li><code>mc_ui_lookup_dataValueField</code>: returned ID field.</li><li><code>mc_ui_lookup_dataTextField</code>: displayed description field.</li><li><code>mc_ui_lookup_filter</code>: base lookup filter.</li><li><code>mc_serverside_operations</code>: server-side search/paging.</li><li><code>mc_ui_pagesize</code>: lookup result page size.</li><li><code>mc_ui_lookup_edit_allow</code>: enables lookup record edit from popup.</li><li><code>mc_ui_lookup_insert_allow</code>: enables lookup record insertion from popup.</li><li><code>mc_ui_lookup_search_grid</code>: enables search grid in the popup.</li><li><code>mc_logic_allow_navigation</code>: navigation to linked route.</li></ul><h2>Useful mc_props_bag</h2><p>Snippet 1:</p><ul><li><code>lookup.filter</code>: additional constraint on the client/server side in the lookup query.</li><li><code>lookup.virtualize</code>: enables virtual scroll on the lookup widget&#39;s <code>p-autocomplete</code>.</li><li><code>lookup.virtualize.enabled</code>: tolerant parser (<code>true/false</code>, <code>1/0</code>, equivalent strings); default <code>false</code> when absent.</li><li><code>lookup.virtualize.itemSize</code>: virtual row height in px; default <code>44</code>.</li><li><code>lookup.endpoint</code>: override of the dropdown&#39;s data provider. When set, the lookup-editor&#39;s nested datasource no longer goes through the metadata combo endpoint (<code>MetaService.getFlatRecordComboData</code>) but directly through the provider declared in <code>type</code> with the URL in <code>uri</code>.</li><li><code>lookup.endpoint.type</code>: <code>&#39;odata&#39;</code> (dispatcher routes to <code>DataProviderOdataService.selectCombo</code> which issues a GET with <code>$top</code>, <code>$count=true</code>, <code>$select=valueField,textField</code> and <code>$filter=contains(textField,&#39;&lt;query&gt;&#39;)</code> for server-side search). Other values follow the same logic as table-level <code>extraProps.endpoint.type</code>.</li><li><code>lookup.endpoint.uri</code>: base URL of the entity set (e.g., <code>/odata/&lt;EntitySet&gt;</code>), with or without explicit origin. Existing query params are ignored — the provider rebuilds the params on each fetch.</li><li><code>form.columns</code>: field width in the edit form.</li><li><code>style.editCss</code>: custom editor style.</li></ul><h2>Assisted Workflow: Clone Lookup + Default Filter + Lookup Hierarchy</h2><ul><li>Inline operational flow: <code>suggestLookup2 -&gt; suggestLookupDefaultFilter -&gt; getLookupListByRoute -&gt; getSeletClauseByLookupHierarchy</code>.</li><li>Inline snippet main fields: <code>mc_ui_lookup_entity_name</code>, <code>mc_ui_lookup_dataValueField</code>, <code>mc_ui_lookup_dataTextField</code>, <code>mc_ui_lookup_filter</code>.</li><li>Inline snippet quick filter: <code>lookup.filter: {&quot;logic&quot;:&quot;AND&quot;,&quot;filters&quot;:[{&quot;field&quot;:&quot;is_active&quot;,&quot;operatore&quot;:&quot;eq&quot;,&quot;value&quot;:true}]}</code>.</li><li>Client effect: accelerated lookup setup in the metadata-editor and consistent filter already ready in the widget.</li><li>Server effect: lookup query and select clause remain aligned with <code>MetaService.*</code> endpoints without manual mapping.</li></ul><p>Quick operational checklist:</p><p>1. Clone base configuration with <code>suggestLookup2</code>;</p><p>2. Generate default filter with <code>suggestLookupDefaultFilter</code>;</p><p>3. Refine hierarchy/select clause from the lookup tree;</p><p>4. Validate at runtime that search and value/text mapping are consistent.</p><h2>Client/Server Effects</h2><ul><li>Client: lookup widget rendering, search, label/value binding.</li><li>Client: with <code>lookup.virtualize</code> active, the autocomplete renders only the visual subset of options.</li><li>Server: paginated/filtered lookup query via <code>MetaService.getFlatRecordData</code>.</li></ul><h2>Operational Notes</h2><ul><li>In <code>multiple_check</code>, the lookup part remains the same; the multi-value renderer changes.</li><li>If <code>mc_ui_lookup_entity_name</code> is missing, the widget cannot resolve the source.</li></ul>",
8118
+ "html": "<h1>Widget Lookup</h1><p>Column widget for relationships (<code>lookupByID</code>, <code>multiple_check</code>) with a dedicated <strong>Lookup</strong> tab in the column metadata-editor.</p><h2>Examples</h2><ul><li><a class=\"wuic-demo-link\" href=\"#/examples/3c-odata-cities-grid\" target=\"_blank\" rel=\"noopener noreferrer\">Pattern 3c — pure-OData lookup (State/Province from `/odata/Stateprovinces`)</a> — demonstrates <code>mc_props_bag.lookup.endpoint</code> with the <code>odata</code> provider and inline rendering via <code>$expand</code>.</li></ul><h2>When to Use It</h2><ul><li>Selecting related records from an external metadata table.</li><li>Scenarios with server-side lookup search and paging.</li><li>Support for single selection (<code>lookupByID</code>) and multi-selection (<code>multiple_check</code>).</li></ul><h2>Column Metadata Properties (Lookup tab)</h2><ul><li><code>mc_ui_lookup_entity_name</code>: lookup data source metadata route.</li><li><code>mc_ui_lookup_dataValueField</code>: returned ID field.</li><li><code>mc_ui_lookup_dataTextField</code>: displayed description field.</li><li><code>mc_ui_lookup_filter</code>: base lookup filter.</li><li><code>mc_serverside_operations</code>: server-side search/paging.</li><li><code>mc_ui_pagesize</code>: lookup result page size.</li><li><code>mc_ui_lookup_edit_allow</code>: enables lookup record edit from popup.</li><li><code>mc_ui_lookup_insert_allow</code>: enables lookup record insertion from popup.</li><li><code>mc_ui_lookup_search_grid</code>: enables search grid in the popup.</li><li><code>mc_logic_allow_navigation</code>: navigation to linked route.</li></ul><h2>Useful mc_props_bag</h2><p>Snippet 1:</p><ul><li><code>lookup.filter</code>: additional constraint on the client/server side in the lookup query.</li><li><code>lookup.virtualize</code>: enables virtual scroll on the lookup widget&#39;s <code>p-autocomplete</code>.</li><li><code>lookup.virtualize.enabled</code>: tolerant parser (<code>true/false</code>, <code>1/0</code>, equivalent strings); default <code>false</code> when absent.</li><li><code>lookup.virtualize.itemSize</code>: virtual row height in px; default <code>44</code>.</li><li><code>lookup.endpoint</code>: override of the dropdown&#39;s data provider. When set, the lookup-editor&#39;s nested datasource no longer goes through the metadata combo endpoint (<code>MetaService.getFlatRecordComboData</code>) but directly through the provider declared in <code>type</code> with the URL in <code>uri</code>.</li><li><code>lookup.endpoint.type</code>: <code>&#39;odata&#39;</code> (dispatcher routes to <code>DataProviderOdataService.selectCombo</code> which issues a GET with <code>$top</code>, <code>$count=true</code>, <code>$select=valueField,textField</code> and <code>$filter=contains(textField,&#39;&lt;query&gt;&#39;)</code> for server-side search). Other values follow the same logic as table-level <code>extraProps.endpoint.type</code>.</li><li><code>lookup.endpoint.uri</code>: base URL of the entity set (e.g., <code>/odata/&lt;EntitySet&gt;</code>), with or without explicit origin. Existing query params are ignored — the provider rebuilds the params on each fetch.</li><li><code>form.columns</code>: field width in the edit form.</li><li><code>style.editCss</code>: custom editor style.</li></ul><h2>Slim combo — extra fields via <code>mc_props_bag.slimCombo</code></h2><p>The lookup dropdown by default issues a minimal SELECT on the related table: PK + <code>mc_ui_lookup_dataValueField</code> + <code>mc_ui_lookup_dataTextField</code>. This shrinks the combo payload. When the client needs additional columns from the related table — typically to pre-fill dependent fields via <code>mc_selection_changed_custom_function__fn</code> — use <code>mc_props_bag.slimCombo</code> as an <strong>array of extra column names</strong> to include in the combo SELECT:</p><p>Snippet 2:</p><ul><li>Array: the names are added to the <code>columnRestrictionList</code> of the combo response (logic in <code>DataProviderMetaService.buildSlimComboRestriction</code>).</li><li><code>slimCombo: false</code>: disables the restriction → the combo returns <strong>all</strong> columns. Useful for debugging, larger payload.</li><li>If the column has <code>mc_ui_lookup_combo_text_edit_computed_dataTextField</code> set (computed display formula), <code>slimCombo</code> is ignored and the combo returns all columns (the formula may reference arbitrary fields).</li></ul><p>The extra fields land in <code>record[&#39;&lt;col&gt;__lookup_obj&#39;].value</code> (full record from the related table, already including the fields declared in <code>slimCombo</code>) and are accessible from the <code>mc_selection_changed_custom_function__fn</code> callback:</p><p>Snippet 3:</p><p>Tradeoff: slightly larger combo payload but no extra HTTP roundtrip (<code>getFlatRecordData</code> to fetch the full record by id) on every selection.</p><h2>Assisted Workflow: Clone Lookup + Default Filter + Lookup Hierarchy</h2><ul><li>Inline operational flow: <code>suggestLookup2 -&gt; suggestLookupDefaultFilter -&gt; getLookupListByRoute -&gt; getSeletClauseByLookupHierarchy</code>.</li><li>Inline snippet main fields: <code>mc_ui_lookup_entity_name</code>, <code>mc_ui_lookup_dataValueField</code>, <code>mc_ui_lookup_dataTextField</code>, <code>mc_ui_lookup_filter</code>.</li><li>Inline snippet quick filter: <code>lookup.filter: {&quot;logic&quot;:&quot;AND&quot;,&quot;filters&quot;:[{&quot;field&quot;:&quot;is_active&quot;,&quot;operatore&quot;:&quot;eq&quot;,&quot;value&quot;:true}]}</code>.</li><li>Client effect: accelerated lookup setup in the metadata-editor and consistent filter already ready in the widget.</li><li>Server effect: lookup query and select clause remain aligned with <code>MetaService.*</code> endpoints without manual mapping.</li></ul><p>Quick operational checklist:</p><p>1. Clone base configuration with <code>suggestLookup2</code>;</p><p>2. Generate default filter with <code>suggestLookupDefaultFilter</code>;</p><p>3. Refine hierarchy/select clause from the lookup tree;</p><p>4. Validate at runtime that search and value/text mapping are consistent.</p><h2>Client/Server Effects</h2><ul><li>Client: lookup widget rendering, search, label/value binding.</li><li>Client: with <code>lookup.virtualize</code> active, the autocomplete renders only the visual subset of options.</li><li>Server: paginated/filtered lookup query via <code>MetaService.getFlatRecordData</code>.</li></ul><h2>Operational Notes</h2><ul><li>In <code>multiple_check</code>, the lookup part remains the same; the multi-value renderer changes.</li><li>If <code>mc_ui_lookup_entity_name</code> is missing, the widget cannot resolve the source.</li></ul><h2>Auto-fetch on programmatic FK changes</h2><p>The <code>wuic-lookup-editor</code> automatically subscribes to <code>record[mc_nome_colonna]</code> BehaviorSubject and handles programmatic FK changes (e.g. import from source document, custom callback that does <code>record.cliente_id.next(99)</code>):</p><ul><li>If the new value is <strong>already in `items`</strong> (the loaded lookup page): only <code>__lookup_obj</code> is updated for consistency, no HTTP call.</li><li>If the new value is <strong>not in items</strong> (e.g. value outside current combo page, or not yet fetched): a <strong>second-stage fetch</strong> is triggered, filtered <code>WHERE &lt;valueField&gt; eqor &lt;newValue&gt;</code>, to retrieve the related record and automatically populate the dropdown.</li></ul><p>Skip cases (no-op):</p><ul><li>Multi-value lookup (<code>isFilter</code> with <code>eqor</code>, <code>multiple_check</code>)</li><li>First emit of the BS (already covered by the init flow)</li><li>null/undefined value (user cleared)</li><li><code>hasActiveLookupQuery=true</code> (user is typing a manual query)</li></ul><p><strong>Implication for custom forms</strong>: setting just the FK is enough — manual workarounds to populate <code>__lookup_obj</code> or the joined alias are no longer required. The dropdown display updates by itself.</p><p>Snippet 4:</p><p>&gt; <strong>Note</strong>: auto-fetch fires only when the <code>&lt;wuic-lookup-editor&gt;</code> is <strong>mounted</strong> (cell in edit mode). For <strong>list display</strong> (read-only cell), <code>formatGridViewValue</code> reads the joined alias <code>&lt;route&gt;___&lt;textField&gt;__&lt;col&gt;</code> directly from the record — that still must be populated manually if it doesn&#39;t come from the backend.</p>",
7855
8119
  "codeSamples": [
7856
8120
  {
7857
8121
  "id": "code_1",
7858
8122
  "title": "Snippet 1",
7859
8123
  "language": "json",
7860
8124
  "code": "{\n \"lookup\": {\n \"filter\": {\n \"logic\": \"AND\",\n \"filters\": [\n { \"field\": \"is_active\", \"operatore\": \"eq\", \"value\": true }\n ]\n },\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n },\n \"endpoint\": {\n \"type\": \"odata\",\n \"uri\": \"http://localhost:5000/odata/StateProvinces\"\n }\n },\n \"form\": {\n \"columns\": 1,\n \"disabled\": false\n },\n \"style\": {\n \"editCss\": \"max-width: 420px;\"\n }\n}"
8125
+ },
8126
+ {
8127
+ "id": "code_2",
8128
+ "title": "Snippet 2",
8129
+ "language": "json",
8130
+ "code": "{\n \"slimCombo\": [\"unita_misura_id\",\"codice_iva_id\",\"prezzo_vendita\",\"sconto_default\"]\n}"
8131
+ },
8132
+ {
8133
+ "id": "code_3",
8134
+ "title": "Snippet 3",
8135
+ "language": "ts",
8136
+ "code": "prodCol.mc_selection_changed_custom_function__fn = (record, _f, _m, newValue) => {\n const prod = record['prodotto_id__lookup_obj']?.value;\n if (!prod) return;\n record['descrizione']?.next(prod.descrizione);\n record['prezzo_unitario']?.next(prod.prezzo_vendita);\n record['unita_misura_id']?.next(prod.unita_misura_id);\n record['codice_iva_id']?.next(prod.codice_iva_id);\n};"
8137
+ },
8138
+ {
8139
+ "id": "code_4",
8140
+ "title": "Snippet 4",
8141
+ "language": "ts",
8142
+ "code": "// BEFORE (manual workaround required)\nrecord.cliente_id.next(99);\nrecord.cliente_id__lookup_obj.next({ id: 99, ragione_sociale: 'Acme S.r.l.' });\nrecord['clienti___ragione_sociale__cliente_id'].next('Acme S.r.l.');\n\n// NOW (auto-fetch by lookup-editor)\nrecord.cliente_id.next(99);\n// → lookup-editor mounted? Subscribe detects change → filtered fetch → __lookup_obj populated → dropdown shows \"Acme S.r.l.\""
7861
8143
  }
7862
8144
  ]
7863
8145
  }
@@ -8120,10 +8402,14 @@ const frameworkDocsContent = {
8120
8402
  },
8121
8403
  {
8122
8404
  "id": "sec_4",
8123
- "title": "Recommended Operational Flow"
8405
+ "title": "Element context menu"
8124
8406
  },
8125
8407
  {
8126
8408
  "id": "sec_5",
8409
+ "title": "Recommended Operational Flow"
8410
+ },
8411
+ {
8412
+ "id": "sec_6",
8127
8413
  "title": "Operational Notes"
8128
8414
  }
8129
8415
  ],
@@ -8131,7 +8417,7 @@ const frameworkDocsContent = {
8131
8417
  {
8132
8418
  "id": "overview",
8133
8419
  "title": "Overview",
8134
- "html": "<h1>Designer</h1><p>Visual editor for metadata-driven dashboards on route <code>#/cities/dashboard</code>.</p><h2>Scope</h2><ul><li>Board composition via drag-and-drop of nodes and UI blocks.</li><li>Layout and styling driven by <code>boardcontent</code> and table/column metadata.</li><li>Property panel editing without hardcoding in runtime components.</li></ul><h2>Designer Palette</h2><ul><li><code>Datasource</code>: metadata data source (<code>route</code>, filters, sort, paging).</li><li><code>DataRepeater</code>: list/card view connected to a datasource.</li><li><code>Dynamic Template</code>: dynamic template rendering with metadata-driven inputs.</li><li><code>Action/Button</code>: user actions attached to records and callbacks.</li><li><code>Container/Layout</code>: structural blocks for columns, rows, and wrappers.</li></ul><h2>Main Features</h2><ul><li>Drag-and-drop nodes onto the canvas with persistence in <code>boardcontent</code>.</li><li>Contextual property panel with inputs consistent with the selected components.</li><li>Binding between nodes (<code>datasource -&gt; repeater/template</code>) with minimal validation.</li><li>Integrated runtime preview and layout realignment.</li><li>Pending changes management with guard before exit.</li></ul><h2>Recommended Operational Flow</h2><ul><li>Open the target dashboard route.</li><li>Insert/connect nodes from the palette.</li><li>Configure essential <code>inputs</code> (e.g., <code>route</code>, <code>action</code>, <code>datasource</code>).</li><li>Save and validate the result at runtime.</li></ul><h2>Operational Notes</h2><ul><li>For specific dashboard fixes, prefer <code>boardcontent</code> patches instead of global changes.</li><li>Avoid duplicate properties between <code>inputs</code> and inconsistent inline styles.</li><li>Always verify that datasources have a populated <code>inputs.route</code>.</li></ul>",
8420
+ "html": "<h1>Designer</h1><p>Visual editor for metadata-driven dashboards on route <code>#/cities/dashboard</code>.</p><h2>Scope</h2><ul><li>Board composition via drag-and-drop of nodes and UI blocks.</li><li>Layout and styling driven by <code>boardcontent</code> and table/column metadata.</li><li>Property panel editing without hardcoding in runtime components.</li></ul><h2>Designer Palette</h2><ul><li><code>Datasource</code>: metadata data source (<code>route</code>, filters, sort, paging).</li><li><code>DataRepeater</code>: list/card view connected to a datasource.</li><li><code>Dynamic Template</code>: dynamic template rendering with metadata-driven inputs.</li><li><code>Action/Button</code>: user actions attached to records and callbacks.</li><li><code>Container/Layout</code>: structural blocks for columns, rows, and wrappers.</li></ul><h2>Main Features</h2><ul><li>Drag-and-drop nodes onto the canvas with persistence in <code>boardcontent</code>.</li><li>Contextual property panel with inputs consistent with the selected components.</li><li>Binding between nodes (<code>datasource -&gt; repeater/template</code>) with minimal validation.</li><li>Integrated runtime preview and layout realignment.</li><li>Pending changes management with guard before exit.</li></ul><h2>Element context menu</h2><p>Right-click on a canvas element, a breadcrumb item at the bottom or a node in the hierarchy tree shows a context menu with three items:</p><ul><li><strong>Edit properties</strong> (pi-cog) — selects the element as the &quot;current tool&quot; and shows its property panel on the right; equivalent to clicking in the tree. Useful to edit properties of nested elements without hunting for them in the hierarchy.</li><li><strong>Rename</strong> (breadcrumb only) — opens a dialog to rename the <code>displayName</code>.</li><li><strong>Remove</strong> — deletes the element from the board.</li></ul><h2>Recommended Operational Flow</h2><ul><li>Open the target dashboard route.</li><li>Insert/connect nodes from the palette.</li><li>Configure essential <code>inputs</code> (e.g., <code>route</code>, <code>action</code>, <code>datasource</code>).</li><li>Save and validate the result at runtime.</li></ul><h2>Operational Notes</h2><ul><li>For specific dashboard fixes, prefer <code>boardcontent</code> patches instead of global changes.</li><li>Avoid duplicate properties between <code>inputs</code> and inconsistent inline styles.</li><li>Always verify that datasources have a populated <code>inputs.route</code>.</li></ul>",
8135
8421
  "codeSamples": []
8136
8422
  }
8137
8423
  ],
@@ -8195,14 +8481,18 @@ const frameworkDocsContent = {
8195
8481
  },
8196
8482
  {
8197
8483
  "id": "sec_3",
8198
- "title": "Recommended Operational Flow"
8484
+ "title": "View Builder Tab — Aggregations and GROUP BY (Tier 1, 2026-05-08)"
8199
8485
  },
8200
8486
  {
8201
8487
  "id": "sec_4",
8202
- "title": "Action Buttons"
8488
+ "title": "Recommended Operational Flow"
8203
8489
  },
8204
8490
  {
8205
8491
  "id": "sec_5",
8492
+ "title": "Action Buttons"
8493
+ },
8494
+ {
8495
+ "id": "sec_6",
8206
8496
  "title": "Usage Notes"
8207
8497
  }
8208
8498
  ],
@@ -8210,7 +8500,7 @@ const frameworkDocsContent = {
8210
8500
  {
8211
8501
  "id": "overview",
8212
8502
  "title": "Overview",
8213
- "html": "<h1>View/Pivot Builder</h1><p>The Pivot Builder allows you to build dynamic analytical views starting from metadata routes, without writing manual queries on the UI side.</p><h2>Screenshot Reference</h2><ul><li><code>manual__pivot_builder__01.png</code>: builder configuration and runtime result.</li><li><code>manual__pivot_builder__02.png</code>: materialized table creation dialog detail and operational options.</li></ul><h2>Main Features</h2><ul><li>Field selection in <code>rows</code>, <code>columns</code>, and <code>values</code> areas.</li><li>Available aggregations on values (sum, count, average, minimum, maximum).</li><li>Multidimensional grouping with sorting by dimension.</li><li>Interactive filters to reduce the analyzed dataset.</li><li>Real-time pivot view update after layout or metric change.</li><li>Export/reuse of the pivot configuration in the operational context.</li></ul><h2>Recommended Operational Flow</h2><p>1. Open the pivot builder route.</p><p>2. Select the starting datasource/metadata route.</p><p>3. Drag fields into the <code>rows</code>, <code>columns</code>, <code>values</code> zones.</p><p>4. Configure the aggregation for each metric.</p><p>5. Apply filters and verify the totals.</p><p>6. Save or reuse the configuration according to the application flow.</p><h2>Action Buttons</h2><p><code>Create view</code></p><ul><li>Generates a SQL view from the current pivot configuration.</li><li>Useful when you want to reuse the result as a stable data source in other routes/reports.</li><li>Keeps the logic centralized in the DB, avoiding manual layout reconstruction each time.</li></ul><p><code>Create materialized table</code></p><ul><li>Creates a physical table with the aggregated data from the pivot configuration.</li><li>Useful for large datasets or dashboards where fast read times are needed.</li><li>Automatically scaffolds the route associated with the created physical table and generates a schedule for data refresh/alignment.</li><li>Pressing Refresh on the grid in the associated route forces a schedule run.</li></ul><h2>Usage Notes</h2><ul><li>For consistent analysis, use columns with the correct data type (numeric for metrics, text/dates for dimensions).</li><li>When dealing with large volumes, prefer targeted filters and aggregations before expanding the detail.</li></ul>",
8503
+ "html": "<h1>View/Pivot Builder</h1><p>The Pivot Builder allows you to build dynamic analytical views starting from metadata routes, without writing manual queries on the UI side.</p><h2>Screenshot Reference</h2><ul><li><code>manual__pivot_builder__01.png</code>: builder configuration and runtime result.</li><li><code>manual__pivot_builder__02.png</code>: materialized table creation dialog detail and operational options.</li></ul><h2>Main Features</h2><ul><li>Field selection in <code>rows</code>, <code>columns</code>, and <code>values</code> areas.</li><li>Available aggregations on values (sum, count, average, minimum, maximum).</li><li>Multidimensional grouping with sorting by dimension.</li><li>Interactive filters to reduce the analyzed dataset.</li><li>Real-time pivot view update after layout or metric change.</li><li>Export/reuse of the pivot configuration in the operational context.</li></ul><h2>View Builder Tab — Aggregations and GROUP BY (Tier 1, 2026-05-08)</h2><p>Tab 1 &quot;View Builder&quot; now lets you emit <code>GROUP BY</code> with <code>SUM/COUNT/AVG/MIN/MAX/COUNT(DISTINCT)</code> aggregations directly from the <code>ViewDefinition</code>, without falling back to manualSql mode.</p><p>Panel below the canvas:</p><ul><li><strong>Aggregations</strong>: form to add <code>function(column) AS alias</code>. The source column can be any column on the canvas or <code>*</code> (for <code>COUNT(*)</code>); the output alias is auto-generated when blank.</li><li><strong>GROUP BY</strong>: checkbox per selected column on canvas nodes. If none is explicitly checked, the backend infers dimensions from the <code>selected=true</code> non-aggregated columns.</li><li><strong>CASE WHEN for pivoting</strong>: for patterns like <code>SUM(CASE WHEN bucket=&#39;X&#39; THEN amount ELSE 0 END)</code> use a formula column (right-click → Edit formula) as aggregation source. The formula is emitted as the aggregate argument.</li></ul><p>Backend output (<a href=\"../../Services/MetaService.cs\">`MetaService.previewViewDefinition`</a>):</p><ul><li><code>viewDefinition.aggregations[]</code> → emits <code>&lt;FN&gt;(&lt;col|formula&gt;) AS &lt;alias&gt;</code> as additional SELECT columns</li><li><code>viewDefinition.groupByColumns[]</code> → emits <code>GROUP BY</code> clause</li><li>Auto-infers GROUP BY when <code>aggregations</code> are present without an explicit <code>groupByColumns</code></li></ul><h3>UNION blocks (Tier 2, 2026-05-08)</h3><p>A subsequent extension lets you concatenate multiple SELECTs with <code>UNION</code> or <code>UNION ALL</code> directly from a single <code>ViewDefinition</code>:</p><ul><li><strong>Block 0</strong> = top-level (canvas tables/joins/aggregations) → defines the <strong>canonical vocabulary</strong> of SELECT columns</li><li><strong>Block N (N≥1)</strong> = each additional <code>unionBlocks[]</code> is a single-table sub-definition with a required <code>columnMapping</code>: for every canonical alias of block 0, block N specifies <code>sourceQualified</code> (column from the block) <strong>or</strong> <code>constantValue</code> (SQL literal like <code>&#39;CLIENT&#39;</code>) <strong>or</strong> <code>formula</code> (SQL expression for cross-block cast)</li><li><strong>UNION ALL / UNION toggle</strong>: default <code>unionAll: true</code> (no dedup, better performance); <code>false</code> for <code>UNION DISTINCT</code></li><li><strong>TOP/LIMIT</strong>: applied to the whole UNION, wrapped in subquery <code>SELECT TOP N * FROM (&lt;union&gt;) AS u</code></li></ul><p>UI below the canvas: route picker + &quot;Add UNION block&quot; button → each block shows as an accordion with header (caption + route) + column-mapping matrix (per canonical alias: dropdown of block source col, or constant SQL input).</p><p>v0 limits: each UNION block is single-table (no JOIN within block). For cross-block JOIN (e.g. &#39;BOTH&#39; category in <code>vw_anagrafica_unificata</code> that requires <code>clients INNER JOIN suppliers ON vat</code>) Tier 2.5 is needed.</p><h2>Recommended Operational Flow</h2><p>1. Open the pivot builder route.</p><p>2. Select the starting datasource/metadata route.</p><p>3. Drag fields into the <code>rows</code>, <code>columns</code>, <code>values</code> zones.</p><p>4. Configure the aggregation for each metric.</p><p>5. Apply filters and verify the totals.</p><p>6. Save or reuse the configuration according to the application flow.</p><h2>Action Buttons</h2><p><code>Create view</code></p><ul><li>Generates a SQL view from the current pivot configuration.</li><li>Useful when you want to reuse the result as a stable data source in other routes/reports.</li><li>Keeps the logic centralized in the DB, avoiding manual layout reconstruction each time.</li></ul><p><code>Create materialized table</code></p><ul><li>Creates a physical table with the aggregated data from the pivot configuration.</li><li>Useful for large datasets or dashboards where fast read times are needed.</li><li>Automatically scaffolds the route associated with the created physical table and generates a schedule for data refresh/alignment.</li><li>Pressing Refresh on the grid in the associated route forces a schedule run.</li></ul><h2>Usage Notes</h2><ul><li>For consistent analysis, use columns with the correct data type (numeric for metrics, text/dates for dimensions).</li><li>When dealing with large volumes, prefer targeted filters and aggregations before expanding the detail.</li></ul>",
8214
8504
  "codeSamples": []
8215
8505
  }
8216
8506
  ],
@@ -9165,7 +9455,7 @@ const frameworkDocsContent = {
9165
9455
  {
9166
9456
  "id": "overview",
9167
9457
  "title": "Overview",
9168
- "html": "<h1>Component Catalog</h1><p>Total component folders detected: 48.</p><h2>Available components</h2><ul><li>app-settings-editor</li><li>archetype-configurator</li><li>bounded-repeater</li><li>carousel-list</li><li>chart-list</li><li>code-editor</li><li>css-sheet-editor</li><li>dashboard</li><li>data-action-button</li><li>data-repeater</li><li>data-source</li><li>designer</li><li>dynamic-dashboard-template</li><li>dynamic-field-template</li><li>dynamic-form-template</li><li>dynamic-generic-template</li><li>dynamic-repeater-template</li><li>dynamic-template</li><li>field</li><li>filter-bar</li><li>first-run-wizard</li><li>framework-docs</li><li>image-wrapper</li><li>import-export-button</li><li>kanban-list</li><li>list-grid</li><li>map-list</li><li>meta-menu</li><li>metadata-editor</li><li>notification-bell</li><li>pager</li><li>parametric-dialog</li><li>pivot-builder</li><li>prompt-dialog-fallback-host</li><li>rag-chatbot</li><li>rag-chatbot-fab</li><li>report-designer</li><li>report-viewer</li><li>scheduler-list</li><li>split</li><li>split-area</li><li>spreadsheet-list-sf</li><li>tab-panel-wrapper</li><li>tab-view-wrapper</li><li>tree-list</li><li>upload-dialog</li><li>workflow-designer</li><li>workflow-runner</li></ul><h2>Exports from public-api</h2><ul><li>data-source/data-source.component</li><li>list-grid/list-grid.component</li><li>import-export-button/import-export-button.component</li><li>pager/pager.component</li><li>filter-bar/filter-bar.component</li><li>map-list/map-list.component</li><li>carousel-list/carousel-list.component</li><li>tree-list/tree-list.component</li><li>parametric-dialog/parametric-dialog.component</li><li>upload-dialog/upload-dialog.component</li><li>app-settings-editor/app-settings-editor.component</li><li>data-action-button/data-action-button.component</li><li>meta-menu/meta-menu.component</li><li>field/text-editor/text-editor.component</li><li>field/text-area-editor/text-area-editor.component</li><li>field/number-editor/number-editor.component</li><li>field/lookup-editor/lookup-editor.component</li><li>field/dictionary-editor/dictionary-editor.component</li><li>field/boolean-editor/boolean-editor.component</li><li>field/date-editor/date-editor.component</li><li>field/html-editor/html-editor.component</li><li>field/upload-editor/upload-editor.component</li><li>field/button-editor/button-editor.component</li><li>field/field-editor/field-editor.component</li><li>field/code-area-editor/code-area-editor.component</li><li>dynamic-template/dynamic-template.component</li><li>image-wrapper/image-wrapper.component</li><li>field/color-editor/color-editor.component</li><li>field/property-array-editor/property-array-editor.component</li><li>field/property-object-editor/property-object-editor.component</li><li>field/tree-view-selector/tree-view-selector.component</li><li>workflow-designer/workflow-designer.component</li><li>workflow-runner/workflow-runner.component</li><li>notification-bell/notification-bell.component</li><li>bounded-repeater/bounded-repeater.component</li><li>framework-docs/framework-docs.component</li><li>designer/designer.route.component</li><li>workflow-designer/workflow-designer.route.component</li><li>kanban-list/kanban-list.component</li><li>data-repeater/data-repeater.component</li><li>bounded-repeater/bounded-repeater-pending-changes.guard</li><li>rag-chatbot/rag-chatbot.component</li><li>rag-chatbot-fab/rag-chatbot-fab.component</li></ul>",
9458
+ "html": "<h1>Component Catalog</h1><p>Total component folders detected: 49.</p><h2>Available components</h2><ul><li>app-settings-editor</li><li>archetype-configurator</li><li>bounded-repeater</li><li>carousel-list</li><li>chart-list</li><li>code-editor</li><li>css-sheet-editor</li><li>dashboard</li><li>data-action-button</li><li>data-repeater</li><li>data-source</li><li>designer</li><li>dynamic-dashboard-template</li><li>dynamic-field-template</li><li>dynamic-form-template</li><li>dynamic-generic-template</li><li>dynamic-repeater-template</li><li>dynamic-template</li><li>error-dialog</li><li>field</li><li>filter-bar</li><li>first-run-wizard</li><li>framework-docs</li><li>image-wrapper</li><li>import-export-button</li><li>kanban-list</li><li>list-grid</li><li>map-list</li><li>meta-menu</li><li>metadata-editor</li><li>notification-bell</li><li>pager</li><li>parametric-dialog</li><li>pivot-builder</li><li>prompt-dialog-fallback-host</li><li>rag-chatbot</li><li>rag-chatbot-fab</li><li>report-designer</li><li>report-viewer</li><li>scheduler-list</li><li>split</li><li>split-area</li><li>spreadsheet-list-sf</li><li>tab-panel-wrapper</li><li>tab-view-wrapper</li><li>tree-list</li><li>upload-dialog</li><li>workflow-designer</li><li>workflow-runner</li></ul><h2>Exports from public-api</h2><ul><li>data-source/data-source.component</li><li>list-grid/list-grid.component</li><li>import-export-button/import-export-button.component</li><li>pager/pager.component</li><li>filter-bar/filter-bar.component</li><li>map-list/map-list.component</li><li>carousel-list/carousel-list.component</li><li>tree-list/tree-list.component</li><li>parametric-dialog/parametric-dialog.component</li><li>upload-dialog/upload-dialog.component</li><li>app-settings-editor/app-settings-editor.component</li><li>data-action-button/data-action-button.component</li><li>meta-menu/meta-menu.component</li><li>field/text-editor/text-editor.component</li><li>field/text-area-editor/text-area-editor.component</li><li>field/number-editor/number-editor.component</li><li>field/lookup-editor/lookup-editor.component</li><li>field/dictionary-editor/dictionary-editor.component</li><li>field/boolean-editor/boolean-editor.component</li><li>field/date-editor/date-editor.component</li><li>field/html-editor/html-editor.component</li><li>field/upload-editor/upload-editor.component</li><li>field/button-editor/button-editor.component</li><li>field/field-editor/field-editor.component</li><li>field/code-area-editor/code-area-editor.component</li><li>dynamic-template/dynamic-template.component</li><li>image-wrapper/image-wrapper.component</li><li>field/color-editor/color-editor.component</li><li>field/property-array-editor/property-array-editor.component</li><li>field/property-object-editor/property-object-editor.component</li><li>field/tree-view-selector/tree-view-selector.component</li><li>workflow-designer/workflow-designer.component</li><li>workflow-runner/workflow-runner.component</li><li>notification-bell/notification-bell.component</li><li>bounded-repeater/bounded-repeater.component</li><li>framework-docs/framework-docs.component</li><li>designer/designer.route.component</li><li>workflow-designer/workflow-designer.route.component</li><li>kanban-list/kanban-list.component</li><li>data-repeater/data-repeater.component</li><li>bounded-repeater/bounded-repeater-pending-changes.guard</li><li>rag-chatbot/rag-chatbot.component</li><li>rag-chatbot-fab/rag-chatbot-fab.component</li><li>error-dialog/error-dialog.component</li></ul>",
9169
9459
  "codeSamples": []
9170
9460
  }
9171
9461
  ],
@@ -9286,13 +9576,21 @@ const frameworkDocsContent = {
9286
9576
  {
9287
9577
  "id": "sec_3",
9288
9578
  "title": "Examples"
9579
+ },
9580
+ {
9581
+ "id": "sec_4",
9582
+ "title": "Runtime `metaInfo` patches via `fetchInfo$.subscribe`"
9583
+ },
9584
+ {
9585
+ "id": "sec_5",
9586
+ "title": "Multiple `<wuic-data-source>` in the same component"
9289
9587
  }
9290
9588
  ],
9291
9589
  "sections": [
9292
9590
  {
9293
9591
  "id": "overview",
9294
9592
  "title": "Overview",
9295
- "html": "<h1>Datasource</h1><p><code>wuic-data-source</code> is the component that manages metadata, data fetching, current state, change tracking, and CRUD sync.</p><h2>Description</h2><ul><li>Resolves the route (<code>route</code>/<code>hardcodedRoute</code> or URL routing).</li><li>Loads table/column metadata and publishes state on <code>fetchInfo$</code>.</li><li>Manages <code>filterInfo</code>, <code>sortInfo</code>, <code>groupInfo</code>, paging (<code>pageSize</code>, <code>currentPage</code>) and cursor paging.</li><li>Exposes runtime methods for insert/update/delete, batch save, rollback, and export.</li><li>It is the central point to which <code>wuic-data-repeater</code>, <code>wuic-filter-bar</code>, and <code>wuic-pager</code> connect.</li></ul><h2>API</h2><p>Selector:</p><ul><li><code>wuic-data-source</code></li></ul><p>Main inputs:</p><ul><li><code>route: BehaviorSubject&lt;string&gt;</code></li><li><code>routeFromRouting: boolean</code></li><li><code>hardcodedRoute: string</code></li><li><code>autoload?: boolean</code></li><li><code>loading: BehaviorSubject&lt;boolean&gt;</code></li><li><code>changeTracking?: boolean</code></li><li><code>parentRecord: any</code></li><li><code>parentMetaInfo: MetaInfo</code></li><li><code>parentDatasource: DataSourceComponent</code></li><li><code>componentRef: BehaviorSubject&lt;{ component, id, name, uniqueName }&gt;</code></li></ul><p>Main state/runtime:</p><ul><li><code>metaInfo: MetaInfo</code></li><li><code>resultInfo: ResultInfo</code></li><li><code>filterInfo?: FilterInfo</code></li><li><code>sortInfo: SortInfo[]</code></li><li><code>groupInfo: GroupInfo[]</code></li><li><code>aggregationInfo: AggregationInfo[]</code></li><li><code>fetchInfo$: BehaviorSubject&lt;{ resultInfo, metaInfo, filterDescriptor }&gt;</code></li><li><code>datasourceReady$: BehaviorSubject&lt;DataSourceComponent | null&gt;</code></li><li><code>afterFirstLoad$: BehaviorSubject&lt;any&gt;</code></li><li><code>beforeSync$: Subject&lt;DataSourceBeforeSyncEvent&gt;</code> (cancellable)</li><li><code>afterSync$: Subject&lt;DataSourceAfterSyncEvent&gt;</code></li></ul><p>Most used runtime methods:</p><ul><li><code>fetchData()</code></li><li><code>syncData(entita, original, deleting?, cloning?)</code></li><li><code>addNewRecord(record?)</code></li><li><code>setCurrent(data)</code></li><li><code>clearColumnFilter(col, fetch?)</code></li><li><code>getPendingChanges()</code></li><li><code>hasPendingChanges()</code></li><li><code>batchSave(targetChanges?)</code></li><li><code>rollbackChanges(targetChanges?)</code></li><li><code>exportXls()</code></li></ul><p>Runtime subscriptions/events:</p><ul><li><code>fetchInfo$</code>: main data/metadata state stream.</li><li><code>datasourceReady$</code>: emitted when the datasource instance is ready.</li><li><code>afterFirstLoad$</code>: emitted once on the first useful payload.</li><li><code>beforeSync$</code>: emitted before <code>insert/update/delete/clone/batch</code>; you can cancel with <code>event.cancelSync(...)</code>.</li><li><code>afterSync$</code>: emitted after successful sync.</li></ul><p>Compatibility note:</p><ul><li>The <code>fetchInfo</code> alias (getter/setter) is also available, but the recommended naming is <code>fetchInfo$</code>.</li></ul><h2>Examples</h2><h3>Basic Example (Hardcoded Route + Autoload)</h3><p>Snippet 1:</p><h3>Master-Detail Example (Nested Datasource)</h3><p>Snippet 2:</p><h3>Operational Example (Manual Fetch + Sync)</h3><p>Snippet 3:</p><h3>Subscriptions Example (Host)</h3><p>Snippet 4:</p>",
9593
+ "html": "<h1>Datasource</h1><p><code>wuic-data-source</code> is the component that manages metadata, data fetching, current state, change tracking, and CRUD sync.</p><h2>Description</h2><ul><li>Resolves the route (<code>route</code>/<code>hardcodedRoute</code> or URL routing).</li><li>Loads table/column metadata and publishes state on <code>fetchInfo$</code>.</li><li>Manages <code>filterInfo</code>, <code>sortInfo</code>, <code>groupInfo</code>, paging (<code>pageSize</code>, <code>currentPage</code>) and cursor paging.</li><li>Exposes runtime methods for insert/update/delete, batch save, rollback, and export.</li><li>It is the central point to which <code>wuic-data-repeater</code>, <code>wuic-filter-bar</code>, and <code>wuic-pager</code> connect.</li></ul><h2>API</h2><p>Selector:</p><ul><li><code>wuic-data-source</code></li></ul><p>Main inputs:</p><ul><li><code>route: BehaviorSubject&lt;string&gt;</code></li><li><code>routeFromRouting: boolean</code></li><li><code>hardcodedRoute: string</code></li><li><code>autoload?: boolean</code></li><li><code>loading: BehaviorSubject&lt;boolean&gt;</code></li><li><code>changeTracking?: boolean</code></li><li><code>parentRecord: any</code></li><li><code>parentMetaInfo: MetaInfo</code></li><li><code>parentDatasource: DataSourceComponent</code></li><li><code>componentRef: BehaviorSubject&lt;{ component, id, name, uniqueName }&gt;</code></li></ul><p>Main state/runtime:</p><ul><li><code>metaInfo: MetaInfo</code></li><li><code>resultInfo: ResultInfo</code></li><li><code>filterInfo?: FilterInfo</code></li><li><code>sortInfo: SortInfo[]</code></li><li><code>groupInfo: GroupInfo[]</code></li><li><code>aggregationInfo: AggregationInfo[]</code></li><li><code>fetchInfo$: BehaviorSubject&lt;{ resultInfo, metaInfo, filterDescriptor }&gt;</code></li><li><code>datasourceReady$: BehaviorSubject&lt;DataSourceComponent | null&gt;</code></li><li><code>afterFirstLoad$: BehaviorSubject&lt;any&gt;</code></li><li><code>beforeSync$: Subject&lt;DataSourceBeforeSyncEvent&gt;</code> (cancellable)</li><li><code>afterSync$: Subject&lt;DataSourceAfterSyncEvent&gt;</code></li></ul><p>Most used runtime methods:</p><ul><li><code>fetchData()</code></li><li><code>syncData(entita, original, deleting?, cloning?)</code></li><li><code>addNewRecord(record?)</code></li><li><code>setCurrent(data)</code></li><li><code>clearColumnFilter(col, fetch?)</code></li><li><code>getPendingChanges()</code></li><li><code>hasPendingChanges()</code></li><li><code>batchSave(targetChanges?)</code></li><li><code>rollbackChanges(targetChanges?)</code></li><li><code>exportXls()</code></li></ul><p>Runtime subscriptions/events:</p><ul><li><code>fetchInfo$</code>: main data/metadata state stream.</li><li><code>datasourceReady$</code>: emitted when the datasource instance is ready.</li><li><code>afterFirstLoad$</code>: emitted once on the first useful payload.</li><li><code>beforeSync$</code>: emitted before <code>insert/update/delete/clone/batch</code>; you can cancel with <code>event.cancelSync(...)</code>.</li><li><code>afterSync$</code>: emitted after successful sync.</li></ul><p>Compatibility note:</p><ul><li>The <code>fetchInfo</code> alias (getter/setter) is also available, but the recommended naming is <code>fetchInfo$</code>.</li></ul><h2>Examples</h2><h3>Basic Example (Hardcoded Route + Autoload)</h3><p>Snippet 1:</p><h3>Master-Detail Example (Nested Datasource)</h3><p>Snippet 2:</p><h3>Operational Example (Manual Fetch + Sync)</h3><p>Snippet 3:</p><h3>Subscriptions Example (Host)</h3><p>Snippet 4:</p><h2>Runtime <code>metaInfo</code> patches via <code>fetchInfo$.subscribe</code></h2><p>To modify a nested datasource&#39;s metadata at runtime (e.g. <code>mc_hide_in_list</code>, <code>md_inline_cell_editing</code>, <code>md_pageable=false</code>, <code>mc_logic_editable=false</code>) <strong>without touching the DB</strong> — local patch on the reference cached by <code>MetadataProviderService</code>, latest-wins, single-dialog scenario — subscribe to the data-source&#39;s <code>fetchInfo$</code>:</p><p>Snippet 5:</p><p><strong>Scope</strong>: session-only + instance-only. The patch survives only until the <code>MetadataProviderService</code> cache is invalidated or the component is destroyed. Useful when a custom form needs a different representation of a nested grid compared to the standalone rendering.</p><p><strong>When NOT to use runtime patches</strong>: for changes targeted at all instances of the route (including standalone <code>&lt;route&gt;/list</code>) → direct SQL patch on metadata; for changes persisting across restarts → SQL + <code>MetaService.invalidateMetadataRuntime</code>.</p><h2>Multiple <code>&lt;wuic-data-source&gt;</code> in the same component</h2><p>When a custom component contains more than one <code>&lt;wuic-data-source&gt;</code> (e.g. master + nested rows + nested due-dates), the <code>@ViewChild(DataSourceComponent)</code> by-type pattern matches <strong>only the first</strong> data-source in template top-down order. For the others you need a <strong>template reference variable</strong> + <code>@ViewChild(&#39;refName&#39;)</code>:</p><p>Snippet 6:</p><p>Snippet 7:</p><p><strong>Antipattern</strong>: <code>document.querySelectorAll(&#39;wuic-data-source&#39;)</code> + <code>window.ng.getComponent(el)</code> to filter by <code>hardcodedRoute</code>. Works but is fragile, depends on the <code>window.ng</code> debug API (not guaranteed in production). Always use template refs.</p>",
9296
9594
  "codeSamples": [
9297
9595
  {
9298
9596
  "id": "code_1",
@@ -9317,6 +9615,24 @@ const frameworkDocsContent = {
9317
9615
  "title": "Snippet 4",
9318
9616
  "language": "ts",
9319
9617
  "code": "const subs = new Subscription();\n\nsubs.add(\n datasource.fetchInfo$.subscribe((info) => {\n if (!info) return;\n console.log('records', info.resultInfo?.dato?.length || 0);\n })\n);\n\nsubs.add(\n datasource.beforeSync$.subscribe((event) => {\n if (event.operation === 'delete' && !canDelete()) {\n event.cancelSync('Delete not allowed by host context');\n }\n })\n);\n\nsubs.add(\n datasource.afterSync$.subscribe((event) => {\n console.log('sync ok', event.operation, event.syncedData);\n })\n);"
9618
+ },
9619
+ {
9620
+ "id": "code_5",
9621
+ "title": "Snippet 5",
9622
+ "language": "ts",
9623
+ "code": "private bindNestedMetaPatches(): void {\n if (!this.scadenzeDs) return;\n const ESSENTIAL = new Set(['data_scadenza', 'importo', 'pagamento_id', 'stato', 'note']);\n const sub = this.scadenzeDs.fetchInfo$.subscribe((info) => {\n const route = this.scadenzeDs?.route?.value;\n if (!info || route !== info.metaInfo?.tableMetadata?.md_route_name) return;\n const cols: any[] = info.metaInfo?.columnMetadata || [];\n for (const c of cols) {\n const name = c?.mc_nome_colonna;\n if (!name || c.mc_is_primary_key) continue;\n c.mc_hide_in_list = !ESSENTIAL.has(name);\n }\n });\n this.subs.add(sub); // cleanup in ngOnDestroy\n}"
9624
+ },
9625
+ {
9626
+ "id": "code_6",
9627
+ "title": "Snippet 6",
9628
+ "language": "html",
9629
+ "code": "<!-- TEMPLATE -->\n<wuic-data-source #righeDs [hardcodedRoute]=\"'fatture_inviate_righe'\" [parentRecord]=\"record\"></wuic-data-source>\n<wuic-data-source #scadenzeDs [hardcodedRoute]=\"'scadenze'\" [parentRecord]=\"record\"></wuic-data-source>"
9630
+ },
9631
+ {
9632
+ "id": "code_7",
9633
+ "title": "Snippet 7",
9634
+ "language": "ts",
9635
+ "code": "// COMPONENT\nimport { DataSourceComponent } from 'wuic-framework-lib';\n\n// First data-source (matched by type) -> no template ref needed\n@ViewChild(DataSourceComponent) righeDs?: DataSourceComponent;\n\n// Second data-source -> needs `#scadenzeDs` in the template + by-name ViewChild\n@ViewChild('scadenzeDs') scadenzeDs?: DataSourceComponent;"
9320
9636
  }
9321
9637
  ]
9322
9638
  }
@@ -11766,26 +12082,34 @@ const frameworkDocsContent = {
11766
12082
  },
11767
12083
  {
11768
12084
  "id": "sec_6",
11769
- "title": "md_props_bag: archetypes.list"
12085
+ "title": "md_props_bag: toolbar"
11770
12086
  },
11771
12087
  {
11772
12088
  "id": "sec_7",
11773
- "title": "Pagination : md_pagesize et md_page_size_choice"
12089
+ "title": "md_props_bag: archetypes.list"
11774
12090
  },
11775
12091
  {
11776
12092
  "id": "sec_8",
11777
- "title": "Activation forcee de la virtualisation sur une taille de page elevee"
12093
+ "title": "Pagination : md_pagesize et md_page_size_choice"
11778
12094
  },
11779
12095
  {
11780
12096
  "id": "sec_9",
12097
+ "title": "Activation forcee de la virtualisation sur une taille de page elevee"
12098
+ },
12099
+ {
12100
+ "id": "sec_10",
11781
12101
  "title": "Evenements et subscriptions (host)"
12102
+ },
12103
+ {
12104
+ "id": "sec_11",
12105
+ "title": "`rowCustomSelect` — signature reelle (piege)"
11782
12106
  }
11783
12107
  ],
11784
12108
  "sections": [
11785
12109
  {
11786
12110
  "id": "overview",
11787
12111
  "title": "Overview",
11788
- "html": "<h1>List Grid</h1><p>Composant principal pour les listes tabulaires avec filtres, tri, pagination cote serveur et actions sur les lignes.</p><h2>Cas d&#39;utilisation</h2><ul><li>CRUD tabulaires enterprise.</li><li>Rapports operationnels avec filtres multi-colonnes.</li><li>Jeux de donnees tres volumineux avec <code>cursorMode</code>.</li></ul><h2>Reference de capture d&#39;ecran (manuel utilisateur)</h2><ul><li><code>manual__grid__01.png</code> : mode <code>inline cell edit</code> dans list-grid.</li><li><code>manual__grid__02.png</code> : mode <code>inline edit</code> (au niveau ligne) dans list-grid.</li></ul><h2>Metadonnees inline editing</h2><p>Configuration dans le <code>md_props_bag</code> de la table metadata :</p><ul><li><code>md_inline_edit</code></li></ul><p> - active l&#39;edition inline de la ligne dans list-grid (cellules editables dans le contexte de la ligne, sans ouverture de popup).</p><p> - utile lorsque vous souhaitez conserver l&#39;UX tabulaire avec une edition rapide par enregistrement.</p><ul><li><code>md_inline_cell_edit</code></li></ul><p> - active l&#39;edition inline &quot;cellule par cellule&quot; (focus sur la cellule individuelle).</p><p> - note : dans les configurations legacy, il peut apparaitre sous le nom <code>md_inline_cell_editing</code> ; le comportement runtime est le meme.</p><p> - <strong>promotion runtime</strong> : lorsque <code>md_inline_cell_edit</code> est <code>true</code>, le composant force</p><p> a runtime egalement <code>md_inline_edit = true</code> independamment de la valeur en DB.</p><p> Les deux UX necessitent que la colonne d&#39;actions soit visible, elles sont donc mutuellement</p><p> non exclusives au niveau du rendu.</p><ul><li><code>md_batch_save</code></li></ul><p> - active la sauvegarde batch des modifications en attente (<code>Save changes</code> / <code>Cancel changes</code>).</p><p> - si utilise conjointement avec inline-cell, les modifications restent en attente jusqu&#39;a la sauvegarde explicite.</p><p> - <strong>prerequis</strong> : <code>md_batch_save</code> n&#39;a d&#39;effet <strong>que</strong> si <code>md_inline_cell_edit</code> est <code>true</code>.</p><p> Avec <code>md_inline_cell_edit:false</code>, le flag est ignore a runtime et les boutons</p><p> &quot;Sauvegarder les modifications / Annuler les modifications&quot; ne sont pas affiches.</p><h3>Combinaisons valides</h3><table><thead><tr><th><code>md_inline_edit</code></th><th><code>md_inline_cell_edit</code></th><th><code>md_batch_save</code></th><th>Resultat runtime</th></tr></thead><tbody><tr><td><code>true</code></td><td><code>false</code></td><td>quelconque</td><td>Edition inline au niveau ligne avec crayon. <code>md_batch_save</code> ignore.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>false</code></td><td>Autosave cellule par cellule au blur. <code>md_inline_edit</code> force a <code>true</code> a runtime.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>true</code></td><td>Cellule par cellule avec buffer en attente + barre d&#39;outils <code>Sauvegarder / Annuler les modifications</code>.</td></tr><tr><td><code>false</code></td><td><code>false</code></td><td>quelconque</td><td>Aucune edition inline. <code>md_batch_save</code> ignore.</td></tr></tbody></table><p>Exemple edition inline au niveau ligne :</p><p>Snippet 1:</p><p>Exemple cellule par cellule avec sauvegarde batch :</p><p>Snippet 2:</p><h2>Barre d&#39;outils export/import</h2><ul><li>Export XLS :</li></ul><p> - affiche une boite de dialogue de progression avec pourcentage en temps reel ;</p><p> - actions : <code>Continuer en arriere-plan</code>, <code>Annuler la tache</code>, <code>Interrompre et telecharger le partiel</code> ;</p><p> - en arriere-plan, cree une notification avec progression ; un clic rouvre la boite de dialogue.</p><ul><li>Import XLS/XLSX (si <code>md_importable = true</code>) :</li></ul><p> - boite de dialogue de progression apres la confirmation ;</p><p> - actions : <code>Continuer en arriere-plan</code>, <code>Annuler l&#39;import (rollback)</code>, <code>Arreter et commit partiel</code> ;</p><p> - a la fin de l&#39;import, cree une notification recapitulative qui redirige vers la route.</p><h2>Config metadata</h2><p>Parametres cles dans <code>md_props_bag</code> et metadonnees de colonnes.</p><p>Snippet 3:</p><h2>md_props_bag: archetypes.list</h2><p>Le composant lit <code>md_props_bag.archetypes.list</code>.</p><p>Snippet 4:</p><ul><li><code>proportionalColwidth</code> :</li><li>type : boolean (avec parser tolerant)</li><li>valeurs acceptees : <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>&quot;true&quot;</code>, <code>&quot;false&quot;</code>, <code>&quot;1&quot;</code>, <code>&quot;0&quot;</code></li><li>valeur par defaut runtime : <code>true</code> lorsqu&#39;absent ou vide</li><li>effet : distribue les largeurs de colonnes en pourcentage (au lieu de px) lorsqu&#39;il n&#39;y a pas de largeurs utilisateur persistees.</li></ul><ul><li><code>virtualize</code> :</li><li>type : <code>boolean</code> ou <code>object</code></li><li>valeurs acceptees :</li><li><code>true</code>/<code>false</code>, <code>1</code>/<code>0</code>, <code>&quot;true&quot;</code>/<code>&quot;false&quot;</code>, <code>&quot;1&quot;</code>/<code>&quot;0&quot;</code></li><li>object avec <code>enabled</code> (optionnel) et <code>itemSize</code> (optionnel)</li><li>valeur par defaut runtime : desactive (<code>false</code>) lorsqu&#39;absent</li><li><code>enabled</code> :</li><li>par defaut : <code>true</code> si le noeud <code>virtualize</code> est un objet sans <code>enabled</code>, sinon parser tolerant</li><li>effet : active <code>virtualScroll</code> sur <code>p-table</code></li><li><code>itemSize</code> :</li><li>par defaut : <code>44</code></li><li>effet : definit <code>virtualScrollItemSize</code> (hauteur de la ligne virtuelle en px)</li></ul><ul><li><code>advancedFilter</code> :</li><li>type : boolean (avec parser tolerant)</li><li>valeur par defaut runtime : <code>false</code></li><li>effet :</li><li>lorsque <code>true</code>, la grille masque les icones de filtre de colonne (<code>p-columnFilter</code>) et utilise la <code>wuic-filter-bar</code> au-dessus de la table (au niveau <code>data-repeater</code>/<code>bounded-repeater</code>) pour appliquer les filtres.</li></ul><p>Note operationnelle :</p><ul><li>le suggest <code>md_props_bag</code> dans le metadata editor expose egalement le noeud cochable <code>archetypes.list.advancedFilter</code>.</li></ul><h2>Pagination : md_pagesize et md_page_size_choice</h2><p>La grille utilise <code>md_pagesize</code> comme taille de page par defaut et <code>md_page_size_choice</code> comme liste de valeurs selectionnables dans le paginateur.</p><p>Regles runtime :</p><ul><li>si <code>md_pagesize</code> est superieur a la valeur maximale presente dans <code>md_page_size_choice</code>, le framework ajoute automatiquement <code>md_pagesize</code> a la liste d&#39;options ;</li><li>la liste est normalisee (nombres valides, deduplication, tri croissant).</li></ul><p>Exemple :</p><ul><li><code>md_pagesize = 200</code></li><li><code>md_page_size_choice = &quot;10,25,50,100&quot;</code></li><li>resultat runtime : <code>10,25,50,100,200</code></li></ul><h2>Activation forcee de la virtualisation sur une taille de page elevee</h2><p>Lorsque l&#39;utilisateur selectionne dans le paginateur une valeur <code>pageSize &gt;= 1000</code> :</p><ul><li>la virtualisation est forcee automatiquement meme si <code>md_props_bag.archetypes.list.virtualize</code> est absent ou desactive ;</li><li><code>virtualScrollItemSize</code> est force a la valeur par defaut <code>44</code>.</li></ul><p>Ce comportement protege le rendu de la table sur des pages tres volumineuses.</p><h2>Evenements et subscriptions (host)</h2><p><code>wuic-list-grid</code> expose des evenements runtime utiles pour intercepter le cycle de rendu et les callbacks p-table cote projet host.</p><p>Evenements disponibles :</p><ul><li><code>onAfterRender</code> : emis a la fin du binding des donnees de la grille (<code>rows</code>, <code>totalRecords</code>, <code>metaInfo</code>, <code>datasource</code>).</li><li><code>onBeforeRowRender</code> : emis avant le rendu logique de la ligne individuelle ; supporte l&#39;annulation via <code>event.cancelRender()</code>.</li><li><code>onAfterRowRender</code> : emis apres le rendu logique de la ligne individuelle.</li><li><code>onPaging</code> : emis sur les evenements de pagination (<code>p-table onPage</code>).</li><li><code>onSorting</code> : emis sur les evenements de tri (<code>p-table onSort</code>).</li><li><code>onFiltering</code> : emis sur les evenements de filtrage (<code>p-table onFilter</code>).</li><li><code>onPTableSelectionChange</code> : emis lors du changement de selection des lignes.</li><li><code>onPTableRowExpand</code> : emis lors de l&#39;expansion d&#39;une ligne.</li><li><code>onPTableRowCollapse</code> : emis lors de la reduction d&#39;une ligne.</li><li><code>onPTableColumnResize</code> : emis lors du redimensionnement d&#39;une colonne.</li><li><code>onPTableColumnReorder</code> : emis lors de la reorganisation des colonnes.</li></ul><h3>Exemple 1 : binding direct dans le template</h3><p>Snippet 5:</p><h3>Exemple 2 : subscribe via ViewChild</h3><p>Snippet 6:</p>",
12112
+ "html": "<h1>List Grid</h1><p>Composant principal pour les listes tabulaires avec filtres, tri, pagination cote serveur et actions sur les lignes.</p><h2>Cas d&#39;utilisation</h2><ul><li>CRUD tabulaires enterprise.</li><li>Rapports operationnels avec filtres multi-colonnes.</li><li>Jeux de donnees tres volumineux avec <code>cursorMode</code>.</li></ul><h2>Reference de capture d&#39;ecran (manuel utilisateur)</h2><ul><li><code>manual__grid__01.png</code> : mode <code>inline cell edit</code> dans list-grid.</li><li><code>manual__grid__02.png</code> : mode <code>inline edit</code> (au niveau ligne) dans list-grid.</li></ul><h2>Metadonnees inline editing</h2><p>Configuration dans le <code>md_props_bag</code> de la table metadata :</p><ul><li><code>md_inline_edit</code></li></ul><p> - active l&#39;edition inline de la ligne dans list-grid (cellules editables dans le contexte de la ligne, sans ouverture de popup).</p><p> - utile lorsque vous souhaitez conserver l&#39;UX tabulaire avec une edition rapide par enregistrement.</p><ul><li><code>md_inline_cell_edit</code></li></ul><p> - active l&#39;edition inline &quot;cellule par cellule&quot; (focus sur la cellule individuelle).</p><p> - note : dans les configurations legacy, il peut apparaitre sous le nom <code>md_inline_cell_editing</code> ; le comportement runtime est le meme.</p><p> - <strong>promotion runtime</strong> : lorsque <code>md_inline_cell_edit</code> est <code>true</code>, le composant force</p><p> a runtime egalement <code>md_inline_edit = true</code> independamment de la valeur en DB.</p><p> Les deux UX necessitent que la colonne d&#39;actions soit visible, elles sont donc mutuellement</p><p> non exclusives au niveau du rendu.</p><ul><li><code>md_batch_save</code></li></ul><p> - active la sauvegarde batch des modifications en attente (<code>Save changes</code> / <code>Cancel changes</code>).</p><p> - si utilise conjointement avec inline-cell, les modifications restent en attente jusqu&#39;a la sauvegarde explicite.</p><p> - <strong>prerequis</strong> : <code>md_batch_save</code> n&#39;a d&#39;effet <strong>que</strong> si <code>md_inline_cell_edit</code> est <code>true</code>.</p><p> Avec <code>md_inline_cell_edit:false</code>, le flag est ignore a runtime et les boutons</p><p> &quot;Sauvegarder les modifications / Annuler les modifications&quot; ne sont pas affiches.</p><h3>Combinaisons valides</h3><table><thead><tr><th><code>md_inline_edit</code></th><th><code>md_inline_cell_edit</code></th><th><code>md_batch_save</code></th><th>Resultat runtime</th></tr></thead><tbody><tr><td><code>true</code></td><td><code>false</code></td><td>quelconque</td><td>Edition inline au niveau ligne avec crayon. <code>md_batch_save</code> ignore.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>false</code></td><td>Autosave cellule par cellule au blur. <code>md_inline_edit</code> force a <code>true</code> a runtime.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>true</code></td><td>Cellule par cellule avec buffer en attente + barre d&#39;outils <code>Sauvegarder / Annuler les modifications</code>.</td></tr><tr><td><code>false</code></td><td><code>false</code></td><td>quelconque</td><td>Aucune edition inline. <code>md_batch_save</code> ignore.</td></tr></tbody></table><p>Exemple edition inline au niveau ligne :</p><p>Snippet 1:</p><p>Exemple cellule par cellule avec sauvegarde batch :</p><p>Snippet 2:</p><h2>Barre d&#39;outils export/import</h2><ul><li>Export XLS :</li></ul><p> - affiche une boite de dialogue de progression avec pourcentage en temps reel ;</p><p> - actions : <code>Continuer en arriere-plan</code>, <code>Annuler la tache</code>, <code>Interrompre et telecharger le partiel</code> ;</p><p> - en arriere-plan, cree une notification avec progression ; un clic rouvre la boite de dialogue.</p><ul><li>Import XLS/XLSX (si <code>md_importable = true</code>) :</li></ul><p> - boite de dialogue de progression apres la confirmation ;</p><p> - actions : <code>Continuer en arriere-plan</code>, <code>Annuler l&#39;import (rollback)</code>, <code>Arreter et commit partiel</code> ;</p><p> - a la fin de l&#39;import, cree une notification recapitulative qui redirige vers la route.</p><h2>Config metadata</h2><p>Parametres cles dans <code>md_props_bag</code> et metadonnees de colonnes.</p><p>Snippet 3:</p><h2>md_props_bag: toolbar</h2><p>Flags opt-in qui masquent des blocs de la barre d&#39;outils de la list-grid (<code>caption-bar</code>). Tous sous <code>md_props_bag.toolbar.*</code> (parse au runtime comme <code>tableMetadata.extraProps.toolbar</code>).</p><p>Snippet 4:</p><ul><li><code>hideManageState</code> (boolean, defaut <code>false</code>) : masque dans la <strong>caption-right</strong> le bouton &quot;Gestion etat&quot; (icone bookmark) + le <code>&lt;select&gt;</code> des etats enregistres. Utile pour les routes hardcoded / demo ou la fonctionnalite saved-state (persistance par <code>user_id</code> + route via <code>MetaService</code>) n&#39;a pas de sens — par ex. Pattern 3 pur OData sans route metadata enregistree.</li><li><code>hideBatchActions</code> (boolean, defaut <code>false</code>) : masque dans la <strong>caption-left</strong> les boutons &quot;Enregistrer les modifications&quot; (<code>pi-save</code>) + &quot;Annuler les modifications&quot; (<code>pi-times</code>) + l&#39;indicateur de comptage des changements (<code>grid-changes-indicator</code>, badge pencil + count). Generes par le framework quand <code>md_inline_cell_editing</code> + <code>md_batch_save</code> sont actifs. Concu pour les <strong>grids imbriquees dans un parametric-dialog</strong> : le save/cancel du parent persiste le master + les lignes en un seul coup via le batch save framework, et les boutons en double sur la grid imbriquee perturbent l&#39;UX.</li></ul><p>Exemple de lignes imbriquees dans un edit-form custom (patch runtime) :</p><p>Snippet 5:</p><p>Type TS dans <a href=\"../api/wuic-framework-lib.metadatitabella.html\">`metadati_tabella.ts`</a> (propriete <code>extraProps.toolbar</code>). Les flags ne desactivent PAS la logique <code>md_batch_save</code> sous-jacente (les changements restent traces) ; ils suppriment uniquement l&#39;UI de la barre d&#39;outils — le save effectif passe par le flux du parent.</p><h2>md_props_bag: archetypes.list</h2><p>Le composant lit <code>md_props_bag.archetypes.list</code>.</p><p>Snippet 6:</p><ul><li><code>proportionalColwidth</code> :</li><li>type : boolean (avec parser tolerant)</li><li>valeurs acceptees : <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>&quot;true&quot;</code>, <code>&quot;false&quot;</code>, <code>&quot;1&quot;</code>, <code>&quot;0&quot;</code></li><li>valeur par defaut runtime : <code>true</code> lorsqu&#39;absent ou vide</li><li>effet : distribue les largeurs de colonnes en pourcentage (au lieu de px) lorsqu&#39;il n&#39;y a pas de largeurs utilisateur persistees.</li></ul><ul><li><code>virtualize</code> :</li><li>type : <code>boolean</code> ou <code>object</code></li><li>valeurs acceptees :</li><li><code>true</code>/<code>false</code>, <code>1</code>/<code>0</code>, <code>&quot;true&quot;</code>/<code>&quot;false&quot;</code>, <code>&quot;1&quot;</code>/<code>&quot;0&quot;</code></li><li>object avec <code>enabled</code> (optionnel) et <code>itemSize</code> (optionnel)</li><li>valeur par defaut runtime : desactive (<code>false</code>) lorsqu&#39;absent</li><li><code>enabled</code> :</li><li>par defaut : <code>true</code> si le noeud <code>virtualize</code> est un objet sans <code>enabled</code>, sinon parser tolerant</li><li>effet : active <code>virtualScroll</code> sur <code>p-table</code></li><li><code>itemSize</code> :</li><li>par defaut : <code>44</code></li><li>effet : definit <code>virtualScrollItemSize</code> (hauteur de la ligne virtuelle en px)</li></ul><ul><li><code>advancedFilter</code> :</li><li>type : boolean (avec parser tolerant)</li><li>valeur par defaut runtime : <code>false</code></li><li>effet :</li><li>lorsque <code>true</code>, la grille masque les icones de filtre de colonne (<code>p-columnFilter</code>) et utilise la <code>wuic-filter-bar</code> au-dessus de la table (au niveau <code>data-repeater</code>/<code>bounded-repeater</code>) pour appliquer les filtres.</li></ul><p>Note operationnelle :</p><ul><li>le suggest <code>md_props_bag</code> dans le metadata editor expose egalement le noeud cochable <code>archetypes.list.advancedFilter</code>.</li></ul><h2>Pagination : md_pagesize et md_page_size_choice</h2><p>La grille utilise <code>md_pagesize</code> comme taille de page par defaut et <code>md_page_size_choice</code> comme liste de valeurs selectionnables dans le paginateur.</p><p>Regles runtime :</p><ul><li>si <code>md_pagesize</code> est superieur a la valeur maximale presente dans <code>md_page_size_choice</code>, le framework ajoute automatiquement <code>md_pagesize</code> a la liste d&#39;options ;</li><li>la liste est normalisee (nombres valides, deduplication, tri croissant).</li></ul><p>Exemple :</p><ul><li><code>md_pagesize = 200</code></li><li><code>md_page_size_choice = &quot;10,25,50,100&quot;</code></li><li>resultat runtime : <code>10,25,50,100,200</code></li></ul><h2>Activation forcee de la virtualisation sur une taille de page elevee</h2><p>Lorsque l&#39;utilisateur selectionne dans le paginateur une valeur <code>pageSize &gt;= 1000</code> :</p><ul><li>la virtualisation est forcee automatiquement meme si <code>md_props_bag.archetypes.list.virtualize</code> est absent ou desactive ;</li><li><code>virtualScrollItemSize</code> est force a la valeur par defaut <code>44</code>.</li></ul><p>Ce comportement protege le rendu de la table sur des pages tres volumineuses.</p><h2>Evenements et subscriptions (host)</h2><p><code>wuic-list-grid</code> expose des evenements runtime utiles pour intercepter le cycle de rendu et les callbacks p-table cote projet host.</p><p>Evenements disponibles :</p><ul><li><code>onAfterRender</code> : emis a la fin du binding des donnees de la grille (<code>rows</code>, <code>totalRecords</code>, <code>metaInfo</code>, <code>datasource</code>).</li><li><code>onBeforeRowRender</code> : emis avant le rendu logique de la ligne individuelle ; supporte l&#39;annulation via <code>event.cancelRender()</code>.</li><li><code>onAfterRowRender</code> : emis apres le rendu logique de la ligne individuelle.</li><li><code>onPaging</code> : emis sur les evenements de pagination (<code>p-table onPage</code>).</li><li><code>onSorting</code> : emis sur les evenements de tri (<code>p-table onSort</code>).</li><li><code>onFiltering</code> : emis sur les evenements de filtrage (<code>p-table onFilter</code>).</li><li><code>onPTableSelectionChange</code> : emis lors du changement de selection des lignes.</li><li><code>onPTableRowExpand</code> : emis lors de l&#39;expansion d&#39;une ligne.</li><li><code>onPTableRowCollapse</code> : emis lors de la reduction d&#39;une ligne.</li><li><code>onPTableColumnResize</code> : emis lors du redimensionnement d&#39;une colonne.</li><li><code>onPTableColumnReorder</code> : emis lors de la reorganisation des colonnes.</li></ul><h3>Exemple 1 : binding direct dans le template</h3><p>Snippet 7:</p><h3>Exemple 2 : subscribe via ViewChild</h3><p>Snippet 8:</p><h2><code>rowCustomSelect</code> — signature reelle (piege)</h2><p><code>&lt;wuic-list-grid&gt;</code> accepte une entree <code>[rowCustomSelect]</code> pour intercepter la selection d&#39;une ligne (ouverture d&#39;une dialog &quot;choisir un document&quot;, flux master-detail, etc.). La signature TypeScript declaree dans <a href=\"../../src/lib/component/list-grid/list-grid.component.ts#L140\">list-grid.component.ts:140</a> est :</p><p>Snippet 9:</p><p><strong>Mais au runtime la callback est invoquee avec les arguments inverses</strong> — le framework appelle <code>rowCustomSelect($event, rowData, dt)</code> (voir <a href=\"../../src/lib/component/dynamic-template/dynamic-template.component.ts#L447\">dynamic-template.component.ts:447</a> et <a href=\"../../src/lib/component/dynamic-template/dynamic-card-template.component.ts#L205\">dynamic-card-template.component.ts:205</a>).</p><p>Symptome en cas d&#39;ordre incorrect : <code>rowData?.id</code> est undefined → guard precoce dans votre handler → la callback retourne silencieusement et votre dialog/action ne demarre pas. Aucune erreur en console.</p><p>Forme correcte (alignee a l&#39;invocation runtime) :</p><p>Snippet 10:</p><p>Utilisation dans le template :</p><p>Snippet 11:</p><p>&gt; Note : les tests dans <code>designer.component.spec.ts</code> (par ex. ligne 512) invoquent la callback avec <code>({currentTarget: rowCell}, {id: 42}, null)</code>, confirmant l&#39;ordre <code>($event, rowData, dt)</code>. Si le framework finit par aligner la signature declaree, cette page sera mise a jour.</p>",
11789
12113
  "codeSamples": [
11790
12114
  {
11791
12115
  "id": "code_1",
@@ -11809,19 +12133,49 @@ const frameworkDocsContent = {
11809
12133
  "id": "code_4",
11810
12134
  "title": "Snippet 4",
11811
12135
  "language": "json",
11812
- "code": "{\n \"archetypes\": {\n \"list\": {\n \"proportionalColwidth\": true,\n \"advancedFilter\": false,\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n }\n }\n }\n}"
12136
+ "code": "{\n \"toolbar\": {\n \"hideManageState\": true,\n \"hideBatchActions\": true\n }\n}"
11813
12137
  },
11814
12138
  {
11815
12139
  "id": "code_5",
11816
12140
  "title": "Snippet 5",
11817
- "language": "html",
11818
- "code": "<wuic-list-grid\n [hardcodedDatasource]=\"citiesDs\"\n (onAfterRender)=\"handleAfterRender($event)\"\n (onBeforeRowRender)=\"handleBeforeRowRender($event)\"\n (onAfterRowRender)=\"handleAfterRowRender($event)\"\n (onPaging)=\"handlePaging($event)\"\n (onSorting)=\"handleSorting($event)\"\n (onFiltering)=\"handleFiltering($event)\"\n (onPTableSelectionChange)=\"handleSelection($event)\"\n (onPTableRowExpand)=\"handleRowExpand($event)\"\n (onPTableRowCollapse)=\"handleRowCollapse($event)\"\n (onPTableColumnResize)=\"handleColumnResize($event)\"\n (onPTableColumnReorder)=\"handleColumnReorder($event)\">\n</wuic-list-grid>"
12141
+ "language": "ts",
12142
+ "code": "this.righeDs.metaInfo.tableMetadata.extraProps = Object.assign(\n this.righeDs.metaInfo.tableMetadata.extraProps || {},\n { toolbar: { hideManageState: true, hideBatchActions: true } }\n) as any;"
11819
12143
  },
11820
12144
  {
11821
12145
  "id": "code_6",
11822
12146
  "title": "Snippet 6",
12147
+ "language": "json",
12148
+ "code": "{\n \"archetypes\": {\n \"list\": {\n \"proportionalColwidth\": true,\n \"advancedFilter\": false,\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n }\n }\n }\n}"
12149
+ },
12150
+ {
12151
+ "id": "code_7",
12152
+ "title": "Snippet 7",
12153
+ "language": "html",
12154
+ "code": "<wuic-list-grid\n [hardcodedDatasource]=\"citiesDs\"\n (onAfterRender)=\"handleAfterRender($event)\"\n (onBeforeRowRender)=\"handleBeforeRowRender($event)\"\n (onAfterRowRender)=\"handleAfterRowRender($event)\"\n (onPaging)=\"handlePaging($event)\"\n (onSorting)=\"handleSorting($event)\"\n (onFiltering)=\"handleFiltering($event)\"\n (onPTableSelectionChange)=\"handleSelection($event)\"\n (onPTableRowExpand)=\"handleRowExpand($event)\"\n (onPTableRowCollapse)=\"handleRowCollapse($event)\"\n (onPTableColumnResize)=\"handleColumnResize($event)\"\n (onPTableColumnReorder)=\"handleColumnReorder($event)\">\n</wuic-list-grid>"
12155
+ },
12156
+ {
12157
+ "id": "code_8",
12158
+ "title": "Snippet 8",
11823
12159
  "language": "ts",
11824
12160
  "code": "import { AfterViewInit, OnDestroy, ViewChild } from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport {\n ListGridAfterRenderEvent,\n ListGridAfterRowRenderEvent,\n ListGridBeforeRowRenderEvent,\n ListGridComponent\n} from 'wuic-framework-lib';\n\nexport class HostComponent implements AfterViewInit, OnDestroy {\n @ViewChild(ListGridComponent) grid?: ListGridComponent;\n private readonly subs = new Subscription();\n\n ngAfterViewInit(): void {\n if (!this.grid) return;\n\n this.subs.add(this.grid.onAfterRender.subscribe((e: ListGridAfterRenderEvent) => console.log(e)));\n this.subs.add(this.grid.onBeforeRowRender.subscribe((e: ListGridBeforeRowRenderEvent) => {\n // exemple d'annulation de ligne\n if (e.row?.deleted === true) e.cancelRender();\n }));\n this.subs.add(this.grid.onAfterRowRender.subscribe((e: ListGridAfterRowRenderEvent) => console.log(e)));\n this.subs.add(this.grid.onPaging.subscribe((e) => console.log('paging', e)));\n this.subs.add(this.grid.onSorting.subscribe((e) => console.log('sorting', e)));\n this.subs.add(this.grid.onFiltering.subscribe((e) => console.log('filtering', e)));\n this.subs.add(this.grid.onPTableSelectionChange.subscribe((e) => console.log('selection', e)));\n this.subs.add(this.grid.onPTableRowExpand.subscribe((e) => console.log('expand', e)));\n this.subs.add(this.grid.onPTableRowCollapse.subscribe((e) => console.log('collapse', e)));\n this.subs.add(this.grid.onPTableColumnResize.subscribe((e) => console.log('resize', e)));\n this.subs.add(this.grid.onPTableColumnReorder.subscribe((e) => console.log('reorder', e)));\n }\n\n ngOnDestroy(): void {\n this.subs.unsubscribe();\n }\n}"
12161
+ },
12162
+ {
12163
+ "id": "code_9",
12164
+ "title": "Snippet 9",
12165
+ "language": "ts",
12166
+ "code": "@Input() rowCustomSelect: (rowData: any, $event: any, dt: Table) => void;"
12167
+ },
12168
+ {
12169
+ "id": "code_10",
12170
+ "title": "Snippet 10",
12171
+ "language": "ts",
12172
+ "code": "onRowSelected = (_event: any, rowData: any, _dt: any): void => {\n // rowData est l'enregistrement fourni par list-grid\n this.handleSelection(rowData);\n};"
12173
+ },
12174
+ {
12175
+ "id": "code_11",
12176
+ "title": "Snippet 11",
12177
+ "language": "html",
12178
+ "code": "<wuic-data-source #ds [hardcodedRoute]=\"'preventivi'\" [autoload]=\"true\"></wuic-data-source>\n<wuic-list-grid [hardcodedDatasource]=\"ds\" [rowCustomSelect]=\"onRowSelected\"></wuic-list-grid>"
11825
12179
  }
11826
12180
  ]
11827
12181
  }
@@ -11993,7 +12347,7 @@ const frameworkDocsContent = {
11993
12347
  "id": "code_1",
11994
12348
  "title": "Snippet 1",
11995
12349
  "language": "json",
11996
- "code": "{\n \"archetypes\": {\n \"map\": {\n \"advancedFilter\": false,\n \"center\": {\n \"lat\": 39.8283,\n \"lng\": -98.5795\n },\n \"zoom\": 4,\n \"minZoom\": 3,\n \"maxZoom\": 14,\n \"useCurrentLocation\": false,\n \"useClusterer\": true,\n \"filterByBoundaries\": false,\n \"titleField\": \"CityName\",\n \"infoField\": \"StateProvinceID__dictionary_display_value\",\n \"customMarkerImageSrc\": \"\",\n \"customMarkerImageSrcField\": \"\",\n \"markerContentCallback\": \"const glyph = String(record?.CityName || 'C').trim().charAt(0).toUpperCase() || 'C'; const pop = Number(record?.LatestRecordedPopulation || 0); const bg = pop > 1000000 ? '#B91C1C' : (pop > 300000 ? '#EA580C' : '#2563EB'); const pin = new google.maps.marker.PinElement({ background: bg, borderColor: '#0F172A', glyphColor: '#FFFFFF', glyph: glyph, scale: 1.2 }); return pin.element;\",\n \"infoFunction\": \"\",\n \"itemTemplateString\": \"<div class=\\\"map-info\\\"> <div class=\\\"map-info-title\\\">{{ rowData.record?.CityName || rowData.title }}</div> <div>{{ rowData.record?.StateProvinceID__dictionary_display_value || rowData.record?.StateProvinceID || '' }}</div> <div>Popolazione: {{ rowData.record?.LatestRecordedPopulation | number:'1.0-0' }}</div> <div>{{ rowData.record?.Location || '' }}</div> <wuic-data-action-button-lazy [data]=\\\"rowData.record\\\" [metaInfo]=\\\"metaInfo\\\" [datasource]=\\\"datasource\\\" [simplified]=\\\"true\\\"></wuic-data-action-button-lazy> </div>\"\n }\n }\n}"
12350
+ "code": "{\n \"archetypes\": {\n \"map\": {\n \"advancedFilter\": false,\n \"center\": {\n \"lat\": 39.8283,\n \"lng\": -98.5795\n },\n \"zoom\": 4,\n \"minZoom\": 3,\n \"maxZoom\": 14,\n \"useCurrentLocation\": false,\n \"useClusterer\": true,\n \"filterByBoundaries\": false,\n \"titleField\": \"CityName\",\n \"infoField\": \"StateProvinceID__dictionary_display_value\",\n \"customMarkerImageSrc\": \"\",\n \"customMarkerImageSrcField\": \"\",\n \"markerContentCallback\": \"const glyph = String(record?.CityName || 'C').trim().charAt(0).toUpperCase() || 'C'; const pop = Number(record?.LatestRecordedPopulation || 0); const bg = pop > 1000000 ? '#B91C1C' : (pop > 300000 ? '#EA580C' : '#2563EB'); const pin = new google.maps.marker.PinElement({ background: bg, borderColor: '#0F172A', glyphColor: '#FFFFFF', glyphText: glyph, scale: 1.2 }); return pin;\",\n \"infoFunction\": \"\",\n \"itemTemplateString\": \"<div class=\\\"map-info\\\"> <div class=\\\"map-info-title\\\">{{ rowData.record?.CityName || rowData.title }}</div> <div>{{ rowData.record?.StateProvinceID__dictionary_display_value || rowData.record?.StateProvinceID || '' }}</div> <div>Popolazione: {{ rowData.record?.LatestRecordedPopulation | number:'1.0-0' }}</div> <div>{{ rowData.record?.Location || '' }}</div> <wuic-data-action-button-lazy [data]=\\\"rowData.record\\\" [metaInfo]=\\\"metaInfo\\\" [datasource]=\\\"datasource\\\" [simplified]=\\\"true\\\"></wuic-data-action-button-lazy> </div>\"\n }\n }\n}"
11997
12351
  },
11998
12352
  {
11999
12353
  "id": "code_2",
@@ -12078,14 +12432,47 @@ const frameworkDocsContent = {
12078
12432
  {
12079
12433
  "id": "sec_3",
12080
12434
  "title": "Reference au designer"
12435
+ },
12436
+ {
12437
+ "id": "sec_4",
12438
+ "title": "Parametres de rapport (variables SQL filtrees)"
12439
+ },
12440
+ {
12441
+ "id": "sec_5",
12442
+ "title": "Configuration backend requise"
12081
12443
  }
12082
12444
  ],
12083
12445
  "sections": [
12084
12446
  {
12085
12447
  "id": "overview",
12086
12448
  "title": "Overview",
12087
- "html": "<h1>Reporting</h1><p>Rapports operationnels sur la route <code>#/&lt;route&gt;/report-viewer</code>.</p><h2>Reference captures d&#39;ecran (manuel utilisateur)</h2><ul><li><code>manual__reporting__01.png</code> : ouverture du report viewer avec rendu du rapport et barre d&#39;outils operationnelle.</li><li><code>manual__reporting__02.png</code> : exemple runtime avec parametres/filtre appliques au rapport.</li></ul><h2>Report viewer</h2><p>Le <code>report-viewer</code> execute et affiche les rapports Stimulsoft associes a la route metadata courante.</p><p>Fonctionnalites principales :</p><ul><li>ouverture du rapport depuis list/grid avec le nom du rapport (<code>reportName</code>) et les parametres optionnels.</li><li>rendu des donnees a l&#39;execution avec la barre d&#39;outils du viewer (zoom, export, impression).</li><li>support des filtres/parametres propages depuis la page appelante.</li></ul><h2>Reference au designer</h2><p>Pour la creation ou la modification du rapport <code>.mrt</code>, voir la sous-section</p><p><a href=\"#/framework-docs/report-designer\">Report designer</a> dans <code>Designer &amp; Workflow</code>.</p>",
12088
- "codeSamples": []
12449
+ "html": "<h1>Reporting</h1><p>Rapports operationnels sur la route <code>#/&lt;route&gt;/report-viewer</code>.</p><h2>Reference captures d&#39;ecran (manuel utilisateur)</h2><ul><li><code>manual__reporting__01.png</code> : ouverture du report viewer avec rendu du rapport et barre d&#39;outils operationnelle.</li><li><code>manual__reporting__02.png</code> : exemple runtime avec parametres/filtre appliques au rapport.</li></ul><h2>Report viewer</h2><p>Le <code>report-viewer</code> execute et affiche les rapports Stimulsoft associes a la route metadata courante.</p><p>Fonctionnalites principales :</p><ul><li>ouverture du rapport depuis list/grid avec le nom du rapport (<code>reportName</code>) et les parametres optionnels.</li><li>rendu des donnees a l&#39;execution avec la barre d&#39;outils du viewer (zoom, export, impression).</li><li>support des filtres/parametres propages depuis la page appelante.</li></ul><h2>Reference au designer</h2><p>Pour la creation ou la modification du rapport <code>.mrt</code>, voir la sous-section</p><p><a href=\"#/framework-docs/report-designer\">Report designer</a> dans <code>Designer &amp; Workflow</code>.</p><h2>Parametres de rapport (variables SQL filtrees)</h2><p>Pour filtrer un rapport sur une entite specifique (par ex. impression d&#39;une seule facture), le pattern canonique est :</p><p>1. <strong>Dans le `.mrt`</strong> : declarer une <strong>Variable</strong> dans le Dictionary avec <code>AllowUseAsSqlParameter = True</code> (Stimulsoft 2026.1+). Utiliser la variable comme <code>@nomParam</code> dans les SqlCommand :</p><p>Snippet 1:</p><p> &gt; <strong>Piege</strong> : NE PAS utiliser le bloc legacy <code>&lt;StiDataParameter&gt;</code> dans le DataSource — il n&#39;est plus supporte par la version actuelle de Stimulsoft et produit <code>ArgumentException: Int32 is not a valid value for Int32. (Parameter &#39;value&#39;)</code> pendant <code>report.Load()</code>.</p><p>2. <strong>Dans l&#39;URL du viewer</strong> : passer la valeur via le query param <code>parameters</code> (PAS comme query param direct) :</p><p>Snippet 2:</p><p> Syntaxe <code>parameters</code> URL-encoded : <code>nom||eq||valeur</code> (separes par <code>@</code> pour plusieurs parametres).</p><p>3. <strong>Depuis une row action ou un lien UI</strong> : utiliser le callback canonique pour construire l&#39;URL :</p><p>Snippet 3:</p><h2>Configuration backend requise</h2><p>Dans le fichier <code>appsettings.json</code> de l&#39;application, le parametre <code>AppSettings.reportQueryTimeout</code> doit exister (par ex. <code>&quot;120&quot;</code>). Sans lui, <code>ReportViewerController.prepareDataSql</code> leve <code>int.Parse(null)</code> → 500. Exemple :</p><p>Snippet 4:</p>",
12450
+ "codeSamples": [
12451
+ {
12452
+ "id": "code_1",
12453
+ "title": "Snippet 1",
12454
+ "language": "sql",
12455
+ "code": " SELECT ... FROM fatture_inviate WHERE id = @fattura_id\n SELECT ... FROM fatture_inviate_righe WHERE fattura_id = @fattura_id"
12456
+ },
12457
+ {
12458
+ "id": "code_2",
12459
+ "title": "Snippet 2",
12460
+ "language": "text",
12461
+ "code": " ✅ #/<route>/report-viewer?reportName=Report.mrt&parameters=fattura_id%7C%7Ceq%7C%7C42\n ❌ #/<route>/report-viewer?reportName=Report.mrt&fattura_id=42 <-- ignore"
12462
+ },
12463
+ {
12464
+ "id": "code_3",
12465
+ "title": "Snippet 3",
12466
+ "language": "js",
12467
+ "code": " const recordId = (record && record.id && record.id.value) ?? (record && record.id);\n const params = 'fattura_id||eq||' + recordId;\n window.location.hash = '#/<route>/report-viewer?reportName=Report.mrt&parameters='\n + encodeURIComponent(params);"
12468
+ },
12469
+ {
12470
+ "id": "code_4",
12471
+ "title": "Snippet 4",
12472
+ "language": "json",
12473
+ "code": "\"AppSettings\": {\n \"reportQueryTimeout\": \"120\"\n}"
12474
+ }
12475
+ ]
12089
12476
  }
12090
12477
  ],
12091
12478
  "images": [
@@ -12818,28 +13205,54 @@ const frameworkDocsContent = {
12818
13205
  },
12819
13206
  {
12820
13207
  "id": "sec_5",
12821
- "title": "Flux assiste : Clone Lookup + Filtre par defaut + Hierarchie Lookup"
13208
+ "title": "Combo slim — champs supplementaires via `mc_props_bag.slimCombo`"
12822
13209
  },
12823
13210
  {
12824
13211
  "id": "sec_6",
12825
- "title": "Effets Client/Serveur"
13212
+ "title": "Flux assiste : Clone Lookup + Filtre par defaut + Hierarchie Lookup"
12826
13213
  },
12827
13214
  {
12828
13215
  "id": "sec_7",
13216
+ "title": "Effets Client/Serveur"
13217
+ },
13218
+ {
13219
+ "id": "sec_8",
12829
13220
  "title": "Notes operationnelles"
13221
+ },
13222
+ {
13223
+ "id": "sec_9",
13224
+ "title": "Auto-fetch sur changement programmatique de FK"
12830
13225
  }
12831
13226
  ],
12832
13227
  "sections": [
12833
13228
  {
12834
13229
  "id": "overview",
12835
13230
  "title": "Overview",
12836
- "html": "<h1>Widget Lookup</h1><p>Widget colonne pour les relations (<code>lookupByID</code>, <code>multiple_check</code>) avec onglet dedie <strong>Lookup</strong> dans le metadata-editor colonnes.</p><h2>Exemples</h2><ul><li><a class=\"wuic-demo-link\" href=\"#/examples/3c-odata-cities-grid\" target=\"_blank\" rel=\"noopener noreferrer\">Pattern 3c — lookup pur OData (State/Province depuis `/odata/Stateprovinces`)</a> — demontre <code>mc_props_bag.lookup.endpoint</code> avec provider <code>odata</code> et rendu inline via <code>$expand</code>.</li></ul><h2>Quand l&#39;utiliser</h2><ul><li>selection d&#39;enregistrements correles depuis une table metadata externe.</li><li>scenarios avec recherche cote serveur et pagination lookup.</li><li>support de la selection simple (<code>lookupByID</code>) et de la selection multiple (<code>multiple_check</code>).</li></ul><h2>Proprietes metadata colonne (onglet Lookup)</h2><ul><li><code>mc_ui_lookup_entity_name</code> : route metadata source du lookup.</li><li><code>mc_ui_lookup_dataValueField</code> : champ ID retourne.</li><li><code>mc_ui_lookup_dataTextField</code> : champ description affiche.</li><li><code>mc_ui_lookup_filter</code> : filtre de base du lookup.</li><li><code>mc_serverside_operations</code> : recherche/pagination cote serveur.</li><li><code>mc_ui_pagesize</code> : taille de page des resultats lookup.</li><li><code>mc_ui_lookup_edit_allow</code> : autorise l&#39;edition d&#39;enregistrement lookup depuis le popup.</li><li><code>mc_ui_lookup_insert_allow</code> : autorise l&#39;insertion d&#39;enregistrement lookup depuis le popup.</li><li><code>mc_ui_lookup_search_grid</code> : active la grille de recherche dans le popup.</li><li><code>mc_logic_allow_navigation</code> : navigation vers la route liee.</li></ul><h2>mc_props_bag utile</h2><p>Snippet 1:</p><ul><li><code>lookup.filter</code> : contrainte additionnelle cote client/serveur dans la requete lookup.</li><li><code>lookup.virtualize</code> : active le defilement virtuel sur <code>p-autocomplete</code> du widget lookup.</li><li><code>lookup.virtualize.enabled</code> : parseur tolerant (<code>true/false</code>, <code>1/0</code>, chaines equivalentes) ; defaut <code>false</code> lorsqu&#39;absent.</li><li><code>lookup.virtualize.itemSize</code> : hauteur de ligne virtuelle en px ; defaut <code>44</code>.</li><li><code>lookup.endpoint</code> : override du provider de donnees du dropdown. Lorsqu&#39;il est defini, la datasource imbriquee du lookup-editor ne passe plus par le combo endpoint metadata (<code>MetaService.getFlatRecordComboData</code>) mais directement par le provider declare dans <code>type</code> avec l&#39;URL dans <code>uri</code>.</li><li><code>lookup.endpoint.type</code> : <code>&#39;odata&#39;</code> (le dispatcher route vers <code>DataProviderOdataService.selectCombo</code> qui emet un GET avec <code>$top</code>, <code>$count=true</code>, <code>$select=valueField,textField</code> et <code>$filter=contains(textField,&#39;&lt;query&gt;&#39;)</code> pour la recherche server-side). Les autres valeurs suivent la meme logique que le table-level <code>extraProps.endpoint.type</code>.</li><li><code>lookup.endpoint.uri</code> : URL de base de l&#39;entity set (ex. <code>/odata/&lt;EntitySet&gt;</code>), avec ou sans origine explicite. Les query params existants sont ignores — le provider reconstruit les params a chaque fetch.</li><li><code>form.columns</code> : largeur du champ dans le formulaire d&#39;edition.</li><li><code>style.editCss</code> : style personnalise de l&#39;editeur.</li></ul><h2>Flux assiste : Clone Lookup + Filtre par defaut + Hierarchie Lookup</h2><ul><li>Flux operationnel inline : <code>suggestLookup2 -&gt; suggestLookupDefaultFilter -&gt; getLookupListByRoute -&gt; getSeletClauseByLookupHierarchy</code>.</li><li>Snippet inline champs principaux : <code>mc_ui_lookup_entity_name</code>, <code>mc_ui_lookup_dataValueField</code>, <code>mc_ui_lookup_dataTextField</code>, <code>mc_ui_lookup_filter</code>.</li><li>Snippet inline filtre rapide : <code>lookup.filter: {&quot;logic&quot;:&quot;AND&quot;,&quot;filters&quot;:[{&quot;field&quot;:&quot;is_active&quot;,&quot;operatore&quot;:&quot;eq&quot;,&quot;value&quot;:true}]}</code>.</li><li>Effet client : configuration du lookup acceleree dans le metadata-editor et filtre coherent deja pret dans le widget.</li><li>Effet serveur : la requete lookup et la clause select restent alignees avec les endpoints <code>MetaService.*</code> sans mapping manuel.</li></ul><p>Checklist operationnelle rapide :</p><p>1. cloner la configuration de base avec <code>suggestLookup2</code> ;</p><p>2. generer le filtre par defaut avec <code>suggestLookupDefaultFilter</code> ;</p><p>3. affiner la hierarchie/clause select depuis l&#39;arbre lookup ;</p><p>4. valider en runtime que la recherche et le mapping value/text sont coherents.</p><h2>Effets Client/Serveur</h2><ul><li>Client : rendu du widget lookup, recherche, binding libelle/valeur.</li><li>Client : avec <code>lookup.virtualize</code> actif, l&#39;autocompletion rend uniquement le sous-ensemble visuel des options.</li><li>Serveur : requete lookup paginee/filtree via <code>MetaService.getFlatRecordData</code>.</li></ul><h2>Notes operationnelles</h2><ul><li>en <code>multiple_check</code>, la partie lookup reste la meme, seul le rendu multi-valeurs change.</li><li>si <code>mc_ui_lookup_entity_name</code> est absent, le widget ne peut pas resoudre la source.</li></ul>",
13231
+ "html": "<h1>Widget Lookup</h1><p>Widget colonne pour les relations (<code>lookupByID</code>, <code>multiple_check</code>) avec onglet dedie <strong>Lookup</strong> dans le metadata-editor colonnes.</p><h2>Exemples</h2><ul><li><a class=\"wuic-demo-link\" href=\"#/examples/3c-odata-cities-grid\" target=\"_blank\" rel=\"noopener noreferrer\">Pattern 3c — lookup pur OData (State/Province depuis `/odata/Stateprovinces`)</a> — demontre <code>mc_props_bag.lookup.endpoint</code> avec provider <code>odata</code> et rendu inline via <code>$expand</code>.</li></ul><h2>Quand l&#39;utiliser</h2><ul><li>selection d&#39;enregistrements correles depuis une table metadata externe.</li><li>scenarios avec recherche cote serveur et pagination lookup.</li><li>support de la selection simple (<code>lookupByID</code>) et de la selection multiple (<code>multiple_check</code>).</li></ul><h2>Proprietes metadata colonne (onglet Lookup)</h2><ul><li><code>mc_ui_lookup_entity_name</code> : route metadata source du lookup.</li><li><code>mc_ui_lookup_dataValueField</code> : champ ID retourne.</li><li><code>mc_ui_lookup_dataTextField</code> : champ description affiche.</li><li><code>mc_ui_lookup_filter</code> : filtre de base du lookup.</li><li><code>mc_serverside_operations</code> : recherche/pagination cote serveur.</li><li><code>mc_ui_pagesize</code> : taille de page des resultats lookup.</li><li><code>mc_ui_lookup_edit_allow</code> : autorise l&#39;edition d&#39;enregistrement lookup depuis le popup.</li><li><code>mc_ui_lookup_insert_allow</code> : autorise l&#39;insertion d&#39;enregistrement lookup depuis le popup.</li><li><code>mc_ui_lookup_search_grid</code> : active la grille de recherche dans le popup.</li><li><code>mc_logic_allow_navigation</code> : navigation vers la route liee.</li></ul><h2>mc_props_bag utile</h2><p>Snippet 1:</p><ul><li><code>lookup.filter</code> : contrainte additionnelle cote client/serveur dans la requete lookup.</li><li><code>lookup.virtualize</code> : active le defilement virtuel sur <code>p-autocomplete</code> du widget lookup.</li><li><code>lookup.virtualize.enabled</code> : parseur tolerant (<code>true/false</code>, <code>1/0</code>, chaines equivalentes) ; defaut <code>false</code> lorsqu&#39;absent.</li><li><code>lookup.virtualize.itemSize</code> : hauteur de ligne virtuelle en px ; defaut <code>44</code>.</li><li><code>lookup.endpoint</code> : override du provider de donnees du dropdown. Lorsqu&#39;il est defini, la datasource imbriquee du lookup-editor ne passe plus par le combo endpoint metadata (<code>MetaService.getFlatRecordComboData</code>) mais directement par le provider declare dans <code>type</code> avec l&#39;URL dans <code>uri</code>.</li><li><code>lookup.endpoint.type</code> : <code>&#39;odata&#39;</code> (le dispatcher route vers <code>DataProviderOdataService.selectCombo</code> qui emet un GET avec <code>$top</code>, <code>$count=true</code>, <code>$select=valueField,textField</code> et <code>$filter=contains(textField,&#39;&lt;query&gt;&#39;)</code> pour la recherche server-side). Les autres valeurs suivent la meme logique que le table-level <code>extraProps.endpoint.type</code>.</li><li><code>lookup.endpoint.uri</code> : URL de base de l&#39;entity set (ex. <code>/odata/&lt;EntitySet&gt;</code>), avec ou sans origine explicite. Les query params existants sont ignores — le provider reconstruit les params a chaque fetch.</li><li><code>form.columns</code> : largeur du champ dans le formulaire d&#39;edition.</li><li><code>style.editCss</code> : style personnalise de l&#39;editeur.</li></ul><h2>Combo slim — champs supplementaires via <code>mc_props_bag.slimCombo</code></h2><p>Le dropdown lookup emet par defaut un SELECT minimal sur la table liee : PK + <code>mc_ui_lookup_dataValueField</code> + <code>mc_ui_lookup_dataTextField</code>. Cela reduit le payload du combo. Quand le client a besoin d&#39;autres colonnes de la table liee — typiquement pour pre-remplir des champs dependants via <code>mc_selection_changed_custom_function__fn</code> — utiliser <code>mc_props_bag.slimCombo</code> comme <strong>tableau de noms de colonnes supplementaires</strong> a inclure dans le SELECT du combo :</p><p>Snippet 2:</p><ul><li>Tableau : les noms sont ajoutes au <code>columnRestrictionList</code> de la reponse combo (logique dans <code>DataProviderMetaService.buildSlimComboRestriction</code>).</li><li><code>slimCombo: false</code> : desactive la restriction → le combo retourne <strong>toutes</strong> les colonnes. Utile en debug, payload plus gros.</li><li>Si la colonne a <code>mc_ui_lookup_combo_text_edit_computed_dataTextField</code> valorise (formule d&#39;affichage calculee), <code>slimCombo</code> est ignore et le combo retourne toutes les colonnes (la formule peut referencer des champs arbitraires).</li></ul><p>Les champs supplementaires arrivent dans <code>record[&#39;&lt;col&gt;__lookup_obj&#39;].value</code> (record complet de la table liee, deja avec les champs declares dans <code>slimCombo</code>) et sont accessibles depuis le callback <code>mc_selection_changed_custom_function__fn</code> :</p><p>Snippet 3:</p><p>Compromis : payload combo legerement plus gros mais aucun roundtrip HTTP supplementaire (<code>getFlatRecordData</code> pour recuperer le record complet par id) a chaque selection.</p><h2>Flux assiste : Clone Lookup + Filtre par defaut + Hierarchie Lookup</h2><ul><li>Flux operationnel inline : <code>suggestLookup2 -&gt; suggestLookupDefaultFilter -&gt; getLookupListByRoute -&gt; getSeletClauseByLookupHierarchy</code>.</li><li>Snippet inline champs principaux : <code>mc_ui_lookup_entity_name</code>, <code>mc_ui_lookup_dataValueField</code>, <code>mc_ui_lookup_dataTextField</code>, <code>mc_ui_lookup_filter</code>.</li><li>Snippet inline filtre rapide : <code>lookup.filter: {&quot;logic&quot;:&quot;AND&quot;,&quot;filters&quot;:[{&quot;field&quot;:&quot;is_active&quot;,&quot;operatore&quot;:&quot;eq&quot;,&quot;value&quot;:true}]}</code>.</li><li>Effet client : configuration du lookup acceleree dans le metadata-editor et filtre coherent deja pret dans le widget.</li><li>Effet serveur : la requete lookup et la clause select restent alignees avec les endpoints <code>MetaService.*</code> sans mapping manuel.</li></ul><p>Checklist operationnelle rapide :</p><p>1. cloner la configuration de base avec <code>suggestLookup2</code> ;</p><p>2. generer le filtre par defaut avec <code>suggestLookupDefaultFilter</code> ;</p><p>3. affiner la hierarchie/clause select depuis l&#39;arbre lookup ;</p><p>4. valider en runtime que la recherche et le mapping value/text sont coherents.</p><h2>Effets Client/Serveur</h2><ul><li>Client : rendu du widget lookup, recherche, binding libelle/valeur.</li><li>Client : avec <code>lookup.virtualize</code> actif, l&#39;autocompletion rend uniquement le sous-ensemble visuel des options.</li><li>Serveur : requete lookup paginee/filtree via <code>MetaService.getFlatRecordData</code>.</li></ul><h2>Notes operationnelles</h2><ul><li>en <code>multiple_check</code>, la partie lookup reste la meme, seul le rendu multi-valeurs change.</li><li>si <code>mc_ui_lookup_entity_name</code> est absent, le widget ne peut pas resoudre la source.</li></ul><h2>Auto-fetch sur changement programmatique de FK</h2><p>Le <code>wuic-lookup-editor</code> souscrit automatiquement au <code>BehaviorSubject</code> de <code>record[mc_nome_colonna]</code> et gere les changements de FK programmatiques (par ex. import depuis un document source, callback custom qui fait <code>record.cliente_id.next(99)</code>) :</p><ul><li>Si la nouvelle valeur est <strong>deja presente dans `items`</strong> (la page chargee du lookup) : seul <code>__lookup_obj</code> est mis a jour pour la coherence, aucun HTTP.</li><li>Si la nouvelle valeur <strong>n&#39;est pas dans items</strong> (par ex. valeur hors page courante du combo, ou pas encore fetchee) : un <strong>fetch second-stage</strong> filtre <code>WHERE &lt;valueField&gt; eqor &lt;newValue&gt;</code> est declenche pour recuperer l&#39;enregistrement lie et remplir automatiquement le dropdown.</li></ul><p>Skip cases (no-op) :</p><ul><li>Lookup multi-valeur (<code>isFilter</code> avec <code>eqor</code>, <code>multiple_check</code>)</li><li>Premier emit du BS (deja couvert par le flux d&#39;init)</li><li>Valeur null/undefined (utilisateur a clear)</li><li><code>hasActiveLookupQuery=true</code> (l&#39;utilisateur tape une requete manuelle)</li></ul><p><strong>Implication pour les custom forms</strong> : il suffit de setter le FK — plus besoin de workaround manuel pour remplir <code>__lookup_obj</code> ou l&#39;alias jointure. Le dropdown se met a jour tout seul.</p><p>Snippet 4:</p><p>&gt; <strong>Note</strong> : l&#39;auto-fetch ne se declenche que lorsque le <code>&lt;wuic-lookup-editor&gt;</code> est <strong>monte</strong> (cellule en mode edit). Pour l&#39;affichage en <strong>liste</strong> (cellule read-only), <code>formatGridViewValue</code> lit l&#39;alias jointure <code>&lt;route&gt;___&lt;textField&gt;__&lt;col&gt;</code> directement depuis le record — celui-ci doit toujours etre rempli manuellement s&#39;il ne vient pas du backend.</p>",
12837
13232
  "codeSamples": [
12838
13233
  {
12839
13234
  "id": "code_1",
12840
13235
  "title": "Snippet 1",
12841
13236
  "language": "json",
12842
13237
  "code": "{\n \"lookup\": {\n \"filter\": {\n \"logic\": \"AND\",\n \"filters\": [\n { \"field\": \"is_active\", \"operatore\": \"eq\", \"value\": true }\n ]\n },\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n },\n \"endpoint\": {\n \"type\": \"odata\",\n \"uri\": \"http://localhost:5000/odata/StateProvinces\"\n }\n },\n \"form\": {\n \"columns\": 1,\n \"disabled\": false\n },\n \"style\": {\n \"editCss\": \"max-width: 420px;\"\n }\n}"
13238
+ },
13239
+ {
13240
+ "id": "code_2",
13241
+ "title": "Snippet 2",
13242
+ "language": "json",
13243
+ "code": "{\n \"slimCombo\": [\"unita_misura_id\",\"codice_iva_id\",\"prezzo_vendita\",\"sconto_default\"]\n}"
13244
+ },
13245
+ {
13246
+ "id": "code_3",
13247
+ "title": "Snippet 3",
13248
+ "language": "ts",
13249
+ "code": "prodCol.mc_selection_changed_custom_function__fn = (record, _f, _m, newValue) => {\n const prod = record['prodotto_id__lookup_obj']?.value;\n if (!prod) return;\n record['descrizione']?.next(prod.descrizione);\n record['prezzo_unitario']?.next(prod.prezzo_vendita);\n record['unita_misura_id']?.next(prod.unita_misura_id);\n record['codice_iva_id']?.next(prod.codice_iva_id);\n};"
13250
+ },
13251
+ {
13252
+ "id": "code_4",
13253
+ "title": "Snippet 4",
13254
+ "language": "ts",
13255
+ "code": "// AVANT (workaround manuel requis)\nrecord.cliente_id.next(99);\nrecord.cliente_id__lookup_obj.next({ id: 99, ragione_sociale: 'Acme S.r.l.' });\nrecord['clienti___ragione_sociale__cliente_id'].next('Acme S.r.l.');\n\n// MAINTENANT (auto-fetch du lookup-editor)\nrecord.cliente_id.next(99);\n// → lookup-editor monte ? Subscribe detecte le change → fetch filtre → __lookup_obj rempli → dropdown affiche \"Acme S.r.l.\""
12843
13256
  }
12844
13257
  ]
12845
13258
  }
@@ -13102,10 +13515,14 @@ const frameworkDocsContent = {
13102
13515
  },
13103
13516
  {
13104
13517
  "id": "sec_4",
13105
- "title": "Flux operationnel conseille"
13518
+ "title": "Menu contextuel des elements"
13106
13519
  },
13107
13520
  {
13108
13521
  "id": "sec_5",
13522
+ "title": "Flux operationnel conseille"
13523
+ },
13524
+ {
13525
+ "id": "sec_6",
13109
13526
  "title": "Notes operationnelles"
13110
13527
  }
13111
13528
  ],
@@ -13113,7 +13530,7 @@ const frameworkDocsContent = {
13113
13530
  {
13114
13531
  "id": "overview",
13115
13532
  "title": "Overview",
13116
- "html": "<h1>Designer</h1><p>Editeur visuel pour les dashboards pilotes par metadata sur la route <code>#/cities/dashboard</code>.</p><h2>Scope</h2><ul><li>Composition de board par glisser-deposer de noeuds et blocs UI.</li><li>Mise en page et styles pilotes par <code>boardcontent</code> et metadata table/colonne.</li><li>Edition via le panneau de proprietes sans codage en dur dans les composants runtime.</li></ul><h2>Palette Designer</h2><ul><li><code>Datasource</code> : source de donnees metadata (<code>route</code>, filtres, tri, pagination).</li><li><code>DataRepeater</code> : visualisation liste/carte liee a une datasource.</li><li><code>Dynamic Template</code> : rendu de template dynamique avec inputs pilotes par metadata.</li><li><code>Action/Button</code> : actions utilisateur liees aux enregistrements et callbacks.</li><li><code>Container/Layout</code> : blocs structurels pour colonnes, lignes et wrappers.</li></ul><h2>Fonctionnalites principales</h2><ul><li>Glisser-deposer de noeuds sur le canvas avec persistance dans le <code>boardcontent</code>.</li><li>Panneau de proprietes contextuel avec des inputs coherents aux composants selectionnes.</li><li>Liaison entre noeuds (<code>datasource -&gt; repeater/template</code>) avec validation minimale.</li><li>Apercu runtime integre et realignement de la mise en page.</li><li>Gestion des modifications en attente avec guard avant la sortie.</li></ul><h2>Flux operationnel conseille</h2><ul><li>Ouvrir la route dashboard cible.</li><li>Inserer/relier les noeuds depuis la palette.</li><li>Configurer les <code>inputs</code> essentiels (ex. <code>route</code>, <code>action</code>, <code>datasource</code>).</li><li>Sauvegarder et valider le resultat en runtime.</li></ul><h2>Notes operationnelles</h2><ul><li>Pour les correctifs specifiques a un dashboard, privilegier la patch du <code>boardcontent</code> plutot que des modifications globales.</li><li>Eviter les proprietes dupliquees entre <code>inputs</code> et le style inline incoherent.</li><li>Toujours verifier que les datasources ont <code>inputs.route</code> renseignee.</li></ul>",
13533
+ "html": "<h1>Designer</h1><p>Editeur visuel pour les dashboards pilotes par metadata sur la route <code>#/cities/dashboard</code>.</p><h2>Scope</h2><ul><li>Composition de board par glisser-deposer de noeuds et blocs UI.</li><li>Mise en page et styles pilotes par <code>boardcontent</code> et metadata table/colonne.</li><li>Edition via le panneau de proprietes sans codage en dur dans les composants runtime.</li></ul><h2>Palette Designer</h2><ul><li><code>Datasource</code> : source de donnees metadata (<code>route</code>, filtres, tri, pagination).</li><li><code>DataRepeater</code> : visualisation liste/carte liee a une datasource.</li><li><code>Dynamic Template</code> : rendu de template dynamique avec inputs pilotes par metadata.</li><li><code>Action/Button</code> : actions utilisateur liees aux enregistrements et callbacks.</li><li><code>Container/Layout</code> : blocs structurels pour colonnes, lignes et wrappers.</li></ul><h2>Fonctionnalites principales</h2><ul><li>Glisser-deposer de noeuds sur le canvas avec persistance dans le <code>boardcontent</code>.</li><li>Panneau de proprietes contextuel avec des inputs coherents aux composants selectionnes.</li><li>Liaison entre noeuds (<code>datasource -&gt; repeater/template</code>) avec validation minimale.</li><li>Apercu runtime integre et realignement de la mise en page.</li><li>Gestion des modifications en attente avec guard avant la sortie.</li></ul><h2>Menu contextuel des elements</h2><p>Le clic droit sur un element du canvas, sur un element du fil d&#39;Ariane en bas ou sur un noeud de l&#39;arbre de hierarchie affiche un menu contextuel avec trois entrees :</p><ul><li><strong>Modifier les proprietes</strong> (pi-cog) - selectionne l&#39;element comme &quot;current tool&quot; et affiche son panneau de proprietes a droite ; equivalent au clic dans l&#39;arbre. Utile pour editer les proprietes d&#39;elements imbriques sans devoir les chercher dans la hierarchie.</li><li><strong>Renommer</strong> (uniquement fil d&#39;Ariane) - ouvre une boite de dialogue pour renommer le <code>displayName</code>.</li><li><strong>Supprimer</strong> - retire l&#39;element du board.</li></ul><h2>Flux operationnel conseille</h2><ul><li>Ouvrir la route dashboard cible.</li><li>Inserer/relier les noeuds depuis la palette.</li><li>Configurer les <code>inputs</code> essentiels (ex. <code>route</code>, <code>action</code>, <code>datasource</code>).</li><li>Sauvegarder et valider le resultat en runtime.</li></ul><h2>Notes operationnelles</h2><ul><li>Pour les correctifs specifiques a un dashboard, privilegier la patch du <code>boardcontent</code> plutot que des modifications globales.</li><li>Eviter les proprietes dupliquees entre <code>inputs</code> et le style inline incoherent.</li><li>Toujours verifier que les datasources ont <code>inputs.route</code> renseignee.</li></ul>",
13117
13534
  "codeSamples": []
13118
13535
  }
13119
13536
  ],
@@ -13177,14 +13594,18 @@ const frameworkDocsContent = {
13177
13594
  },
13178
13595
  {
13179
13596
  "id": "sec_3",
13180
- "title": "Flux operationnel conseille"
13597
+ "title": "Onglet View Builder — Agregations et GROUP BY (Tier 1, 2026-05-08)"
13181
13598
  },
13182
13599
  {
13183
13600
  "id": "sec_4",
13184
- "title": "Boutons d'action"
13601
+ "title": "Flux operationnel conseille"
13185
13602
  },
13186
13603
  {
13187
13604
  "id": "sec_5",
13605
+ "title": "Boutons d'action"
13606
+ },
13607
+ {
13608
+ "id": "sec_6",
13188
13609
  "title": "Notes d'utilisation"
13189
13610
  }
13190
13611
  ],
@@ -13192,7 +13613,7 @@ const frameworkDocsContent = {
13192
13613
  {
13193
13614
  "id": "overview",
13194
13615
  "title": "Overview",
13195
- "html": "<h1>Constructeur Vue/Pivot</h1><p>Le Pivot Builder permet de construire des vues analytiques dynamiques a partir des routes metadata, sans ecrire de requetes manuelles cote interface.</p><h2>Reference captures d&#39;ecran</h2><ul><li><code>manual__pivot_builder__01.png</code> : configuration et resultat du builder a l&#39;execution.</li><li><code>manual__pivot_builder__02.png</code> : detail du dialogue de creation de table materialisee et options operationnelles.</li></ul><h2>Fonctionnalites principales</h2><ul><li>Selection des champs dans les zones <code>rows</code>, <code>columns</code> et <code>values</code>.</li><li>Agregations disponibles sur les valeurs (somme, comptage, moyenne, minimum, maximum).</li><li>Regroupement multidimensionnel avec tri par dimension.</li><li>Filtres interactifs pour reduire le jeu de donnees analyse.</li><li>Mise a jour de la vue pivot en temps reel apres modification de la mise en page ou de la metrique.</li><li>Exportation/reutilisation de la configuration pivot dans le contexte operationnel.</li></ul><h2>Flux operationnel conseille</h2><p>1. Ouvrez la route pivot builder.</p><p>2. Selectionnez la datasource/route metadata de depart.</p><p>3. Faites glisser les champs dans les zones <code>rows</code>, <code>columns</code>, <code>values</code>.</p><p>4. Configurez l&#39;agregation pour chaque metrique.</p><p>5. Appliquez les filtres et verifiez les totaux.</p><p>6. Sauvegardez ou reutilisez la configuration selon le flux applicatif.</p><h2>Boutons d&#39;action</h2><p><code>Crea vista</code></p><ul><li>Genere une vue SQL a partir de la configuration pivot courante.</li><li>Utile lorsque vous souhaitez reutiliser le resultat comme source de donnees stable dans d&#39;autres routes/rapports.</li><li>Maintient la logique centralisee dans la base de donnees, evitant de reconstruire la mise en page manuellement a chaque fois.</li></ul><p><code>Crea tabella materializzata</code></p><ul><li>Cree une table physique avec les donnees agregees de la configuration pivot.</li><li>Utile pour les gros jeux de donnees ou les dashboards necessitant des temps de lecture rapides.</li><li>Scaffolde automatiquement la route associee a la table physique creee et genere une planification pour le rafraichissement/alignement des donnees.</li><li>En appuyant sur Actualiser dans la grille de la route associee, une execution forcee de la planification est declenchee.</li></ul><h2>Notes d&#39;utilisation</h2><ul><li>Pour des analyses coherentes, utiliser des colonnes avec un type de donnees correct (numeriques pour les metriques, textuelles/dates pour les dimensions).</li><li>En presence de gros volumes, preferer les filtres et agregations ciblees avant de developper le detail.</li></ul>",
13616
+ "html": "<h1>Constructeur Vue/Pivot</h1><p>Le Pivot Builder permet de construire des vues analytiques dynamiques a partir des routes metadata, sans ecrire de requetes manuelles cote interface.</p><h2>Reference captures d&#39;ecran</h2><ul><li><code>manual__pivot_builder__01.png</code> : configuration et resultat du builder a l&#39;execution.</li><li><code>manual__pivot_builder__02.png</code> : detail du dialogue de creation de table materialisee et options operationnelles.</li></ul><h2>Fonctionnalites principales</h2><ul><li>Selection des champs dans les zones <code>rows</code>, <code>columns</code> et <code>values</code>.</li><li>Agregations disponibles sur les valeurs (somme, comptage, moyenne, minimum, maximum).</li><li>Regroupement multidimensionnel avec tri par dimension.</li><li>Filtres interactifs pour reduire le jeu de donnees analyse.</li><li>Mise a jour de la vue pivot en temps reel apres modification de la mise en page ou de la metrique.</li><li>Exportation/reutilisation de la configuration pivot dans le contexte operationnel.</li></ul><h2>Onglet View Builder — Agregations et GROUP BY (Tier 1, 2026-05-08)</h2><p>L&#39;onglet 1 &quot;View Builder&quot; permet desormais d&#39;emettre <code>GROUP BY</code> avec agregations <code>SUM/COUNT/AVG/MIN/MAX/COUNT(DISTINCT)</code> directement depuis la <code>ViewDefinition</code>, sans passer en mode manualSql.</p><p>Panneau sous le canvas :</p><ul><li><strong>Agregations</strong> : formulaire pour ajouter <code>fonction(colonne) AS alias</code>. La colonne source peut etre n&#39;importe quelle colonne du canvas ou <code>*</code> (pour <code>COUNT(*)</code>) ; l&#39;alias de sortie est genere automatiquement si vide.</li><li><strong>GROUP BY</strong> : case a cocher par colonne selectionnee sur les noeuds du canvas. Si aucune n&#39;est cochee explicitement, le backend deduit les dimensions des colonnes <code>selected=true</code> non agregees.</li><li><strong>CASE WHEN pour pivot</strong> : pour des motifs comme <code>SUM(CASE WHEN bucket=&#39;X&#39; THEN montant ELSE 0 END)</code>, utilisez une colonne formule (clic droit → Modifier la formule) comme source d&#39;agregation. La formule est emise comme argument de l&#39;agregat.</li></ul><p>Sortie backend (<a href=\"../../Services/MetaService.cs\">`MetaService.previewViewDefinition`</a>) :</p><ul><li><code>viewDefinition.aggregations[]</code> → emet <code>&lt;FN&gt;(&lt;col|formule&gt;) AS &lt;alias&gt;</code> comme colonnes SELECT supplementaires</li><li><code>viewDefinition.groupByColumns[]</code> → emet la clause <code>GROUP BY</code></li><li>Deduction automatique du GROUP BY si <code>aggregations</code> sont presentes sans <code>groupByColumns</code> explicite</li></ul><h3>Blocs UNION (Tier 2, 2026-05-08)</h3><p>Une extension ulterieure permet de concatener plusieurs SELECT avec <code>UNION</code> ou <code>UNION ALL</code> directement depuis une seule <code>ViewDefinition</code> :</p><ul><li><strong>Bloc 0</strong> = top-level (tables/joins/agregations sur le canvas) -&gt; definit le <strong>vocabulaire canonique</strong> des colonnes SELECT</li><li><strong>Bloc N (N&gt;=1)</strong> = chaque <code>unionBlocks[]</code> supplementaire est une sub-definition mono-table avec un <code>columnMapping</code> obligatoire : pour chaque alias canonique du bloc 0, le bloc N specifie <code>sourceQualified</code> (colonne du bloc) <strong>ou</strong> <code>constantValue</code> (litteral SQL comme <code>&#39;CLIENT&#39;</code>) <strong>ou</strong> <code>formula</code> (expression SQL pour cast cross-block)</li><li><strong>Bascule UNION ALL / UNION</strong> : par defaut <code>unionAll: true</code> (sans dedup, meilleure performance) ; <code>false</code> pour <code>UNION DISTINCT</code></li><li><strong>TOP/LIMIT</strong> : applique a toute l&#39;UNION, encapsule dans la sous-requete <code>SELECT TOP N * FROM (&lt;union&gt;) AS u</code></li></ul><p>UI sous le canvas : selecteur de route + bouton &quot;Ajouter un bloc UNION&quot; -&gt; chaque bloc apparait comme un accordeon avec en-tete (caption + route) + matrice column-mapping (par alias canonique : liste deroulante de colonne source du bloc, ou saisie de constante SQL).</p><p>Limites v0 : chaque bloc UNION est mono-table (pas de JOIN dans le bloc). Pour des JOIN cross-block (par ex. categorie &#39;LES DEUX&#39; de <code>vw_anagrafica_unificata</code> qui necessite <code>clients INNER JOIN fournisseurs ON tva</code>) le Tier 2.5 est necessaire.</p><h2>Flux operationnel conseille</h2><p>1. Ouvrez la route pivot builder.</p><p>2. Selectionnez la datasource/route metadata de depart.</p><p>3. Faites glisser les champs dans les zones <code>rows</code>, <code>columns</code>, <code>values</code>.</p><p>4. Configurez l&#39;agregation pour chaque metrique.</p><p>5. Appliquez les filtres et verifiez les totaux.</p><p>6. Sauvegardez ou reutilisez la configuration selon le flux applicatif.</p><h2>Boutons d&#39;action</h2><p><code>Crea vista</code></p><ul><li>Genere une vue SQL a partir de la configuration pivot courante.</li><li>Utile lorsque vous souhaitez reutiliser le resultat comme source de donnees stable dans d&#39;autres routes/rapports.</li><li>Maintient la logique centralisee dans la base de donnees, evitant de reconstruire la mise en page manuellement a chaque fois.</li></ul><p><code>Crea tabella materializzata</code></p><ul><li>Cree une table physique avec les donnees agregees de la configuration pivot.</li><li>Utile pour les gros jeux de donnees ou les dashboards necessitant des temps de lecture rapides.</li><li>Scaffolde automatiquement la route associee a la table physique creee et genere une planification pour le rafraichissement/alignement des donnees.</li><li>En appuyant sur Actualiser dans la grille de la route associee, une execution forcee de la planification est declenchee.</li></ul><h2>Notes d&#39;utilisation</h2><ul><li>Pour des analyses coherentes, utiliser des colonnes avec un type de donnees correct (numeriques pour les metriques, textuelles/dates pour les dimensions).</li><li>En presence de gros volumes, preferer les filtres et agregations ciblees avant de developper le detail.</li></ul>",
13196
13617
  "codeSamples": []
13197
13618
  }
13198
13619
  ],
@@ -14147,7 +14568,7 @@ const frameworkDocsContent = {
14147
14568
  {
14148
14569
  "id": "overview",
14149
14570
  "title": "Overview",
14150
- "html": "<h1>Component Catalog</h1><p>Total component folders detected: 48.</p><h2>Available components</h2><ul><li>app-settings-editor</li><li>archetype-configurator</li><li>bounded-repeater</li><li>carousel-list</li><li>chart-list</li><li>code-editor</li><li>css-sheet-editor</li><li>dashboard</li><li>data-action-button</li><li>data-repeater</li><li>data-source</li><li>designer</li><li>dynamic-dashboard-template</li><li>dynamic-field-template</li><li>dynamic-form-template</li><li>dynamic-generic-template</li><li>dynamic-repeater-template</li><li>dynamic-template</li><li>field</li><li>filter-bar</li><li>first-run-wizard</li><li>framework-docs</li><li>image-wrapper</li><li>import-export-button</li><li>kanban-list</li><li>list-grid</li><li>map-list</li><li>meta-menu</li><li>metadata-editor</li><li>notification-bell</li><li>pager</li><li>parametric-dialog</li><li>pivot-builder</li><li>prompt-dialog-fallback-host</li><li>rag-chatbot</li><li>rag-chatbot-fab</li><li>report-designer</li><li>report-viewer</li><li>scheduler-list</li><li>split</li><li>split-area</li><li>spreadsheet-list-sf</li><li>tab-panel-wrapper</li><li>tab-view-wrapper</li><li>tree-list</li><li>upload-dialog</li><li>workflow-designer</li><li>workflow-runner</li></ul><h2>Exports from public-api</h2><ul><li>data-source/data-source.component</li><li>list-grid/list-grid.component</li><li>import-export-button/import-export-button.component</li><li>pager/pager.component</li><li>filter-bar/filter-bar.component</li><li>map-list/map-list.component</li><li>carousel-list/carousel-list.component</li><li>tree-list/tree-list.component</li><li>parametric-dialog/parametric-dialog.component</li><li>upload-dialog/upload-dialog.component</li><li>app-settings-editor/app-settings-editor.component</li><li>data-action-button/data-action-button.component</li><li>meta-menu/meta-menu.component</li><li>field/text-editor/text-editor.component</li><li>field/text-area-editor/text-area-editor.component</li><li>field/number-editor/number-editor.component</li><li>field/lookup-editor/lookup-editor.component</li><li>field/dictionary-editor/dictionary-editor.component</li><li>field/boolean-editor/boolean-editor.component</li><li>field/date-editor/date-editor.component</li><li>field/html-editor/html-editor.component</li><li>field/upload-editor/upload-editor.component</li><li>field/button-editor/button-editor.component</li><li>field/field-editor/field-editor.component</li><li>field/code-area-editor/code-area-editor.component</li><li>dynamic-template/dynamic-template.component</li><li>image-wrapper/image-wrapper.component</li><li>field/color-editor/color-editor.component</li><li>field/property-array-editor/property-array-editor.component</li><li>field/property-object-editor/property-object-editor.component</li><li>field/tree-view-selector/tree-view-selector.component</li><li>workflow-designer/workflow-designer.component</li><li>workflow-runner/workflow-runner.component</li><li>notification-bell/notification-bell.component</li><li>bounded-repeater/bounded-repeater.component</li><li>framework-docs/framework-docs.component</li><li>designer/designer.route.component</li><li>workflow-designer/workflow-designer.route.component</li><li>kanban-list/kanban-list.component</li><li>data-repeater/data-repeater.component</li><li>bounded-repeater/bounded-repeater-pending-changes.guard</li><li>rag-chatbot/rag-chatbot.component</li><li>rag-chatbot-fab/rag-chatbot-fab.component</li></ul>",
14571
+ "html": "<h1>Component Catalog</h1><p>Total component folders detected: 49.</p><h2>Available components</h2><ul><li>app-settings-editor</li><li>archetype-configurator</li><li>bounded-repeater</li><li>carousel-list</li><li>chart-list</li><li>code-editor</li><li>css-sheet-editor</li><li>dashboard</li><li>data-action-button</li><li>data-repeater</li><li>data-source</li><li>designer</li><li>dynamic-dashboard-template</li><li>dynamic-field-template</li><li>dynamic-form-template</li><li>dynamic-generic-template</li><li>dynamic-repeater-template</li><li>dynamic-template</li><li>error-dialog</li><li>field</li><li>filter-bar</li><li>first-run-wizard</li><li>framework-docs</li><li>image-wrapper</li><li>import-export-button</li><li>kanban-list</li><li>list-grid</li><li>map-list</li><li>meta-menu</li><li>metadata-editor</li><li>notification-bell</li><li>pager</li><li>parametric-dialog</li><li>pivot-builder</li><li>prompt-dialog-fallback-host</li><li>rag-chatbot</li><li>rag-chatbot-fab</li><li>report-designer</li><li>report-viewer</li><li>scheduler-list</li><li>split</li><li>split-area</li><li>spreadsheet-list-sf</li><li>tab-panel-wrapper</li><li>tab-view-wrapper</li><li>tree-list</li><li>upload-dialog</li><li>workflow-designer</li><li>workflow-runner</li></ul><h2>Exports from public-api</h2><ul><li>data-source/data-source.component</li><li>list-grid/list-grid.component</li><li>import-export-button/import-export-button.component</li><li>pager/pager.component</li><li>filter-bar/filter-bar.component</li><li>map-list/map-list.component</li><li>carousel-list/carousel-list.component</li><li>tree-list/tree-list.component</li><li>parametric-dialog/parametric-dialog.component</li><li>upload-dialog/upload-dialog.component</li><li>app-settings-editor/app-settings-editor.component</li><li>data-action-button/data-action-button.component</li><li>meta-menu/meta-menu.component</li><li>field/text-editor/text-editor.component</li><li>field/text-area-editor/text-area-editor.component</li><li>field/number-editor/number-editor.component</li><li>field/lookup-editor/lookup-editor.component</li><li>field/dictionary-editor/dictionary-editor.component</li><li>field/boolean-editor/boolean-editor.component</li><li>field/date-editor/date-editor.component</li><li>field/html-editor/html-editor.component</li><li>field/upload-editor/upload-editor.component</li><li>field/button-editor/button-editor.component</li><li>field/field-editor/field-editor.component</li><li>field/code-area-editor/code-area-editor.component</li><li>dynamic-template/dynamic-template.component</li><li>image-wrapper/image-wrapper.component</li><li>field/color-editor/color-editor.component</li><li>field/property-array-editor/property-array-editor.component</li><li>field/property-object-editor/property-object-editor.component</li><li>field/tree-view-selector/tree-view-selector.component</li><li>workflow-designer/workflow-designer.component</li><li>workflow-runner/workflow-runner.component</li><li>notification-bell/notification-bell.component</li><li>bounded-repeater/bounded-repeater.component</li><li>framework-docs/framework-docs.component</li><li>designer/designer.route.component</li><li>workflow-designer/workflow-designer.route.component</li><li>kanban-list/kanban-list.component</li><li>data-repeater/data-repeater.component</li><li>bounded-repeater/bounded-repeater-pending-changes.guard</li><li>rag-chatbot/rag-chatbot.component</li><li>rag-chatbot-fab/rag-chatbot-fab.component</li><li>error-dialog/error-dialog.component</li></ul>",
14151
14572
  "codeSamples": []
14152
14573
  }
14153
14574
  ],
@@ -14268,13 +14689,21 @@ const frameworkDocsContent = {
14268
14689
  {
14269
14690
  "id": "sec_3",
14270
14691
  "title": "Exemples"
14692
+ },
14693
+ {
14694
+ "id": "sec_4",
14695
+ "title": "Patch runtime de `metaInfo` via `fetchInfo$.subscribe`"
14696
+ },
14697
+ {
14698
+ "id": "sec_5",
14699
+ "title": "Plusieurs `<wuic-data-source>` dans le meme composant"
14271
14700
  }
14272
14701
  ],
14273
14702
  "sections": [
14274
14703
  {
14275
14704
  "id": "overview",
14276
14705
  "title": "Overview",
14277
- "html": "<h1>Datasource</h1><p><code>wuic-data-source</code> est le composant qui gere les metadata, le chargement de donnees, l&#39;etat courant, le suivi des modifications et la synchronisation CRUD.</p><h2>Description</h2><ul><li>Resout la route (<code>route</code>/<code>hardcodedRoute</code> ou routage URL).</li><li>Charge les metadata table/colonnes et publie l&#39;etat sur <code>fetchInfo$</code>.</li><li>Gere <code>filterInfo</code>, <code>sortInfo</code>, <code>groupInfo</code>, pagination (<code>pageSize</code>, <code>currentPage</code>) et cursor paging.</li><li>Expose les methodes runtime pour insert/update/delete, sauvegarde batch, rollback et export.</li><li>C&#39;est le point central auquel se raccordent <code>wuic-data-repeater</code>, <code>wuic-filter-bar</code> et <code>wuic-pager</code>.</li></ul><h2>API</h2><p>Selecteur :</p><ul><li><code>wuic-data-source</code></li></ul><p>Inputs principaux :</p><ul><li><code>route: BehaviorSubject&lt;string&gt;</code></li><li><code>routeFromRouting: boolean</code></li><li><code>hardcodedRoute: string</code></li><li><code>autoload?: boolean</code></li><li><code>loading: BehaviorSubject&lt;boolean&gt;</code></li><li><code>changeTracking?: boolean</code></li><li><code>parentRecord: any</code></li><li><code>parentMetaInfo: MetaInfo</code></li><li><code>parentDatasource: DataSourceComponent</code></li><li><code>componentRef: BehaviorSubject&lt;{ component, id, name, uniqueName }&gt;</code></li></ul><p>Etat/runtime principal :</p><ul><li><code>metaInfo: MetaInfo</code></li><li><code>resultInfo: ResultInfo</code></li><li><code>filterInfo?: FilterInfo</code></li><li><code>sortInfo: SortInfo[]</code></li><li><code>groupInfo: GroupInfo[]</code></li><li><code>aggregationInfo: AggregationInfo[]</code></li><li><code>fetchInfo$: BehaviorSubject&lt;{ resultInfo, metaInfo, filterDescriptor }&gt;</code></li><li><code>datasourceReady$: BehaviorSubject&lt;DataSourceComponent | null&gt;</code></li><li><code>afterFirstLoad$: BehaviorSubject&lt;any&gt;</code></li><li><code>beforeSync$: Subject&lt;DataSourceBeforeSyncEvent&gt;</code> (annulable)</li><li><code>afterSync$: Subject&lt;DataSourceAfterSyncEvent&gt;</code></li></ul><p>Methodes runtime les plus utilisees :</p><ul><li><code>fetchData()</code></li><li><code>syncData(entita, original, deleting?, cloning?)</code></li><li><code>addNewRecord(record?)</code></li><li><code>setCurrent(data)</code></li><li><code>clearColumnFilter(col, fetch?)</code></li><li><code>getPendingChanges()</code></li><li><code>hasPendingChanges()</code></li><li><code>batchSave(targetChanges?)</code></li><li><code>rollbackChanges(targetChanges?)</code></li><li><code>exportXls()</code></li></ul><p>Souscriptions/evenements runtime :</p><ul><li><code>fetchInfo$</code> : flux principal de l&#39;etat donnees/metadonnees.</li><li><code>datasourceReady$</code> : emis lorsque l&#39;instance de la datasource est prete.</li><li><code>afterFirstLoad$</code> : emis une seule fois au premier payload utile.</li><li><code>beforeSync$</code> : emis avant <code>insert/update/delete/clone/batch</code> ; vous pouvez annuler avec <code>event.cancelSync(...)</code>.</li><li><code>afterSync$</code> : emis apres une synchronisation reussie.</li></ul><p>Note de compatibilite :</p><ul><li>un alias <code>fetchInfo</code> (getter/setter) est egalement disponible, mais le nommage conseille est <code>fetchInfo$</code>.</li></ul><h2>Exemples</h2><h3>Exemple de base (route codee en dur + chargement automatique)</h3><p>Snippet 1:</p><h3>Exemple master-detail (datasource imbriquee)</h3><p>Snippet 2:</p><h3>Exemple operationnel (chargement manuel + synchronisation)</h3><p>Snippet 3:</p><h3>Exemple de souscriptions (hote)</h3><p>Snippet 4:</p>",
14706
+ "html": "<h1>Datasource</h1><p><code>wuic-data-source</code> est le composant qui gere les metadata, le chargement de donnees, l&#39;etat courant, le suivi des modifications et la synchronisation CRUD.</p><h2>Description</h2><ul><li>Resout la route (<code>route</code>/<code>hardcodedRoute</code> ou routage URL).</li><li>Charge les metadata table/colonnes et publie l&#39;etat sur <code>fetchInfo$</code>.</li><li>Gere <code>filterInfo</code>, <code>sortInfo</code>, <code>groupInfo</code>, pagination (<code>pageSize</code>, <code>currentPage</code>) et cursor paging.</li><li>Expose les methodes runtime pour insert/update/delete, sauvegarde batch, rollback et export.</li><li>C&#39;est le point central auquel se raccordent <code>wuic-data-repeater</code>, <code>wuic-filter-bar</code> et <code>wuic-pager</code>.</li></ul><h2>API</h2><p>Selecteur :</p><ul><li><code>wuic-data-source</code></li></ul><p>Inputs principaux :</p><ul><li><code>route: BehaviorSubject&lt;string&gt;</code></li><li><code>routeFromRouting: boolean</code></li><li><code>hardcodedRoute: string</code></li><li><code>autoload?: boolean</code></li><li><code>loading: BehaviorSubject&lt;boolean&gt;</code></li><li><code>changeTracking?: boolean</code></li><li><code>parentRecord: any</code></li><li><code>parentMetaInfo: MetaInfo</code></li><li><code>parentDatasource: DataSourceComponent</code></li><li><code>componentRef: BehaviorSubject&lt;{ component, id, name, uniqueName }&gt;</code></li></ul><p>Etat/runtime principal :</p><ul><li><code>metaInfo: MetaInfo</code></li><li><code>resultInfo: ResultInfo</code></li><li><code>filterInfo?: FilterInfo</code></li><li><code>sortInfo: SortInfo[]</code></li><li><code>groupInfo: GroupInfo[]</code></li><li><code>aggregationInfo: AggregationInfo[]</code></li><li><code>fetchInfo$: BehaviorSubject&lt;{ resultInfo, metaInfo, filterDescriptor }&gt;</code></li><li><code>datasourceReady$: BehaviorSubject&lt;DataSourceComponent | null&gt;</code></li><li><code>afterFirstLoad$: BehaviorSubject&lt;any&gt;</code></li><li><code>beforeSync$: Subject&lt;DataSourceBeforeSyncEvent&gt;</code> (annulable)</li><li><code>afterSync$: Subject&lt;DataSourceAfterSyncEvent&gt;</code></li></ul><p>Methodes runtime les plus utilisees :</p><ul><li><code>fetchData()</code></li><li><code>syncData(entita, original, deleting?, cloning?)</code></li><li><code>addNewRecord(record?)</code></li><li><code>setCurrent(data)</code></li><li><code>clearColumnFilter(col, fetch?)</code></li><li><code>getPendingChanges()</code></li><li><code>hasPendingChanges()</code></li><li><code>batchSave(targetChanges?)</code></li><li><code>rollbackChanges(targetChanges?)</code></li><li><code>exportXls()</code></li></ul><p>Souscriptions/evenements runtime :</p><ul><li><code>fetchInfo$</code> : flux principal de l&#39;etat donnees/metadonnees.</li><li><code>datasourceReady$</code> : emis lorsque l&#39;instance de la datasource est prete.</li><li><code>afterFirstLoad$</code> : emis une seule fois au premier payload utile.</li><li><code>beforeSync$</code> : emis avant <code>insert/update/delete/clone/batch</code> ; vous pouvez annuler avec <code>event.cancelSync(...)</code>.</li><li><code>afterSync$</code> : emis apres une synchronisation reussie.</li></ul><p>Note de compatibilite :</p><ul><li>un alias <code>fetchInfo</code> (getter/setter) est egalement disponible, mais le nommage conseille est <code>fetchInfo$</code>.</li></ul><h2>Exemples</h2><h3>Exemple de base (route codee en dur + chargement automatique)</h3><p>Snippet 1:</p><h3>Exemple master-detail (datasource imbriquee)</h3><p>Snippet 2:</p><h3>Exemple operationnel (chargement manuel + synchronisation)</h3><p>Snippet 3:</p><h3>Exemple de souscriptions (hote)</h3><p>Snippet 4:</p><h2>Patch runtime de <code>metaInfo</code> via <code>fetchInfo$.subscribe</code></h2><p>Pour modifier au runtime les metadonnees d&#39;un nested data-source (par ex. <code>mc_hide_in_list</code>, <code>md_inline_cell_editing</code>, <code>md_pageable=false</code>, <code>mc_logic_editable=false</code>) <strong>sans toucher au DB</strong> — patch local sur la reference mise en cache par <code>MetadataProviderService</code>, latest-wins, scenario single-dialog — souscris au <code>fetchInfo$</code> du data-source :</p><p>Snippet 5:</p><p><strong>Scope</strong> : session-only + instance-only. Le patch survit uniquement tant que le cache <code>MetadataProviderService</code> n&#39;est pas invalide ou que le composant n&#39;est pas detruit. Utile lorsqu&#39;un custom form veut une representation differente d&#39;une nested grid par rapport au rendu standalone.</p><p><strong>Quand NE PAS utiliser les patch runtime</strong> : pour des modifications destinees a toutes les instances de la route (y compris standalone <code>&lt;route&gt;/list</code>) → patch SQL direct sur les metadonnees ; pour des modifications persistantes apres restart → SQL + <code>MetaService.invalidateMetadataRuntime</code>.</p><h2>Plusieurs <code>&lt;wuic-data-source&gt;</code> dans le meme composant</h2><p>Lorsqu&#39;un composant custom contient plus d&#39;un <code>&lt;wuic-data-source&gt;</code> (par ex. master + nested rows + nested echeances), le pattern <code>@ViewChild(DataSourceComponent)</code> by-type matche <strong>seulement le premier</strong> data-source dans l&#39;ordre top-down du template. Pour les suivants il faut un <strong>template reference variable</strong> + <code>@ViewChild(&#39;refName&#39;)</code> :</p><p>Snippet 6:</p><p>Snippet 7:</p><p><strong>Antipattern</strong> : <code>document.querySelectorAll(&#39;wuic-data-source&#39;)</code> + <code>window.ng.getComponent(el)</code> pour filtrer par <code>hardcodedRoute</code>. Fonctionne mais est fragile, depend de l&#39;API debug <code>window.ng</code> (non garantie en production). Toujours utiliser les template refs.</p>",
14278
14707
  "codeSamples": [
14279
14708
  {
14280
14709
  "id": "code_1",
@@ -14289,16 +14718,34 @@ const frameworkDocsContent = {
14289
14718
  "code": "<wuic-data-source #masterDs [hardcodedRoute]=\"'orders'\" [autoload]=\"true\"></wuic-data-source>\n\n<wuic-data-source\n [hardcodedRoute]=\"'order_rows'\"\n [parentDatasource]=\"masterDs\"\n [parentRecord]=\"masterDs.resultInfo?.current\"\n [parentMetaInfo]=\"masterDs.metaInfo\"\n [autoload]=\"true\">\n</wuic-data-source>"
14290
14719
  },
14291
14720
  {
14292
- "id": "code_3",
14293
- "title": "Snippet 3",
14294
- "language": "ts",
14295
- "code": "await datasource.fetchData();\n\nconst row = datasource.addNewRecord({ customer_name: 'ACME' });\nawait datasource.syncData(row, null, false, false);"
14721
+ "id": "code_3",
14722
+ "title": "Snippet 3",
14723
+ "language": "ts",
14724
+ "code": "await datasource.fetchData();\n\nconst row = datasource.addNewRecord({ customer_name: 'ACME' });\nawait datasource.syncData(row, null, false, false);"
14725
+ },
14726
+ {
14727
+ "id": "code_4",
14728
+ "title": "Snippet 4",
14729
+ "language": "ts",
14730
+ "code": "const subs = new Subscription();\n\nsubs.add(\n datasource.fetchInfo$.subscribe((info) => {\n if (!info) return;\n console.log('records', info.resultInfo?.dato?.length || 0);\n })\n);\n\nsubs.add(\n datasource.beforeSync$.subscribe((event) => {\n if (event.operation === 'delete' && !canDelete()) {\n event.cancelSync('Delete non consentito dal contesto host');\n }\n })\n);\n\nsubs.add(\n datasource.afterSync$.subscribe((event) => {\n console.log('sync ok', event.operation, event.syncedData);\n })\n);"
14731
+ },
14732
+ {
14733
+ "id": "code_5",
14734
+ "title": "Snippet 5",
14735
+ "language": "ts",
14736
+ "code": "private bindNestedMetaPatches(): void {\n if (!this.scadenzeDs) return;\n const ESSENTIAL = new Set(['data_scadenza', 'importo', 'pagamento_id', 'stato', 'note']);\n const sub = this.scadenzeDs.fetchInfo$.subscribe((info) => {\n const route = this.scadenzeDs?.route?.value;\n if (!info || route !== info.metaInfo?.tableMetadata?.md_route_name) return;\n const cols: any[] = info.metaInfo?.columnMetadata || [];\n for (const c of cols) {\n const name = c?.mc_nome_colonna;\n if (!name || c.mc_is_primary_key) continue;\n c.mc_hide_in_list = !ESSENTIAL.has(name);\n }\n });\n this.subs.add(sub); // cleanup dans ngOnDestroy\n}"
14737
+ },
14738
+ {
14739
+ "id": "code_6",
14740
+ "title": "Snippet 6",
14741
+ "language": "html",
14742
+ "code": "<!-- TEMPLATE -->\n<wuic-data-source #righeDs [hardcodedRoute]=\"'fatture_inviate_righe'\" [parentRecord]=\"record\"></wuic-data-source>\n<wuic-data-source #scadenzeDs [hardcodedRoute]=\"'scadenze'\" [parentRecord]=\"record\"></wuic-data-source>"
14296
14743
  },
14297
14744
  {
14298
- "id": "code_4",
14299
- "title": "Snippet 4",
14745
+ "id": "code_7",
14746
+ "title": "Snippet 7",
14300
14747
  "language": "ts",
14301
- "code": "const subs = new Subscription();\n\nsubs.add(\n datasource.fetchInfo$.subscribe((info) => {\n if (!info) return;\n console.log('records', info.resultInfo?.dato?.length || 0);\n })\n);\n\nsubs.add(\n datasource.beforeSync$.subscribe((event) => {\n if (event.operation === 'delete' && !canDelete()) {\n event.cancelSync('Delete non consentito dal contesto host');\n }\n })\n);\n\nsubs.add(\n datasource.afterSync$.subscribe((event) => {\n console.log('sync ok', event.operation, event.syncedData);\n })\n);"
14748
+ "code": "// COMPONENT\nimport { DataSourceComponent } from 'wuic-framework-lib';\n\n// Premier data-source (match by type) -> pas de template ref necessaire\n@ViewChild(DataSourceComponent) righeDs?: DataSourceComponent;\n\n// Deuxieme data-source -> requiert `#scadenzeDs` dans le template + ViewChild by-name\n@ViewChild('scadenzeDs') scadenzeDs?: DataSourceComponent;"
14302
14749
  }
14303
14750
  ]
14304
14751
  }
@@ -16759,26 +17206,34 @@ const frameworkDocsContent = {
16759
17206
  },
16760
17207
  {
16761
17208
  "id": "sec_6",
16762
- "title": "md_props_bag: archetypes.list"
17209
+ "title": "md_props_bag: toolbar"
16763
17210
  },
16764
17211
  {
16765
17212
  "id": "sec_7",
16766
- "title": "Paging: md_pagesize y md_page_size_choice"
17213
+ "title": "md_props_bag: archetypes.list"
16767
17214
  },
16768
17215
  {
16769
17216
  "id": "sec_8",
16770
- "title": "Forzado de virtualización en page size alto"
17217
+ "title": "Paging: md_pagesize y md_page_size_choice"
16771
17218
  },
16772
17219
  {
16773
17220
  "id": "sec_9",
17221
+ "title": "Forzado de virtualización en page size alto"
17222
+ },
17223
+ {
17224
+ "id": "sec_10",
16774
17225
  "title": "Eventos y subscriptions (host)"
17226
+ },
17227
+ {
17228
+ "id": "sec_11",
17229
+ "title": "`rowCustomSelect` — firma real (gotcha)"
16775
17230
  }
16776
17231
  ],
16777
17232
  "sections": [
16778
17233
  {
16779
17234
  "id": "overview",
16780
17235
  "title": "Overview",
16781
- "html": "<h1>List Grid</h1><p>Componente principal para listas tabulares con filtros, sorting, paging server-side y acciones de fila.</p><h2>Casos de uso</h2><ul><li>CRUD tabulares enterprise.</li><li>Informes operativos con filtros multi-columna.</li><li>Datasets muy grandes con <code>cursorMode</code>.</li></ul><h2>Screenshot de referencia (manual de usuario)</h2><ul><li><code>manual__grid__01.png</code>: modalidad <code>inline cell edit</code> en list-grid.</li><li><code>manual__grid__02.png</code>: modalidad <code>inline edit</code> (row-level) en list-grid.</li></ul><h2>Metadatos de inline editing</h2><p>Configuración en el <code>md_props_bag</code> de la tabla metadata:</p><ul><li><code>md_inline_edit</code></li></ul><p> - habilita el editing inline de la fila en list-grid (celdas editables en contexto de fila, sin apertura de popup).</p><p> - útil cuando se quiere mantener la UX de tabla con edición rápida por registro.</p><ul><li><code>md_inline_cell_edit</code></li></ul><p> - habilita el editing inline &quot;celda por celda&quot; (foco en la celda individual).</p><p> - nota: en configuraciones legacy puede aparecer como <code>md_inline_cell_editing</code>; el comportamiento runtime es el mismo.</p><p> - <strong>promoción runtime</strong>: cuando <code>md_inline_cell_edit</code> es <code>true</code>, el componente fuerza</p><p> en runtime también <code>md_inline_edit = true</code> independientemente del valor en DB.</p><p> Ambas UX necesitan la action column visible, por lo que son mutuamente</p><p> no-exclusivas a nivel de renderizado.</p><ul><li><code>md_batch_save</code></li></ul><p> - habilita el guardado batch de los cambios pendientes (<code>Save changes</code> / <code>Cancel changes</code>).</p><p> - si se usa junto con inline-cell, los cambios quedan pending hasta el guardado explícito.</p><p> - <strong>prerequisito</strong>: <code>md_batch_save</code> tiene efecto <strong>solo</strong> si <code>md_inline_cell_edit</code> es <code>true</code>.</p><p> Con <code>md_inline_cell_edit:false</code> el flag se ignora en runtime y los botones</p><p> &quot;Guardar cambios / Cancelar cambios&quot; no se renderizan.</p><h3>Combinaciones válidas</h3><table><thead><tr><th><code>md_inline_edit</code></th><th><code>md_inline_cell_edit</code></th><th><code>md_batch_save</code></th><th>Resultado runtime</th></tr></thead><tbody><tr><td><code>true</code></td><td><code>false</code></td><td>cualquiera</td><td>Inline edit a nivel de fila con pencil. <code>md_batch_save</code> ignorado.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>false</code></td><td>Cell-by-cell autosave on blur. <code>md_inline_edit</code> forzado a <code>true</code> en runtime.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>true</code></td><td>Cell-by-cell con buffer pending + toolbar <code>Guardar / Cancelar cambios</code>.</td></tr><tr><td><code>false</code></td><td><code>false</code></td><td>cualquiera</td><td>Sin inline editing. <code>md_batch_save</code> ignorado.</td></tr></tbody></table><p>Ejemplo row-level inline edit:</p><p>Snippet 1:</p><p>Ejemplo cell-by-cell con batch save:</p><p>Snippet 2:</p><h2>Toolbar export/import</h2><ul><li>Export XLS:</li></ul><p> - muestra diálogo de progreso con porcentaje en tiempo real;</p><p> - acciones: <code>Continuar en background</code>, <code>Cancelar tarea</code>, <code>Interrumpir y descargar parcial</code>;</p><p> - en background crea una notificación con progreso; el clic reabre el diálogo.</p><ul><li>Import XLS/XLSX (si <code>md_importable = true</code>):</li></ul><p> - diálogo de progreso tras la confirmación;</p><p> - acciones: <code>Continuar en background</code>, <code>Cancelar import (rollback)</code>, <code>Detener y commit parcial</code>;</p><p> - al finalizar el import crea una notificación de resumen que lleva a la route.</p><h2>Config metadata</h2><p>Ajustes clave en <code>md_props_bag</code> y metadatos de columna.</p><p>Snippet 3:</p><h2>md_props_bag: archetypes.list</h2><p>El componente lee <code>md_props_bag.archetypes.list</code>.</p><p>Snippet 4:</p><ul><li><code>proportionalColwidth</code>:</li><li>tipo: boolean (con parser tolerante)</li><li>valores aceptados: <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>&quot;true&quot;</code>, <code>&quot;false&quot;</code>, <code>&quot;1&quot;</code>, <code>&quot;0&quot;</code></li><li>default runtime: <code>true</code> cuando ausente o vacío</li><li>efecto: distribuye los anchos de columna en porcentaje (en lugar de px) cuando no hay anchos de usuario persistidos.</li></ul><ul><li><code>virtualize</code>:</li><li>tipo: <code>boolean</code> o <code>object</code></li><li>valores aceptados:</li><li><code>true</code>/<code>false</code>, <code>1</code>/<code>0</code>, <code>&quot;true&quot;</code>/<code>&quot;false&quot;</code>, <code>&quot;1&quot;</code>/<code>&quot;0&quot;</code></li><li>object con <code>enabled</code> (opcional) y <code>itemSize</code> (opcional)</li><li>default runtime: deshabilitado (<code>false</code>) cuando ausente</li><li><code>enabled</code>:</li><li>default: <code>true</code> si el nodo <code>virtualize</code> es un object sin <code>enabled</code>, de lo contrario parser tolerante</li><li>efecto: habilita <code>virtualScroll</code> en <code>p-table</code></li><li><code>itemSize</code>:</li><li>default: <code>44</code></li><li>efecto: establece <code>virtualScrollItemSize</code> (altura de fila virtual en px)</li></ul><ul><li><code>advancedFilter</code>:</li><li>tipo: boolean (con parser tolerante)</li><li>default runtime: <code>false</code></li><li>efecto:</li><li>cuando <code>true</code>, la grilla oculta los iconos de filtro de columna (<code>p-columnFilter</code>) y usa la <code>wuic-filter-bar</code> sobre la tabla (a nivel <code>data-repeater</code>/<code>bounded-repeater</code>) para aplicar los filtros.</li></ul><p>Nota operativa:</p><ul><li>el suggest <code>md_props_bag</code> en el metadata editor expone también el nodo checkeable <code>archetypes.list.advancedFilter</code>.</li></ul><h2>Paging: md_pagesize y md_page_size_choice</h2><p>La grilla usa <code>md_pagesize</code> como tamaño de página por defecto y <code>md_page_size_choice</code> como lista de valores seleccionables en el paginador.</p><p>Reglas runtime:</p><ul><li>si <code>md_pagesize</code> es mayor que el valor máximo presente en <code>md_page_size_choice</code>, el framework agrega automáticamente <code>md_pagesize</code> a la lista de opciones;</li><li>la lista se normaliza (números válidos, deduplicación, orden ascendente).</li></ul><p>Ejemplo:</p><ul><li><code>md_pagesize = 200</code></li><li><code>md_page_size_choice = &quot;10,25,50,100&quot;</code></li><li>resultado runtime: <code>10,25,50,100,200</code></li></ul><h2>Forzado de virtualización en page size alto</h2><p>Cuando el usuario selecciona en el paginador un valor <code>pageSize &gt;= 1000</code>:</p><ul><li>la virtualización se fuerza automáticamente aunque <code>md_props_bag.archetypes.list.virtualize</code> esté ausente o deshabilitado;</li><li><code>virtualScrollItemSize</code> se fuerza al valor predeterminado <code>44</code>.</li></ul><p>Este comportamiento protege el rendimiento de la tabla en páginas muy grandes.</p><h2>Eventos y subscriptions (host)</h2><p><code>wuic-list-grid</code> expone eventos runtime útiles para interceptar el ciclo de render y callbacks de p-table en el proyecto host.</p><p>Eventos disponibles:</p><ul><li><code>onAfterRender</code>: emitido al finalizar el binding de datos de la grilla (<code>rows</code>, <code>totalRecords</code>, <code>metaInfo</code>, <code>datasource</code>).</li><li><code>onBeforeRowRender</code>: emitido antes del renderizado lógico de la fila individual; soporta cancelación mediante <code>event.cancelRender()</code>.</li><li><code>onAfterRowRender</code>: emitido después del renderizado lógico de la fila individual.</li><li><code>onPaging</code>: emitido en los eventos de paging (<code>p-table onPage</code>).</li><li><code>onSorting</code>: emitido en los eventos de sorting (<code>p-table onSort</code>).</li><li><code>onFiltering</code>: emitido en los eventos de filtering (<code>p-table onFilter</code>).</li><li><code>onPTableSelectionChange</code>: emitido al cambiar la selección de filas.</li><li><code>onPTableRowExpand</code>: emitido al expandir una fila.</li><li><code>onPTableRowCollapse</code>: emitido al colapsar una fila.</li><li><code>onPTableColumnResize</code>: emitido al redimensionar una columna.</li><li><code>onPTableColumnReorder</code>: emitido al reordenar columnas.</li></ul><h3>Ejemplo 1: binding directo en el template</h3><p>Snippet 5:</p><h3>Ejemplo 2: subscribe vía ViewChild</h3><p>Snippet 6:</p>",
17236
+ "html": "<h1>List Grid</h1><p>Componente principal para listas tabulares con filtros, sorting, paging server-side y acciones de fila.</p><h2>Casos de uso</h2><ul><li>CRUD tabulares enterprise.</li><li>Informes operativos con filtros multi-columna.</li><li>Datasets muy grandes con <code>cursorMode</code>.</li></ul><h2>Screenshot de referencia (manual de usuario)</h2><ul><li><code>manual__grid__01.png</code>: modalidad <code>inline cell edit</code> en list-grid.</li><li><code>manual__grid__02.png</code>: modalidad <code>inline edit</code> (row-level) en list-grid.</li></ul><h2>Metadatos de inline editing</h2><p>Configuración en el <code>md_props_bag</code> de la tabla metadata:</p><ul><li><code>md_inline_edit</code></li></ul><p> - habilita el editing inline de la fila en list-grid (celdas editables en contexto de fila, sin apertura de popup).</p><p> - útil cuando se quiere mantener la UX de tabla con edición rápida por registro.</p><ul><li><code>md_inline_cell_edit</code></li></ul><p> - habilita el editing inline &quot;celda por celda&quot; (foco en la celda individual).</p><p> - nota: en configuraciones legacy puede aparecer como <code>md_inline_cell_editing</code>; el comportamiento runtime es el mismo.</p><p> - <strong>promoción runtime</strong>: cuando <code>md_inline_cell_edit</code> es <code>true</code>, el componente fuerza</p><p> en runtime también <code>md_inline_edit = true</code> independientemente del valor en DB.</p><p> Ambas UX necesitan la action column visible, por lo que son mutuamente</p><p> no-exclusivas a nivel de renderizado.</p><ul><li><code>md_batch_save</code></li></ul><p> - habilita el guardado batch de los cambios pendientes (<code>Save changes</code> / <code>Cancel changes</code>).</p><p> - si se usa junto con inline-cell, los cambios quedan pending hasta el guardado explícito.</p><p> - <strong>prerequisito</strong>: <code>md_batch_save</code> tiene efecto <strong>solo</strong> si <code>md_inline_cell_edit</code> es <code>true</code>.</p><p> Con <code>md_inline_cell_edit:false</code> el flag se ignora en runtime y los botones</p><p> &quot;Guardar cambios / Cancelar cambios&quot; no se renderizan.</p><h3>Combinaciones válidas</h3><table><thead><tr><th><code>md_inline_edit</code></th><th><code>md_inline_cell_edit</code></th><th><code>md_batch_save</code></th><th>Resultado runtime</th></tr></thead><tbody><tr><td><code>true</code></td><td><code>false</code></td><td>cualquiera</td><td>Inline edit a nivel de fila con pencil. <code>md_batch_save</code> ignorado.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>false</code></td><td>Cell-by-cell autosave on blur. <code>md_inline_edit</code> forzado a <code>true</code> en runtime.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>true</code></td><td>Cell-by-cell con buffer pending + toolbar <code>Guardar / Cancelar cambios</code>.</td></tr><tr><td><code>false</code></td><td><code>false</code></td><td>cualquiera</td><td>Sin inline editing. <code>md_batch_save</code> ignorado.</td></tr></tbody></table><p>Ejemplo row-level inline edit:</p><p>Snippet 1:</p><p>Ejemplo cell-by-cell con batch save:</p><p>Snippet 2:</p><h2>Toolbar export/import</h2><ul><li>Export XLS:</li></ul><p> - muestra diálogo de progreso con porcentaje en tiempo real;</p><p> - acciones: <code>Continuar en background</code>, <code>Cancelar tarea</code>, <code>Interrumpir y descargar parcial</code>;</p><p> - en background crea una notificación con progreso; el clic reabre el diálogo.</p><ul><li>Import XLS/XLSX (si <code>md_importable = true</code>):</li></ul><p> - diálogo de progreso tras la confirmación;</p><p> - acciones: <code>Continuar en background</code>, <code>Cancelar import (rollback)</code>, <code>Detener y commit parcial</code>;</p><p> - al finalizar el import crea una notificación de resumen que lleva a la route.</p><h2>Config metadata</h2><p>Ajustes clave en <code>md_props_bag</code> y metadatos de columna.</p><p>Snippet 3:</p><h2>md_props_bag: toolbar</h2><p>Flags opt-in que ocultan bloques de la toolbar de la list-grid (<code>caption-bar</code>). Todos bajo <code>md_props_bag.toolbar.*</code> (parseado en runtime como <code>tableMetadata.extraProps.toolbar</code>).</p><p>Snippet 4:</p><ul><li><code>hideManageState</code> (boolean, default <code>false</code>): oculta en la <strong>caption-right</strong> el botón &quot;Gestionar estado&quot; (icono bookmark) + el <code>&lt;select&gt;</code> de estados guardados. Útil para rutas hardcoded / demo donde el feature saved-state (persistencia por <code>user_id</code> + ruta vía <code>MetaService</code>) no tiene sentido — p. ej. Pattern 3 puro OData sin route metadata registrada.</li><li><code>hideBatchActions</code> (boolean, default <code>false</code>): oculta en la <strong>caption-left</strong> los botones &quot;Guardar cambios&quot; (<code>pi-save</code>) + &quot;Descartar cambios&quot; (<code>pi-times</code>) + el indicador de conteo de changes (<code>grid-changes-indicator</code>, badge pencil + count). Generados por el framework cuando <code>md_inline_cell_editing</code> + <code>md_batch_save</code> están activos. Diseñado para <strong>nested grids dentro de un parametric-dialog</strong>: el save/cancel del padre persiste master + filas en un golpe vía batch save framework, y los botones duplicados en la nested grid confunden la UX.</li></ul><p>Ejemplo nested rows en custom edit-form (runtime patch):</p><p>Snippet 5:</p><p>Tipo TS en <a href=\"../api/wuic-framework-lib.metadatitabella.html\">`metadati_tabella.ts`</a> (propiedad <code>extraProps.toolbar</code>). Los flags NO deshabilitan la lógica <code>md_batch_save</code> subyacente (los changes siguen siendo trackeados); solo eliminan la UI de la toolbar — el save efectivo pasa por el flujo del padre.</p><h2>md_props_bag: archetypes.list</h2><p>El componente lee <code>md_props_bag.archetypes.list</code>.</p><p>Snippet 6:</p><ul><li><code>proportionalColwidth</code>:</li><li>tipo: boolean (con parser tolerante)</li><li>valores aceptados: <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>&quot;true&quot;</code>, <code>&quot;false&quot;</code>, <code>&quot;1&quot;</code>, <code>&quot;0&quot;</code></li><li>default runtime: <code>true</code> cuando ausente o vacío</li><li>efecto: distribuye los anchos de columna en porcentaje (en lugar de px) cuando no hay anchos de usuario persistidos.</li></ul><ul><li><code>virtualize</code>:</li><li>tipo: <code>boolean</code> o <code>object</code></li><li>valores aceptados:</li><li><code>true</code>/<code>false</code>, <code>1</code>/<code>0</code>, <code>&quot;true&quot;</code>/<code>&quot;false&quot;</code>, <code>&quot;1&quot;</code>/<code>&quot;0&quot;</code></li><li>object con <code>enabled</code> (opcional) y <code>itemSize</code> (opcional)</li><li>default runtime: deshabilitado (<code>false</code>) cuando ausente</li><li><code>enabled</code>:</li><li>default: <code>true</code> si el nodo <code>virtualize</code> es un object sin <code>enabled</code>, de lo contrario parser tolerante</li><li>efecto: habilita <code>virtualScroll</code> en <code>p-table</code></li><li><code>itemSize</code>:</li><li>default: <code>44</code></li><li>efecto: establece <code>virtualScrollItemSize</code> (altura de fila virtual en px)</li></ul><ul><li><code>advancedFilter</code>:</li><li>tipo: boolean (con parser tolerante)</li><li>default runtime: <code>false</code></li><li>efecto:</li><li>cuando <code>true</code>, la grilla oculta los iconos de filtro de columna (<code>p-columnFilter</code>) y usa la <code>wuic-filter-bar</code> sobre la tabla (a nivel <code>data-repeater</code>/<code>bounded-repeater</code>) para aplicar los filtros.</li></ul><p>Nota operativa:</p><ul><li>el suggest <code>md_props_bag</code> en el metadata editor expone también el nodo checkeable <code>archetypes.list.advancedFilter</code>.</li></ul><h2>Paging: md_pagesize y md_page_size_choice</h2><p>La grilla usa <code>md_pagesize</code> como tamaño de página por defecto y <code>md_page_size_choice</code> como lista de valores seleccionables en el paginador.</p><p>Reglas runtime:</p><ul><li>si <code>md_pagesize</code> es mayor que el valor máximo presente en <code>md_page_size_choice</code>, el framework agrega automáticamente <code>md_pagesize</code> a la lista de opciones;</li><li>la lista se normaliza (números válidos, deduplicación, orden ascendente).</li></ul><p>Ejemplo:</p><ul><li><code>md_pagesize = 200</code></li><li><code>md_page_size_choice = &quot;10,25,50,100&quot;</code></li><li>resultado runtime: <code>10,25,50,100,200</code></li></ul><h2>Forzado de virtualización en page size alto</h2><p>Cuando el usuario selecciona en el paginador un valor <code>pageSize &gt;= 1000</code>:</p><ul><li>la virtualización se fuerza automáticamente aunque <code>md_props_bag.archetypes.list.virtualize</code> esté ausente o deshabilitado;</li><li><code>virtualScrollItemSize</code> se fuerza al valor predeterminado <code>44</code>.</li></ul><p>Este comportamiento protege el rendimiento de la tabla en páginas muy grandes.</p><h2>Eventos y subscriptions (host)</h2><p><code>wuic-list-grid</code> expone eventos runtime útiles para interceptar el ciclo de render y callbacks de p-table en el proyecto host.</p><p>Eventos disponibles:</p><ul><li><code>onAfterRender</code>: emitido al finalizar el binding de datos de la grilla (<code>rows</code>, <code>totalRecords</code>, <code>metaInfo</code>, <code>datasource</code>).</li><li><code>onBeforeRowRender</code>: emitido antes del renderizado lógico de la fila individual; soporta cancelación mediante <code>event.cancelRender()</code>.</li><li><code>onAfterRowRender</code>: emitido después del renderizado lógico de la fila individual.</li><li><code>onPaging</code>: emitido en los eventos de paging (<code>p-table onPage</code>).</li><li><code>onSorting</code>: emitido en los eventos de sorting (<code>p-table onSort</code>).</li><li><code>onFiltering</code>: emitido en los eventos de filtering (<code>p-table onFilter</code>).</li><li><code>onPTableSelectionChange</code>: emitido al cambiar la selección de filas.</li><li><code>onPTableRowExpand</code>: emitido al expandir una fila.</li><li><code>onPTableRowCollapse</code>: emitido al colapsar una fila.</li><li><code>onPTableColumnResize</code>: emitido al redimensionar una columna.</li><li><code>onPTableColumnReorder</code>: emitido al reordenar columnas.</li></ul><h3>Ejemplo 1: binding directo en el template</h3><p>Snippet 7:</p><h3>Ejemplo 2: subscribe vía ViewChild</h3><p>Snippet 8:</p><h2><code>rowCustomSelect</code> — firma real (gotcha)</h2><p><code>&lt;wuic-list-grid&gt;</code> acepta una entrada <code>[rowCustomSelect]</code> para interceptar la seleccion de una fila (apertura de un dialog &quot;elige un documento&quot;, flujo master-detail, etc.). La firma TypeScript declarada en <a href=\"../../src/lib/component/list-grid/list-grid.component.ts#L140\">list-grid.component.ts:140</a> es:</p><p>Snippet 9:</p><p><strong>Pero en runtime la callback se invoca con argumentos invertidos</strong> — el framework llama <code>rowCustomSelect($event, rowData, dt)</code> (ver <a href=\"../../src/lib/component/dynamic-template/dynamic-template.component.ts#L447\">dynamic-template.component.ts:447</a> y <a href=\"../../src/lib/component/dynamic-template/dynamic-card-template.component.ts#L205\">dynamic-card-template.component.ts:205</a>).</p><p>Sintoma cuando el orden es erroneo: <code>rowData?.id</code> es undefined → guard temprano en tu handler → la callback retorna silenciosamente y tu dialog/accion no arranca. Ningun error en consola.</p><p>Forma correcta (alineada con la invocacion runtime):</p><p>Snippet 10:</p><p>Uso en el template:</p><p>Snippet 11:</p><p>&gt; Nota: los tests en <code>designer.component.spec.ts</code> (p. ej. linea 512) invocan la callback con <code>({currentTarget: rowCell}, {id: 42}, null)</code>, confirmando el orden <code>($event, rowData, dt)</code>. Si en el futuro el framework alinea la firma declarada, esta pagina se actualizara.</p>",
16782
17237
  "codeSamples": [
16783
17238
  {
16784
17239
  "id": "code_1",
@@ -16802,19 +17257,49 @@ const frameworkDocsContent = {
16802
17257
  "id": "code_4",
16803
17258
  "title": "Snippet 4",
16804
17259
  "language": "json",
16805
- "code": "{\n \"archetypes\": {\n \"list\": {\n \"proportionalColwidth\": true,\n \"advancedFilter\": false,\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n }\n }\n }\n}"
17260
+ "code": "{\n \"toolbar\": {\n \"hideManageState\": true,\n \"hideBatchActions\": true\n }\n}"
16806
17261
  },
16807
17262
  {
16808
17263
  "id": "code_5",
16809
17264
  "title": "Snippet 5",
16810
- "language": "html",
16811
- "code": "<wuic-list-grid\n [hardcodedDatasource]=\"citiesDs\"\n (onAfterRender)=\"handleAfterRender($event)\"\n (onBeforeRowRender)=\"handleBeforeRowRender($event)\"\n (onAfterRowRender)=\"handleAfterRowRender($event)\"\n (onPaging)=\"handlePaging($event)\"\n (onSorting)=\"handleSorting($event)\"\n (onFiltering)=\"handleFiltering($event)\"\n (onPTableSelectionChange)=\"handleSelection($event)\"\n (onPTableRowExpand)=\"handleRowExpand($event)\"\n (onPTableRowCollapse)=\"handleRowCollapse($event)\"\n (onPTableColumnResize)=\"handleColumnResize($event)\"\n (onPTableColumnReorder)=\"handleColumnReorder($event)\">\n</wuic-list-grid>"
17265
+ "language": "ts",
17266
+ "code": "this.righeDs.metaInfo.tableMetadata.extraProps = Object.assign(\n this.righeDs.metaInfo.tableMetadata.extraProps || {},\n { toolbar: { hideManageState: true, hideBatchActions: true } }\n) as any;"
16812
17267
  },
16813
17268
  {
16814
17269
  "id": "code_6",
16815
17270
  "title": "Snippet 6",
17271
+ "language": "json",
17272
+ "code": "{\n \"archetypes\": {\n \"list\": {\n \"proportionalColwidth\": true,\n \"advancedFilter\": false,\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n }\n }\n }\n}"
17273
+ },
17274
+ {
17275
+ "id": "code_7",
17276
+ "title": "Snippet 7",
17277
+ "language": "html",
17278
+ "code": "<wuic-list-grid\n [hardcodedDatasource]=\"citiesDs\"\n (onAfterRender)=\"handleAfterRender($event)\"\n (onBeforeRowRender)=\"handleBeforeRowRender($event)\"\n (onAfterRowRender)=\"handleAfterRowRender($event)\"\n (onPaging)=\"handlePaging($event)\"\n (onSorting)=\"handleSorting($event)\"\n (onFiltering)=\"handleFiltering($event)\"\n (onPTableSelectionChange)=\"handleSelection($event)\"\n (onPTableRowExpand)=\"handleRowExpand($event)\"\n (onPTableRowCollapse)=\"handleRowCollapse($event)\"\n (onPTableColumnResize)=\"handleColumnResize($event)\"\n (onPTableColumnReorder)=\"handleColumnReorder($event)\">\n</wuic-list-grid>"
17279
+ },
17280
+ {
17281
+ "id": "code_8",
17282
+ "title": "Snippet 8",
16816
17283
  "language": "ts",
16817
17284
  "code": "import { AfterViewInit, OnDestroy, ViewChild } from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport {\n ListGridAfterRenderEvent,\n ListGridAfterRowRenderEvent,\n ListGridBeforeRowRenderEvent,\n ListGridComponent\n} from 'wuic-framework-lib';\n\nexport class HostComponent implements AfterViewInit, OnDestroy {\n @ViewChild(ListGridComponent) grid?: ListGridComponent;\n private readonly subs = new Subscription();\n\n ngAfterViewInit(): void {\n if (!this.grid) return;\n\n this.subs.add(this.grid.onAfterRender.subscribe((e: ListGridAfterRenderEvent) => console.log(e)));\n this.subs.add(this.grid.onBeforeRowRender.subscribe((e: ListGridBeforeRowRenderEvent) => {\n // ejemplo cancelar fila\n if (e.row?.deleted === true) e.cancelRender();\n }));\n this.subs.add(this.grid.onAfterRowRender.subscribe((e: ListGridAfterRowRenderEvent) => console.log(e)));\n this.subs.add(this.grid.onPaging.subscribe((e) => console.log('paging', e)));\n this.subs.add(this.grid.onSorting.subscribe((e) => console.log('sorting', e)));\n this.subs.add(this.grid.onFiltering.subscribe((e) => console.log('filtering', e)));\n this.subs.add(this.grid.onPTableSelectionChange.subscribe((e) => console.log('selection', e)));\n this.subs.add(this.grid.onPTableRowExpand.subscribe((e) => console.log('expand', e)));\n this.subs.add(this.grid.onPTableRowCollapse.subscribe((e) => console.log('collapse', e)));\n this.subs.add(this.grid.onPTableColumnResize.subscribe((e) => console.log('resize', e)));\n this.subs.add(this.grid.onPTableColumnReorder.subscribe((e) => console.log('reorder', e)));\n }\n\n ngOnDestroy(): void {\n this.subs.unsubscribe();\n }\n}"
17285
+ },
17286
+ {
17287
+ "id": "code_9",
17288
+ "title": "Snippet 9",
17289
+ "language": "ts",
17290
+ "code": "@Input() rowCustomSelect: (rowData: any, $event: any, dt: Table) => void;"
17291
+ },
17292
+ {
17293
+ "id": "code_10",
17294
+ "title": "Snippet 10",
17295
+ "language": "ts",
17296
+ "code": "onRowSelected = (_event: any, rowData: any, _dt: any): void => {\n // rowData es el registro proporcionado por list-grid\n this.handleSelection(rowData);\n};"
17297
+ },
17298
+ {
17299
+ "id": "code_11",
17300
+ "title": "Snippet 11",
17301
+ "language": "html",
17302
+ "code": "<wuic-data-source #ds [hardcodedRoute]=\"'preventivi'\" [autoload]=\"true\"></wuic-data-source>\n<wuic-list-grid [hardcodedDatasource]=\"ds\" [rowCustomSelect]=\"onRowSelected\"></wuic-list-grid>"
16818
17303
  }
16819
17304
  ]
16820
17305
  }
@@ -16986,7 +17471,7 @@ const frameworkDocsContent = {
16986
17471
  "id": "code_1",
16987
17472
  "title": "Snippet 1",
16988
17473
  "language": "json",
16989
- "code": "{\n \"archetypes\": {\n \"map\": {\n \"advancedFilter\": false,\n \"center\": {\n \"lat\": 39.8283,\n \"lng\": -98.5795\n },\n \"zoom\": 4,\n \"minZoom\": 3,\n \"maxZoom\": 14,\n \"useCurrentLocation\": false,\n \"useClusterer\": true,\n \"filterByBoundaries\": false,\n \"titleField\": \"CityName\",\n \"infoField\": \"StateProvinceID__dictionary_display_value\",\n \"customMarkerImageSrc\": \"\",\n \"customMarkerImageSrcField\": \"\",\n \"markerContentCallback\": \"const glyph = String(record?.CityName || 'C').trim().charAt(0).toUpperCase() || 'C'; const pop = Number(record?.LatestRecordedPopulation || 0); const bg = pop > 1000000 ? '#B91C1C' : (pop > 300000 ? '#EA580C' : '#2563EB'); const pin = new google.maps.marker.PinElement({ background: bg, borderColor: '#0F172A', glyphColor: '#FFFFFF', glyph: glyph, scale: 1.2 }); return pin.element;\",\n \"infoFunction\": \"\",\n \"itemTemplateString\": \"<div class=\\\"map-info\\\"> <div class=\\\"map-info-title\\\">{{ rowData.record?.CityName || rowData.title }}</div> <div>{{ rowData.record?.StateProvinceID__dictionary_display_value || rowData.record?.StateProvinceID || '' }}</div> <div>Popolazione: {{ rowData.record?.LatestRecordedPopulation | number:'1.0-0' }}</div> <div>{{ rowData.record?.Location || '' }}</div> <wuic-data-action-button-lazy [data]=\\\"rowData.record\\\" [metaInfo]=\\\"metaInfo\\\" [datasource]=\\\"datasource\\\" [simplified]=\\\"true\\\"></wuic-data-action-button-lazy> </div>\"\n }\n }\n}"
17474
+ "code": "{\n \"archetypes\": {\n \"map\": {\n \"advancedFilter\": false,\n \"center\": {\n \"lat\": 39.8283,\n \"lng\": -98.5795\n },\n \"zoom\": 4,\n \"minZoom\": 3,\n \"maxZoom\": 14,\n \"useCurrentLocation\": false,\n \"useClusterer\": true,\n \"filterByBoundaries\": false,\n \"titleField\": \"CityName\",\n \"infoField\": \"StateProvinceID__dictionary_display_value\",\n \"customMarkerImageSrc\": \"\",\n \"customMarkerImageSrcField\": \"\",\n \"markerContentCallback\": \"const glyph = String(record?.CityName || 'C').trim().charAt(0).toUpperCase() || 'C'; const pop = Number(record?.LatestRecordedPopulation || 0); const bg = pop > 1000000 ? '#B91C1C' : (pop > 300000 ? '#EA580C' : '#2563EB'); const pin = new google.maps.marker.PinElement({ background: bg, borderColor: '#0F172A', glyphColor: '#FFFFFF', glyphText: glyph, scale: 1.2 }); return pin;\",\n \"infoFunction\": \"\",\n \"itemTemplateString\": \"<div class=\\\"map-info\\\"> <div class=\\\"map-info-title\\\">{{ rowData.record?.CityName || rowData.title }}</div> <div>{{ rowData.record?.StateProvinceID__dictionary_display_value || rowData.record?.StateProvinceID || '' }}</div> <div>Popolazione: {{ rowData.record?.LatestRecordedPopulation | number:'1.0-0' }}</div> <div>{{ rowData.record?.Location || '' }}</div> <wuic-data-action-button-lazy [data]=\\\"rowData.record\\\" [metaInfo]=\\\"metaInfo\\\" [datasource]=\\\"datasource\\\" [simplified]=\\\"true\\\"></wuic-data-action-button-lazy> </div>\"\n }\n }\n}"
16990
17475
  },
16991
17476
  {
16992
17477
  "id": "code_2",
@@ -17071,14 +17556,47 @@ const frameworkDocsContent = {
17071
17556
  {
17072
17557
  "id": "sec_3",
17073
17558
  "title": "Referencia al designer"
17559
+ },
17560
+ {
17561
+ "id": "sec_4",
17562
+ "title": "Parametros del reporte (variables SQL filtradas)"
17563
+ },
17564
+ {
17565
+ "id": "sec_5",
17566
+ "title": "Configuracion backend obligatoria"
17074
17567
  }
17075
17568
  ],
17076
17569
  "sections": [
17077
17570
  {
17078
17571
  "id": "overview",
17079
17572
  "title": "Overview",
17080
- "html": "<h1>Reporting</h1><p>Reportística operativa en la route <code>#/&lt;route&gt;/report-viewer</code>.</p><h2>Screenshot reference (manual de usuario)</h2><ul><li><code>manual__reporting__01.png</code>: apertura del report viewer con rendering del reporte y toolbar operativa.</li><li><code>manual__reporting__02.png</code>: ejemplo runtime con parámetros/filtro aplicados al reporte.</li></ul><h2>Report viewer</h2><p>El <code>report-viewer</code> ejecuta y muestra reportes Stimulsoft asociados a la route metadata actual.</p><p>Funcionalidades principales:</p><ul><li>apertura del reporte desde list/grid con nombre del reporte (<code>reportName</code>) y parámetros opcionales.</li><li>rendering de datos en runtime con toolbar del viewer (zoom, export, impresión).</li><li>soporte de filtros/parámetros propagados desde la página de origen.</li></ul><h2>Referencia al designer</h2><p>Para creación o modificación del reporte <code>.mrt</code>, ver la subsección</p><p><a href=\"#/framework-docs/report-designer\">Report designer</a> en <code>Designer &amp; Workflow</code>.</p>",
17081
- "codeSamples": []
17573
+ "html": "<h1>Reporting</h1><p>Reportística operativa en la route <code>#/&lt;route&gt;/report-viewer</code>.</p><h2>Screenshot reference (manual de usuario)</h2><ul><li><code>manual__reporting__01.png</code>: apertura del report viewer con rendering del reporte y toolbar operativa.</li><li><code>manual__reporting__02.png</code>: ejemplo runtime con parámetros/filtro aplicados al reporte.</li></ul><h2>Report viewer</h2><p>El <code>report-viewer</code> ejecuta y muestra reportes Stimulsoft asociados a la route metadata actual.</p><p>Funcionalidades principales:</p><ul><li>apertura del reporte desde list/grid con nombre del reporte (<code>reportName</code>) y parámetros opcionales.</li><li>rendering de datos en runtime con toolbar del viewer (zoom, export, impresión).</li><li>soporte de filtros/parámetros propagados desde la página de origen.</li></ul><h2>Referencia al designer</h2><p>Para creación o modificación del reporte <code>.mrt</code>, ver la subsección</p><p><a href=\"#/framework-docs/report-designer\">Report designer</a> en <code>Designer &amp; Workflow</code>.</p><h2>Parametros del reporte (variables SQL filtradas)</h2><p>Para filtrar un reporte sobre una entidad especifica (ej. imprimir una factura especifica), el patron canonico es:</p><p>1. <strong>En el `.mrt`</strong>: declarar una <strong>Variable</strong> en el Dictionary con <code>AllowUseAsSqlParameter = True</code> (Stimulsoft 2026.1+). Usar la variable como <code>@nombreParam</code> en las SqlCommand:</p><p>Snippet 1:</p><p> &gt; <strong>Trampa</strong>: NO uses el bloque legacy <code>&lt;StiDataParameter&gt;</code> en el DataSource — ya no es soportado por la version actual de Stimulsoft y produce <code>ArgumentException: Int32 is not a valid value for Int32. (Parameter &#39;value&#39;)</code> durante <code>report.Load()</code>.</p><p>2. <strong>En la URL del viewer</strong>: pasa el valor via el query param <code>parameters</code> (NO como query param directo):</p><p>Snippet 2:</p><p> Sintaxis <code>parameters</code> URL-encoded: <code>nombre||eq||valor</code> (separados por <code>@</code> para multiples parametros).</p><p>3. <strong>Desde una row action o link UI</strong>: usa el callback canonico para construir la URL:</p><p>Snippet 3:</p><h2>Configuracion backend obligatoria</h2><p>En el <code>appsettings.json</code> de la app, debe existir <code>AppSettings.reportQueryTimeout</code> (ej. <code>&quot;120&quot;</code>). Sin el, <code>ReportViewerController.prepareDataSql</code> lanza <code>int.Parse(null)</code> → 500. Ejemplo:</p><p>Snippet 4:</p>",
17574
+ "codeSamples": [
17575
+ {
17576
+ "id": "code_1",
17577
+ "title": "Snippet 1",
17578
+ "language": "sql",
17579
+ "code": " SELECT ... FROM fatture_inviate WHERE id = @fattura_id\n SELECT ... FROM fatture_inviate_righe WHERE fattura_id = @fattura_id"
17580
+ },
17581
+ {
17582
+ "id": "code_2",
17583
+ "title": "Snippet 2",
17584
+ "language": "text",
17585
+ "code": " ✅ #/<route>/report-viewer?reportName=Report.mrt&parameters=fattura_id%7C%7Ceq%7C%7C42\n ❌ #/<route>/report-viewer?reportName=Report.mrt&fattura_id=42 <-- ignorado"
17586
+ },
17587
+ {
17588
+ "id": "code_3",
17589
+ "title": "Snippet 3",
17590
+ "language": "js",
17591
+ "code": " const recordId = (record && record.id && record.id.value) ?? (record && record.id);\n const params = 'fattura_id||eq||' + recordId;\n window.location.hash = '#/<route>/report-viewer?reportName=Report.mrt&parameters='\n + encodeURIComponent(params);"
17592
+ },
17593
+ {
17594
+ "id": "code_4",
17595
+ "title": "Snippet 4",
17596
+ "language": "json",
17597
+ "code": "\"AppSettings\": {\n \"reportQueryTimeout\": \"120\"\n}"
17598
+ }
17599
+ ]
17082
17600
  }
17083
17601
  ],
17084
17602
  "images": [
@@ -17811,28 +18329,54 @@ const frameworkDocsContent = {
17811
18329
  },
17812
18330
  {
17813
18331
  "id": "sec_5",
17814
- "title": "Flujo Asistido: Clonar Lookup + Filtro Default + Lookup Hierarchy"
18332
+ "title": "Combo slim — campos extra vía `mc_props_bag.slimCombo`"
17815
18333
  },
17816
18334
  {
17817
18335
  "id": "sec_6",
17818
- "title": "Efectos Cliente/Servidor"
18336
+ "title": "Flujo Asistido: Clonar Lookup + Filtro Default + Lookup Hierarchy"
17819
18337
  },
17820
18338
  {
17821
18339
  "id": "sec_7",
18340
+ "title": "Efectos Cliente/Servidor"
18341
+ },
18342
+ {
18343
+ "id": "sec_8",
17822
18344
  "title": "Notas operativas"
18345
+ },
18346
+ {
18347
+ "id": "sec_9",
18348
+ "title": "Auto-fetch sobre cambio programatico de FK"
17823
18349
  }
17824
18350
  ],
17825
18351
  "sections": [
17826
18352
  {
17827
18353
  "id": "overview",
17828
18354
  "title": "Overview",
17829
- "html": "<h1>Widget Lookup</h1><p>Widget de columna para relaciones (<code>lookupByID</code>, <code>multiple_check</code>) con tab dedicado <strong>Lookup</strong> en el metadata-editor de columnas.</p><h2>Ejemplos</h2><ul><li><a class=\"wuic-demo-link\" href=\"#/examples/3c-odata-cities-grid\" target=\"_blank\" rel=\"noopener noreferrer\">Patrón 3c — lookup puro-OData (State/Province desde `/odata/Stateprovinces`)</a> — demuestra <code>mc_props_bag.lookup.endpoint</code> con provider <code>odata</code> y rendering inline mediante <code>$expand</code>.</li></ul><h2>Cuándo usarlo</h2><ul><li>selección de registros relacionados desde una tabla metadata externa.</li><li>escenarios con búsqueda server-side y paging de lookup.</li><li>soporte de selección simple (<code>lookupByID</code>) y selección múltiple (<code>multiple_check</code>).</li></ul><h2>Propiedades metadata de columna (tab Lookup)</h2><ul><li><code>mc_ui_lookup_entity_name</code>: route metadata fuente del lookup.</li><li><code>mc_ui_lookup_dataValueField</code>: campo ID devuelto.</li><li><code>mc_ui_lookup_dataTextField</code>: campo descripción mostrado.</li><li><code>mc_ui_lookup_filter</code>: filtro base del lookup.</li><li><code>mc_serverside_operations</code>: búsqueda/paging server-side.</li><li><code>mc_ui_pagesize</code>: tamaño de página de resultados del lookup.</li><li><code>mc_ui_lookup_edit_allow</code>: habilita edición de registro lookup desde el popup.</li><li><code>mc_ui_lookup_insert_allow</code>: habilita inserción de registro lookup desde el popup.</li><li><code>mc_ui_lookup_search_grid</code>: habilita grilla de búsqueda en el popup.</li><li><code>mc_logic_allow_navigation</code>: navegación a la route conectada.</li></ul><h2>mc_props_bag útil</h2><p>Snippet 1:</p><ul><li><code>lookup.filter</code>: restricción adicional del lado cliente/servidor en la query del lookup.</li><li><code>lookup.virtualize</code>: habilita virtual scroll en <code>p-autocomplete</code> del widget lookup.</li><li><code>lookup.virtualize.enabled</code>: parser tolerante (<code>true/false</code>, <code>1/0</code>, cadenas equivalentes); default <code>false</code> cuando ausente.</li><li><code>lookup.virtualize.itemSize</code>: altura de fila virtual en px; default <code>44</code>.</li><li><code>lookup.endpoint</code>: override del provider de datos del dropdown. Cuando está configurado, el nested datasource del lookup-editor ya no pasa por el combo endpoint metadata (<code>MetaService.getFlatRecordComboData</code>) sino directamente por el provider declarado en <code>type</code> con la URL en <code>uri</code>.</li><li><code>lookup.endpoint.type</code>: <code>&#39;odata&#39;</code> (el dispatcher enruta a <code>DataProviderOdataService.selectCombo</code> que emite GET con <code>$top</code>, <code>$count=true</code>, <code>$select=valueField,textField</code> y <code>$filter=contains(textField,&#39;&lt;query&gt;&#39;)</code> para la búsqueda server-side). Otros valores siguen la misma lógica del table-level <code>extraProps.endpoint.type</code>.</li><li><code>lookup.endpoint.uri</code>: URL base del entity set (por ejemplo, <code>/odata/&lt;EntitySet&gt;</code>), con o sin origen explícito. Los query params existentes se ignoran — el provider reconstruye los params en cada fetch.</li><li><code>form.columns</code>: ancho del campo en el formulario de edición.</li><li><code>style.editCss</code>: estilo custom del editor.</li></ul><h2>Flujo Asistido: Clonar Lookup + Filtro Default + Lookup Hierarchy</h2><ul><li>Flujo operativo inline: <code>suggestLookup2 -&gt; suggestLookupDefaultFilter -&gt; getLookupListByRoute -&gt; getSeletClauseByLookupHierarchy</code>.</li><li>Snippet inline de campos principales: <code>mc_ui_lookup_entity_name</code>, <code>mc_ui_lookup_dataValueField</code>, <code>mc_ui_lookup_dataTextField</code>, <code>mc_ui_lookup_filter</code>.</li><li>Snippet inline de filtro rápido: <code>lookup.filter: {&quot;logic&quot;:&quot;AND&quot;,&quot;filters&quot;:[{&quot;field&quot;:&quot;is_active&quot;,&quot;operatore&quot;:&quot;eq&quot;,&quot;value&quot;:true}]}</code>.</li><li>Efecto cliente: configuración acelerada del lookup en el metadata-editor y filtro coherente ya listo en el widget.</li><li>Efecto servidor: la query del lookup y la select clause quedan alineadas a los endpoints <code>MetaService.*</code> sin mapeo manual.</li></ul><p>Checklist operativa rápida:</p><p>1. clonar configuración base con <code>suggestLookup2</code>;</p><p>2. generar filtro default con <code>suggestLookupDefaultFilter</code>;</p><p>3. refinar jerarquía/select clause desde el tree del lookup;</p><p>4. validar en runtime que la búsqueda y el mapeo value/text sean coherentes.</p><h2>Efectos Cliente/Servidor</h2><ul><li>Cliente: renderizado del widget lookup, búsqueda, binding etiqueta/valor.</li><li>Cliente: con <code>lookup.virtualize</code> activo, el autocomplete renderiza solo el subconjunto visual de las opciones.</li><li>Servidor: query de lookup paginada/filtrada mediante <code>MetaService.getFlatRecordData</code>.</li></ul><h2>Notas operativas</h2><ul><li>en <code>multiple_check</code> la parte de lookup es la misma, cambia el renderer multi-valor.</li><li>si falta <code>mc_ui_lookup_entity_name</code>, el widget no puede resolver la fuente.</li></ul>",
18355
+ "html": "<h1>Widget Lookup</h1><p>Widget de columna para relaciones (<code>lookupByID</code>, <code>multiple_check</code>) con tab dedicado <strong>Lookup</strong> en el metadata-editor de columnas.</p><h2>Ejemplos</h2><ul><li><a class=\"wuic-demo-link\" href=\"#/examples/3c-odata-cities-grid\" target=\"_blank\" rel=\"noopener noreferrer\">Patrón 3c — lookup puro-OData (State/Province desde `/odata/Stateprovinces`)</a> — demuestra <code>mc_props_bag.lookup.endpoint</code> con provider <code>odata</code> y rendering inline mediante <code>$expand</code>.</li></ul><h2>Cuándo usarlo</h2><ul><li>selección de registros relacionados desde una tabla metadata externa.</li><li>escenarios con búsqueda server-side y paging de lookup.</li><li>soporte de selección simple (<code>lookupByID</code>) y selección múltiple (<code>multiple_check</code>).</li></ul><h2>Propiedades metadata de columna (tab Lookup)</h2><ul><li><code>mc_ui_lookup_entity_name</code>: route metadata fuente del lookup.</li><li><code>mc_ui_lookup_dataValueField</code>: campo ID devuelto.</li><li><code>mc_ui_lookup_dataTextField</code>: campo descripción mostrado.</li><li><code>mc_ui_lookup_filter</code>: filtro base del lookup.</li><li><code>mc_serverside_operations</code>: búsqueda/paging server-side.</li><li><code>mc_ui_pagesize</code>: tamaño de página de resultados del lookup.</li><li><code>mc_ui_lookup_edit_allow</code>: habilita edición de registro lookup desde el popup.</li><li><code>mc_ui_lookup_insert_allow</code>: habilita inserción de registro lookup desde el popup.</li><li><code>mc_ui_lookup_search_grid</code>: habilita grilla de búsqueda en el popup.</li><li><code>mc_logic_allow_navigation</code>: navegación a la route conectada.</li></ul><h2>mc_props_bag útil</h2><p>Snippet 1:</p><ul><li><code>lookup.filter</code>: restricción adicional del lado cliente/servidor en la query del lookup.</li><li><code>lookup.virtualize</code>: habilita virtual scroll en <code>p-autocomplete</code> del widget lookup.</li><li><code>lookup.virtualize.enabled</code>: parser tolerante (<code>true/false</code>, <code>1/0</code>, cadenas equivalentes); default <code>false</code> cuando ausente.</li><li><code>lookup.virtualize.itemSize</code>: altura de fila virtual en px; default <code>44</code>.</li><li><code>lookup.endpoint</code>: override del provider de datos del dropdown. Cuando está configurado, el nested datasource del lookup-editor ya no pasa por el combo endpoint metadata (<code>MetaService.getFlatRecordComboData</code>) sino directamente por el provider declarado en <code>type</code> con la URL en <code>uri</code>.</li><li><code>lookup.endpoint.type</code>: <code>&#39;odata&#39;</code> (el dispatcher enruta a <code>DataProviderOdataService.selectCombo</code> que emite GET con <code>$top</code>, <code>$count=true</code>, <code>$select=valueField,textField</code> y <code>$filter=contains(textField,&#39;&lt;query&gt;&#39;)</code> para la búsqueda server-side). Otros valores siguen la misma lógica del table-level <code>extraProps.endpoint.type</code>.</li><li><code>lookup.endpoint.uri</code>: URL base del entity set (por ejemplo, <code>/odata/&lt;EntitySet&gt;</code>), con o sin origen explícito. Los query params existentes se ignoran — el provider reconstruye los params en cada fetch.</li><li><code>form.columns</code>: ancho del campo en el formulario de edición.</li><li><code>style.editCss</code>: estilo custom del editor.</li></ul><h2>Combo slim — campos extra vía <code>mc_props_bag.slimCombo</code></h2><p>El dropdown lookup por defecto emite un SELECT mínimo sobre la tabla relacionada: PK + <code>mc_ui_lookup_dataValueField</code> + <code>mc_ui_lookup_dataTextField</code>. Reduce el payload del combo. Cuando el cliente necesita otras columnas de la tabla relacionada — típicamente para pre-poblar campos dependientes vía <code>mc_selection_changed_custom_function__fn</code> — se usa <code>mc_props_bag.slimCombo</code> como <strong>array de nombres de columnas extra</strong> a incluir en el SELECT del combo:</p><p>Snippet 2:</p><ul><li>Array: los nombres se añaden al <code>columnRestrictionList</code> de la respuesta combo (lógica en <code>DataProviderMetaService.buildSlimComboRestriction</code>).</li><li><code>slimCombo: false</code>: deshabilita la restricción → el combo retorna <strong>todas</strong> las columnas. Útil para debug, payload más grande.</li><li>Si la columna tiene <code>mc_ui_lookup_combo_text_edit_computed_dataTextField</code> valorado (fórmula display computada), <code>slimCombo</code> se ignora y el combo retorna todas las columnas (la fórmula puede referenciar campos arbitrarios).</li></ul><p>Los campos extra van a parar a <code>record[&#39;&lt;col&gt;__lookup_obj&#39;].value</code> (registro completo de la tabla relacionada, ya con los campos declarados en <code>slimCombo</code>) y son accesibles desde el callback <code>mc_selection_changed_custom_function__fn</code>:</p><p>Snippet 3:</p><p>Tradeoff: payload combo ligeramente más grande pero sin roundtrip HTTP adicional (<code>getFlatRecordData</code> para recuperar el registro completo por id) en cada selección.</p><h2>Flujo Asistido: Clonar Lookup + Filtro Default + Lookup Hierarchy</h2><ul><li>Flujo operativo inline: <code>suggestLookup2 -&gt; suggestLookupDefaultFilter -&gt; getLookupListByRoute -&gt; getSeletClauseByLookupHierarchy</code>.</li><li>Snippet inline de campos principales: <code>mc_ui_lookup_entity_name</code>, <code>mc_ui_lookup_dataValueField</code>, <code>mc_ui_lookup_dataTextField</code>, <code>mc_ui_lookup_filter</code>.</li><li>Snippet inline de filtro rápido: <code>lookup.filter: {&quot;logic&quot;:&quot;AND&quot;,&quot;filters&quot;:[{&quot;field&quot;:&quot;is_active&quot;,&quot;operatore&quot;:&quot;eq&quot;,&quot;value&quot;:true}]}</code>.</li><li>Efecto cliente: configuración acelerada del lookup en el metadata-editor y filtro coherente ya listo en el widget.</li><li>Efecto servidor: la query del lookup y la select clause quedan alineadas a los endpoints <code>MetaService.*</code> sin mapeo manual.</li></ul><p>Checklist operativa rápida:</p><p>1. clonar configuración base con <code>suggestLookup2</code>;</p><p>2. generar filtro default con <code>suggestLookupDefaultFilter</code>;</p><p>3. refinar jerarquía/select clause desde el tree del lookup;</p><p>4. validar en runtime que la búsqueda y el mapeo value/text sean coherentes.</p><h2>Efectos Cliente/Servidor</h2><ul><li>Cliente: renderizado del widget lookup, búsqueda, binding etiqueta/valor.</li><li>Cliente: con <code>lookup.virtualize</code> activo, el autocomplete renderiza solo el subconjunto visual de las opciones.</li><li>Servidor: query de lookup paginada/filtrada mediante <code>MetaService.getFlatRecordData</code>.</li></ul><h2>Notas operativas</h2><ul><li>en <code>multiple_check</code> la parte de lookup es la misma, cambia el renderer multi-valor.</li><li>si falta <code>mc_ui_lookup_entity_name</code>, el widget no puede resolver la fuente.</li></ul><h2>Auto-fetch sobre cambio programatico de FK</h2><p>El <code>wuic-lookup-editor</code> se suscribe automaticamente al <code>BehaviorSubject</code> de <code>record[mc_nome_colonna]</code> y maneja los cambios de FK programaticos (ej. import desde documento fuente, callback custom que hace <code>record.cliente_id.next(99)</code>):</p><ul><li>Si el nuevo valor esta <strong>ya en `items`</strong> (la pagina cargada del lookup): solo se actualiza <code>__lookup_obj</code> por coherencia, sin HTTP.</li><li>Si el nuevo valor <strong>no esta en items</strong> (ej. valor fuera de la pagina actual del combo, o aun no fetcheado): se dispara un <strong>fetch second-stage</strong> filtrado <code>WHERE &lt;valueField&gt; eqor &lt;newValue&gt;</code> para recuperar el registro relacionado y llenar automaticamente el dropdown.</li></ul><p>Skip cases (no-op):</p><ul><li>Lookup multi-valor (<code>isFilter</code> con <code>eqor</code>, <code>multiple_check</code>)</li><li>Primer emit del BS (ya cubierto por el flujo init)</li><li>Valor null/undefined (usuario clearo)</li><li><code>hasActiveLookupQuery=true</code> (el usuario esta tipeando una query manual)</li></ul><p><strong>Implicacion para custom forms</strong>: setear solo el FK basta — ya no hacen falta workaround manuales para llenar <code>__lookup_obj</code> o el alias joined. El display del dropdown se actualiza solo.</p><p>Snippet 4:</p><p>&gt; <strong>Nota</strong>: el auto-fetch solo se dispara cuando el <code>&lt;wuic-lookup-editor&gt;</code> esta <strong>montado</strong> (celda en edit mode). Para el display en <strong>lista</strong> (celda read-only), <code>formatGridViewValue</code> lee el alias joined <code>&lt;route&gt;___&lt;textField&gt;__&lt;col&gt;</code> directamente del record — ese debe seguir siendo llenado manualmente si no viene del backend.</p>",
17830
18356
  "codeSamples": [
17831
18357
  {
17832
18358
  "id": "code_1",
17833
18359
  "title": "Snippet 1",
17834
18360
  "language": "json",
17835
18361
  "code": "{\n \"lookup\": {\n \"filter\": {\n \"logic\": \"AND\",\n \"filters\": [\n { \"field\": \"is_active\", \"operatore\": \"eq\", \"value\": true }\n ]\n },\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n },\n \"endpoint\": {\n \"type\": \"odata\",\n \"uri\": \"http://localhost:5000/odata/StateProvinces\"\n }\n },\n \"form\": {\n \"columns\": 1,\n \"disabled\": false\n },\n \"style\": {\n \"editCss\": \"max-width: 420px;\"\n }\n}"
18362
+ },
18363
+ {
18364
+ "id": "code_2",
18365
+ "title": "Snippet 2",
18366
+ "language": "json",
18367
+ "code": "{\n \"slimCombo\": [\"unita_misura_id\",\"codice_iva_id\",\"prezzo_vendita\",\"sconto_default\"]\n}"
18368
+ },
18369
+ {
18370
+ "id": "code_3",
18371
+ "title": "Snippet 3",
18372
+ "language": "ts",
18373
+ "code": "prodCol.mc_selection_changed_custom_function__fn = (record, _f, _m, newValue) => {\n const prod = record['prodotto_id__lookup_obj']?.value;\n if (!prod) return;\n record['descrizione']?.next(prod.descrizione);\n record['prezzo_unitario']?.next(prod.prezzo_vendita);\n record['unita_misura_id']?.next(prod.unita_misura_id);\n record['codice_iva_id']?.next(prod.codice_iva_id);\n};"
18374
+ },
18375
+ {
18376
+ "id": "code_4",
18377
+ "title": "Snippet 4",
18378
+ "language": "ts",
18379
+ "code": "// ANTES (workaround manual requerido)\nrecord.cliente_id.next(99);\nrecord.cliente_id__lookup_obj.next({ id: 99, ragione_sociale: 'Acme S.r.l.' });\nrecord['clienti___ragione_sociale__cliente_id'].next('Acme S.r.l.');\n\n// AHORA (auto-fetch del lookup-editor)\nrecord.cliente_id.next(99);\n// → lookup-editor montado? Subscribe detecta el change → fetch filtrada → __lookup_obj llenado → dropdown muestra \"Acme S.r.l.\""
17836
18380
  }
17837
18381
  ]
17838
18382
  }
@@ -18095,10 +18639,14 @@ const frameworkDocsContent = {
18095
18639
  },
18096
18640
  {
18097
18641
  "id": "sec_4",
18098
- "title": "Flujo operativo recomendado"
18642
+ "title": "Menú contextual de los elementos"
18099
18643
  },
18100
18644
  {
18101
18645
  "id": "sec_5",
18646
+ "title": "Flujo operativo recomendado"
18647
+ },
18648
+ {
18649
+ "id": "sec_6",
18102
18650
  "title": "Notas operativas"
18103
18651
  }
18104
18652
  ],
@@ -18106,7 +18654,7 @@ const frameworkDocsContent = {
18106
18654
  {
18107
18655
  "id": "overview",
18108
18656
  "title": "Overview",
18109
- "html": "<h1>Designer</h1><p>Editor visual para dashboards metadata-driven en la route <code>#/cities/dashboard</code>.</p><h2>Scope</h2><ul><li>Composición de boards mediante drag-and-drop de nodos y bloques UI.</li><li>Layout y estilos controlados por <code>boardcontent</code> y metadata de tabla/columna.</li><li>Edición en el panel de propiedades sin hardcode en los componentes runtime.</li></ul><h2>Palette Designer</h2><ul><li><code>Datasource</code>: fuente de datos metadata (<code>route</code>, filtros, orden, paginación).</li><li><code>DataRepeater</code>: visualización de lista/tarjeta vinculada a un datasource.</li><li><code>Dynamic Template</code>: renderizado de plantilla dinámica con inputs metadata-driven.</li><li><code>Action/Button</code>: acciones de usuario vinculadas a registros y callbacks.</li><li><code>Container/Layout</code>: bloques estructurales para columnas, filas y wrappers.</li></ul><h2>Funcionalidades principales</h2><ul><li>Drag-and-drop de nodos sobre el canvas con persistencia en el <code>boardcontent</code>.</li><li>Panel de propiedades contextual con inputs coherentes con los componentes seleccionados.</li><li>Binding entre nodos (<code>datasource -&gt; repeater/template</code>) con validación mínima.</li><li>Preview runtime integrada y realineamiento de layout.</li><li>Gestión de cambios pendientes con guard antes de salir.</li></ul><h2>Flujo operativo recomendado</h2><ul><li>Abrir la route del dashboard objetivo.</li><li>Insertar/vincular nodos desde la palette.</li><li>Configurar los <code>inputs</code> esenciales (ej. <code>route</code>, <code>action</code>, <code>datasource</code>).</li><li>Guardar y validar el resultado en runtime.</li></ul><h2>Notas operativas</h2><ul><li>Para correcciones específicas del dashboard, privilegiar patches del <code>boardcontent</code> en lugar de modificaciones globales.</li><li>Evitar propiedades duplicadas entre <code>inputs</code> y estilos inline no coherentes.</li><li>Verificar siempre que los datasources tengan <code>inputs.route</code> valorizada.</li></ul>",
18657
+ "html": "<h1>Designer</h1><p>Editor visual para dashboards metadata-driven en la route <code>#/cities/dashboard</code>.</p><h2>Scope</h2><ul><li>Composición de boards mediante drag-and-drop de nodos y bloques UI.</li><li>Layout y estilos controlados por <code>boardcontent</code> y metadata de tabla/columna.</li><li>Edición en el panel de propiedades sin hardcode en los componentes runtime.</li></ul><h2>Palette Designer</h2><ul><li><code>Datasource</code>: fuente de datos metadata (<code>route</code>, filtros, orden, paginación).</li><li><code>DataRepeater</code>: visualización de lista/tarjeta vinculada a un datasource.</li><li><code>Dynamic Template</code>: renderizado de plantilla dinámica con inputs metadata-driven.</li><li><code>Action/Button</code>: acciones de usuario vinculadas a registros y callbacks.</li><li><code>Container/Layout</code>: bloques estructurales para columnas, filas y wrappers.</li></ul><h2>Funcionalidades principales</h2><ul><li>Drag-and-drop de nodos sobre el canvas con persistencia en el <code>boardcontent</code>.</li><li>Panel de propiedades contextual con inputs coherentes con los componentes seleccionados.</li><li>Binding entre nodos (<code>datasource -&gt; repeater/template</code>) con validación mínima.</li><li>Preview runtime integrada y realineamiento de layout.</li><li>Gestión de cambios pendientes con guard antes de salir.</li></ul><h2>Menú contextual de los elementos</h2><p>Clic derecho sobre un elemento del canvas, sobre un elemento del breadcrumb inferior o sobre un nodo del árbol de jerarquía muestra un menú contextual con tres opciones:</p><ul><li><strong>Editar propiedades</strong> (pi-cog) — selecciona el elemento como &quot;current tool&quot; y muestra su panel de propiedades a la derecha; equivalente al clic en el árbol. Útil para editar propiedades de elementos anidados sin tener que buscarlos en la jerarquía.</li><li><strong>Renombrar</strong> (solo breadcrumb) — abre un diálogo para renombrar el <code>displayName</code>.</li><li><strong>Eliminar</strong> — borra el elemento del board.</li></ul><h2>Flujo operativo recomendado</h2><ul><li>Abrir la route del dashboard objetivo.</li><li>Insertar/vincular nodos desde la palette.</li><li>Configurar los <code>inputs</code> esenciales (ej. <code>route</code>, <code>action</code>, <code>datasource</code>).</li><li>Guardar y validar el resultado en runtime.</li></ul><h2>Notas operativas</h2><ul><li>Para correcciones específicas del dashboard, privilegiar patches del <code>boardcontent</code> en lugar de modificaciones globales.</li><li>Evitar propiedades duplicadas entre <code>inputs</code> y estilos inline no coherentes.</li><li>Verificar siempre que los datasources tengan <code>inputs.route</code> valorizada.</li></ul>",
18110
18658
  "codeSamples": []
18111
18659
  }
18112
18660
  ],
@@ -18170,14 +18718,18 @@ const frameworkDocsContent = {
18170
18718
  },
18171
18719
  {
18172
18720
  "id": "sec_3",
18173
- "title": "Flujo operativo recomendado"
18721
+ "title": "Pestaña View Builder — Agregaciones y GROUP BY (Tier 1, 2026-05-08)"
18174
18722
  },
18175
18723
  {
18176
18724
  "id": "sec_4",
18177
- "title": "Botones de acción"
18725
+ "title": "Flujo operativo recomendado"
18178
18726
  },
18179
18727
  {
18180
18728
  "id": "sec_5",
18729
+ "title": "Botones de acción"
18730
+ },
18731
+ {
18732
+ "id": "sec_6",
18181
18733
  "title": "Notas de uso"
18182
18734
  }
18183
18735
  ],
@@ -18185,7 +18737,7 @@ const frameworkDocsContent = {
18185
18737
  {
18186
18738
  "id": "overview",
18187
18739
  "title": "Overview",
18188
- "html": "<h1>Constructor Vista/Pivot</h1><p>El Pivot Builder permite construir vistas analíticas dinámicas partiendo de las routes metadata, sin escribir queries manuales del lado UI.</p><h2>Screenshot reference</h2><ul><li><code>manual__pivot_builder__01.png</code>: configuración y resultado del builder en runtime.</li><li><code>manual__pivot_builder__02.png</code>: detalle del diálogo de creación de tabla materializada y opciones operativas.</li></ul><h2>Funcionalidades principales</h2><ul><li>Selección de campos en áreas <code>rows</code>, <code>columns</code> y <code>values</code>.</li><li>Agregaciones disponibles sobre los valores (suma, conteo, promedio, mínimo, máximo).</li><li>Agrupamiento multidimensional con ordenamiento por dimensión.</li><li>Filtros interactivos para reducir el dataset analizado.</li><li>Actualización de la vista pivot en tiempo real tras cambio de layout o métrica.</li><li>Exportación/reutilización de la configuración pivot en el contexto operativo.</li></ul><h2>Flujo operativo recomendado</h2><p>1. Abre la route del pivot builder.</p><p>2. Selecciona datasource/route metadata de partida.</p><p>3. Arrastra los campos a las zonas <code>rows</code>, <code>columns</code>, <code>values</code>.</p><p>4. Configura la agregación para cada métrica.</p><p>5. Aplica filtros y verifica los totales.</p><p>6. Guarda o reutiliza la configuración según el flujo aplicativo.</p><h2>Botones de acción</h2><p><code>Crear vista</code></p><ul><li>Genera una vista SQL a partir de la configuración pivot actual.</li><li>Útil cuando quieres reutilizar el resultado como fuente de datos estable en otras routes/reportes.</li><li>Mantiene la lógica centralizada en el DB, evitando reconstruir cada vez el layout manualmente.</li></ul><p><code>Crear tabla materializada</code></p><ul><li>Crea una tabla física con los datos agregados de la configuración pivot.</li><li>Útil para datasets grandes o dashboards donde se necesitan tiempos de lectura rápidos.</li><li>Genera automáticamente el scaffolding de la route asociada a la tabla física creada y genera una programación para refresh/alineación de datos.</li><li>Presionando Actualizar en el grid de la route asociada se fuerza una ejecución de la programación.</li></ul><h2>Notas de uso</h2><ul><li>Para análisis coherentes, usar columnas con tipología de datos correcta (numéricas para métricas, textuales/fechas para dimensiones).</li><li>En presencia de grandes volúmenes, preferir filtros y agregaciones específicas antes de expandir el detalle.</li></ul>",
18740
+ "html": "<h1>Constructor Vista/Pivot</h1><p>El Pivot Builder permite construir vistas analíticas dinámicas partiendo de las routes metadata, sin escribir queries manuales del lado UI.</p><h2>Screenshot reference</h2><ul><li><code>manual__pivot_builder__01.png</code>: configuración y resultado del builder en runtime.</li><li><code>manual__pivot_builder__02.png</code>: detalle del diálogo de creación de tabla materializada y opciones operativas.</li></ul><h2>Funcionalidades principales</h2><ul><li>Selección de campos en áreas <code>rows</code>, <code>columns</code> y <code>values</code>.</li><li>Agregaciones disponibles sobre los valores (suma, conteo, promedio, mínimo, máximo).</li><li>Agrupamiento multidimensional con ordenamiento por dimensión.</li><li>Filtros interactivos para reducir el dataset analizado.</li><li>Actualización de la vista pivot en tiempo real tras cambio de layout o métrica.</li><li>Exportación/reutilización de la configuración pivot en el contexto operativo.</li></ul><h2>Pestaña View Builder — Agregaciones y GROUP BY (Tier 1, 2026-05-08)</h2><p>La pestaña 1 &quot;View Builder&quot; ahora permite emitir <code>GROUP BY</code> con agregaciones <code>SUM/COUNT/AVG/MIN/MAX/COUNT(DISTINCT)</code> directamente desde la <code>ViewDefinition</code>, sin pasar a modo manualSql.</p><p>Panel bajo el canvas:</p><ul><li><strong>Agregaciones</strong>: formulario para añadir <code>función(columna) AS alias</code>. La columna fuente puede ser cualquier columna del canvas o <code>*</code> (para <code>COUNT(*)</code>); el alias de salida se genera automáticamente si está vacío.</li><li><strong>GROUP BY</strong>: checkbox por cada columna seleccionada en los nodos del canvas. Si ninguna está marcada explícitamente, el backend infiere las dimensiones de las columnas <code>selected=true</code> no agregadas.</li><li><strong>CASE WHEN para pivoting</strong>: para patrones como <code>SUM(CASE WHEN bucket=&#39;X&#39; THEN importe ELSE 0 END)</code> usa una columna fórmula (clic derecho → Editar fórmula) como fuente de agregación. La fórmula se emite como argumento del agregado.</li></ul><p>Salida backend (<a href=\"../../Services/MetaService.cs\">`MetaService.previewViewDefinition`</a>):</p><ul><li><code>viewDefinition.aggregations[]</code> → emite <code>&lt;FN&gt;(&lt;col|fórmula&gt;) AS &lt;alias&gt;</code> como columnas SELECT adicionales</li><li><code>viewDefinition.groupByColumns[]</code> → emite la cláusula <code>GROUP BY</code></li><li>Inferencia automática de GROUP BY si hay <code>aggregations</code> sin <code>groupByColumns</code> explícito</li></ul><h3>Bloques UNION (Tier 2, 2026-05-08)</h3><p>Una extensión posterior permite concatenar varios SELECT con <code>UNION</code> o <code>UNION ALL</code> directamente desde una sola <code>ViewDefinition</code>:</p><ul><li><strong>Bloque 0</strong> = nivel superior (tablas/joins/agregaciones en el canvas) → define el <strong>vocabulario canónico</strong> de columnas SELECT</li><li><strong>Bloque N (N≥1)</strong> = cada <code>unionBlocks[]</code> adicional es una sub-definición de tabla única con un <code>columnMapping</code> obligatorio: para cada alias canónico del bloque 0, el bloque N especifica <code>sourceQualified</code> (columna del bloque) <strong>o</strong> <code>constantValue</code> (literal SQL como <code>&#39;CLIENTE&#39;</code>) <strong>o</strong> <code>formula</code> (expresión SQL para cast cross-block)</li><li><strong>Toggle UNION ALL / UNION</strong>: por defecto <code>unionAll: true</code> (sin dedup, mejor rendimiento); <code>false</code> para <code>UNION DISTINCT</code></li><li><strong>TOP/LIMIT</strong>: se aplica a toda la UNION, envuelto en subquery <code>SELECT TOP N * FROM (&lt;union&gt;) AS u</code></li></ul><p>UI bajo el canvas: selector de ruta + botón &quot;Añadir bloque UNION&quot; → cada bloque aparece como acordeón con cabecera (caption + ruta) + matriz column-mapping (por alias canónico: dropdown de columna fuente del bloque, o input constante SQL).</p><p>Límites v0: cada bloque UNION es de tabla única (sin JOIN dentro del bloque). Para JOIN cross-block (p.ej. categoría &#39;AMBOS&#39; de <code>vw_anagrafica_unificata</code> que requiere <code>clientes INNER JOIN proveedores ON nif</code>) se necesita Tier 2.5.</p><h2>Flujo operativo recomendado</h2><p>1. Abre la route del pivot builder.</p><p>2. Selecciona datasource/route metadata de partida.</p><p>3. Arrastra los campos a las zonas <code>rows</code>, <code>columns</code>, <code>values</code>.</p><p>4. Configura la agregación para cada métrica.</p><p>5. Aplica filtros y verifica los totales.</p><p>6. Guarda o reutiliza la configuración según el flujo aplicativo.</p><h2>Botones de acción</h2><p><code>Crear vista</code></p><ul><li>Genera una vista SQL a partir de la configuración pivot actual.</li><li>Útil cuando quieres reutilizar el resultado como fuente de datos estable en otras routes/reportes.</li><li>Mantiene la lógica centralizada en el DB, evitando reconstruir cada vez el layout manualmente.</li></ul><p><code>Crear tabla materializada</code></p><ul><li>Crea una tabla física con los datos agregados de la configuración pivot.</li><li>Útil para datasets grandes o dashboards donde se necesitan tiempos de lectura rápidos.</li><li>Genera automáticamente el scaffolding de la route asociada a la tabla física creada y genera una programación para refresh/alineación de datos.</li><li>Presionando Actualizar en el grid de la route asociada se fuerza una ejecución de la programación.</li></ul><h2>Notas de uso</h2><ul><li>Para análisis coherentes, usar columnas con tipología de datos correcta (numéricas para métricas, textuales/fechas para dimensiones).</li><li>En presencia de grandes volúmenes, preferir filtros y agregaciones específicas antes de expandir el detalle.</li></ul>",
18189
18741
  "codeSamples": []
18190
18742
  }
18191
18743
  ],
@@ -19140,7 +19692,7 @@ const frameworkDocsContent = {
19140
19692
  {
19141
19693
  "id": "overview",
19142
19694
  "title": "Overview",
19143
- "html": "<h1>Component Catalog</h1><p>Total component folders detected: 48.</p><h2>Available components</h2><ul><li>app-settings-editor</li><li>archetype-configurator</li><li>bounded-repeater</li><li>carousel-list</li><li>chart-list</li><li>code-editor</li><li>css-sheet-editor</li><li>dashboard</li><li>data-action-button</li><li>data-repeater</li><li>data-source</li><li>designer</li><li>dynamic-dashboard-template</li><li>dynamic-field-template</li><li>dynamic-form-template</li><li>dynamic-generic-template</li><li>dynamic-repeater-template</li><li>dynamic-template</li><li>field</li><li>filter-bar</li><li>first-run-wizard</li><li>framework-docs</li><li>image-wrapper</li><li>import-export-button</li><li>kanban-list</li><li>list-grid</li><li>map-list</li><li>meta-menu</li><li>metadata-editor</li><li>notification-bell</li><li>pager</li><li>parametric-dialog</li><li>pivot-builder</li><li>prompt-dialog-fallback-host</li><li>rag-chatbot</li><li>rag-chatbot-fab</li><li>report-designer</li><li>report-viewer</li><li>scheduler-list</li><li>split</li><li>split-area</li><li>spreadsheet-list-sf</li><li>tab-panel-wrapper</li><li>tab-view-wrapper</li><li>tree-list</li><li>upload-dialog</li><li>workflow-designer</li><li>workflow-runner</li></ul><h2>Exports from public-api</h2><ul><li>data-source/data-source.component</li><li>list-grid/list-grid.component</li><li>import-export-button/import-export-button.component</li><li>pager/pager.component</li><li>filter-bar/filter-bar.component</li><li>map-list/map-list.component</li><li>carousel-list/carousel-list.component</li><li>tree-list/tree-list.component</li><li>parametric-dialog/parametric-dialog.component</li><li>upload-dialog/upload-dialog.component</li><li>app-settings-editor/app-settings-editor.component</li><li>data-action-button/data-action-button.component</li><li>meta-menu/meta-menu.component</li><li>field/text-editor/text-editor.component</li><li>field/text-area-editor/text-area-editor.component</li><li>field/number-editor/number-editor.component</li><li>field/lookup-editor/lookup-editor.component</li><li>field/dictionary-editor/dictionary-editor.component</li><li>field/boolean-editor/boolean-editor.component</li><li>field/date-editor/date-editor.component</li><li>field/html-editor/html-editor.component</li><li>field/upload-editor/upload-editor.component</li><li>field/button-editor/button-editor.component</li><li>field/field-editor/field-editor.component</li><li>field/code-area-editor/code-area-editor.component</li><li>dynamic-template/dynamic-template.component</li><li>image-wrapper/image-wrapper.component</li><li>field/color-editor/color-editor.component</li><li>field/property-array-editor/property-array-editor.component</li><li>field/property-object-editor/property-object-editor.component</li><li>field/tree-view-selector/tree-view-selector.component</li><li>workflow-designer/workflow-designer.component</li><li>workflow-runner/workflow-runner.component</li><li>notification-bell/notification-bell.component</li><li>bounded-repeater/bounded-repeater.component</li><li>framework-docs/framework-docs.component</li><li>designer/designer.route.component</li><li>workflow-designer/workflow-designer.route.component</li><li>kanban-list/kanban-list.component</li><li>data-repeater/data-repeater.component</li><li>bounded-repeater/bounded-repeater-pending-changes.guard</li><li>rag-chatbot/rag-chatbot.component</li><li>rag-chatbot-fab/rag-chatbot-fab.component</li></ul>",
19695
+ "html": "<h1>Component Catalog</h1><p>Total component folders detected: 49.</p><h2>Available components</h2><ul><li>app-settings-editor</li><li>archetype-configurator</li><li>bounded-repeater</li><li>carousel-list</li><li>chart-list</li><li>code-editor</li><li>css-sheet-editor</li><li>dashboard</li><li>data-action-button</li><li>data-repeater</li><li>data-source</li><li>designer</li><li>dynamic-dashboard-template</li><li>dynamic-field-template</li><li>dynamic-form-template</li><li>dynamic-generic-template</li><li>dynamic-repeater-template</li><li>dynamic-template</li><li>error-dialog</li><li>field</li><li>filter-bar</li><li>first-run-wizard</li><li>framework-docs</li><li>image-wrapper</li><li>import-export-button</li><li>kanban-list</li><li>list-grid</li><li>map-list</li><li>meta-menu</li><li>metadata-editor</li><li>notification-bell</li><li>pager</li><li>parametric-dialog</li><li>pivot-builder</li><li>prompt-dialog-fallback-host</li><li>rag-chatbot</li><li>rag-chatbot-fab</li><li>report-designer</li><li>report-viewer</li><li>scheduler-list</li><li>split</li><li>split-area</li><li>spreadsheet-list-sf</li><li>tab-panel-wrapper</li><li>tab-view-wrapper</li><li>tree-list</li><li>upload-dialog</li><li>workflow-designer</li><li>workflow-runner</li></ul><h2>Exports from public-api</h2><ul><li>data-source/data-source.component</li><li>list-grid/list-grid.component</li><li>import-export-button/import-export-button.component</li><li>pager/pager.component</li><li>filter-bar/filter-bar.component</li><li>map-list/map-list.component</li><li>carousel-list/carousel-list.component</li><li>tree-list/tree-list.component</li><li>parametric-dialog/parametric-dialog.component</li><li>upload-dialog/upload-dialog.component</li><li>app-settings-editor/app-settings-editor.component</li><li>data-action-button/data-action-button.component</li><li>meta-menu/meta-menu.component</li><li>field/text-editor/text-editor.component</li><li>field/text-area-editor/text-area-editor.component</li><li>field/number-editor/number-editor.component</li><li>field/lookup-editor/lookup-editor.component</li><li>field/dictionary-editor/dictionary-editor.component</li><li>field/boolean-editor/boolean-editor.component</li><li>field/date-editor/date-editor.component</li><li>field/html-editor/html-editor.component</li><li>field/upload-editor/upload-editor.component</li><li>field/button-editor/button-editor.component</li><li>field/field-editor/field-editor.component</li><li>field/code-area-editor/code-area-editor.component</li><li>dynamic-template/dynamic-template.component</li><li>image-wrapper/image-wrapper.component</li><li>field/color-editor/color-editor.component</li><li>field/property-array-editor/property-array-editor.component</li><li>field/property-object-editor/property-object-editor.component</li><li>field/tree-view-selector/tree-view-selector.component</li><li>workflow-designer/workflow-designer.component</li><li>workflow-runner/workflow-runner.component</li><li>notification-bell/notification-bell.component</li><li>bounded-repeater/bounded-repeater.component</li><li>framework-docs/framework-docs.component</li><li>designer/designer.route.component</li><li>workflow-designer/workflow-designer.route.component</li><li>kanban-list/kanban-list.component</li><li>data-repeater/data-repeater.component</li><li>bounded-repeater/bounded-repeater-pending-changes.guard</li><li>rag-chatbot/rag-chatbot.component</li><li>rag-chatbot-fab/rag-chatbot-fab.component</li><li>error-dialog/error-dialog.component</li></ul>",
19144
19696
  "codeSamples": []
19145
19697
  }
19146
19698
  ],
@@ -19261,13 +19813,21 @@ const frameworkDocsContent = {
19261
19813
  {
19262
19814
  "id": "sec_3",
19263
19815
  "title": "Ejemplos"
19816
+ },
19817
+ {
19818
+ "id": "sec_4",
19819
+ "title": "Patch runtime de `metaInfo` via `fetchInfo$.subscribe`"
19820
+ },
19821
+ {
19822
+ "id": "sec_5",
19823
+ "title": "Multiples `<wuic-data-source>` en el mismo componente"
19264
19824
  }
19265
19825
  ],
19266
19826
  "sections": [
19267
19827
  {
19268
19828
  "id": "overview",
19269
19829
  "title": "Overview",
19270
- "html": "<h1>Datasource</h1><p><code>wuic-data-source</code> es el componente que gestiona metadata, fetch de datos, estado actual, tracking de cambios y sincronización CRUD.</p><h2>Descripción</h2><ul><li>Resuelve la route (<code>route</code>/<code>hardcodedRoute</code> o routing URL).</li><li>Carga metadata de tabla/columnas y publica el estado en <code>fetchInfo$</code>.</li><li>Gestiona <code>filterInfo</code>, <code>sortInfo</code>, <code>groupInfo</code>, paging (<code>pageSize</code>, <code>currentPage</code>) y cursor paging.</li><li>Expone métodos runtime para insert/update/delete, batch save, rollback y export.</li><li>Es el punto central al que se conectan <code>wuic-data-repeater</code>, <code>wuic-filter-bar</code> y <code>wuic-pager</code>.</li></ul><h2>API</h2><p>Selector:</p><ul><li><code>wuic-data-source</code></li></ul><p>Inputs principales:</p><ul><li><code>route: BehaviorSubject&lt;string&gt;</code></li><li><code>routeFromRouting: boolean</code></li><li><code>hardcodedRoute: string</code></li><li><code>autoload?: boolean</code></li><li><code>loading: BehaviorSubject&lt;boolean&gt;</code></li><li><code>changeTracking?: boolean</code></li><li><code>parentRecord: any</code></li><li><code>parentMetaInfo: MetaInfo</code></li><li><code>parentDatasource: DataSourceComponent</code></li><li><code>componentRef: BehaviorSubject&lt;{ component, id, name, uniqueName }&gt;</code></li></ul><p>Estado/runtime principal:</p><ul><li><code>metaInfo: MetaInfo</code></li><li><code>resultInfo: ResultInfo</code></li><li><code>filterInfo?: FilterInfo</code></li><li><code>sortInfo: SortInfo[]</code></li><li><code>groupInfo: GroupInfo[]</code></li><li><code>aggregationInfo: AggregationInfo[]</code></li><li><code>fetchInfo$: BehaviorSubject&lt;{ resultInfo, metaInfo, filterDescriptor }&gt;</code></li><li><code>datasourceReady$: BehaviorSubject&lt;DataSourceComponent | null&gt;</code></li><li><code>afterFirstLoad$: BehaviorSubject&lt;any&gt;</code></li><li><code>beforeSync$: Subject&lt;DataSourceBeforeSyncEvent&gt;</code> (cancelable)</li><li><code>afterSync$: Subject&lt;DataSourceAfterSyncEvent&gt;</code></li></ul><p>Métodos runtime más usados:</p><ul><li><code>fetchData()</code></li><li><code>syncData(entita, original, deleting?, cloning?)</code></li><li><code>addNewRecord(record?)</code></li><li><code>setCurrent(data)</code></li><li><code>clearColumnFilter(col, fetch?)</code></li><li><code>getPendingChanges()</code></li><li><code>hasPendingChanges()</code></li><li><code>batchSave(targetChanges?)</code></li><li><code>rollbackChanges(targetChanges?)</code></li><li><code>exportXls()</code></li></ul><p>Subscriptions/eventos runtime:</p><ul><li><code>fetchInfo$</code>: stream principal de estado datos/metadatos.</li><li><code>datasourceReady$</code>: emitido cuando la instancia datasource está lista.</li><li><code>afterFirstLoad$</code>: emitido una sola vez con el primer payload útil.</li><li><code>beforeSync$</code>: emitido antes de <code>insert/update/delete/clone/batch</code>; puedes cancelar con <code>event.cancelSync(...)</code>.</li><li><code>afterSync$</code>: emitido después de una sincronización exitosa.</li></ul><p>Nota de compatibilidad:</p><ul><li>también está disponible el alias <code>fetchInfo</code> (getter/setter), pero el naming recomendado es <code>fetchInfo$</code>.</li></ul><h2>Ejemplos</h2><h3>Ejemplo básico (route hardcoded + autoload)</h3><p>Snippet 1:</p><h3>Ejemplo master-detail (nested datasource)</h3><p>Snippet 2:</p><h3>Ejemplo operativo (fetch manual + sync)</h3><p>Snippet 3:</p><h3>Ejemplo de subscriptions (host)</h3><p>Snippet 4:</p>",
19830
+ "html": "<h1>Datasource</h1><p><code>wuic-data-source</code> es el componente que gestiona metadata, fetch de datos, estado actual, tracking de cambios y sincronización CRUD.</p><h2>Descripción</h2><ul><li>Resuelve la route (<code>route</code>/<code>hardcodedRoute</code> o routing URL).</li><li>Carga metadata de tabla/columnas y publica el estado en <code>fetchInfo$</code>.</li><li>Gestiona <code>filterInfo</code>, <code>sortInfo</code>, <code>groupInfo</code>, paging (<code>pageSize</code>, <code>currentPage</code>) y cursor paging.</li><li>Expone métodos runtime para insert/update/delete, batch save, rollback y export.</li><li>Es el punto central al que se conectan <code>wuic-data-repeater</code>, <code>wuic-filter-bar</code> y <code>wuic-pager</code>.</li></ul><h2>API</h2><p>Selector:</p><ul><li><code>wuic-data-source</code></li></ul><p>Inputs principales:</p><ul><li><code>route: BehaviorSubject&lt;string&gt;</code></li><li><code>routeFromRouting: boolean</code></li><li><code>hardcodedRoute: string</code></li><li><code>autoload?: boolean</code></li><li><code>loading: BehaviorSubject&lt;boolean&gt;</code></li><li><code>changeTracking?: boolean</code></li><li><code>parentRecord: any</code></li><li><code>parentMetaInfo: MetaInfo</code></li><li><code>parentDatasource: DataSourceComponent</code></li><li><code>componentRef: BehaviorSubject&lt;{ component, id, name, uniqueName }&gt;</code></li></ul><p>Estado/runtime principal:</p><ul><li><code>metaInfo: MetaInfo</code></li><li><code>resultInfo: ResultInfo</code></li><li><code>filterInfo?: FilterInfo</code></li><li><code>sortInfo: SortInfo[]</code></li><li><code>groupInfo: GroupInfo[]</code></li><li><code>aggregationInfo: AggregationInfo[]</code></li><li><code>fetchInfo$: BehaviorSubject&lt;{ resultInfo, metaInfo, filterDescriptor }&gt;</code></li><li><code>datasourceReady$: BehaviorSubject&lt;DataSourceComponent | null&gt;</code></li><li><code>afterFirstLoad$: BehaviorSubject&lt;any&gt;</code></li><li><code>beforeSync$: Subject&lt;DataSourceBeforeSyncEvent&gt;</code> (cancelable)</li><li><code>afterSync$: Subject&lt;DataSourceAfterSyncEvent&gt;</code></li></ul><p>Métodos runtime más usados:</p><ul><li><code>fetchData()</code></li><li><code>syncData(entita, original, deleting?, cloning?)</code></li><li><code>addNewRecord(record?)</code></li><li><code>setCurrent(data)</code></li><li><code>clearColumnFilter(col, fetch?)</code></li><li><code>getPendingChanges()</code></li><li><code>hasPendingChanges()</code></li><li><code>batchSave(targetChanges?)</code></li><li><code>rollbackChanges(targetChanges?)</code></li><li><code>exportXls()</code></li></ul><p>Subscriptions/eventos runtime:</p><ul><li><code>fetchInfo$</code>: stream principal de estado datos/metadatos.</li><li><code>datasourceReady$</code>: emitido cuando la instancia datasource está lista.</li><li><code>afterFirstLoad$</code>: emitido una sola vez con el primer payload útil.</li><li><code>beforeSync$</code>: emitido antes de <code>insert/update/delete/clone/batch</code>; puedes cancelar con <code>event.cancelSync(...)</code>.</li><li><code>afterSync$</code>: emitido después de una sincronización exitosa.</li></ul><p>Nota de compatibilidad:</p><ul><li>también está disponible el alias <code>fetchInfo</code> (getter/setter), pero el naming recomendado es <code>fetchInfo$</code>.</li></ul><h2>Ejemplos</h2><h3>Ejemplo básico (route hardcoded + autoload)</h3><p>Snippet 1:</p><h3>Ejemplo master-detail (nested datasource)</h3><p>Snippet 2:</p><h3>Ejemplo operativo (fetch manual + sync)</h3><p>Snippet 3:</p><h3>Ejemplo de subscriptions (host)</h3><p>Snippet 4:</p><h2>Patch runtime de <code>metaInfo</code> via <code>fetchInfo$.subscribe</code></h2><p>Para modificar en runtime los metadatos de un nested data-source (ej. <code>mc_hide_in_list</code>, <code>md_inline_cell_editing</code>, <code>md_pageable=false</code>, <code>mc_logic_editable=false</code>) <strong>sin tocar la DB</strong> — patch local sobre la referencia cacheada por <code>MetadataProviderService</code>, latest-wins, escenario single-dialog — suscribete al <code>fetchInfo$</code> del data-source:</p><p>Snippet 5:</p><p><strong>Scope</strong>: session-only + instance-only. El patch sobrevive solo hasta que la cache de <code>MetadataProviderService</code> se invalida o el componente se destruye. Util cuando un custom form quiere una representacion diferente de un nested grid respecto al rendering standalone.</p><p><strong>Cuando NO usar patches runtime</strong>: para modificaciones destinadas a todas las instancias de la route (incluido standalone <code>&lt;route&gt;/list</code>) → patch SQL directo sobre los metadatos; para modificaciones persistentes entre restart → SQL + <code>MetaService.invalidateMetadataRuntime</code>.</p><h2>Multiples <code>&lt;wuic-data-source&gt;</code> en el mismo componente</h2><p>Cuando un componente custom contiene mas de un <code>&lt;wuic-data-source&gt;</code> (ej. master + nested rows + nested vencimientos), el patron <code>@ViewChild(DataSourceComponent)</code> by-type matchea <strong>solo el primer</strong> data-source en el orden top-down del template. Para los siguientes hace falta una <strong>template reference variable</strong> + <code>@ViewChild(&#39;refName&#39;)</code>:</p><p>Snippet 6:</p><p>Snippet 7:</p><p><strong>Antipatron</strong>: <code>document.querySelectorAll(&#39;wuic-data-source&#39;)</code> + <code>window.ng.getComponent(el)</code> para filtrar por <code>hardcodedRoute</code>. Funciona pero es fragil, depende de la API debug <code>window.ng</code> (no garantizada en produccion). Usa siempre template refs.</p>",
19271
19831
  "codeSamples": [
19272
19832
  {
19273
19833
  "id": "code_1",
@@ -19292,6 +19852,24 @@ const frameworkDocsContent = {
19292
19852
  "title": "Snippet 4",
19293
19853
  "language": "ts",
19294
19854
  "code": "const subs = new Subscription();\n\nsubs.add(\n datasource.fetchInfo$.subscribe((info) => {\n if (!info) return;\n console.log('records', info.resultInfo?.dato?.length || 0);\n })\n);\n\nsubs.add(\n datasource.beforeSync$.subscribe((event) => {\n if (event.operation === 'delete' && !canDelete()) {\n // Delete no permitido desde el contexto host\n event.cancelSync('Delete non consentito dal contesto host');\n }\n })\n);\n\nsubs.add(\n datasource.afterSync$.subscribe((event) => {\n console.log('sync ok', event.operation, event.syncedData);\n })\n);"
19855
+ },
19856
+ {
19857
+ "id": "code_5",
19858
+ "title": "Snippet 5",
19859
+ "language": "ts",
19860
+ "code": "private bindNestedMetaPatches(): void {\n if (!this.scadenzeDs) return;\n const ESSENTIAL = new Set(['data_scadenza', 'importo', 'pagamento_id', 'stato', 'note']);\n const sub = this.scadenzeDs.fetchInfo$.subscribe((info) => {\n const route = this.scadenzeDs?.route?.value;\n if (!info || route !== info.metaInfo?.tableMetadata?.md_route_name) return;\n const cols: any[] = info.metaInfo?.columnMetadata || [];\n for (const c of cols) {\n const name = c?.mc_nome_colonna;\n if (!name || c.mc_is_primary_key) continue;\n c.mc_hide_in_list = !ESSENTIAL.has(name);\n }\n });\n this.subs.add(sub); // cleanup en ngOnDestroy\n}"
19861
+ },
19862
+ {
19863
+ "id": "code_6",
19864
+ "title": "Snippet 6",
19865
+ "language": "html",
19866
+ "code": "<!-- TEMPLATE -->\n<wuic-data-source #righeDs [hardcodedRoute]=\"'fatture_inviate_righe'\" [parentRecord]=\"record\"></wuic-data-source>\n<wuic-data-source #scadenzeDs [hardcodedRoute]=\"'scadenze'\" [parentRecord]=\"record\"></wuic-data-source>"
19867
+ },
19868
+ {
19869
+ "id": "code_7",
19870
+ "title": "Snippet 7",
19871
+ "language": "ts",
19872
+ "code": "// COMPONENT\nimport { DataSourceComponent } from 'wuic-framework-lib';\n\n// Primer data-source (match by type) -> no hace falta template ref\n@ViewChild(DataSourceComponent) righeDs?: DataSourceComponent;\n\n// Segundo data-source -> requiere `#scadenzeDs` en el template + ViewChild by-name\n@ViewChild('scadenzeDs') scadenzeDs?: DataSourceComponent;"
19295
19873
  }
19296
19874
  ]
19297
19875
  }
@@ -21752,26 +22330,34 @@ const frameworkDocsContent = {
21752
22330
  },
21753
22331
  {
21754
22332
  "id": "sec_6",
21755
- "title": "md_props_bag: archetypes.list"
22333
+ "title": "md_props_bag: toolbar"
21756
22334
  },
21757
22335
  {
21758
22336
  "id": "sec_7",
21759
- "title": "Paging: md_pagesize und md_page_size_choice"
22337
+ "title": "md_props_bag: archetypes.list"
21760
22338
  },
21761
22339
  {
21762
22340
  "id": "sec_8",
21763
- "title": "Erzwungene Virtualisierung bei hoher Seitengroesse"
22341
+ "title": "Paging: md_pagesize und md_page_size_choice"
21764
22342
  },
21765
22343
  {
21766
22344
  "id": "sec_9",
22345
+ "title": "Erzwungene Virtualisierung bei hoher Seitengroesse"
22346
+ },
22347
+ {
22348
+ "id": "sec_10",
21767
22349
  "title": "Events und Subscriptions (Host)"
22350
+ },
22351
+ {
22352
+ "id": "sec_11",
22353
+ "title": "`rowCustomSelect` — tatsachliche Signatur (Falle)"
21768
22354
  }
21769
22355
  ],
21770
22356
  "sections": [
21771
22357
  {
21772
22358
  "id": "overview",
21773
22359
  "title": "Overview",
21774
- "html": "<h1>List Grid</h1><p>Hauptkomponente fuer tabellarische Listen mit Filtern, Sortierung, serverseitigem Paging und Zeilenaktionen.</p><h2>Anwendungsfaelle</h2><ul><li>Enterprise-CRUD-Tabellen.</li><li>Operative Reports mit Multi-Spalten-Filtern.</li><li>Sehr grosse Datasets mit <code>cursorMode</code>.</li></ul><h2>Screenshot-Referenz (Benutzerhandbuch)</h2><ul><li><code>manual__grid__01.png</code>: Modus <code>inline cell edit</code> im List-Grid.</li><li><code>manual__grid__02.png</code>: Modus <code>inline edit</code> (Row-Level) im List-Grid.</li></ul><h2>Metadaten Inline-Editing</h2><p>Konfiguration im <code>md_props_bag</code> der Tabellen-Metadata:</p><ul><li><code>md_inline_edit</code></li></ul><p> - aktiviert das Inline-Editing der Zeile im List-Grid (editierbare Zellen im Zeilenkontext, ohne Popup-Oeffnung).</p><p> - nuetzlich wenn die Tabellen-UX mit schnellem Editing pro Datensatz beibehalten werden soll.</p><ul><li><code>md_inline_cell_edit</code></li></ul><p> - aktiviert das Inline-Editing &quot;Zelle fuer Zelle&quot; (Fokus auf einzelne Zelle).</p><p> - Hinweis: in Legacy-Konfigurationen kann dies als <code>md_inline_cell_editing</code> erscheinen; das Runtime-Verhalten ist identisch.</p><p> - <strong>Runtime-Promotion</strong>: wenn <code>md_inline_cell_edit</code> <code>true</code> ist, erzwingt die Komponente</p><p> zur Laufzeit auch <code>md_inline_edit = true</code>, unabhaengig vom DB-Wert.</p><p> Beide UX benoetigen die sichtbare Action-Spalte, daher sind sie auf Rendering-Ebene</p><p> nicht gegenseitig ausschliessend.</p><ul><li><code>md_batch_save</code></li></ul><p> - aktiviert das Batch-Speichern der ausstehenden Aenderungen (<code>Save changes</code> / <code>Cancel changes</code>).</p><p> - bei Verwendung zusammen mit Inline-Cell bleiben die Aenderungen ausstehend bis zum expliziten Speichern.</p><p> - <strong>Voraussetzung</strong>: <code>md_batch_save</code> wirkt <strong>nur</strong> wenn <code>md_inline_cell_edit</code> <code>true</code> ist.</p><p> Mit <code>md_inline_cell_edit:false</code> wird das Flag zur Laufzeit ignoriert und die Buttons</p><p> &quot;Aenderungen speichern / Aenderungen verwerfen&quot; werden nicht gerendert.</p><h3>Gueltige Kombinationen</h3><table><thead><tr><th><code>md_inline_edit</code></th><th><code>md_inline_cell_edit</code></th><th><code>md_batch_save</code></th><th>Runtime-Ergebnis</th></tr></thead><tbody><tr><td><code>true</code></td><td><code>false</code></td><td>beliebig</td><td>Row-Level Inline-Edit mit Pencil. <code>md_batch_save</code> ignoriert.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>false</code></td><td>Zelle-fuer-Zelle Autosave bei Blur. <code>md_inline_edit</code> wird zur Laufzeit auf <code>true</code> erzwungen.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>true</code></td><td>Zelle-fuer-Zelle mit Pending-Buffer + Toolbar <code>Speichern / Aenderungen verwerfen</code>.</td></tr><tr><td><code>false</code></td><td><code>false</code></td><td>beliebig</td><td>Kein Inline-Editing. <code>md_batch_save</code> ignoriert.</td></tr></tbody></table><p>Beispiel Row-Level Inline-Edit:</p><p>Snippet 1:</p><p>Beispiel Zelle-fuer-Zelle mit Batch-Save:</p><p>Snippet 2:</p><h2>Toolbar Export/Import</h2><ul><li>Export XLS:</li></ul><p> - zeigt Fortschrittsdialog mit Echtzeit-Prozentsatz;</p><p> - Aktionen: <code>Im Hintergrund fortsetzen</code>, <code>Task abbrechen</code>, <code>Unterbrechen und teilweise herunterladen</code>;</p><p> - im Hintergrund wird eine Benachrichtigung mit Fortschritt erstellt; Klick oeffnet den Dialog erneut.</p><ul><li>Import XLS/XLSX (wenn <code>md_importable = true</code>):</li></ul><p> - Fortschrittsdialog nach Bestaetigung;</p><p> - Aktionen: <code>Im Hintergrund fortsetzen</code>, <code>Import abbrechen (Rollback)</code>, <code>Stop und teilweise Commit</code>;</p><p> - nach Import-Ende wird eine Zusammenfassungs-Benachrichtigung erstellt, die zur Route fuehrt.</p><h2>Config Metadata</h2><p>Schluessel-Einstellungen in <code>md_props_bag</code> und Spalten-Metadaten.</p><p>Snippet 3:</p><h2>md_props_bag: archetypes.list</h2><p>Die Komponente liest <code>md_props_bag.archetypes.list</code>.</p><p>Snippet 4:</p><ul><li><code>proportionalColwidth</code>:</li><li>Typ: boolean (mit tolerantem Parser)</li><li>akzeptierte Werte: <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>&quot;true&quot;</code>, <code>&quot;false&quot;</code>, <code>&quot;1&quot;</code>, <code>&quot;0&quot;</code></li><li>Runtime-Default: <code>true</code> wenn absent oder leer</li><li>Effekt: verteilt die Spaltenbreiten prozentual (anstelle von px), wenn keine vom Benutzer persistierten Breiten vorhanden sind.</li></ul><ul><li><code>virtualize</code>:</li><li>Typ: <code>boolean</code> oder <code>object</code></li><li>akzeptierte Werte:</li><li><code>true</code>/<code>false</code>, <code>1</code>/<code>0</code>, <code>&quot;true&quot;</code>/<code>&quot;false&quot;</code>, <code>&quot;1&quot;</code>/<code>&quot;0&quot;</code></li><li>Object mit <code>enabled</code> (optional) und <code>itemSize</code> (optional)</li><li>Runtime-Default: deaktiviert (<code>false</code>) wenn absent</li><li><code>enabled</code>:</li><li>Default: <code>true</code> wenn der <code>virtualize</code>-Knoten ein Object ohne <code>enabled</code> ist, sonst toleranter Parser</li><li>Effekt: aktiviert <code>virtualScroll</code> auf <code>p-table</code></li><li><code>itemSize</code>:</li><li>Default: <code>44</code></li><li>Effekt: setzt <code>virtualScrollItemSize</code> (virtuelle Zeilenhoehe in px)</li></ul><ul><li><code>advancedFilter</code>:</li><li>Typ: boolean (mit tolerantem Parser)</li><li>Runtime-Default: <code>false</code></li><li>Effekt:</li><li>wenn <code>true</code>, blendet das Grid die Spalten-Filter-Icons (<code>p-columnFilter</code>) aus und verwendet die <code>wuic-filter-bar</code> ueber der Tabelle (auf <code>data-repeater</code>/<code>bounded-repeater</code>-Ebene), um die Filter anzuwenden.</li></ul><p>Betriebshinweis:</p><ul><li>der <code>md_props_bag</code>-Suggest im Metadata-Editor stellt auch den checkbaren Knoten <code>archetypes.list.advancedFilter</code> bereit.</li></ul><h2>Paging: md_pagesize und md_page_size_choice</h2><p>Das Grid verwendet <code>md_pagesize</code> als Standard-Seitengroesse und <code>md_page_size_choice</code> als Liste der im Paginator auswaehlbaren Werte.</p><p>Runtime-Regeln:</p><ul><li>wenn <code>md_pagesize</code> groesser als der maximale Wert in <code>md_page_size_choice</code> ist, fuegt das Framework <code>md_pagesize</code> automatisch zur Optionsliste hinzu;</li><li>die Liste wird normalisiert (gueltige Zahlen, Deduplizierung, aufsteigende Sortierung).</li></ul><p>Beispiel:</p><ul><li><code>md_pagesize = 200</code></li><li><code>md_page_size_choice = &quot;10,25,50,100&quot;</code></li><li>Runtime-Ergebnis: <code>10,25,50,100,200</code></li></ul><h2>Erzwungene Virtualisierung bei hoher Seitengroesse</h2><p>Wenn der Benutzer im Paginator einen Wert <code>pageSize &gt;= 1000</code> auswaehlt:</p><ul><li>wird die Virtualisierung automatisch erzwungen, auch wenn <code>md_props_bag.archetypes.list.virtualize</code> absent oder deaktiviert ist;</li><li><code>virtualScrollItemSize</code> wird auf den Standardwert <code>44</code> gesetzt.</li></ul><p>Dieses Verhalten schuetzt das Rendering der Tabelle bei sehr grossen Seiten.</p><h2>Events und Subscriptions (Host)</h2><p><code>wuic-list-grid</code> stellt Runtime-Events bereit, die nuetzlich sind, um den Render-Zyklus und die p-table-Callbacks auf der Host-Projektseite zu intercepten.</p><p>Verfuegbare Events:</p><ul><li><code>onAfterRender</code>: wird am Ende des Datenbindings des Grids emittiert (<code>rows</code>, <code>totalRecords</code>, <code>metaInfo</code>, <code>datasource</code>).</li><li><code>onBeforeRowRender</code>: wird vor dem logischen Rendering der einzelnen Zeile emittiert; unterstuetzt Cancel via <code>event.cancelRender()</code>.</li><li><code>onAfterRowRender</code>: wird nach dem logischen Rendering der einzelnen Zeile emittiert.</li><li><code>onPaging</code>: wird bei Paging-Events emittiert (<code>p-table onPage</code>).</li><li><code>onSorting</code>: wird bei Sorting-Events emittiert (<code>p-table onSort</code>).</li><li><code>onFiltering</code>: wird bei Filtering-Events emittiert (<code>p-table onFilter</code>).</li><li><code>onPTableSelectionChange</code>: wird bei Aenderung der Zeilenauswahl emittiert.</li><li><code>onPTableRowExpand</code>: wird beim Expandieren einer Zeile emittiert.</li><li><code>onPTableRowCollapse</code>: wird beim Collapsen einer Zeile emittiert.</li><li><code>onPTableColumnResize</code>: wird beim Spalten-Resize emittiert.</li><li><code>onPTableColumnReorder</code>: wird beim Spalten-Reorder emittiert.</li></ul><h3>Beispiel 1: direktes Binding im Template</h3><p>Snippet 5:</p><h3>Beispiel 2: Subscribe via ViewChild</h3><p>Snippet 6:</p>",
22360
+ "html": "<h1>List Grid</h1><p>Hauptkomponente fuer tabellarische Listen mit Filtern, Sortierung, serverseitigem Paging und Zeilenaktionen.</p><h2>Anwendungsfaelle</h2><ul><li>Enterprise-CRUD-Tabellen.</li><li>Operative Reports mit Multi-Spalten-Filtern.</li><li>Sehr grosse Datasets mit <code>cursorMode</code>.</li></ul><h2>Screenshot-Referenz (Benutzerhandbuch)</h2><ul><li><code>manual__grid__01.png</code>: Modus <code>inline cell edit</code> im List-Grid.</li><li><code>manual__grid__02.png</code>: Modus <code>inline edit</code> (Row-Level) im List-Grid.</li></ul><h2>Metadaten Inline-Editing</h2><p>Konfiguration im <code>md_props_bag</code> der Tabellen-Metadata:</p><ul><li><code>md_inline_edit</code></li></ul><p> - aktiviert das Inline-Editing der Zeile im List-Grid (editierbare Zellen im Zeilenkontext, ohne Popup-Oeffnung).</p><p> - nuetzlich wenn die Tabellen-UX mit schnellem Editing pro Datensatz beibehalten werden soll.</p><ul><li><code>md_inline_cell_edit</code></li></ul><p> - aktiviert das Inline-Editing &quot;Zelle fuer Zelle&quot; (Fokus auf einzelne Zelle).</p><p> - Hinweis: in Legacy-Konfigurationen kann dies als <code>md_inline_cell_editing</code> erscheinen; das Runtime-Verhalten ist identisch.</p><p> - <strong>Runtime-Promotion</strong>: wenn <code>md_inline_cell_edit</code> <code>true</code> ist, erzwingt die Komponente</p><p> zur Laufzeit auch <code>md_inline_edit = true</code>, unabhaengig vom DB-Wert.</p><p> Beide UX benoetigen die sichtbare Action-Spalte, daher sind sie auf Rendering-Ebene</p><p> nicht gegenseitig ausschliessend.</p><ul><li><code>md_batch_save</code></li></ul><p> - aktiviert das Batch-Speichern der ausstehenden Aenderungen (<code>Save changes</code> / <code>Cancel changes</code>).</p><p> - bei Verwendung zusammen mit Inline-Cell bleiben die Aenderungen ausstehend bis zum expliziten Speichern.</p><p> - <strong>Voraussetzung</strong>: <code>md_batch_save</code> wirkt <strong>nur</strong> wenn <code>md_inline_cell_edit</code> <code>true</code> ist.</p><p> Mit <code>md_inline_cell_edit:false</code> wird das Flag zur Laufzeit ignoriert und die Buttons</p><p> &quot;Aenderungen speichern / Aenderungen verwerfen&quot; werden nicht gerendert.</p><h3>Gueltige Kombinationen</h3><table><thead><tr><th><code>md_inline_edit</code></th><th><code>md_inline_cell_edit</code></th><th><code>md_batch_save</code></th><th>Runtime-Ergebnis</th></tr></thead><tbody><tr><td><code>true</code></td><td><code>false</code></td><td>beliebig</td><td>Row-Level Inline-Edit mit Pencil. <code>md_batch_save</code> ignoriert.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>false</code></td><td>Zelle-fuer-Zelle Autosave bei Blur. <code>md_inline_edit</code> wird zur Laufzeit auf <code>true</code> erzwungen.</td></tr><tr><td><code>false</code></td><td><code>true</code></td><td><code>true</code></td><td>Zelle-fuer-Zelle mit Pending-Buffer + Toolbar <code>Speichern / Aenderungen verwerfen</code>.</td></tr><tr><td><code>false</code></td><td><code>false</code></td><td>beliebig</td><td>Kein Inline-Editing. <code>md_batch_save</code> ignoriert.</td></tr></tbody></table><p>Beispiel Row-Level Inline-Edit:</p><p>Snippet 1:</p><p>Beispiel Zelle-fuer-Zelle mit Batch-Save:</p><p>Snippet 2:</p><h2>Toolbar Export/Import</h2><ul><li>Export XLS:</li></ul><p> - zeigt Fortschrittsdialog mit Echtzeit-Prozentsatz;</p><p> - Aktionen: <code>Im Hintergrund fortsetzen</code>, <code>Task abbrechen</code>, <code>Unterbrechen und teilweise herunterladen</code>;</p><p> - im Hintergrund wird eine Benachrichtigung mit Fortschritt erstellt; Klick oeffnet den Dialog erneut.</p><ul><li>Import XLS/XLSX (wenn <code>md_importable = true</code>):</li></ul><p> - Fortschrittsdialog nach Bestaetigung;</p><p> - Aktionen: <code>Im Hintergrund fortsetzen</code>, <code>Import abbrechen (Rollback)</code>, <code>Stop und teilweise Commit</code>;</p><p> - nach Import-Ende wird eine Zusammenfassungs-Benachrichtigung erstellt, die zur Route fuehrt.</p><h2>Config Metadata</h2><p>Schluessel-Einstellungen in <code>md_props_bag</code> und Spalten-Metadaten.</p><p>Snippet 3:</p><h2>md_props_bag: toolbar</h2><p>Opt-in-Flags, die Bloecke der list-grid Toolbar (<code>caption-bar</code>) ausblenden. Alle unter <code>md_props_bag.toolbar.*</code> (zur Laufzeit als <code>tableMetadata.extraProps.toolbar</code> geparst).</p><p>Snippet 4:</p><ul><li><code>hideManageState</code> (boolean, default <code>false</code>): blendet in der <strong>caption-right</strong> den Button &quot;Status verwalten&quot; (Bookmark-Icon) + das <code>&lt;select&gt;</code> der gespeicherten Zustaende aus. Nuetzlich fuer hardcoded / Demo-Routes, bei denen das Saved-State-Feature (Persistenz pro <code>user_id</code> + Route via <code>MetaService</code>) keinen Sinn ergibt — z.B. Pattern 3 reines OData ohne registrierte Route-Metadaten.</li><li><code>hideBatchActions</code> (boolean, default <code>false</code>): blendet in der <strong>caption-left</strong> die Buttons &quot;Aenderungen speichern&quot; (<code>pi-save</code>) + &quot;Aenderungen verwerfen&quot; (<code>pi-times</code>) + den Aenderungs-Zaehler-Indikator (<code>grid-changes-indicator</code>, Pencil-Badge + Count) aus. Vom Framework generiert, wenn <code>md_inline_cell_editing</code> + <code>md_batch_save</code> aktiv sind. Konzipiert fuer <strong>verschachtelte Grids in einem parametric-dialog</strong>: Save/Cancel des Parents persistiert Master + Zeilen in einem Schritt via Framework Batch Save, und die Duplikat-Buttons auf der nested Grid verwirren die UX.</li></ul><p>Beispiel nested rows in custom edit-form (Runtime-Patch):</p><p>Snippet 5:</p><p>TS-Typ in <a href=\"../api/wuic-framework-lib.metadatitabella.html\">`metadati_tabella.ts`</a> (<code>extraProps.toolbar</code> Property). Die Flags deaktivieren NICHT die zugrundeliegende <code>md_batch_save</code>-Logik (Aenderungen werden weiterhin getrackt); sie entfernen nur die Toolbar-UI — der eigentliche Save laeuft ueber den Parent-Flow.</p><h2>md_props_bag: archetypes.list</h2><p>Die Komponente liest <code>md_props_bag.archetypes.list</code>.</p><p>Snippet 6:</p><ul><li><code>proportionalColwidth</code>:</li><li>Typ: boolean (mit tolerantem Parser)</li><li>akzeptierte Werte: <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>&quot;true&quot;</code>, <code>&quot;false&quot;</code>, <code>&quot;1&quot;</code>, <code>&quot;0&quot;</code></li><li>Runtime-Default: <code>true</code> wenn absent oder leer</li><li>Effekt: verteilt die Spaltenbreiten prozentual (anstelle von px), wenn keine vom Benutzer persistierten Breiten vorhanden sind.</li></ul><ul><li><code>virtualize</code>:</li><li>Typ: <code>boolean</code> oder <code>object</code></li><li>akzeptierte Werte:</li><li><code>true</code>/<code>false</code>, <code>1</code>/<code>0</code>, <code>&quot;true&quot;</code>/<code>&quot;false&quot;</code>, <code>&quot;1&quot;</code>/<code>&quot;0&quot;</code></li><li>Object mit <code>enabled</code> (optional) und <code>itemSize</code> (optional)</li><li>Runtime-Default: deaktiviert (<code>false</code>) wenn absent</li><li><code>enabled</code>:</li><li>Default: <code>true</code> wenn der <code>virtualize</code>-Knoten ein Object ohne <code>enabled</code> ist, sonst toleranter Parser</li><li>Effekt: aktiviert <code>virtualScroll</code> auf <code>p-table</code></li><li><code>itemSize</code>:</li><li>Default: <code>44</code></li><li>Effekt: setzt <code>virtualScrollItemSize</code> (virtuelle Zeilenhoehe in px)</li></ul><ul><li><code>advancedFilter</code>:</li><li>Typ: boolean (mit tolerantem Parser)</li><li>Runtime-Default: <code>false</code></li><li>Effekt:</li><li>wenn <code>true</code>, blendet das Grid die Spalten-Filter-Icons (<code>p-columnFilter</code>) aus und verwendet die <code>wuic-filter-bar</code> ueber der Tabelle (auf <code>data-repeater</code>/<code>bounded-repeater</code>-Ebene), um die Filter anzuwenden.</li></ul><p>Betriebshinweis:</p><ul><li>der <code>md_props_bag</code>-Suggest im Metadata-Editor stellt auch den checkbaren Knoten <code>archetypes.list.advancedFilter</code> bereit.</li></ul><h2>Paging: md_pagesize und md_page_size_choice</h2><p>Das Grid verwendet <code>md_pagesize</code> als Standard-Seitengroesse und <code>md_page_size_choice</code> als Liste der im Paginator auswaehlbaren Werte.</p><p>Runtime-Regeln:</p><ul><li>wenn <code>md_pagesize</code> groesser als der maximale Wert in <code>md_page_size_choice</code> ist, fuegt das Framework <code>md_pagesize</code> automatisch zur Optionsliste hinzu;</li><li>die Liste wird normalisiert (gueltige Zahlen, Deduplizierung, aufsteigende Sortierung).</li></ul><p>Beispiel:</p><ul><li><code>md_pagesize = 200</code></li><li><code>md_page_size_choice = &quot;10,25,50,100&quot;</code></li><li>Runtime-Ergebnis: <code>10,25,50,100,200</code></li></ul><h2>Erzwungene Virtualisierung bei hoher Seitengroesse</h2><p>Wenn der Benutzer im Paginator einen Wert <code>pageSize &gt;= 1000</code> auswaehlt:</p><ul><li>wird die Virtualisierung automatisch erzwungen, auch wenn <code>md_props_bag.archetypes.list.virtualize</code> absent oder deaktiviert ist;</li><li><code>virtualScrollItemSize</code> wird auf den Standardwert <code>44</code> gesetzt.</li></ul><p>Dieses Verhalten schuetzt das Rendering der Tabelle bei sehr grossen Seiten.</p><h2>Events und Subscriptions (Host)</h2><p><code>wuic-list-grid</code> stellt Runtime-Events bereit, die nuetzlich sind, um den Render-Zyklus und die p-table-Callbacks auf der Host-Projektseite zu intercepten.</p><p>Verfuegbare Events:</p><ul><li><code>onAfterRender</code>: wird am Ende des Datenbindings des Grids emittiert (<code>rows</code>, <code>totalRecords</code>, <code>metaInfo</code>, <code>datasource</code>).</li><li><code>onBeforeRowRender</code>: wird vor dem logischen Rendering der einzelnen Zeile emittiert; unterstuetzt Cancel via <code>event.cancelRender()</code>.</li><li><code>onAfterRowRender</code>: wird nach dem logischen Rendering der einzelnen Zeile emittiert.</li><li><code>onPaging</code>: wird bei Paging-Events emittiert (<code>p-table onPage</code>).</li><li><code>onSorting</code>: wird bei Sorting-Events emittiert (<code>p-table onSort</code>).</li><li><code>onFiltering</code>: wird bei Filtering-Events emittiert (<code>p-table onFilter</code>).</li><li><code>onPTableSelectionChange</code>: wird bei Aenderung der Zeilenauswahl emittiert.</li><li><code>onPTableRowExpand</code>: wird beim Expandieren einer Zeile emittiert.</li><li><code>onPTableRowCollapse</code>: wird beim Collapsen einer Zeile emittiert.</li><li><code>onPTableColumnResize</code>: wird beim Spalten-Resize emittiert.</li><li><code>onPTableColumnReorder</code>: wird beim Spalten-Reorder emittiert.</li></ul><h3>Beispiel 1: direktes Binding im Template</h3><p>Snippet 7:</p><h3>Beispiel 2: Subscribe via ViewChild</h3><p>Snippet 8:</p><h2><code>rowCustomSelect</code> — tatsachliche Signatur (Falle)</h2><p><code>&lt;wuic-list-grid&gt;</code> akzeptiert ein <code>[rowCustomSelect]</code>-Input zum Abfangen der Zeilenauswahl (Offnen eines &quot;Dokument auswahlen&quot;-Dialogs, Master-Detail-Flow, etc.). Die TypeScript-Signatur in <a href=\"../../src/lib/component/list-grid/list-grid.component.ts#L140\">list-grid.component.ts:140</a> lautet:</p><p>Snippet 9:</p><p><strong>Zur Laufzeit wird der Callback jedoch mit umgekehrten Argumenten aufgerufen</strong> — das Framework ruft <code>rowCustomSelect($event, rowData, dt)</code> auf (siehe <a href=\"../../src/lib/component/dynamic-template/dynamic-template.component.ts#L447\">dynamic-template.component.ts:447</a> und <a href=\"../../src/lib/component/dynamic-template/dynamic-card-template.component.ts#L205\">dynamic-card-template.component.ts:205</a>).</p><p>Symptom bei falscher Reihenfolge: <code>rowData?.id</code> ist undefined → fruhzeitiger Guard im Handler → der Callback kehrt stillschweigend zuruck und der Dialog/die Aktion startet nicht. Kein Konsolenfehler.</p><p>Korrekte Form (auf Laufzeit-Invocation abgestimmt):</p><p>Snippet 10:</p><p>Verwendung im Template:</p><p>Snippet 11:</p><p>&gt; Hinweis: Die Tests in <code>designer.component.spec.ts</code> (z.B. Zeile 512) rufen den Callback mit <code>({currentTarget: rowCell}, {id: 42}, null)</code> auf und bestatigen die Reihenfolge <code>($event, rowData, dt)</code>. Sollte das Framework die deklarierte Signatur kunftig angleichen, wird auch diese Seite aktualisiert.</p>",
21775
22361
  "codeSamples": [
21776
22362
  {
21777
22363
  "id": "code_1",
@@ -21795,19 +22381,49 @@ const frameworkDocsContent = {
21795
22381
  "id": "code_4",
21796
22382
  "title": "Snippet 4",
21797
22383
  "language": "json",
21798
- "code": "{\n \"archetypes\": {\n \"list\": {\n \"proportionalColwidth\": true,\n \"advancedFilter\": false,\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n }\n }\n }\n}"
22384
+ "code": "{\n \"toolbar\": {\n \"hideManageState\": true,\n \"hideBatchActions\": true\n }\n}"
21799
22385
  },
21800
22386
  {
21801
22387
  "id": "code_5",
21802
22388
  "title": "Snippet 5",
21803
- "language": "html",
21804
- "code": "<wuic-list-grid\n [hardcodedDatasource]=\"citiesDs\"\n (onAfterRender)=\"handleAfterRender($event)\"\n (onBeforeRowRender)=\"handleBeforeRowRender($event)\"\n (onAfterRowRender)=\"handleAfterRowRender($event)\"\n (onPaging)=\"handlePaging($event)\"\n (onSorting)=\"handleSorting($event)\"\n (onFiltering)=\"handleFiltering($event)\"\n (onPTableSelectionChange)=\"handleSelection($event)\"\n (onPTableRowExpand)=\"handleRowExpand($event)\"\n (onPTableRowCollapse)=\"handleRowCollapse($event)\"\n (onPTableColumnResize)=\"handleColumnResize($event)\"\n (onPTableColumnReorder)=\"handleColumnReorder($event)\">\n</wuic-list-grid>"
22389
+ "language": "ts",
22390
+ "code": "this.righeDs.metaInfo.tableMetadata.extraProps = Object.assign(\n this.righeDs.metaInfo.tableMetadata.extraProps || {},\n { toolbar: { hideManageState: true, hideBatchActions: true } }\n) as any;"
21805
22391
  },
21806
22392
  {
21807
22393
  "id": "code_6",
21808
22394
  "title": "Snippet 6",
22395
+ "language": "json",
22396
+ "code": "{\n \"archetypes\": {\n \"list\": {\n \"proportionalColwidth\": true,\n \"advancedFilter\": false,\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n }\n }\n }\n}"
22397
+ },
22398
+ {
22399
+ "id": "code_7",
22400
+ "title": "Snippet 7",
22401
+ "language": "html",
22402
+ "code": "<wuic-list-grid\n [hardcodedDatasource]=\"citiesDs\"\n (onAfterRender)=\"handleAfterRender($event)\"\n (onBeforeRowRender)=\"handleBeforeRowRender($event)\"\n (onAfterRowRender)=\"handleAfterRowRender($event)\"\n (onPaging)=\"handlePaging($event)\"\n (onSorting)=\"handleSorting($event)\"\n (onFiltering)=\"handleFiltering($event)\"\n (onPTableSelectionChange)=\"handleSelection($event)\"\n (onPTableRowExpand)=\"handleRowExpand($event)\"\n (onPTableRowCollapse)=\"handleRowCollapse($event)\"\n (onPTableColumnResize)=\"handleColumnResize($event)\"\n (onPTableColumnReorder)=\"handleColumnReorder($event)\">\n</wuic-list-grid>"
22403
+ },
22404
+ {
22405
+ "id": "code_8",
22406
+ "title": "Snippet 8",
21809
22407
  "language": "ts",
21810
22408
  "code": "import { AfterViewInit, OnDestroy, ViewChild } from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport {\n ListGridAfterRenderEvent,\n ListGridAfterRowRenderEvent,\n ListGridBeforeRowRenderEvent,\n ListGridComponent\n} from 'wuic-framework-lib';\n\nexport class HostComponent implements AfterViewInit, OnDestroy {\n @ViewChild(ListGridComponent) grid?: ListGridComponent;\n private readonly subs = new Subscription();\n\n ngAfterViewInit(): void {\n if (!this.grid) return;\n\n this.subs.add(this.grid.onAfterRender.subscribe((e: ListGridAfterRenderEvent) => console.log(e)));\n this.subs.add(this.grid.onBeforeRowRender.subscribe((e: ListGridBeforeRowRenderEvent) => {\n // Beispiel Zeilen-Cancel\n if (e.row?.deleted === true) e.cancelRender();\n }));\n this.subs.add(this.grid.onAfterRowRender.subscribe((e: ListGridAfterRowRenderEvent) => console.log(e)));\n this.subs.add(this.grid.onPaging.subscribe((e) => console.log('paging', e)));\n this.subs.add(this.grid.onSorting.subscribe((e) => console.log('sorting', e)));\n this.subs.add(this.grid.onFiltering.subscribe((e) => console.log('filtering', e)));\n this.subs.add(this.grid.onPTableSelectionChange.subscribe((e) => console.log('selection', e)));\n this.subs.add(this.grid.onPTableRowExpand.subscribe((e) => console.log('expand', e)));\n this.subs.add(this.grid.onPTableRowCollapse.subscribe((e) => console.log('collapse', e)));\n this.subs.add(this.grid.onPTableColumnResize.subscribe((e) => console.log('resize', e)));\n this.subs.add(this.grid.onPTableColumnReorder.subscribe((e) => console.log('reorder', e)));\n }\n\n ngOnDestroy(): void {\n this.subs.unsubscribe();\n }\n}"
22409
+ },
22410
+ {
22411
+ "id": "code_9",
22412
+ "title": "Snippet 9",
22413
+ "language": "ts",
22414
+ "code": "@Input() rowCustomSelect: (rowData: any, $event: any, dt: Table) => void;"
22415
+ },
22416
+ {
22417
+ "id": "code_10",
22418
+ "title": "Snippet 10",
22419
+ "language": "ts",
22420
+ "code": "onRowSelected = (_event: any, rowData: any, _dt: any): void => {\n // rowData ist der von list-grid gelieferte Datensatz\n this.handleSelection(rowData);\n};"
22421
+ },
22422
+ {
22423
+ "id": "code_11",
22424
+ "title": "Snippet 11",
22425
+ "language": "html",
22426
+ "code": "<wuic-data-source #ds [hardcodedRoute]=\"'preventivi'\" [autoload]=\"true\"></wuic-data-source>\n<wuic-list-grid [hardcodedDatasource]=\"ds\" [rowCustomSelect]=\"onRowSelected\"></wuic-list-grid>"
21811
22427
  }
21812
22428
  ]
21813
22429
  }
@@ -21979,7 +22595,7 @@ const frameworkDocsContent = {
21979
22595
  "id": "code_1",
21980
22596
  "title": "Snippet 1",
21981
22597
  "language": "json",
21982
- "code": "{\n \"archetypes\": {\n \"map\": {\n \"advancedFilter\": false,\n \"center\": {\n \"lat\": 39.8283,\n \"lng\": -98.5795\n },\n \"zoom\": 4,\n \"minZoom\": 3,\n \"maxZoom\": 14,\n \"useCurrentLocation\": false,\n \"useClusterer\": true,\n \"filterByBoundaries\": false,\n \"titleField\": \"CityName\",\n \"infoField\": \"StateProvinceID__dictionary_display_value\",\n \"customMarkerImageSrc\": \"\",\n \"customMarkerImageSrcField\": \"\",\n \"markerContentCallback\": \"const glyph = String(record?.CityName || 'C').trim().charAt(0).toUpperCase() || 'C'; const pop = Number(record?.LatestRecordedPopulation || 0); const bg = pop > 1000000 ? '#B91C1C' : (pop > 300000 ? '#EA580C' : '#2563EB'); const pin = new google.maps.marker.PinElement({ background: bg, borderColor: '#0F172A', glyphColor: '#FFFFFF', glyph: glyph, scale: 1.2 }); return pin.element;\",\n \"infoFunction\": \"\",\n \"itemTemplateString\": \"<div class=\\\"map-info\\\"> <div class=\\\"map-info-title\\\">{{ rowData.record?.CityName || rowData.title }}</div> <div>{{ rowData.record?.StateProvinceID__dictionary_display_value || rowData.record?.StateProvinceID || '' }}</div> <div>Popolazione: {{ rowData.record?.LatestRecordedPopulation | number:'1.0-0' }}</div> <div>{{ rowData.record?.Location || '' }}</div> <wuic-data-action-button-lazy [data]=\\\"rowData.record\\\" [metaInfo]=\\\"metaInfo\\\" [datasource]=\\\"datasource\\\" [simplified]=\\\"true\\\"></wuic-data-action-button-lazy> </div>\"\n }\n }\n}"
22598
+ "code": "{\n \"archetypes\": {\n \"map\": {\n \"advancedFilter\": false,\n \"center\": {\n \"lat\": 39.8283,\n \"lng\": -98.5795\n },\n \"zoom\": 4,\n \"minZoom\": 3,\n \"maxZoom\": 14,\n \"useCurrentLocation\": false,\n \"useClusterer\": true,\n \"filterByBoundaries\": false,\n \"titleField\": \"CityName\",\n \"infoField\": \"StateProvinceID__dictionary_display_value\",\n \"customMarkerImageSrc\": \"\",\n \"customMarkerImageSrcField\": \"\",\n \"markerContentCallback\": \"const glyph = String(record?.CityName || 'C').trim().charAt(0).toUpperCase() || 'C'; const pop = Number(record?.LatestRecordedPopulation || 0); const bg = pop > 1000000 ? '#B91C1C' : (pop > 300000 ? '#EA580C' : '#2563EB'); const pin = new google.maps.marker.PinElement({ background: bg, borderColor: '#0F172A', glyphColor: '#FFFFFF', glyphText: glyph, scale: 1.2 }); return pin;\",\n \"infoFunction\": \"\",\n \"itemTemplateString\": \"<div class=\\\"map-info\\\"> <div class=\\\"map-info-title\\\">{{ rowData.record?.CityName || rowData.title }}</div> <div>{{ rowData.record?.StateProvinceID__dictionary_display_value || rowData.record?.StateProvinceID || '' }}</div> <div>Popolazione: {{ rowData.record?.LatestRecordedPopulation | number:'1.0-0' }}</div> <div>{{ rowData.record?.Location || '' }}</div> <wuic-data-action-button-lazy [data]=\\\"rowData.record\\\" [metaInfo]=\\\"metaInfo\\\" [datasource]=\\\"datasource\\\" [simplified]=\\\"true\\\"></wuic-data-action-button-lazy> </div>\"\n }\n }\n}"
21983
22599
  },
21984
22600
  {
21985
22601
  "id": "code_2",
@@ -22064,14 +22680,47 @@ const frameworkDocsContent = {
22064
22680
  {
22065
22681
  "id": "sec_3",
22066
22682
  "title": "Verweis auf den Designer"
22683
+ },
22684
+ {
22685
+ "id": "sec_4",
22686
+ "title": "Report-Parameter (gefilterte SQL-Variablen)"
22687
+ },
22688
+ {
22689
+ "id": "sec_5",
22690
+ "title": "Erforderliche Backend-Konfiguration"
22067
22691
  }
22068
22692
  ],
22069
22693
  "sections": [
22070
22694
  {
22071
22695
  "id": "overview",
22072
22696
  "title": "Overview",
22073
- "html": "<h1>Reporting</h1><p>Operative Berichterstellung auf Route <code>#/&lt;route&gt;/report-viewer</code>.</p><h2>Screenshot-Referenz (Benutzerhandbuch)</h2><ul><li><code>manual__reporting__01.png</code>: Öffnung des Report-Viewers mit Report-Rendering und operativer Toolbar.</li><li><code>manual__reporting__02.png</code>: Runtime-Beispiel mit auf den Report angewendeten Parametern/Filtern.</li></ul><h2>Report Viewer</h2><p>Der <code>report-viewer</code> führt Stimulsoft-Reports aus und zeigt sie an, die der aktuellen Metadata-Route zugeordnet sind.</p><p>Hauptfunktionen:</p><ul><li>Report-Öffnung aus List/Grid mit Reportname (<code>reportName</code>) und optionalen Parametern.</li><li>Datenrendering zur Laufzeit mit Viewer-Toolbar (Zoom, Export, Druck).</li><li>Unterstützung für Filter/Parameter, die von der aufrufenden Seite propagiert werden.</li></ul><h2>Verweis auf den Designer</h2><p>Für die Erstellung oder Bearbeitung der <code>.mrt</code>-Reportdatei siehe den Unterabschnitt</p><p><a href=\"#/framework-docs/report-designer\">Report Designer</a> unter <code>Designer &amp; Workflow</code>.</p>",
22074
- "codeSamples": []
22697
+ "html": "<h1>Reporting</h1><p>Operative Berichterstellung auf Route <code>#/&lt;route&gt;/report-viewer</code>.</p><h2>Screenshot-Referenz (Benutzerhandbuch)</h2><ul><li><code>manual__reporting__01.png</code>: Öffnung des Report-Viewers mit Report-Rendering und operativer Toolbar.</li><li><code>manual__reporting__02.png</code>: Runtime-Beispiel mit auf den Report angewendeten Parametern/Filtern.</li></ul><h2>Report Viewer</h2><p>Der <code>report-viewer</code> führt Stimulsoft-Reports aus und zeigt sie an, die der aktuellen Metadata-Route zugeordnet sind.</p><p>Hauptfunktionen:</p><ul><li>Report-Öffnung aus List/Grid mit Reportname (<code>reportName</code>) und optionalen Parametern.</li><li>Datenrendering zur Laufzeit mit Viewer-Toolbar (Zoom, Export, Druck).</li><li>Unterstützung für Filter/Parameter, die von der aufrufenden Seite propagiert werden.</li></ul><h2>Verweis auf den Designer</h2><p>Für die Erstellung oder Bearbeitung der <code>.mrt</code>-Reportdatei siehe den Unterabschnitt</p><p><a href=\"#/framework-docs/report-designer\">Report Designer</a> unter <code>Designer &amp; Workflow</code>.</p><h2>Report-Parameter (gefilterte SQL-Variablen)</h2><p>Um einen Report auf eine bestimmte Entitat zu filtern (z.B. Druck einer einzelnen Rechnung), lautet das kanonische Muster:</p><p>1. <strong>In der `.mrt`-Datei</strong>: deklariere eine <strong>Variable</strong> im Dictionary mit <code>AllowUseAsSqlParameter = True</code> (Stimulsoft 2026.1+). Verwende die Variable als <code>@paramName</code> in den SqlCommands:</p><p>Snippet 1:</p><p> &gt; <strong>Falle</strong>: KEINE Verwendung des Legacy-Blocks <code>&lt;StiDataParameter&gt;</code> im DataSource — er wird von der aktuellen Stimulsoft-Version nicht mehr unterstutzt und produziert <code>ArgumentException: Int32 is not a valid value for Int32. (Parameter &#39;value&#39;)</code> wahrend <code>report.Load()</code>.</p><p>2. <strong>In der Viewer-URL</strong>: ubergib den Wert uber den <code>parameters</code>-Query-Param (NICHT als direkten Query-Param):</p><p>Snippet 2:</p><p> <code>parameters</code>-URL-encoded-Syntax: <code>name||eq||value</code> (durch <code>@</code> getrennt fur mehrere Parameter).</p><p>3. <strong>Aus einer Row-Action oder einem UI-Link</strong>: verwende den kanonischen Callback zum Erstellen der URL:</p><p>Snippet 3:</p><h2>Erforderliche Backend-Konfiguration</h2><p>In der <code>appsettings.json</code> der App muss die Einstellung <code>AppSettings.reportQueryTimeout</code> vorhanden sein (z.B. <code>&quot;120&quot;</code>). Ohne sie wirft <code>ReportViewerController.prepareDataSql</code> <code>int.Parse(null)</code> → 500. Beispiel:</p><p>Snippet 4:</p>",
22698
+ "codeSamples": [
22699
+ {
22700
+ "id": "code_1",
22701
+ "title": "Snippet 1",
22702
+ "language": "sql",
22703
+ "code": " SELECT ... FROM fatture_inviate WHERE id = @fattura_id\n SELECT ... FROM fatture_inviate_righe WHERE fattura_id = @fattura_id"
22704
+ },
22705
+ {
22706
+ "id": "code_2",
22707
+ "title": "Snippet 2",
22708
+ "language": "text",
22709
+ "code": " ✅ #/<route>/report-viewer?reportName=Report.mrt&parameters=fattura_id%7C%7Ceq%7C%7C42\n ❌ #/<route>/report-viewer?reportName=Report.mrt&fattura_id=42 <-- ignoriert"
22710
+ },
22711
+ {
22712
+ "id": "code_3",
22713
+ "title": "Snippet 3",
22714
+ "language": "js",
22715
+ "code": " const recordId = (record && record.id && record.id.value) ?? (record && record.id);\n const params = 'fattura_id||eq||' + recordId;\n window.location.hash = '#/<route>/report-viewer?reportName=Report.mrt&parameters='\n + encodeURIComponent(params);"
22716
+ },
22717
+ {
22718
+ "id": "code_4",
22719
+ "title": "Snippet 4",
22720
+ "language": "json",
22721
+ "code": "\"AppSettings\": {\n \"reportQueryTimeout\": \"120\"\n}"
22722
+ }
22723
+ ]
22075
22724
  }
22076
22725
  ],
22077
22726
  "images": [
@@ -22804,28 +23453,54 @@ const frameworkDocsContent = {
22804
23453
  },
22805
23454
  {
22806
23455
  "id": "sec_5",
22807
- "title": "Assistierter Workflow: Clone Lookup + Standard-Filter + Lookup-Hierarchie"
23456
+ "title": "Slim-Combo — Zusatzfelder via `mc_props_bag.slimCombo`"
22808
23457
  },
22809
23458
  {
22810
23459
  "id": "sec_6",
22811
- "title": "Client-/Server-Effekte"
23460
+ "title": "Assistierter Workflow: Clone Lookup + Standard-Filter + Lookup-Hierarchie"
22812
23461
  },
22813
23462
  {
22814
23463
  "id": "sec_7",
23464
+ "title": "Client-/Server-Effekte"
23465
+ },
23466
+ {
23467
+ "id": "sec_8",
22815
23468
  "title": "Betriebshinweise"
23469
+ },
23470
+ {
23471
+ "id": "sec_9",
23472
+ "title": "Auto-Fetch bei programmatischen FK-Aenderungen"
22816
23473
  }
22817
23474
  ],
22818
23475
  "sections": [
22819
23476
  {
22820
23477
  "id": "overview",
22821
23478
  "title": "Overview",
22822
- "html": "<h1>Widget Lookup</h1><p>Spalten-Widget fuer Beziehungen (<code>lookupByID</code>, <code>multiple_check</code>) mit dediziertem <strong>Lookup</strong>-Tab im Metadata-Editor fuer Spalten.</p><h2>Beispiele</h2><ul><li><a class=\"wuic-demo-link\" href=\"#/examples/3c-odata-cities-grid\" target=\"_blank\" rel=\"noopener noreferrer\">Pattern 3c — reines OData-Lookup (State/Province aus `/odata/Stateprovinces`)</a> — demonstriert <code>mc_props_bag.lookup.endpoint</code> mit Provider <code>odata</code> und Inline-Rendering via <code>$expand</code>.</li></ul><h2>Wann verwenden</h2><ul><li>Auswahl verknuepfter Datensaetze aus einer externen Metadata-Tabelle.</li><li>Szenarien mit serverseitiger Suche und Lookup-Paging.</li><li>Unterstuetzung von Einzelauswahl (<code>lookupByID</code>) und Mehrfachauswahl (<code>multiple_check</code>).</li></ul><h2>Spalten-Metadata-Eigenschaften (Tab Lookup)</h2><ul><li><code>mc_ui_lookup_entity_name</code>: Metadata-Route der Lookup-Quelle.</li><li><code>mc_ui_lookup_dataValueField</code>: zurueckgegebenes ID-Feld.</li><li><code>mc_ui_lookup_dataTextField</code>: angezeigtes Beschreibungsfeld.</li><li><code>mc_ui_lookup_filter</code>: Lookup-Basisfilter.</li><li><code>mc_serverside_operations</code>: serverseitige Suche/Paging.</li><li><code>mc_ui_pagesize</code>: Seitengroesse der Lookup-Ergebnisse.</li><li><code>mc_ui_lookup_edit_allow</code>: aktiviert Bearbeitung des Lookup-Datensatzes aus dem Popup.</li><li><code>mc_ui_lookup_insert_allow</code>: aktiviert Einfuegung eines Lookup-Datensatzes aus dem Popup.</li><li><code>mc_ui_lookup_search_grid</code>: aktiviert Such-Grid im Popup.</li><li><code>mc_logic_allow_navigation</code>: Navigation zur verknuepften Route.</li></ul><h2>Nuetzlicher mc_props_bag</h2><p>Snippet 1:</p><ul><li><code>lookup.filter</code>: zusaetzliche Einschraenkung client-/serverseitig in der Lookup-Abfrage.</li><li><code>lookup.virtualize</code>: aktiviert virtuelles Scrollen auf <code>p-autocomplete</code> des Lookup-Widgets.</li><li><code>lookup.virtualize.enabled</code>: toleranter Parser (<code>true/false</code>, <code>1/0</code>, aequivalente Strings); Standard <code>false</code> wenn absent.</li><li><code>lookup.virtualize.itemSize</code>: virtuelle Zeilenhoehe in px; Standard <code>44</code>.</li><li><code>lookup.endpoint</code>: Override des Datenproviders fuer das Dropdown. Wenn gesetzt, geht das nested Datasource des Lookup-Editors nicht mehr ueber den Metadata-Combo-Endpoint (<code>MetaService.getFlatRecordComboData</code>), sondern direkt ueber den in <code>type</code> deklarierten Provider mit der URL in <code>uri</code>.</li><li><code>lookup.endpoint.type</code>: <code>&#39;odata&#39;</code> (Dispatcher leitet an <code>DataProviderOdataService.selectCombo</code> weiter, das GET mit <code>$top</code>, <code>$count=true</code>, <code>$select=valueField,textField</code> und <code>$filter=contains(textField,&#39;&lt;query&gt;&#39;)</code> fuer die serverseitige Suche emittiert). Andere Werte folgen derselben Logik wie der Tabellen-Level <code>extraProps.endpoint.type</code>.</li><li><code>lookup.endpoint.uri</code>: Basis-URL des Entity-Sets (z.B. <code>/odata/&lt;EntitySet&gt;</code>), mit oder ohne explizite Origin. Bereits vorhandene Query-Params werden ignoriert — der Provider rekonstruiert die Params bei jedem Fetch.</li><li><code>form.columns</code>: Feldbreite im Bearbeitungsformular.</li><li><code>style.editCss</code>: benutzerdefinierter Editor-Stil.</li></ul><h2>Assistierter Workflow: Clone Lookup + Standard-Filter + Lookup-Hierarchie</h2><ul><li>Operativer Inline-Flow: <code>suggestLookup2 -&gt; suggestLookupDefaultFilter -&gt; getLookupListByRoute -&gt; getSeletClauseByLookupHierarchy</code>.</li><li>Inline-Snippet Hauptfelder: <code>mc_ui_lookup_entity_name</code>, <code>mc_ui_lookup_dataValueField</code>, <code>mc_ui_lookup_dataTextField</code>, <code>mc_ui_lookup_filter</code>.</li><li>Inline-Snippet Schnellfilter: <code>lookup.filter: {&quot;logic&quot;:&quot;AND&quot;,&quot;filters&quot;:[{&quot;field&quot;:&quot;is_active&quot;,&quot;operatore&quot;:&quot;eq&quot;,&quot;value&quot;:true}]}</code>.</li><li>Client-Effekt: beschleunigtes Lookup-Setup im Metadata-Editor und konsistenter, sofort einsatzbereiter Filter im Widget.</li><li>Server-Effekt: Lookup-Abfrage und Select-Clause bleiben mit den <code>MetaService.*</code>-Endpoints abgeglichen, ohne manuelles Mapping.</li></ul><p>Schnelle operative Checkliste:</p><p>1. Basiskonfiguration mit <code>suggestLookup2</code> klonen;</p><p>2. Standard-Filter mit <code>suggestLookupDefaultFilter</code> generieren;</p><p>3. Hierarchie/Select-Clause vom Lookup-Tree verfeinern;</p><p>4. Zur Laufzeit validieren, dass Suche und Value/Text-Mapping konsistent sind.</p><h2>Client-/Server-Effekte</h2><ul><li>Client: Rendering des Lookup-Widgets, Suche, Label-/Value-Binding.</li><li>Client: bei aktiviertem <code>lookup.virtualize</code> rendert das Autocomplete nur das visuell sichtbare Subset der Optionen.</li><li>Server: paginierte/gefilterte Lookup-Abfrage ueber <code>MetaService.getFlatRecordData</code>.</li></ul><h2>Betriebshinweise</h2><ul><li>Bei <code>multiple_check</code> bleibt der Lookup-Teil identisch, nur der Multi-Value-Renderer aendert sich.</li><li>Wenn <code>mc_ui_lookup_entity_name</code> fehlt, kann das Widget die Quelle nicht aufloesen.</li></ul>",
23479
+ "html": "<h1>Widget Lookup</h1><p>Spalten-Widget fuer Beziehungen (<code>lookupByID</code>, <code>multiple_check</code>) mit dediziertem <strong>Lookup</strong>-Tab im Metadata-Editor fuer Spalten.</p><h2>Beispiele</h2><ul><li><a class=\"wuic-demo-link\" href=\"#/examples/3c-odata-cities-grid\" target=\"_blank\" rel=\"noopener noreferrer\">Pattern 3c — reines OData-Lookup (State/Province aus `/odata/Stateprovinces`)</a> — demonstriert <code>mc_props_bag.lookup.endpoint</code> mit Provider <code>odata</code> und Inline-Rendering via <code>$expand</code>.</li></ul><h2>Wann verwenden</h2><ul><li>Auswahl verknuepfter Datensaetze aus einer externen Metadata-Tabelle.</li><li>Szenarien mit serverseitiger Suche und Lookup-Paging.</li><li>Unterstuetzung von Einzelauswahl (<code>lookupByID</code>) und Mehrfachauswahl (<code>multiple_check</code>).</li></ul><h2>Spalten-Metadata-Eigenschaften (Tab Lookup)</h2><ul><li><code>mc_ui_lookup_entity_name</code>: Metadata-Route der Lookup-Quelle.</li><li><code>mc_ui_lookup_dataValueField</code>: zurueckgegebenes ID-Feld.</li><li><code>mc_ui_lookup_dataTextField</code>: angezeigtes Beschreibungsfeld.</li><li><code>mc_ui_lookup_filter</code>: Lookup-Basisfilter.</li><li><code>mc_serverside_operations</code>: serverseitige Suche/Paging.</li><li><code>mc_ui_pagesize</code>: Seitengroesse der Lookup-Ergebnisse.</li><li><code>mc_ui_lookup_edit_allow</code>: aktiviert Bearbeitung des Lookup-Datensatzes aus dem Popup.</li><li><code>mc_ui_lookup_insert_allow</code>: aktiviert Einfuegung eines Lookup-Datensatzes aus dem Popup.</li><li><code>mc_ui_lookup_search_grid</code>: aktiviert Such-Grid im Popup.</li><li><code>mc_logic_allow_navigation</code>: Navigation zur verknuepften Route.</li></ul><h2>Nuetzlicher mc_props_bag</h2><p>Snippet 1:</p><ul><li><code>lookup.filter</code>: zusaetzliche Einschraenkung client-/serverseitig in der Lookup-Abfrage.</li><li><code>lookup.virtualize</code>: aktiviert virtuelles Scrollen auf <code>p-autocomplete</code> des Lookup-Widgets.</li><li><code>lookup.virtualize.enabled</code>: toleranter Parser (<code>true/false</code>, <code>1/0</code>, aequivalente Strings); Standard <code>false</code> wenn absent.</li><li><code>lookup.virtualize.itemSize</code>: virtuelle Zeilenhoehe in px; Standard <code>44</code>.</li><li><code>lookup.endpoint</code>: Override des Datenproviders fuer das Dropdown. Wenn gesetzt, geht das nested Datasource des Lookup-Editors nicht mehr ueber den Metadata-Combo-Endpoint (<code>MetaService.getFlatRecordComboData</code>), sondern direkt ueber den in <code>type</code> deklarierten Provider mit der URL in <code>uri</code>.</li><li><code>lookup.endpoint.type</code>: <code>&#39;odata&#39;</code> (Dispatcher leitet an <code>DataProviderOdataService.selectCombo</code> weiter, das GET mit <code>$top</code>, <code>$count=true</code>, <code>$select=valueField,textField</code> und <code>$filter=contains(textField,&#39;&lt;query&gt;&#39;)</code> fuer die serverseitige Suche emittiert). Andere Werte folgen derselben Logik wie der Tabellen-Level <code>extraProps.endpoint.type</code>.</li><li><code>lookup.endpoint.uri</code>: Basis-URL des Entity-Sets (z.B. <code>/odata/&lt;EntitySet&gt;</code>), mit oder ohne explizite Origin. Bereits vorhandene Query-Params werden ignoriert — der Provider rekonstruiert die Params bei jedem Fetch.</li><li><code>form.columns</code>: Feldbreite im Bearbeitungsformular.</li><li><code>style.editCss</code>: benutzerdefinierter Editor-Stil.</li></ul><h2>Slim-Combo — Zusatzfelder via <code>mc_props_bag.slimCombo</code></h2><p>Das Lookup-Dropdown emittiert standardmaessig ein minimales SELECT auf der verknuepften Tabelle: PK + <code>mc_ui_lookup_dataValueField</code> + <code>mc_ui_lookup_dataTextField</code>. Das reduziert den Combo-Payload. Wenn der Client weitere Spalten aus der verknuepften Tabelle benoetigt — typischerweise um abhaengige Felder via <code>mc_selection_changed_custom_function__fn</code> vorzubefuellen — wird <code>mc_props_bag.slimCombo</code> als <strong>Array zusaetzlicher Spaltennamen</strong> verwendet, die im SELECT des Combos eingeschlossen werden:</p><p>Snippet 2:</p><ul><li>Array: die Namen werden der <code>columnRestrictionList</code> der Combo-Response hinzugefuegt (Logik in <code>DataProviderMetaService.buildSlimComboRestriction</code>).</li><li><code>slimCombo: false</code>: deaktiviert die Restriktion → der Combo gibt <strong>alle</strong> Spalten zurueck. Nuetzlich beim Debugging, groesserer Payload.</li><li>Hat die Spalte <code>mc_ui_lookup_combo_text_edit_computed_dataTextField</code> gesetzt (berechnete Display-Formel), wird <code>slimCombo</code> ignoriert und der Combo gibt alle Spalten zurueck (die Formel kann beliebige Felder referenzieren).</li></ul><p>Die Zusatzfelder landen in <code>record[&#39;&lt;col&gt;__lookup_obj&#39;].value</code> (vollstaendiger Datensatz aus der verknuepften Tabelle, bereits mit den in <code>slimCombo</code> deklarierten Feldern) und sind aus dem Callback <code>mc_selection_changed_custom_function__fn</code> zugaenglich:</p><p>Snippet 3:</p><p>Tradeoff: Combo-Payload etwas groesser, aber kein zusaetzlicher HTTP-Roundtrip (<code>getFlatRecordData</code> zum Abrufen des vollstaendigen Datensatzes per id) bei jeder Auswahl.</p><h2>Assistierter Workflow: Clone Lookup + Standard-Filter + Lookup-Hierarchie</h2><ul><li>Operativer Inline-Flow: <code>suggestLookup2 -&gt; suggestLookupDefaultFilter -&gt; getLookupListByRoute -&gt; getSeletClauseByLookupHierarchy</code>.</li><li>Inline-Snippet Hauptfelder: <code>mc_ui_lookup_entity_name</code>, <code>mc_ui_lookup_dataValueField</code>, <code>mc_ui_lookup_dataTextField</code>, <code>mc_ui_lookup_filter</code>.</li><li>Inline-Snippet Schnellfilter: <code>lookup.filter: {&quot;logic&quot;:&quot;AND&quot;,&quot;filters&quot;:[{&quot;field&quot;:&quot;is_active&quot;,&quot;operatore&quot;:&quot;eq&quot;,&quot;value&quot;:true}]}</code>.</li><li>Client-Effekt: beschleunigtes Lookup-Setup im Metadata-Editor und konsistenter, sofort einsatzbereiter Filter im Widget.</li><li>Server-Effekt: Lookup-Abfrage und Select-Clause bleiben mit den <code>MetaService.*</code>-Endpoints abgeglichen, ohne manuelles Mapping.</li></ul><p>Schnelle operative Checkliste:</p><p>1. Basiskonfiguration mit <code>suggestLookup2</code> klonen;</p><p>2. Standard-Filter mit <code>suggestLookupDefaultFilter</code> generieren;</p><p>3. Hierarchie/Select-Clause vom Lookup-Tree verfeinern;</p><p>4. Zur Laufzeit validieren, dass Suche und Value/Text-Mapping konsistent sind.</p><h2>Client-/Server-Effekte</h2><ul><li>Client: Rendering des Lookup-Widgets, Suche, Label-/Value-Binding.</li><li>Client: bei aktiviertem <code>lookup.virtualize</code> rendert das Autocomplete nur das visuell sichtbare Subset der Optionen.</li><li>Server: paginierte/gefilterte Lookup-Abfrage ueber <code>MetaService.getFlatRecordData</code>.</li></ul><h2>Betriebshinweise</h2><ul><li>Bei <code>multiple_check</code> bleibt der Lookup-Teil identisch, nur der Multi-Value-Renderer aendert sich.</li><li>Wenn <code>mc_ui_lookup_entity_name</code> fehlt, kann das Widget die Quelle nicht aufloesen.</li></ul><h2>Auto-Fetch bei programmatischen FK-Aenderungen</h2><p>Der <code>wuic-lookup-editor</code> abonniert automatisch das <code>BehaviorSubject</code> von <code>record[mc_nome_colonna]</code> und behandelt programmatische FK-Aenderungen (z.B. Import aus Quelldokument, Custom-Callback der <code>record.cliente_id.next(99)</code> aufruft):</p><ul><li>Wenn der neue Wert <strong>bereits in `items`</strong> vorhanden ist (geladene Lookup-Seite): nur <code>__lookup_obj</code> wird zur Konsistenz aktualisiert, kein HTTP-Aufruf.</li><li>Wenn der neue Wert <strong>nicht in items</strong> ist (z.B. Wert ausserhalb der aktuellen Combo-Seite oder noch nicht abgerufen): ein <strong>Second-Stage-Fetch</strong> mit Filter <code>WHERE &lt;valueField&gt; eqor &lt;newValue&gt;</code> wird ausgeloest, um den verknuepften Datensatz abzurufen und das Dropdown automatisch zu fuellen.</li></ul><p>Skip-Faelle (no-op):</p><ul><li>Multi-Value-Lookup (<code>isFilter</code> mit <code>eqor</code>, <code>multiple_check</code>)</li><li>Erstes Emit des BS (bereits durch den Init-Flow abgedeckt)</li><li>null/undefined-Wert (Benutzer hat geleert)</li><li><code>hasActiveLookupQuery=true</code> (Benutzer tippt eine manuelle Abfrage)</li></ul><p><strong>Implikation fuer Custom-Forms</strong>: nur den FK zu setzen genuegt — manuelle Workarounds zum Befuellen von <code>__lookup_obj</code> oder dem Joined-Alias sind nicht mehr noetig. Die Dropdown-Anzeige aktualisiert sich von selbst.</p><p>Snippet 4:</p><p>&gt; <strong>Hinweis</strong>: der Auto-Fetch wird nur ausgeloest, wenn der <code>&lt;wuic-lookup-editor&gt;</code> <strong>montiert</strong> ist (Zelle im Edit-Modus). Fuer die <strong>Listenanzeige</strong> (read-only Zelle) liest <code>formatGridViewValue</code> den Joined-Alias <code>&lt;route&gt;___&lt;textField&gt;__&lt;col&gt;</code> direkt aus dem Record — dieser muss weiterhin manuell befuellt werden, wenn er nicht vom Backend kommt.</p>",
22823
23480
  "codeSamples": [
22824
23481
  {
22825
23482
  "id": "code_1",
22826
23483
  "title": "Snippet 1",
22827
23484
  "language": "json",
22828
23485
  "code": "{\n \"lookup\": {\n \"filter\": {\n \"logic\": \"AND\",\n \"filters\": [\n { \"field\": \"is_active\", \"operatore\": \"eq\", \"value\": true }\n ]\n },\n \"virtualize\": {\n \"enabled\": true,\n \"itemSize\": 44\n },\n \"endpoint\": {\n \"type\": \"odata\",\n \"uri\": \"http://localhost:5000/odata/StateProvinces\"\n }\n },\n \"form\": {\n \"columns\": 1,\n \"disabled\": false\n },\n \"style\": {\n \"editCss\": \"max-width: 420px;\"\n }\n}"
23486
+ },
23487
+ {
23488
+ "id": "code_2",
23489
+ "title": "Snippet 2",
23490
+ "language": "json",
23491
+ "code": "{\n \"slimCombo\": [\"unita_misura_id\",\"codice_iva_id\",\"prezzo_vendita\",\"sconto_default\"]\n}"
23492
+ },
23493
+ {
23494
+ "id": "code_3",
23495
+ "title": "Snippet 3",
23496
+ "language": "ts",
23497
+ "code": "prodCol.mc_selection_changed_custom_function__fn = (record, _f, _m, newValue) => {\n const prod = record['prodotto_id__lookup_obj']?.value;\n if (!prod) return;\n record['descrizione']?.next(prod.descrizione);\n record['prezzo_unitario']?.next(prod.prezzo_vendita);\n record['unita_misura_id']?.next(prod.unita_misura_id);\n record['codice_iva_id']?.next(prod.codice_iva_id);\n};"
23498
+ },
23499
+ {
23500
+ "id": "code_4",
23501
+ "title": "Snippet 4",
23502
+ "language": "ts",
23503
+ "code": "// FRUEHER (manueller Workaround erforderlich)\nrecord.cliente_id.next(99);\nrecord.cliente_id__lookup_obj.next({ id: 99, ragione_sociale: 'Acme S.r.l.' });\nrecord['clienti___ragione_sociale__cliente_id'].next('Acme S.r.l.');\n\n// JETZT (Auto-Fetch durch lookup-editor)\nrecord.cliente_id.next(99);\n// → lookup-editor montiert? Subscribe erkennt Change → gefilterter Fetch → __lookup_obj befuellt → Dropdown zeigt \"Acme S.r.l.\""
22829
23504
  }
22830
23505
  ]
22831
23506
  }
@@ -23088,10 +23763,14 @@ const frameworkDocsContent = {
23088
23763
  },
23089
23764
  {
23090
23765
  "id": "sec_4",
23091
- "title": "Empfohlener operativer Ablauf"
23766
+ "title": "Kontextmenue der Elemente"
23092
23767
  },
23093
23768
  {
23094
23769
  "id": "sec_5",
23770
+ "title": "Empfohlener operativer Ablauf"
23771
+ },
23772
+ {
23773
+ "id": "sec_6",
23095
23774
  "title": "Betriebshinweise"
23096
23775
  }
23097
23776
  ],
@@ -23099,7 +23778,7 @@ const frameworkDocsContent = {
23099
23778
  {
23100
23779
  "id": "overview",
23101
23780
  "title": "Overview",
23102
- "html": "<h1>Designer</h1><p>Visueller Editor fuer metadata-gesteuerte Dashboards auf Route <code>#/cities/dashboard</code>.</p><h2>Geltungsbereich</h2><ul><li>Board-Komposition via Drag-and-Drop von Knoten und UI-Bloecken.</li><li>Layout und Styling gesteuert durch <code>boardcontent</code> und Tabellen-/Spalten-Metadata.</li><li>Property-Panel-Editing ohne Hardcoding in den Runtime-Komponenten.</li></ul><h2>Designer-Palette</h2><ul><li><code>Datasource</code>: Metadata-Datenquelle (<code>route</code>, Filter, Sortierung, Paging).</li><li><code>DataRepeater</code>: Listen-/Karten-Visualisierung verbunden mit Datasource.</li><li><code>Dynamic Template</code>: dynamisches Template-Rendering mit metadata-gesteuerten Inputs.</li><li><code>Action/Button</code>: Benutzeraktionen verknuepft mit Datensaetzen und Callbacks.</li><li><code>Container/Layout</code>: strukturelle Bloecke fuer Spalten, Zeilen und Wrapper.</li></ul><h2>Hauptfunktionen</h2><ul><li>Drag-and-Drop von Knoten auf die Canvas mit Persistierung im <code>boardcontent</code>.</li><li>Kontextbezogenes Property-Panel mit zu den ausgewaehlten Komponenten konsistenten Inputs.</li><li>Binding zwischen Knoten (<code>datasource -&gt; repeater/template</code>) mit Minimalvalidierung.</li><li>Integrierte Runtime-Vorschau und Layout-Neuausrichtung.</li><li>Verwaltung ausstehender Aenderungen mit Guard vor dem Verlassen.</li></ul><h2>Empfohlener operativer Ablauf</h2><ul><li>Ziel-Dashboard-Route oeffnen.</li><li>Knoten aus der Palette einfuegen/verbinden.</li><li>Wesentliche <code>inputs</code> konfigurieren (z.B. <code>route</code>, <code>action</code>, <code>datasource</code>).</li><li>Speichern und Ergebnis in der Runtime validieren.</li></ul><h2>Betriebshinweise</h2><ul><li>Fuer dashboard-spezifische Fixes <code>boardcontent</code>-Patches gegenueber globalen Aenderungen bevorzugen.</li><li>Doppelte Eigenschaften zwischen <code>inputs</code> und inkonsistentem Inline-Stil vermeiden.</li><li>Immer ueberpruefen, dass Datasources eine gesetzte <code>inputs.route</code> haben.</li></ul>",
23781
+ "html": "<h1>Designer</h1><p>Visueller Editor fuer metadata-gesteuerte Dashboards auf Route <code>#/cities/dashboard</code>.</p><h2>Geltungsbereich</h2><ul><li>Board-Komposition via Drag-and-Drop von Knoten und UI-Bloecken.</li><li>Layout und Styling gesteuert durch <code>boardcontent</code> und Tabellen-/Spalten-Metadata.</li><li>Property-Panel-Editing ohne Hardcoding in den Runtime-Komponenten.</li></ul><h2>Designer-Palette</h2><ul><li><code>Datasource</code>: Metadata-Datenquelle (<code>route</code>, Filter, Sortierung, Paging).</li><li><code>DataRepeater</code>: Listen-/Karten-Visualisierung verbunden mit Datasource.</li><li><code>Dynamic Template</code>: dynamisches Template-Rendering mit metadata-gesteuerten Inputs.</li><li><code>Action/Button</code>: Benutzeraktionen verknuepft mit Datensaetzen und Callbacks.</li><li><code>Container/Layout</code>: strukturelle Bloecke fuer Spalten, Zeilen und Wrapper.</li></ul><h2>Hauptfunktionen</h2><ul><li>Drag-and-Drop von Knoten auf die Canvas mit Persistierung im <code>boardcontent</code>.</li><li>Kontextbezogenes Property-Panel mit zu den ausgewaehlten Komponenten konsistenten Inputs.</li><li>Binding zwischen Knoten (<code>datasource -&gt; repeater/template</code>) mit Minimalvalidierung.</li><li>Integrierte Runtime-Vorschau und Layout-Neuausrichtung.</li><li>Verwaltung ausstehender Aenderungen mit Guard vor dem Verlassen.</li></ul><h2>Kontextmenue der Elemente</h2><p>Rechtsklick auf ein Element der Canvas, auf einen Eintrag der unteren Breadcrumb oder auf einen Knoten im Hierarchiebaum zeigt ein Kontextmenue mit drei Eintraegen:</p><ul><li><strong>Eigenschaften bearbeiten</strong> (pi-cog) - waehlt das Element als &quot;current tool&quot; aus und zeigt sein Property-Panel rechts; entspricht dem Klick im Baum. Nuetzlich, um Eigenschaften verschachtelter Elemente zu bearbeiten, ohne sie in der Hierarchie suchen zu muessen.</li><li><strong>Umbenennen</strong> (nur Breadcrumb) - oeffnet einen Dialog zum Umbenennen des <code>displayName</code>.</li><li><strong>Entfernen</strong> - loescht das Element vom Board.</li></ul><h2>Empfohlener operativer Ablauf</h2><ul><li>Ziel-Dashboard-Route oeffnen.</li><li>Knoten aus der Palette einfuegen/verbinden.</li><li>Wesentliche <code>inputs</code> konfigurieren (z.B. <code>route</code>, <code>action</code>, <code>datasource</code>).</li><li>Speichern und Ergebnis in der Runtime validieren.</li></ul><h2>Betriebshinweise</h2><ul><li>Fuer dashboard-spezifische Fixes <code>boardcontent</code>-Patches gegenueber globalen Aenderungen bevorzugen.</li><li>Doppelte Eigenschaften zwischen <code>inputs</code> und inkonsistentem Inline-Stil vermeiden.</li><li>Immer ueberpruefen, dass Datasources eine gesetzte <code>inputs.route</code> haben.</li></ul>",
23103
23782
  "codeSamples": []
23104
23783
  }
23105
23784
  ],
@@ -23163,14 +23842,18 @@ const frameworkDocsContent = {
23163
23842
  },
23164
23843
  {
23165
23844
  "id": "sec_3",
23166
- "title": "Empfohlener operativer Ablauf"
23845
+ "title": "View-Builder-Tab — Aggregationen und GROUP BY (Tier 1, 2026-05-08)"
23167
23846
  },
23168
23847
  {
23169
23848
  "id": "sec_4",
23170
- "title": "Aktionsbuttons"
23849
+ "title": "Empfohlener operativer Ablauf"
23171
23850
  },
23172
23851
  {
23173
23852
  "id": "sec_5",
23853
+ "title": "Aktionsbuttons"
23854
+ },
23855
+ {
23856
+ "id": "sec_6",
23174
23857
  "title": "Nutzungshinweise"
23175
23858
  }
23176
23859
  ],
@@ -23178,7 +23861,7 @@ const frameworkDocsContent = {
23178
23861
  {
23179
23862
  "id": "overview",
23180
23863
  "title": "Overview",
23181
- "html": "<h1>Ansichts-/Pivot-Builder</h1><p>Der Pivot Builder ermöglicht die Erstellung dynamischer analytischer Ansichten ausgehend von Metadata-Routen, ohne manuelle Abfragen auf UI-Seite schreiben zu müssen.</p><h2>Screenshot-Referenz</h2><ul><li><code>manual__pivot_builder__01.png</code>: Konfiguration und Ergebnis des Builders zur Laufzeit.</li><li><code>manual__pivot_builder__02.png</code>: Detail des Dialogs zur Erstellung materialisierter Tabellen und operative Optionen.</li></ul><h2>Hauptfunktionen</h2><ul><li>Feldauswahl in den Bereichen <code>rows</code>, <code>columns</code> und <code>values</code>.</li><li>Verfügbare Aggregationen auf Werten (Summe, Anzahl, Durchschnitt, Minimum, Maximum).</li><li>Mehrdimensionale Gruppierung mit Sortierung nach Dimension.</li><li>Interaktive Filter zur Eingrenzung des analysierten Datasets.</li><li>Echtzeit-Aktualisierung der Pivot-Ansicht nach Layout- oder Metrik-Änderung.</li><li>Export/Wiederverwendung der Pivot-Konfiguration im operativen Kontext.</li></ul><h2>Empfohlener operativer Ablauf</h2><p>1. Pivot-Builder-Route öffnen.</p><p>2. Ausgangs-Datasource/Metadata-Route auswählen.</p><p>3. Felder in die Zonen <code>rows</code>, <code>columns</code>, <code>values</code> ziehen.</p><p>4. Aggregation für jede Metrik konfigurieren.</p><p>5. Filter anwenden und Gesamtsummen überprüfen.</p><p>6. Konfiguration gemäß dem Anwendungsablauf speichern oder wiederverwenden.</p><h2>Aktionsbuttons</h2><p><code>Ansicht erstellen</code></p><ul><li>Generiert eine SQL-View aus der aktuellen Pivot-Konfiguration.</li><li>Nützlich, wenn das Ergebnis als stabile Datenquelle in anderen Routen/Reports wiederverwendet werden soll.</li><li>Hält die Logik zentral in der DB, ohne das Layout jedes Mal manuell neu aufbauen zu müssen.</li></ul><p><code>Materialisierte Tabelle erstellen</code></p><ul><li>Erstellt eine physische Tabelle mit den aggregierten Daten der Pivot-Konfiguration.</li><li>Nützlich für große Datasets oder Dashboards, die schnelle Lesezeiten erfordern.</li><li>Scaffoldet automatisch die der erstellten physischen Tabelle zugeordnete Route und generiert eine Schedulierung für Daten-Refresh/Abgleich.</li><li>Durch Drücken von Aktualisieren im Grid der zugehörigen Route wird ein Lauf der Schedulierung erzwungen.</li></ul><h2>Nutzungshinweise</h2><ul><li>Für konsistente Analysen Spalten mit korrektem Datentyp verwenden (numerisch für Metriken, Text/Datum für Dimensionen).</li><li>Bei großen Datenmengen gezielte Filter und Aggregationen bevorzugen, bevor das Detail erweitert wird.</li></ul>",
23864
+ "html": "<h1>Ansichts-/Pivot-Builder</h1><p>Der Pivot Builder ermöglicht die Erstellung dynamischer analytischer Ansichten ausgehend von Metadata-Routen, ohne manuelle Abfragen auf UI-Seite schreiben zu müssen.</p><h2>Screenshot-Referenz</h2><ul><li><code>manual__pivot_builder__01.png</code>: Konfiguration und Ergebnis des Builders zur Laufzeit.</li><li><code>manual__pivot_builder__02.png</code>: Detail des Dialogs zur Erstellung materialisierter Tabellen und operative Optionen.</li></ul><h2>Hauptfunktionen</h2><ul><li>Feldauswahl in den Bereichen <code>rows</code>, <code>columns</code> und <code>values</code>.</li><li>Verfügbare Aggregationen auf Werten (Summe, Anzahl, Durchschnitt, Minimum, Maximum).</li><li>Mehrdimensionale Gruppierung mit Sortierung nach Dimension.</li><li>Interaktive Filter zur Eingrenzung des analysierten Datasets.</li><li>Echtzeit-Aktualisierung der Pivot-Ansicht nach Layout- oder Metrik-Änderung.</li><li>Export/Wiederverwendung der Pivot-Konfiguration im operativen Kontext.</li></ul><h2>View-Builder-Tab — Aggregationen und GROUP BY (Tier 1, 2026-05-08)</h2><p>Tab 1 &quot;View Builder&quot; erlaubt nun die direkte Ausgabe von <code>GROUP BY</code> mit <code>SUM/COUNT/AVG/MIN/MAX/COUNT(DISTINCT)</code>-Aggregationen aus der <code>ViewDefinition</code>, ohne in den manualSql-Modus zu wechseln.</p><p>Panel unter dem Canvas:</p><ul><li><strong>Aggregationen</strong>: Formular zum Hinzufügen von <code>Funktion(Spalte) AS Alias</code>. Die Quellspalte kann jede Spalte auf der Canvas sein oder <code>*</code> (für <code>COUNT(*)</code>); der Output-Alias wird automatisch erzeugt, wenn leer.</li><li><strong>GROUP BY</strong>: Checkbox pro selektierter Spalte auf den Canvas-Knoten. Ist keine explizit angekreuzt, leitet das Backend die Dimensionen aus den <code>selected=true</code> nicht-aggregierten Spalten ab.</li><li><strong>CASE WHEN für Pivoting</strong>: Für Muster wie <code>SUM(CASE WHEN bucket=&#39;X&#39; THEN betrag ELSE 0 END)</code> verwende eine Formel-Spalte (Rechtsklick → Formel bearbeiten) als Aggregationsquelle. Die Formel wird als Aggregat-Argument ausgegeben.</li></ul><p>Backend-Ausgabe (<a href=\"../../Services/MetaService.cs\">`MetaService.previewViewDefinition`</a>):</p><ul><li><code>viewDefinition.aggregations[]</code> → gibt <code>&lt;FN&gt;(&lt;Spalte|Formel&gt;) AS &lt;Alias&gt;</code> als zusätzliche SELECT-Spalten aus</li><li><code>viewDefinition.groupByColumns[]</code> → gibt die <code>GROUP BY</code>-Klausel aus</li><li>Automatische GROUP BY-Inferenz bei vorhandenen <code>aggregations</code> ohne explizite <code>groupByColumns</code></li></ul><h3>UNION-Blöcke (Tier 2, 2026-05-08)</h3><p>Eine spätere Erweiterung erlaubt das Verketten mehrerer SELECTs mit <code>UNION</code> oder <code>UNION ALL</code> direkt aus einer einzigen <code>ViewDefinition</code>:</p><ul><li><strong>Block 0</strong> = Top-Level (Canvas-Tabellen/Joins/Aggregationen) → definiert das <strong>kanonische Vokabular</strong> der SELECT-Spalten</li><li><strong>Block N (N≥1)</strong> = jeder zusätzliche <code>unionBlocks[]</code> ist eine Single-Table-Sub-Definition mit einem erforderlichen <code>columnMapping</code>: für jeden kanonischen Alias von Block 0 gibt Block N <code>sourceQualified</code> (Spalte aus dem Block) <strong>oder</strong> <code>constantValue</code> (SQL-Literal wie <code>&#39;KUNDE&#39;</code>) <strong>oder</strong> <code>formula</code> (SQL-Ausdruck für Cross-Block-Cast) an</li><li><strong>UNION ALL / UNION-Umschaltung</strong>: Standard <code>unionAll: true</code> (kein Dedup, bessere Performance); <code>false</code> für <code>UNION DISTINCT</code></li><li><strong>TOP/LIMIT</strong>: wird auf die gesamte UNION angewandt, umschlossen in der Subquery <code>SELECT TOP N * FROM (&lt;union&gt;) AS u</code></li></ul><p>UI unterhalb des Canvas: Route-Auswahl + Button &quot;UNION-Block hinzufügen&quot; → jeder Block erscheint als Akkordeon mit Header (Caption + Route) + Spaltenzuordnungs-Matrix (pro kanonischem Alias: Dropdown der Block-Quellspalte oder konstanter SQL-Eingabe).</p><p>v0-Einschränkungen: jeder UNION-Block ist Single-Table (kein JOIN innerhalb des Blocks). Für Cross-Block-JOIN (z.B. Kategorie &#39;BEIDES&#39; von <code>vw_anagrafica_unificata</code>, die <code>kunden INNER JOIN lieferanten ON ust_id</code> erfordert) wird Tier 2.5 benötigt.</p><h2>Empfohlener operativer Ablauf</h2><p>1. Pivot-Builder-Route öffnen.</p><p>2. Ausgangs-Datasource/Metadata-Route auswählen.</p><p>3. Felder in die Zonen <code>rows</code>, <code>columns</code>, <code>values</code> ziehen.</p><p>4. Aggregation für jede Metrik konfigurieren.</p><p>5. Filter anwenden und Gesamtsummen überprüfen.</p><p>6. Konfiguration gemäß dem Anwendungsablauf speichern oder wiederverwenden.</p><h2>Aktionsbuttons</h2><p><code>Ansicht erstellen</code></p><ul><li>Generiert eine SQL-View aus der aktuellen Pivot-Konfiguration.</li><li>Nützlich, wenn das Ergebnis als stabile Datenquelle in anderen Routen/Reports wiederverwendet werden soll.</li><li>Hält die Logik zentral in der DB, ohne das Layout jedes Mal manuell neu aufbauen zu müssen.</li></ul><p><code>Materialisierte Tabelle erstellen</code></p><ul><li>Erstellt eine physische Tabelle mit den aggregierten Daten der Pivot-Konfiguration.</li><li>Nützlich für große Datasets oder Dashboards, die schnelle Lesezeiten erfordern.</li><li>Scaffoldet automatisch die der erstellten physischen Tabelle zugeordnete Route und generiert eine Schedulierung für Daten-Refresh/Abgleich.</li><li>Durch Drücken von Aktualisieren im Grid der zugehörigen Route wird ein Lauf der Schedulierung erzwungen.</li></ul><h2>Nutzungshinweise</h2><ul><li>Für konsistente Analysen Spalten mit korrektem Datentyp verwenden (numerisch für Metriken, Text/Datum für Dimensionen).</li><li>Bei großen Datenmengen gezielte Filter und Aggregationen bevorzugen, bevor das Detail erweitert wird.</li></ul>",
23182
23865
  "codeSamples": []
23183
23866
  }
23184
23867
  ],
@@ -24133,7 +24816,7 @@ const frameworkDocsContent = {
24133
24816
  {
24134
24817
  "id": "overview",
24135
24818
  "title": "Overview",
24136
- "html": "<h1>Component Catalog</h1><p>Total component folders detected: 48.</p><h2>Available components</h2><ul><li>app-settings-editor</li><li>archetype-configurator</li><li>bounded-repeater</li><li>carousel-list</li><li>chart-list</li><li>code-editor</li><li>css-sheet-editor</li><li>dashboard</li><li>data-action-button</li><li>data-repeater</li><li>data-source</li><li>designer</li><li>dynamic-dashboard-template</li><li>dynamic-field-template</li><li>dynamic-form-template</li><li>dynamic-generic-template</li><li>dynamic-repeater-template</li><li>dynamic-template</li><li>field</li><li>filter-bar</li><li>first-run-wizard</li><li>framework-docs</li><li>image-wrapper</li><li>import-export-button</li><li>kanban-list</li><li>list-grid</li><li>map-list</li><li>meta-menu</li><li>metadata-editor</li><li>notification-bell</li><li>pager</li><li>parametric-dialog</li><li>pivot-builder</li><li>prompt-dialog-fallback-host</li><li>rag-chatbot</li><li>rag-chatbot-fab</li><li>report-designer</li><li>report-viewer</li><li>scheduler-list</li><li>split</li><li>split-area</li><li>spreadsheet-list-sf</li><li>tab-panel-wrapper</li><li>tab-view-wrapper</li><li>tree-list</li><li>upload-dialog</li><li>workflow-designer</li><li>workflow-runner</li></ul><h2>Exports from public-api</h2><ul><li>data-source/data-source.component</li><li>list-grid/list-grid.component</li><li>import-export-button/import-export-button.component</li><li>pager/pager.component</li><li>filter-bar/filter-bar.component</li><li>map-list/map-list.component</li><li>carousel-list/carousel-list.component</li><li>tree-list/tree-list.component</li><li>parametric-dialog/parametric-dialog.component</li><li>upload-dialog/upload-dialog.component</li><li>app-settings-editor/app-settings-editor.component</li><li>data-action-button/data-action-button.component</li><li>meta-menu/meta-menu.component</li><li>field/text-editor/text-editor.component</li><li>field/text-area-editor/text-area-editor.component</li><li>field/number-editor/number-editor.component</li><li>field/lookup-editor/lookup-editor.component</li><li>field/dictionary-editor/dictionary-editor.component</li><li>field/boolean-editor/boolean-editor.component</li><li>field/date-editor/date-editor.component</li><li>field/html-editor/html-editor.component</li><li>field/upload-editor/upload-editor.component</li><li>field/button-editor/button-editor.component</li><li>field/field-editor/field-editor.component</li><li>field/code-area-editor/code-area-editor.component</li><li>dynamic-template/dynamic-template.component</li><li>image-wrapper/image-wrapper.component</li><li>field/color-editor/color-editor.component</li><li>field/property-array-editor/property-array-editor.component</li><li>field/property-object-editor/property-object-editor.component</li><li>field/tree-view-selector/tree-view-selector.component</li><li>workflow-designer/workflow-designer.component</li><li>workflow-runner/workflow-runner.component</li><li>notification-bell/notification-bell.component</li><li>bounded-repeater/bounded-repeater.component</li><li>framework-docs/framework-docs.component</li><li>designer/designer.route.component</li><li>workflow-designer/workflow-designer.route.component</li><li>kanban-list/kanban-list.component</li><li>data-repeater/data-repeater.component</li><li>bounded-repeater/bounded-repeater-pending-changes.guard</li><li>rag-chatbot/rag-chatbot.component</li><li>rag-chatbot-fab/rag-chatbot-fab.component</li></ul>",
24819
+ "html": "<h1>Component Catalog</h1><p>Total component folders detected: 49.</p><h2>Available components</h2><ul><li>app-settings-editor</li><li>archetype-configurator</li><li>bounded-repeater</li><li>carousel-list</li><li>chart-list</li><li>code-editor</li><li>css-sheet-editor</li><li>dashboard</li><li>data-action-button</li><li>data-repeater</li><li>data-source</li><li>designer</li><li>dynamic-dashboard-template</li><li>dynamic-field-template</li><li>dynamic-form-template</li><li>dynamic-generic-template</li><li>dynamic-repeater-template</li><li>dynamic-template</li><li>error-dialog</li><li>field</li><li>filter-bar</li><li>first-run-wizard</li><li>framework-docs</li><li>image-wrapper</li><li>import-export-button</li><li>kanban-list</li><li>list-grid</li><li>map-list</li><li>meta-menu</li><li>metadata-editor</li><li>notification-bell</li><li>pager</li><li>parametric-dialog</li><li>pivot-builder</li><li>prompt-dialog-fallback-host</li><li>rag-chatbot</li><li>rag-chatbot-fab</li><li>report-designer</li><li>report-viewer</li><li>scheduler-list</li><li>split</li><li>split-area</li><li>spreadsheet-list-sf</li><li>tab-panel-wrapper</li><li>tab-view-wrapper</li><li>tree-list</li><li>upload-dialog</li><li>workflow-designer</li><li>workflow-runner</li></ul><h2>Exports from public-api</h2><ul><li>data-source/data-source.component</li><li>list-grid/list-grid.component</li><li>import-export-button/import-export-button.component</li><li>pager/pager.component</li><li>filter-bar/filter-bar.component</li><li>map-list/map-list.component</li><li>carousel-list/carousel-list.component</li><li>tree-list/tree-list.component</li><li>parametric-dialog/parametric-dialog.component</li><li>upload-dialog/upload-dialog.component</li><li>app-settings-editor/app-settings-editor.component</li><li>data-action-button/data-action-button.component</li><li>meta-menu/meta-menu.component</li><li>field/text-editor/text-editor.component</li><li>field/text-area-editor/text-area-editor.component</li><li>field/number-editor/number-editor.component</li><li>field/lookup-editor/lookup-editor.component</li><li>field/dictionary-editor/dictionary-editor.component</li><li>field/boolean-editor/boolean-editor.component</li><li>field/date-editor/date-editor.component</li><li>field/html-editor/html-editor.component</li><li>field/upload-editor/upload-editor.component</li><li>field/button-editor/button-editor.component</li><li>field/field-editor/field-editor.component</li><li>field/code-area-editor/code-area-editor.component</li><li>dynamic-template/dynamic-template.component</li><li>image-wrapper/image-wrapper.component</li><li>field/color-editor/color-editor.component</li><li>field/property-array-editor/property-array-editor.component</li><li>field/property-object-editor/property-object-editor.component</li><li>field/tree-view-selector/tree-view-selector.component</li><li>workflow-designer/workflow-designer.component</li><li>workflow-runner/workflow-runner.component</li><li>notification-bell/notification-bell.component</li><li>bounded-repeater/bounded-repeater.component</li><li>framework-docs/framework-docs.component</li><li>designer/designer.route.component</li><li>workflow-designer/workflow-designer.route.component</li><li>kanban-list/kanban-list.component</li><li>data-repeater/data-repeater.component</li><li>bounded-repeater/bounded-repeater-pending-changes.guard</li><li>rag-chatbot/rag-chatbot.component</li><li>rag-chatbot-fab/rag-chatbot-fab.component</li><li>error-dialog/error-dialog.component</li></ul>",
24137
24820
  "codeSamples": []
24138
24821
  }
24139
24822
  ],
@@ -24254,13 +24937,21 @@ const frameworkDocsContent = {
24254
24937
  {
24255
24938
  "id": "sec_3",
24256
24939
  "title": "Beispiele"
24940
+ },
24941
+ {
24942
+ "id": "sec_4",
24943
+ "title": "Runtime-Patches von `metaInfo` via `fetchInfo$.subscribe`"
24944
+ },
24945
+ {
24946
+ "id": "sec_5",
24947
+ "title": "Mehrere `<wuic-data-source>` in derselben Komponente"
24257
24948
  }
24258
24949
  ],
24259
24950
  "sections": [
24260
24951
  {
24261
24952
  "id": "overview",
24262
24953
  "title": "Overview",
24263
- "html": "<h1>Datasource</h1><p><code>wuic-data-source</code> ist die Komponente, die Metadata, Datenabruf, aktuellen Zustand, Änderungsverfolgung und CRUD-Synchronisation verwaltet.</p><h2>Beschreibung</h2><ul><li>Löst die Route auf (<code>route</code>/<code>hardcodedRoute</code> oder URL-Routing).</li><li>Lädt Tabellen-/Spalten-Metadata und veröffentlicht den Zustand über <code>fetchInfo$</code>.</li><li>Verwaltet <code>filterInfo</code>, <code>sortInfo</code>, <code>groupInfo</code>, Paging (<code>pageSize</code>, <code>currentPage</code>) und Cursor-Paging.</li><li>Stellt Runtime-Methoden für Insert/Update/Delete, Batch-Save, Rollback und Export bereit.</li><li>Ist der zentrale Punkt, an den sich <code>wuic-data-repeater</code>, <code>wuic-filter-bar</code> und <code>wuic-pager</code> anhängen.</li></ul><h2>API</h2><p>Selector:</p><ul><li><code>wuic-data-source</code></li></ul><p>Haupt-Inputs:</p><ul><li><code>route: BehaviorSubject&lt;string&gt;</code></li><li><code>routeFromRouting: boolean</code></li><li><code>hardcodedRoute: string</code></li><li><code>autoload?: boolean</code></li><li><code>loading: BehaviorSubject&lt;boolean&gt;</code></li><li><code>changeTracking?: boolean</code></li><li><code>parentRecord: any</code></li><li><code>parentMetaInfo: MetaInfo</code></li><li><code>parentDatasource: DataSourceComponent</code></li><li><code>componentRef: BehaviorSubject&lt;{ component, id, name, uniqueName }&gt;</code></li></ul><p>Haupt-Zustand/Runtime:</p><ul><li><code>metaInfo: MetaInfo</code></li><li><code>resultInfo: ResultInfo</code></li><li><code>filterInfo?: FilterInfo</code></li><li><code>sortInfo: SortInfo[]</code></li><li><code>groupInfo: GroupInfo[]</code></li><li><code>aggregationInfo: AggregationInfo[]</code></li><li><code>fetchInfo$: BehaviorSubject&lt;{ resultInfo, metaInfo, filterDescriptor }&gt;</code></li><li><code>datasourceReady$: BehaviorSubject&lt;DataSourceComponent | null&gt;</code></li><li><code>afterFirstLoad$: BehaviorSubject&lt;any&gt;</code></li><li><code>beforeSync$: Subject&lt;DataSourceBeforeSyncEvent&gt;</code> (abbruchfähig)</li><li><code>afterSync$: Subject&lt;DataSourceAfterSyncEvent&gt;</code></li></ul><p>Meistverwendete Runtime-Methoden:</p><ul><li><code>fetchData()</code></li><li><code>syncData(entita, original, deleting?, cloning?)</code></li><li><code>addNewRecord(record?)</code></li><li><code>setCurrent(data)</code></li><li><code>clearColumnFilter(col, fetch?)</code></li><li><code>getPendingChanges()</code></li><li><code>hasPendingChanges()</code></li><li><code>batchSave(targetChanges?)</code></li><li><code>rollbackChanges(targetChanges?)</code></li><li><code>exportXls()</code></li></ul><p>Subscriptions/Runtime-Events:</p><ul><li><code>fetchInfo$</code>: Haupt-Stream für Daten-/Metadata-Zustand.</li><li><code>datasourceReady$</code>: wird emittiert, wenn die Datasource-Instanz bereit ist.</li><li><code>afterFirstLoad$</code>: wird nur einmal beim ersten nützlichen Payload emittiert.</li><li><code>beforeSync$</code>: wird vor <code>insert/update/delete/clone/batch</code> emittiert; kann mit <code>event.cancelSync(...)</code> abgebrochen werden.</li><li><code>afterSync$</code>: wird nach erfolgreicher Synchronisation emittiert.</li></ul><p>Kompatibilitätshinweis:</p><ul><li>Der Alias <code>fetchInfo</code> (Getter/Setter) ist ebenfalls verfügbar, aber die empfohlene Benennung ist <code>fetchInfo$</code>.</li></ul><h2>Beispiele</h2><h3>Einfaches Beispiel (hardcoded Route + Autoload)</h3><p>Snippet 1:</p><h3>Master-Detail-Beispiel (verschachtelter Datasource)</h3><p>Snippet 2:</p><h3>Operatives Beispiel (manueller Fetch + Sync)</h3><p>Snippet 3:</p><h3>Beispiel Subscriptions (Host)</h3><p>Snippet 4:</p>",
24954
+ "html": "<h1>Datasource</h1><p><code>wuic-data-source</code> ist die Komponente, die Metadata, Datenabruf, aktuellen Zustand, Änderungsverfolgung und CRUD-Synchronisation verwaltet.</p><h2>Beschreibung</h2><ul><li>Löst die Route auf (<code>route</code>/<code>hardcodedRoute</code> oder URL-Routing).</li><li>Lädt Tabellen-/Spalten-Metadata und veröffentlicht den Zustand über <code>fetchInfo$</code>.</li><li>Verwaltet <code>filterInfo</code>, <code>sortInfo</code>, <code>groupInfo</code>, Paging (<code>pageSize</code>, <code>currentPage</code>) und Cursor-Paging.</li><li>Stellt Runtime-Methoden für Insert/Update/Delete, Batch-Save, Rollback und Export bereit.</li><li>Ist der zentrale Punkt, an den sich <code>wuic-data-repeater</code>, <code>wuic-filter-bar</code> und <code>wuic-pager</code> anhängen.</li></ul><h2>API</h2><p>Selector:</p><ul><li><code>wuic-data-source</code></li></ul><p>Haupt-Inputs:</p><ul><li><code>route: BehaviorSubject&lt;string&gt;</code></li><li><code>routeFromRouting: boolean</code></li><li><code>hardcodedRoute: string</code></li><li><code>autoload?: boolean</code></li><li><code>loading: BehaviorSubject&lt;boolean&gt;</code></li><li><code>changeTracking?: boolean</code></li><li><code>parentRecord: any</code></li><li><code>parentMetaInfo: MetaInfo</code></li><li><code>parentDatasource: DataSourceComponent</code></li><li><code>componentRef: BehaviorSubject&lt;{ component, id, name, uniqueName }&gt;</code></li></ul><p>Haupt-Zustand/Runtime:</p><ul><li><code>metaInfo: MetaInfo</code></li><li><code>resultInfo: ResultInfo</code></li><li><code>filterInfo?: FilterInfo</code></li><li><code>sortInfo: SortInfo[]</code></li><li><code>groupInfo: GroupInfo[]</code></li><li><code>aggregationInfo: AggregationInfo[]</code></li><li><code>fetchInfo$: BehaviorSubject&lt;{ resultInfo, metaInfo, filterDescriptor }&gt;</code></li><li><code>datasourceReady$: BehaviorSubject&lt;DataSourceComponent | null&gt;</code></li><li><code>afterFirstLoad$: BehaviorSubject&lt;any&gt;</code></li><li><code>beforeSync$: Subject&lt;DataSourceBeforeSyncEvent&gt;</code> (abbruchfähig)</li><li><code>afterSync$: Subject&lt;DataSourceAfterSyncEvent&gt;</code></li></ul><p>Meistverwendete Runtime-Methoden:</p><ul><li><code>fetchData()</code></li><li><code>syncData(entita, original, deleting?, cloning?)</code></li><li><code>addNewRecord(record?)</code></li><li><code>setCurrent(data)</code></li><li><code>clearColumnFilter(col, fetch?)</code></li><li><code>getPendingChanges()</code></li><li><code>hasPendingChanges()</code></li><li><code>batchSave(targetChanges?)</code></li><li><code>rollbackChanges(targetChanges?)</code></li><li><code>exportXls()</code></li></ul><p>Subscriptions/Runtime-Events:</p><ul><li><code>fetchInfo$</code>: Haupt-Stream für Daten-/Metadata-Zustand.</li><li><code>datasourceReady$</code>: wird emittiert, wenn die Datasource-Instanz bereit ist.</li><li><code>afterFirstLoad$</code>: wird nur einmal beim ersten nützlichen Payload emittiert.</li><li><code>beforeSync$</code>: wird vor <code>insert/update/delete/clone/batch</code> emittiert; kann mit <code>event.cancelSync(...)</code> abgebrochen werden.</li><li><code>afterSync$</code>: wird nach erfolgreicher Synchronisation emittiert.</li></ul><p>Kompatibilitätshinweis:</p><ul><li>Der Alias <code>fetchInfo</code> (Getter/Setter) ist ebenfalls verfügbar, aber die empfohlene Benennung ist <code>fetchInfo$</code>.</li></ul><h2>Beispiele</h2><h3>Einfaches Beispiel (hardcoded Route + Autoload)</h3><p>Snippet 1:</p><h3>Master-Detail-Beispiel (verschachtelter Datasource)</h3><p>Snippet 2:</p><h3>Operatives Beispiel (manueller Fetch + Sync)</h3><p>Snippet 3:</p><h3>Beispiel Subscriptions (Host)</h3><p>Snippet 4:</p><h2>Runtime-Patches von <code>metaInfo</code> via <code>fetchInfo$.subscribe</code></h2><p>Um Metadaten eines nested Data-Sources zur Laufzeit zu modifizieren (z.B. <code>mc_hide_in_list</code>, <code>md_inline_cell_editing</code>, <code>md_pageable=false</code>, <code>mc_logic_editable=false</code>) <strong>ohne die DB anzufassen</strong> — lokaler Patch auf der vom <code>MetadataProviderService</code> gecachten Referenz, latest-wins, Single-Dialog-Szenario — abonniere den <code>fetchInfo$</code> des Data-Sources:</p><p>Snippet 5:</p><p><strong>Scope</strong>: session-only + instance-only. Der Patch ueberlebt nur bis der <code>MetadataProviderService</code>-Cache invalidiert wird oder die Komponente zerstoert wird. Nuetzlich, wenn ein Custom-Form eine andere Darstellung eines nested Grids gegenueber dem Standalone-Rendering haben moechte.</p><p><strong>Wann KEINE Runtime-Patches verwenden</strong>: fuer Aenderungen, die alle Instanzen der Route betreffen (auch standalone <code>&lt;route&gt;/list</code>) → direkter SQL-Patch auf den Metadaten; fuer Aenderungen, die nach einem Restart bestehen bleiben → SQL + <code>MetaService.invalidateMetadataRuntime</code>.</p><h2>Mehrere <code>&lt;wuic-data-source&gt;</code> in derselben Komponente</h2><p>Wenn eine Custom-Komponente mehr als einen <code>&lt;wuic-data-source&gt;</code> enthaelt (z.B. Master + nested Rows + nested Faelligkeiten), matcht das Pattern <code>@ViewChild(DataSourceComponent)</code> by-type <strong>nur den ersten</strong> Data-Source in der Top-Down-Reihenfolge des Templates. Fuer die folgenden wird eine <strong>Template-Reference-Variable</strong> + <code>@ViewChild(&#39;refName&#39;)</code> benoetigt:</p><p>Snippet 6:</p><p>Snippet 7:</p><p><strong>Antipattern</strong>: <code>document.querySelectorAll(&#39;wuic-data-source&#39;)</code> + <code>window.ng.getComponent(el)</code> zum Filtern nach <code>hardcodedRoute</code>. Funktioniert, ist aber fragil, da es von der <code>window.ng</code>-Debug-API abhaengt (in Production nicht garantiert). Immer Template-Refs verwenden.</p>",
24264
24955
  "codeSamples": [
24265
24956
  {
24266
24957
  "id": "code_1",
@@ -24285,6 +24976,24 @@ const frameworkDocsContent = {
24285
24976
  "title": "Snippet 4",
24286
24977
  "language": "ts",
24287
24978
  "code": "const subs = new Subscription();\n\nsubs.add(\n datasource.fetchInfo$.subscribe((info) => {\n if (!info) return;\n console.log('records', info.resultInfo?.dato?.length || 0);\n })\n);\n\nsubs.add(\n datasource.beforeSync$.subscribe((event) => {\n if (event.operation === 'delete' && !canDelete()) {\n event.cancelSync('Löschen vom Host-Kontext nicht erlaubt');\n }\n })\n);\n\nsubs.add(\n datasource.afterSync$.subscribe((event) => {\n console.log('sync ok', event.operation, event.syncedData);\n })\n);"
24979
+ },
24980
+ {
24981
+ "id": "code_5",
24982
+ "title": "Snippet 5",
24983
+ "language": "ts",
24984
+ "code": "private bindNestedMetaPatches(): void {\n if (!this.scadenzeDs) return;\n const ESSENTIAL = new Set(['data_scadenza', 'importo', 'pagamento_id', 'stato', 'note']);\n const sub = this.scadenzeDs.fetchInfo$.subscribe((info) => {\n const route = this.scadenzeDs?.route?.value;\n if (!info || route !== info.metaInfo?.tableMetadata?.md_route_name) return;\n const cols: any[] = info.metaInfo?.columnMetadata || [];\n for (const c of cols) {\n const name = c?.mc_nome_colonna;\n if (!name || c.mc_is_primary_key) continue;\n c.mc_hide_in_list = !ESSENTIAL.has(name);\n }\n });\n this.subs.add(sub); // cleanup in ngOnDestroy\n}"
24985
+ },
24986
+ {
24987
+ "id": "code_6",
24988
+ "title": "Snippet 6",
24989
+ "language": "html",
24990
+ "code": "<!-- TEMPLATE -->\n<wuic-data-source #righeDs [hardcodedRoute]=\"'fatture_inviate_righe'\" [parentRecord]=\"record\"></wuic-data-source>\n<wuic-data-source #scadenzeDs [hardcodedRoute]=\"'scadenze'\" [parentRecord]=\"record\"></wuic-data-source>"
24991
+ },
24992
+ {
24993
+ "id": "code_7",
24994
+ "title": "Snippet 7",
24995
+ "language": "ts",
24996
+ "code": "// COMPONENT\nimport { DataSourceComponent } from 'wuic-framework-lib';\n\n// Erster Data-Source (Match by Type) -> keine Template-Ref noetig\n@ViewChild(DataSourceComponent) righeDs?: DataSourceComponent;\n\n// Zweiter Data-Source -> benoetigt `#scadenzeDs` im Template + ViewChild by-name\n@ViewChild('scadenzeDs') scadenzeDs?: DataSourceComponent;"
24288
24997
  }
24289
24998
  ]
24290
24999
  }
@@ -26390,10 +27099,10 @@ class FrameworkDocsComponent {
26390
27099
  }
26391
27100
  return work;
26392
27101
  }
26393
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FrameworkDocsComponent, deps: [{ token: i1.Router }, { token: i1.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Component });
26394
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: FrameworkDocsComponent, isStandalone: true, selector: "wuic-framework-docs", viewQueries: [{ propertyName: "contentScroller", first: true, predicate: ["contentScroller"], descendants: true }], ngImport: i0, template: "<div class=\"fw-docs\" [class.dark]=\"dark()\">\n <aside class=\"sidebar\">\n <div class=\"brand\">\n <h2>{{ 'docs.title' | translate }}</h2>\n <p>{{ 'docs.subtitle' | translate }}</p>\n </div>\n\n <div class=\"search\">\n <input pInputText type=\"text\" [ngModel]=\"query()\" (ngModelChange)=\"query.set($event || '')\" [placeholder]=\"t('docs.search_placeholder','Search feature, tag, keyword')\" />\n <button pButton type=\"button\" icon=\"pi pi-sun\" *ngIf=\"dark()\" (click)=\"toggleTheme()\"></button>\n <button pButton type=\"button\" icon=\"pi pi-moon\" *ngIf=\"!dark()\" (click)=\"toggleTheme()\"></button>\n </div>\n\n <div class=\"nav-groups\">\n @for (group of filteredGroups(); track group.id) {\n <section>\n <h3>{{ group.title }}</h3>\n <ul>\n @for (item of group.items; track item.slug) {\n <li>\n <a [class.active]=\"item.slug === currentSlug()\" [href]=\"'#/docs/' + item.slug\" (click)=\"openPage(item.slug); $event.preventDefault()\">\n <i class=\"pi\" [ngClass]=\"item.icon || 'pi-file'\"></i>\n <span>{{ item.title }}</span>\n </a>\n </li>\n }\n </ul>\n </section>\n }\n </div>\n </aside>\n\n <main class=\"content\" #contentScroller *ngIf=\"currentPage() as page\">\n <nav class=\"breadcrumbs\">{{ breadcrumbs().join(' / ') }}</nav>\n\n <header>\n <h1>{{ page.title }}</h1>\n <p>{{ page.description }}</p>\n <div class=\"tags\">\n <p-tag *ngFor=\"let tag of page.tags\" [value]=\"tag\"></p-tag>\n <p-tag\n [value]=\"page.status\"\n [severity]=\"page.status === 'beta' ? 'warn' : 'success'\"\n [pTooltip]=\"page.status === 'beta' ? 'Feature in beta: API e comportamento possono cambiare senza preavviso' : ''\"\n tooltipPosition=\"top\"></p-tag>\n <p-tag *ngIf=\"page.tier === 'pro'\" value=\"Pro only\" styleClass=\"tier-tag tier-tag-pro\"></p-tag>\n <p-tag *ngIf=\"!page.tier || page.tier === 'dev-and-pro'\" value=\"Dev & Pro\" styleClass=\"tier-tag tier-tag-base\"></p-tag>\n </div>\n </header>\n\n <div class=\"body-grid\">\n <article>\n @for (section of page.sections; track section.id) {\n <section [attr.id]=\"section.id\" class=\"doc-section\">\n <h3>{{ section.title }}</h3>\n @for (part of sectionParts(section); track $index) {\n @if (part.kind === 'html') {\n <div class=\"section-html\" [innerHTML]=\"getHighlightedHtml(part.html)\"></div>\n } @else {\n @let sampleKey = getSampleKey(page.slug, section.id, part.sample.id);\n <div class=\"code-block\" [ngClass]=\"getSnippetLanguageClass(part.sample.language)\">\n <div class=\"code-header\">\n <div class=\"code-meta\">\n <span>{{ part.sample.title }}</span>\n <span class=\"snippet-lang\" [ngClass]=\"getSnippetLanguageClass(part.sample.language)\">\n <i class=\"pi\" [ngClass]=\"getSnippetLanguageIconClass(part.sample.language)\"></i>\n {{ getSnippetLanguageLabel(part.sample.language) }}\n </span>\n </div>\n <div class=\"code-actions\">\n @if (isSampleCollapsible(part.sample.code)) {\n <button\n pButton\n type=\"button\"\n size=\"small\"\n severity=\"secondary\"\n [icon]=\"isSampleExpanded(sampleKey) ? 'pi pi-chevron-up' : 'pi pi-chevron-down'\"\n [label]=\"isSampleExpanded(sampleKey) ? t('docs.collapse','Collapse') : (t('docs.expand','Expand') + ' (+' + getCollapsedHiddenLines(part.sample.code) + ')')\"\n (click)=\"toggleSampleExpanded(sampleKey)\">\n </button>\n }\n <button pButton type=\"button\" size=\"small\" icon=\"pi pi-copy\" [label]=\"t('docs.copy','Copy')\" (click)=\"copyCode(part.sample.code)\"></button>\n </div>\n </div>\n <pre><code [innerHTML]=\"getVisibleSampleHighlightedCode(part.sample.code, sampleKey, part.sample.language)\"></code></pre>\n </div>\n }\n }\n </section>\n }\n\n @if (page.slug === 'themes') {\n <section class=\"themes-catalog\">\n <h3>{{ 'docs.themes_catalog' | translate }}</h3>\n <div class=\"themes-catalog-grid\">\n @for (theme of allThemes; track theme.value) {\n <div class=\"themes-catalog-item\">\n <span class=\"theme-dot\" [style.backgroundColor]=\"theme.primaryHex\"></span>\n <span>{{ theme.label }}</span>\n </div>\n }\n </div>\n </section>\n }\n\n @if (page.images?.length) {\n <section class=\"screenshots\">\n <h3>{{ 'docs.feature_screenshots' | translate }}</h3>\n <div class=\"shots-grid\">\n @for (img of page.images; track img.id) {\n <figure>\n <p-image\n *ngIf=\"img.status === 'available'\"\n [preview]=\"true\"\n [src]=\"img.path\"\n [previewImageSrc]=\"img.path\"\n [alt]=\"img.caption\"\n />\n <div class=\"missing\" *ngIf=\"img.status !== 'available'\">{{ 'docs.snapshot_unavailable' | translate }}</div>\n <figcaption [innerHTML]=\"getHighlightedText(formatScreenshotCaption(img.caption))\"></figcaption>\n </figure>\n }\n </div>\n </section>\n }\n </article>\n\n <aside class=\"toc\">\n <h4>{{ 'docs.on_this_page' | translate }}</h4>\n <ul>\n @for (item of page.toc; track item.id) {\n <li><a [href]=\"'#' + item.id\" (click)=\"scrollToSection($event, item.id, item.title)\">{{ item.title }}</a></li>\n }\n </ul>\n </aside>\n </div>\n </main>\n</div>\n", styles: ["@charset \"UTF-8\";.fw-docs{--bg: linear-gradient(145deg, #f7f9ff 0%, #edf3ff 40%, #fefbf4 100%);--panel: rgba(255, 255, 255, .86);--text: #18304a;--muted: #4d6278;--accent: #0d6efd;--border: #dce7fb;height:calc(100vh - 70px);display:grid;grid-template-columns:320px 1fr;background:var(--bg);color:var(--text);overflow:hidden}.fw-docs.dark,:host-context(.theme-dark) .fw-docs,:host-context(.dark-theme) .fw-docs,:host-context([data-theme-mode=dark]) .fw-docs{--bg: linear-gradient(145deg, #121b2a 0%, #0f2233 55%, #222134 100%);--panel: rgba(22, 35, 55, .85);--text: #dbe8ff;--muted: #9eb4d2;--accent: #7eb2ff;--border: #294869}.sidebar{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:var(--panel);border-right:1px solid var(--border);padding:18px;position:sticky;top:0;height:calc(100vh - 70px);overflow:auto}.brand h1{margin:0;font-size:1.1rem}.brand p{margin:6px 0 14px;color:var(--muted);font-size:.88rem}.search{display:grid;grid-template-columns:1fr auto;gap:6px;margin-bottom:12px}.nav-groups h3{margin:14px 0 8px;font-size:.86rem;text-transform:uppercase;color:var(--muted);letter-spacing:.06em}.nav-groups ul{list-style:none;padding:0;margin:0}.nav-groups a{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:10px;color:inherit;text-decoration:none;cursor:pointer}.nav-groups a.active,.nav-groups a:hover{background:#0d6efd1f}.content{padding:26px 30px 36px;height:100%;overflow-y:scroll;scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--p-content-border-color, #94a3b8) 80%,transparent) transparent;scrollbar-gutter:stable}.content::-webkit-scrollbar,.sidebar::-webkit-scrollbar{width:10px;height:10px}.content::-webkit-scrollbar-track,.sidebar::-webkit-scrollbar-track{background:transparent}.content::-webkit-scrollbar-thumb,.sidebar::-webkit-scrollbar-thumb{background-color:color-mix(in srgb,var(--p-content-border-color, #94a3b8) 65%,transparent);border-radius:6px;border:2px solid transparent;background-clip:content-box}.content::-webkit-scrollbar-thumb:hover,.sidebar::-webkit-scrollbar-thumb:hover{background-color:var(--p-text-muted-color, #64748b)}.sidebar{scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--p-content-border-color, #94a3b8) 80%,transparent) transparent}.breadcrumbs{font-size:.85rem;color:var(--muted);margin-bottom:10px}header h2{margin:0;font-size:2rem}header p{margin:8px 0 12px;color:var(--muted)}.tags{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:20px}::ng-deep .tier-tag.p-tag{font-weight:700;letter-spacing:.02em}::ng-deep .tier-tag-base.p-tag{background:color-mix(in srgb,#6366f1 15%,var(--panel, #fff));color:color-mix(in srgb,#4338ca 85%,var(--text, #0f172a));border:1px solid color-mix(in srgb,#6366f1 45%,transparent)}::ng-deep .tier-tag-pro.p-tag{background:color-mix(in srgb,#f59e0b 18%,var(--panel, #fff));color:color-mix(in srgb,#b45309 85%,var(--text, #0f172a));border:1px solid color-mix(in srgb,#f59e0b 55%,transparent)}.body-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(320px,360px);gap:20px}article{background:var(--panel);border:1px solid var(--border);border-radius:16px;padding:20px}.doc-section{padding:6px 0 16px;border-bottom:1px dashed var(--border)}.doc-section:last-child{border-bottom:0}.doc-section h3{margin:8px 0}.section-html{color:var(--text)}:host ::ng-deep .section-html p{line-height:1.58}:host ::ng-deep .section-html table{width:100%;border-collapse:collapse;margin:16px 0 24px;border:1px solid var(--border);border-radius:8px;overflow:hidden;font-size:.92rem;box-shadow:0 1px 2px color-mix(in srgb,var(--text) 6%,transparent)}:host ::ng-deep .section-html thead{background:color-mix(in srgb,var(--accent) 14%,var(--panel))}:host ::ng-deep .section-html thead th{text-align:left;color:var(--text);font-weight:700;padding:12px 16px;border:1px solid var(--border);border-bottom:2px solid color-mix(in srgb,var(--accent) 35%,var(--border));text-transform:uppercase;font-size:.78rem;letter-spacing:.4px;white-space:nowrap}:host ::ng-deep .section-html tbody td{padding:11px 16px;border:1px solid color-mix(in srgb,var(--border) 80%,transparent);vertical-align:top;line-height:1.55;color:var(--text)}:host ::ng-deep .section-html tbody tr:nth-child(odd) td{background:color-mix(in srgb,var(--panel) 92%,transparent)}:host ::ng-deep .section-html tbody tr:nth-child(2n) td{background:color-mix(in srgb,var(--panel) 100%,transparent)}:host ::ng-deep .section-html tbody tr:hover td{background:color-mix(in srgb,var(--accent) 8%,var(--panel))}:host ::ng-deep .section-html table code{font-size:.85em;padding:1px 5px}:host ::ng-deep .section-html strong{color:var(--text);font-weight:600}:host ::ng-deep .section-html em{font-style:italic}:host ::ng-deep .section-html a.wuic-demo-link{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;margin:0 2px;background:color-mix(in srgb,var(--accent) 14%,var(--panel));border:1px solid color-mix(in srgb,var(--accent) 35%,var(--border));border-radius:6px;color:var(--accent, #4f46e5);font-size:.85rem;font-weight:600;text-decoration:none;line-height:1.2;transition:background .15s ease}:host ::ng-deep .section-html a.wuic-demo-link:before{content:\"\\25b6\";font-size:.7rem;color:var(--accent, #4f46e5)}:host ::ng-deep .section-html a.wuic-demo-link:hover{background:color-mix(in srgb,var(--accent) 22%,var(--panel));text-decoration:none}:host ::ng-deep .section-html :where(ul,ol){margin:12px 0 16px;padding-left:0}:host ::ng-deep .section-html ul{list-style:none}:host ::ng-deep .section-html ul>li{position:relative;margin:8px 0;padding:8px 12px 8px 34px;border:1px solid color-mix(in srgb,var(--border) 78%,transparent);border-radius:10px;background:color-mix(in srgb,var(--panel) 90%,transparent);line-height:1.52}:host ::ng-deep .section-html ul>li:nth-child(odd){background:color-mix(in srgb,var(--panel) 94%,transparent)}:host ::ng-deep .section-html ul>li:nth-child(2n){background:color-mix(in srgb,var(--accent) 5%,var(--panel));border-color:color-mix(in srgb,var(--accent) 18%,var(--border))}:host ::ng-deep .section-html ul>li:before{content:\"\";position:absolute;left:12px;top:50%;width:10px;height:10px;border-radius:999px;transform:translateY(-50%);background:radial-gradient(circle at 35% 35%,#fff 0 24%,color-mix(in srgb,var(--accent) 80%,#4aa3ff) 25% 100%);box-shadow:0 0 0 1px color-mix(in srgb,var(--accent) 32%,transparent)}:host ::ng-deep .section-html ol{list-style:none;counter-reset:wuic-doc-step}:host ::ng-deep .section-html ol>li{position:relative;counter-increment:wuic-doc-step;margin:8px 0;padding:8px 12px 8px 42px;border-left:3px solid color-mix(in srgb,var(--accent) 55%,transparent);background:color-mix(in srgb,var(--accent) 7%,var(--panel));border-radius:10px;line-height:1.52}:host ::ng-deep .section-html ol>li:nth-child(odd){background:color-mix(in srgb,var(--accent) 7%,var(--panel))}:host ::ng-deep .section-html ol>li:nth-child(2n){background:color-mix(in srgb,var(--accent) 11%,var(--panel));border-left-color:color-mix(in srgb,var(--accent) 72%,transparent)}:host ::ng-deep .section-html ol>li:before{content:counter(wuic-doc-step);position:absolute;left:10px;top:50%;width:22px;height:22px;border-radius:999px;transform:translateY(-50%);display:grid;place-items:center;font-size:.78rem;font-weight:700;color:#fff;background:linear-gradient(160deg,color-mix(in srgb,var(--accent) 80%,#4aa3ff),color-mix(in srgb,var(--accent) 95%,#125dcc));box-shadow:0 2px 10px color-mix(in srgb,var(--accent) 35%,transparent)}:host ::ng-deep .section-html li>:where(ul,ol){margin-top:8px}.code-block{margin:10px 0;border:1px solid var(--border);border-radius:12px;overflow:hidden}.code-header{display:flex;justify-content:space-between;align-items:center;background:#0d6efd14;padding:8px 10px}.code-block pre{margin:0;padding:12px;overflow:auto;background:#1e1e1e}.code-block code{display:block;font-family:Consolas,Cascadia Code,Fira Code,Courier New,monospace;font-size:.88rem;line-height:1.55;color:#d4d4d4;white-space:pre}.code-actions{display:inline-flex;gap:8px;align-items:center}.code-meta{display:inline-flex;align-items:center;gap:10px}.snippet-lang{display:inline-flex;align-items:center;justify-content:center;min-width:44px;height:22px;padding:0 8px;border-radius:999px;font-size:.72rem;font-weight:700;letter-spacing:.04em;border:1px solid transparent;background:color-mix(in srgb,var(--text) 8%,transparent);color:var(--text)}.snippet-lang.lang-html{background:#e36c0924;border-color:#e36c0952;color:#b25d0e}.snippet-lang.lang-json{background:#00808024;border-color:#00808052;color:#0e6b6b}.snippet-lang.lang-ts{background:#3178c624;border-color:#3178c652;color:#1f5f9f}.snippet-lang.lang-cs{background:#68217a24;border-color:#68217a52;color:#6a2b82}.snippet-lang i{font-size:.72rem}:host ::ng-deep .code-block code .tok-comment{color:#6a9955}:host ::ng-deep .code-block code .tok-string{color:#ce9178}:host ::ng-deep .code-block code .tok-number{color:#b5cea8}:host ::ng-deep .code-block code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block code .tok-type{color:#4ec9b0}:host ::ng-deep .code-block code .tok-fn{color:#dcdcaa}:host ::ng-deep .code-block code .tok-prop{color:#9cdcfe}:host ::ng-deep .code-block code .tok-op{color:#d4d4d4}:host ::ng-deep .code-block.lang-html code .tok-tag{color:#569cd6}:host ::ng-deep .code-block.lang-html code .tok-attr{color:#9cdcfe}:host ::ng-deep .code-block.lang-html code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-json code .tok-key{color:#9cdcfe}:host ::ng-deep .code-block.lang-json code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-json code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block.lang-json code .tok-number{color:#b5cea8}:host ::ng-deep .code-block.lang-ts code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block.lang-ts code .tok-type{color:#4ec9b0}:host ::ng-deep .code-block.lang-ts code .tok-fn{color:#dcdcaa}:host ::ng-deep .code-block.lang-ts code .tok-prop{color:#9cdcfe}:host ::ng-deep .code-block.lang-ts code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-ts code .tok-comment{color:#6a9955}:host ::ng-deep .code-block.lang-ts code .tok-number{color:#b5cea8}:host ::ng-deep .code-block.lang-cs code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block.lang-cs code .tok-fn{color:#dcdcaa}:host ::ng-deep .code-block.lang-cs code .tok-prop{color:#9cdcfe}:host ::ng-deep .code-block.lang-cs code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-cs code .tok-comment{color:#6a9955}:host ::ng-deep .code-block.lang-cs code .tok-number{color:#b5cea8}.screenshots .shots-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px;align-items:start}.screenshots figure{margin:0;display:flex;flex-direction:column;gap:8px}.screenshots img{width:100%;border-radius:12px;border:1px solid var(--border)}:host ::ng-deep .screenshots .p-image,:host ::ng-deep .screenshots .p-image img{width:100%;display:block}:host ::ng-deep .screenshots .p-image{aspect-ratio:16/9;overflow:hidden;border-radius:12px;border:1px solid var(--border);background:color-mix(in srgb,var(--panel) 90%,transparent)}:host ::ng-deep .screenshots .p-image img{width:100%;height:100%;object-fit:contain;object-position:center;border:0}:host ::ng-deep .screenshots .p-image .p-image-preview-mask,:host ::ng-deep .screenshots .p-image .p-image-mask{inset:0;width:100%;height:100%}.missing{border:1px dashed var(--border);border-radius:12px;padding:36px 10px;text-align:center;color:var(--muted)}:host ::ng-deep .p-image-mask,:host ::ng-deep .p-image-preview-mask{background:none!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important}:host ::ng-deep .p-image-toolbar{background:#0c1220!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important;filter:none!important;border:1px solid rgba(255,255,255,.14)!important;box-shadow:0 8px 24px #00000059!important}:host ::ng-deep .p-image-action{color:#eaf1ff!important;opacity:1!important;text-shadow:none!important}:host ::ng-deep .p-image-preview{background:#7eb2ff2e!important}::ng-deep .p-image-mask.p-component-overlay,::ng-deep .p-image-preview-mask.p-component-overlay{background:#0c121efa!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important}:host ::ng-deep .screenshots .p-image .p-image-mask,:host ::ng-deep .screenshots .p-image .p-image-preview-indicator{background:#0c121e42!important}.themes-catalog{margin-top:14px;padding:6px 0 16px;border-top:1px dashed var(--border)}.themes-catalog-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:10px}.themes-catalog-item{display:inline-flex;align-items:center;gap:.55rem;min-height:2.25rem;padding:.45rem .65rem;border:1px solid var(--border);border-radius:10px;background:color-mix(in srgb,var(--panel) 92%,transparent)}.theme-dot{width:.8rem;height:.8rem;border-radius:999px;border:1px solid color-mix(in srgb,var(--text) 25%,transparent);box-shadow:0 0 0 1px color-mix(in srgb,#ffffff 55%,transparent) inset;flex:0 0 auto}.toc{position:sticky;top:20px;max-height:calc(100vh - 120px);overflow-y:auto;background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:14px}.toc h4{margin:0 0 8px}.toc ul{margin:0;padding-left:18px;list-style-position:outside}.toc a{display:block;color:var(--text);text-decoration:none;line-height:1.25;overflow-wrap:anywhere;word-break:break-word}.toc li{margin:0 0 6px}.toc li:last-child{margin-bottom:0}@media(max-width:1360px)and (min-width:1101px){.body-grid{grid-template-columns:minmax(0,1fr) minmax(280px,320px)}}@media(max-width:1100px){.fw-docs{grid-template-columns:1fr;height:auto;overflow:visible}.sidebar{height:auto;position:static;border-right:0;border-bottom:1px solid var(--border)}.content{height:auto;overflow:visible}.body-grid{grid-template-columns:1fr}.toc{position:static}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i4.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i13.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i10.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i3.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: ImageModule }, { kind: "component", type: i1$3.Image, selector: "p-image", inputs: ["imageClass", "imageStyle", "styleClass", "src", "srcSet", "sizes", "previewImageSrc", "previewImageSrcSet", "previewImageSizes", "alt", "width", "height", "loading", "preview", "showTransitionOptions", "hideTransitionOptions", "modalEnterAnimation", "modalLeaveAnimation", "appendTo", "maskMotionOptions", "motionOptions"], outputs: ["onShow", "onHide", "onImageError"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }] });
27102
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FrameworkDocsComponent, deps: [{ token: i1.Router }, { token: i1.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Component });
27103
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: FrameworkDocsComponent, isStandalone: true, selector: "wuic-framework-docs", viewQueries: [{ propertyName: "contentScroller", first: true, predicate: ["contentScroller"], descendants: true }], ngImport: i0, template: "<div class=\"fw-docs\" [class.dark]=\"dark()\">\n <aside class=\"sidebar\">\n <div class=\"brand\">\n <h2>{{ 'docs.title' | translate }}</h2>\n <p>{{ 'docs.subtitle' | translate }}</p>\n </div>\n\n <div class=\"search\">\n <input pInputText type=\"text\" [ngModel]=\"query()\" (ngModelChange)=\"query.set($event || '')\" [placeholder]=\"t('docs.search_placeholder','Search feature, tag, keyword')\" />\n <button pButton type=\"button\" icon=\"pi pi-sun\" *ngIf=\"dark()\" (click)=\"toggleTheme()\"></button>\n <button pButton type=\"button\" icon=\"pi pi-moon\" *ngIf=\"!dark()\" (click)=\"toggleTheme()\"></button>\n </div>\n\n <div class=\"nav-groups\">\n @for (group of filteredGroups(); track group.id) {\n <section>\n <h3>{{ group.title }}</h3>\n <ul>\n @for (item of group.items; track item.slug) {\n <li>\n <a [class.active]=\"item.slug === currentSlug()\" [href]=\"'#/docs/' + item.slug\" (click)=\"openPage(item.slug); $event.preventDefault()\">\n <i class=\"pi\" [ngClass]=\"item.icon || 'pi-file'\"></i>\n <span>{{ item.title }}</span>\n </a>\n </li>\n }\n </ul>\n </section>\n }\n </div>\n </aside>\n\n <main class=\"content\" #contentScroller *ngIf=\"currentPage() as page\">\n <nav class=\"breadcrumbs\">{{ breadcrumbs().join(' / ') }}</nav>\n\n <header>\n <h1>{{ page.title }}</h1>\n <p>{{ page.description }}</p>\n <div class=\"tags\">\n <p-tag *ngFor=\"let tag of page.tags\" [value]=\"tag\"></p-tag>\n <p-tag\n [value]=\"page.status\"\n [severity]=\"page.status === 'beta' ? 'warn' : 'success'\"\n [pTooltip]=\"page.status === 'beta' ? 'Feature in beta: API e comportamento possono cambiare senza preavviso' : ''\"\n tooltipPosition=\"top\"></p-tag>\n <p-tag *ngIf=\"page.tier === 'pro'\" value=\"Pro only\" styleClass=\"tier-tag tier-tag-pro\"></p-tag>\n <p-tag *ngIf=\"!page.tier || page.tier === 'dev-and-pro'\" value=\"Dev & Pro\" styleClass=\"tier-tag tier-tag-base\"></p-tag>\n </div>\n </header>\n\n <div class=\"body-grid\">\n <article>\n @for (section of page.sections; track section.id) {\n <section [attr.id]=\"section.id\" class=\"doc-section\">\n <h3>{{ section.title }}</h3>\n @for (part of sectionParts(section); track $index) {\n @if (part.kind === 'html') {\n <div class=\"section-html\" [innerHTML]=\"getHighlightedHtml(part.html)\"></div>\n } @else {\n @let sampleKey = getSampleKey(page.slug, section.id, part.sample.id);\n <div class=\"code-block\" [ngClass]=\"getSnippetLanguageClass(part.sample.language)\">\n <div class=\"code-header\">\n <div class=\"code-meta\">\n <span>{{ part.sample.title }}</span>\n <span class=\"snippet-lang\" [ngClass]=\"getSnippetLanguageClass(part.sample.language)\">\n <i class=\"pi\" [ngClass]=\"getSnippetLanguageIconClass(part.sample.language)\"></i>\n {{ getSnippetLanguageLabel(part.sample.language) }}\n </span>\n </div>\n <div class=\"code-actions\">\n @if (isSampleCollapsible(part.sample.code)) {\n <button\n pButton\n type=\"button\"\n size=\"small\"\n severity=\"secondary\"\n [icon]=\"isSampleExpanded(sampleKey) ? 'pi pi-chevron-up' : 'pi pi-chevron-down'\"\n [label]=\"isSampleExpanded(sampleKey) ? t('docs.collapse','Collapse') : (t('docs.expand','Expand') + ' (+' + getCollapsedHiddenLines(part.sample.code) + ')')\"\n (click)=\"toggleSampleExpanded(sampleKey)\">\n </button>\n }\n <button pButton type=\"button\" size=\"small\" icon=\"pi pi-copy\" [label]=\"t('docs.copy','Copy')\" (click)=\"copyCode(part.sample.code)\"></button>\n </div>\n </div>\n <pre><code [innerHTML]=\"getVisibleSampleHighlightedCode(part.sample.code, sampleKey, part.sample.language)\"></code></pre>\n </div>\n }\n }\n </section>\n }\n\n @if (page.slug === 'themes') {\n <section class=\"themes-catalog\">\n <h3>{{ 'docs.themes_catalog' | translate }}</h3>\n <div class=\"themes-catalog-grid\">\n @for (theme of allThemes; track theme.value) {\n <div class=\"themes-catalog-item\">\n <span class=\"theme-dot\" [style.backgroundColor]=\"theme.primaryHex\"></span>\n <span>{{ theme.label }}</span>\n </div>\n }\n </div>\n </section>\n }\n\n @if (page.images?.length) {\n <section class=\"screenshots\">\n <h3>{{ 'docs.feature_screenshots' | translate }}</h3>\n <div class=\"shots-grid\">\n @for (img of page.images; track img.id) {\n <figure>\n <p-image\n *ngIf=\"img.status === 'available'\"\n [preview]=\"true\"\n [src]=\"img.path\"\n [previewImageSrc]=\"img.path\"\n [alt]=\"img.caption\"\n />\n <div class=\"missing\" *ngIf=\"img.status !== 'available'\">{{ 'docs.snapshot_unavailable' | translate }}</div>\n <figcaption [innerHTML]=\"getHighlightedText(formatScreenshotCaption(img.caption))\"></figcaption>\n </figure>\n }\n </div>\n </section>\n }\n </article>\n\n <aside class=\"toc\">\n <h4>{{ 'docs.on_this_page' | translate }}</h4>\n <ul>\n @for (item of page.toc; track item.id) {\n <li><a [href]=\"'#' + item.id\" (click)=\"scrollToSection($event, item.id, item.title)\">{{ item.title }}</a></li>\n }\n </ul>\n </aside>\n </div>\n </main>\n</div>\n", styles: ["@charset \"UTF-8\";.fw-docs{--bg: linear-gradient(145deg, #f7f9ff 0%, #edf3ff 40%, #fefbf4 100%);--panel: rgba(255, 255, 255, .86);--text: #18304a;--muted: #4d6278;--accent: #0d6efd;--border: #dce7fb;height:calc(100vh - 70px);display:grid;grid-template-columns:320px 1fr;background:var(--bg);color:var(--text);overflow:hidden}.fw-docs.dark,:host-context(.theme-dark) .fw-docs,:host-context(.dark-theme) .fw-docs,:host-context([data-theme-mode=dark]) .fw-docs{--bg: linear-gradient(145deg, #121b2a 0%, #0f2233 55%, #222134 100%);--panel: rgba(22, 35, 55, .85);--text: #dbe8ff;--muted: #9eb4d2;--accent: #7eb2ff;--border: #294869}.sidebar{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:var(--panel);border-right:1px solid var(--border);padding:18px;position:sticky;top:0;height:calc(100vh - 70px);overflow:auto}.brand h1{margin:0;font-size:1.1rem}.brand p{margin:6px 0 14px;color:var(--muted);font-size:.88rem}.search{display:grid;grid-template-columns:1fr auto;gap:6px;margin-bottom:12px}.nav-groups h3{margin:14px 0 8px;font-size:.86rem;text-transform:uppercase;color:var(--muted);letter-spacing:.06em}.nav-groups ul{list-style:none;padding:0;margin:0}.nav-groups a{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:10px;color:inherit;text-decoration:none;cursor:pointer}.nav-groups a.active,.nav-groups a:hover{background:#0d6efd1f}.content{padding:26px 30px 36px;height:100%;overflow-y:scroll;scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--p-content-border-color, #94a3b8) 80%,transparent) transparent;scrollbar-gutter:stable}.content::-webkit-scrollbar,.sidebar::-webkit-scrollbar{width:10px;height:10px}.content::-webkit-scrollbar-track,.sidebar::-webkit-scrollbar-track{background:transparent}.content::-webkit-scrollbar-thumb,.sidebar::-webkit-scrollbar-thumb{background-color:color-mix(in srgb,var(--p-content-border-color, #94a3b8) 65%,transparent);border-radius:6px;border:2px solid transparent;background-clip:content-box}.content::-webkit-scrollbar-thumb:hover,.sidebar::-webkit-scrollbar-thumb:hover{background-color:var(--p-text-muted-color, #64748b)}.sidebar{scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--p-content-border-color, #94a3b8) 80%,transparent) transparent}.breadcrumbs{font-size:.85rem;color:var(--muted);margin-bottom:10px}header h2{margin:0;font-size:2rem}header p{margin:8px 0 12px;color:var(--muted)}.tags{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:20px}::ng-deep .tier-tag.p-tag{font-weight:700;letter-spacing:.02em}::ng-deep .tier-tag-base.p-tag{background:color-mix(in srgb,#6366f1 15%,var(--panel, #fff));color:color-mix(in srgb,#4338ca 85%,var(--text, #0f172a));border:1px solid color-mix(in srgb,#6366f1 45%,transparent)}::ng-deep .tier-tag-pro.p-tag{background:color-mix(in srgb,#f59e0b 18%,var(--panel, #fff));color:color-mix(in srgb,#b45309 85%,var(--text, #0f172a));border:1px solid color-mix(in srgb,#f59e0b 55%,transparent)}.body-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(320px,360px);gap:20px}article{background:var(--panel);border:1px solid var(--border);border-radius:16px;padding:20px}.doc-section{padding:6px 0 16px;border-bottom:1px dashed var(--border)}.doc-section:last-child{border-bottom:0}.doc-section h3{margin:8px 0}.section-html{color:var(--text)}:host ::ng-deep .section-html p{line-height:1.58}:host ::ng-deep .section-html table{width:100%;border-collapse:collapse;margin:16px 0 24px;border:1px solid var(--border);border-radius:8px;overflow:hidden;font-size:.92rem;box-shadow:0 1px 2px color-mix(in srgb,var(--text) 6%,transparent)}:host ::ng-deep .section-html thead{background:color-mix(in srgb,var(--accent) 14%,var(--panel))}:host ::ng-deep .section-html thead th{text-align:left;color:var(--text);font-weight:700;padding:12px 16px;border:1px solid var(--border);border-bottom:2px solid color-mix(in srgb,var(--accent) 35%,var(--border));text-transform:uppercase;font-size:.78rem;letter-spacing:.4px;white-space:nowrap}:host ::ng-deep .section-html tbody td{padding:11px 16px;border:1px solid color-mix(in srgb,var(--border) 80%,transparent);vertical-align:top;line-height:1.55;color:var(--text)}:host ::ng-deep .section-html tbody tr:nth-child(odd) td{background:color-mix(in srgb,var(--panel) 92%,transparent)}:host ::ng-deep .section-html tbody tr:nth-child(2n) td{background:color-mix(in srgb,var(--panel) 100%,transparent)}:host ::ng-deep .section-html tbody tr:hover td{background:color-mix(in srgb,var(--accent) 8%,var(--panel))}:host ::ng-deep .section-html table code{font-size:.85em;padding:1px 5px}:host ::ng-deep .section-html strong{color:var(--text);font-weight:600}:host ::ng-deep .section-html em{font-style:italic}:host ::ng-deep .section-html a.wuic-demo-link{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;margin:0 2px;background:color-mix(in srgb,var(--accent) 14%,var(--panel));border:1px solid color-mix(in srgb,var(--accent) 35%,var(--border));border-radius:6px;color:var(--accent, #4f46e5);font-size:.85rem;font-weight:600;text-decoration:none;line-height:1.2;transition:background .15s ease}:host ::ng-deep .section-html a.wuic-demo-link:before{content:\"\\25b6\";font-size:.7rem;color:var(--accent, #4f46e5)}:host ::ng-deep .section-html a.wuic-demo-link:hover{background:color-mix(in srgb,var(--accent) 22%,var(--panel));text-decoration:none}:host ::ng-deep .section-html :where(ul,ol){margin:12px 0 16px;padding-left:0}:host ::ng-deep .section-html ul{list-style:none}:host ::ng-deep .section-html ul>li{position:relative;margin:8px 0;padding:8px 12px 8px 34px;border:1px solid color-mix(in srgb,var(--border) 78%,transparent);border-radius:10px;background:color-mix(in srgb,var(--panel) 90%,transparent);line-height:1.52}:host ::ng-deep .section-html ul>li:nth-child(odd){background:color-mix(in srgb,var(--panel) 94%,transparent)}:host ::ng-deep .section-html ul>li:nth-child(2n){background:color-mix(in srgb,var(--accent) 5%,var(--panel));border-color:color-mix(in srgb,var(--accent) 18%,var(--border))}:host ::ng-deep .section-html ul>li:before{content:\"\";position:absolute;left:12px;top:50%;width:10px;height:10px;border-radius:999px;transform:translateY(-50%);background:radial-gradient(circle at 35% 35%,#fff 0 24%,color-mix(in srgb,var(--accent) 80%,#4aa3ff) 25% 100%);box-shadow:0 0 0 1px color-mix(in srgb,var(--accent) 32%,transparent)}:host ::ng-deep .section-html ol{list-style:none;counter-reset:wuic-doc-step}:host ::ng-deep .section-html ol>li{position:relative;counter-increment:wuic-doc-step;margin:8px 0;padding:8px 12px 8px 42px;border-left:3px solid color-mix(in srgb,var(--accent) 55%,transparent);background:color-mix(in srgb,var(--accent) 7%,var(--panel));border-radius:10px;line-height:1.52}:host ::ng-deep .section-html ol>li:nth-child(odd){background:color-mix(in srgb,var(--accent) 7%,var(--panel))}:host ::ng-deep .section-html ol>li:nth-child(2n){background:color-mix(in srgb,var(--accent) 11%,var(--panel));border-left-color:color-mix(in srgb,var(--accent) 72%,transparent)}:host ::ng-deep .section-html ol>li:before{content:counter(wuic-doc-step);position:absolute;left:10px;top:50%;width:22px;height:22px;border-radius:999px;transform:translateY(-50%);display:grid;place-items:center;font-size:.78rem;font-weight:700;color:#fff;background:linear-gradient(160deg,color-mix(in srgb,var(--accent) 80%,#4aa3ff),color-mix(in srgb,var(--accent) 95%,#125dcc));box-shadow:0 2px 10px color-mix(in srgb,var(--accent) 35%,transparent)}:host ::ng-deep .section-html li>:where(ul,ol){margin-top:8px}.code-block{margin:10px 0;border:1px solid var(--border);border-radius:12px;overflow:hidden}.code-header{display:flex;justify-content:space-between;align-items:center;background:#0d6efd14;padding:8px 10px}.code-block pre{margin:0;padding:12px;overflow:auto;background:#1e1e1e}.code-block code{display:block;font-family:Consolas,Cascadia Code,Fira Code,Courier New,monospace;font-size:.88rem;line-height:1.55;color:#d4d4d4;white-space:pre}.code-actions{display:inline-flex;gap:8px;align-items:center}.code-meta{display:inline-flex;align-items:center;gap:10px}.snippet-lang{display:inline-flex;align-items:center;justify-content:center;min-width:44px;height:22px;padding:0 8px;border-radius:999px;font-size:.72rem;font-weight:700;letter-spacing:.04em;border:1px solid transparent;background:color-mix(in srgb,var(--text) 8%,transparent);color:var(--text)}.snippet-lang.lang-html{background:#e36c0924;border-color:#e36c0952;color:#b25d0e}.snippet-lang.lang-json{background:#00808024;border-color:#00808052;color:#0e6b6b}.snippet-lang.lang-ts{background:#3178c624;border-color:#3178c652;color:#1f5f9f}.snippet-lang.lang-cs{background:#68217a24;border-color:#68217a52;color:#6a2b82}.snippet-lang i{font-size:.72rem}:host ::ng-deep .code-block code .tok-comment{color:#6a9955}:host ::ng-deep .code-block code .tok-string{color:#ce9178}:host ::ng-deep .code-block code .tok-number{color:#b5cea8}:host ::ng-deep .code-block code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block code .tok-type{color:#4ec9b0}:host ::ng-deep .code-block code .tok-fn{color:#dcdcaa}:host ::ng-deep .code-block code .tok-prop{color:#9cdcfe}:host ::ng-deep .code-block code .tok-op{color:#d4d4d4}:host ::ng-deep .code-block.lang-html code .tok-tag{color:#569cd6}:host ::ng-deep .code-block.lang-html code .tok-attr{color:#9cdcfe}:host ::ng-deep .code-block.lang-html code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-json code .tok-key{color:#9cdcfe}:host ::ng-deep .code-block.lang-json code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-json code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block.lang-json code .tok-number{color:#b5cea8}:host ::ng-deep .code-block.lang-ts code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block.lang-ts code .tok-type{color:#4ec9b0}:host ::ng-deep .code-block.lang-ts code .tok-fn{color:#dcdcaa}:host ::ng-deep .code-block.lang-ts code .tok-prop{color:#9cdcfe}:host ::ng-deep .code-block.lang-ts code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-ts code .tok-comment{color:#6a9955}:host ::ng-deep .code-block.lang-ts code .tok-number{color:#b5cea8}:host ::ng-deep .code-block.lang-cs code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block.lang-cs code .tok-fn{color:#dcdcaa}:host ::ng-deep .code-block.lang-cs code .tok-prop{color:#9cdcfe}:host ::ng-deep .code-block.lang-cs code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-cs code .tok-comment{color:#6a9955}:host ::ng-deep .code-block.lang-cs code .tok-number{color:#b5cea8}.screenshots .shots-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px;align-items:start}.screenshots figure{margin:0;display:flex;flex-direction:column;gap:8px}.screenshots img{width:100%;border-radius:12px;border:1px solid var(--border)}:host ::ng-deep .screenshots .p-image,:host ::ng-deep .screenshots .p-image img{width:100%;display:block}:host ::ng-deep .screenshots .p-image{aspect-ratio:16/9;overflow:hidden;border-radius:12px;border:1px solid var(--border);background:color-mix(in srgb,var(--panel) 90%,transparent)}:host ::ng-deep .screenshots .p-image img{width:100%;height:100%;object-fit:contain;object-position:center;border:0}:host ::ng-deep .screenshots .p-image .p-image-preview-mask,:host ::ng-deep .screenshots .p-image .p-image-mask{inset:0;width:100%;height:100%}.missing{border:1px dashed var(--border);border-radius:12px;padding:36px 10px;text-align:center;color:var(--muted)}:host ::ng-deep .p-image-mask,:host ::ng-deep .p-image-preview-mask{background:none!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important}:host ::ng-deep .p-image-toolbar{background:#0c1220!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important;filter:none!important;border:1px solid rgba(255,255,255,.14)!important;box-shadow:0 8px 24px #00000059!important}:host ::ng-deep .p-image-action{color:#eaf1ff!important;opacity:1!important;text-shadow:none!important}:host ::ng-deep .p-image-preview{background:#7eb2ff2e!important}::ng-deep .p-image-mask.p-component-overlay,::ng-deep .p-image-preview-mask.p-component-overlay{background:#0c121efa!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important}:host ::ng-deep .screenshots .p-image .p-image-mask,:host ::ng-deep .screenshots .p-image .p-image-preview-indicator{background:#0c121e42!important}.themes-catalog{margin-top:14px;padding:6px 0 16px;border-top:1px dashed var(--border)}.themes-catalog-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:10px}.themes-catalog-item{display:inline-flex;align-items:center;gap:.55rem;min-height:2.25rem;padding:.45rem .65rem;border:1px solid var(--border);border-radius:10px;background:color-mix(in srgb,var(--panel) 92%,transparent)}.theme-dot{width:.8rem;height:.8rem;border-radius:999px;border:1px solid color-mix(in srgb,var(--text) 25%,transparent);box-shadow:0 0 0 1px color-mix(in srgb,#ffffff 55%,transparent) inset;flex:0 0 auto}.toc{position:sticky;top:20px;max-height:calc(100vh - 120px);overflow-y:auto;background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:14px}.toc h4{margin:0 0 8px}.toc ul{margin:0;padding-left:18px;list-style-position:outside}.toc a{display:block;color:var(--text);text-decoration:none;line-height:1.25;overflow-wrap:anywhere;word-break:break-word}.toc li{margin:0 0 6px}.toc li:last-child{margin-bottom:0}@media(max-width:1360px)and (min-width:1101px){.body-grid{grid-template-columns:minmax(0,1fr) minmax(280px,320px)}}@media(max-width:1100px){.fw-docs{grid-template-columns:1fr;height:auto;overflow:visible}.sidebar{height:auto;position:static;border-right:0;border-bottom:1px solid var(--border)}.content{height:auto;overflow:visible}.body-grid{grid-template-columns:1fr}.toc{position:static}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i4.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i13.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i10.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i3.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: ImageModule }, { kind: "component", type: i1$3.Image, selector: "p-image", inputs: ["imageClass", "imageStyle", "styleClass", "src", "srcSet", "sizes", "previewImageSrc", "previewImageSrcSet", "previewImageSizes", "alt", "width", "height", "loading", "preview", "showTransitionOptions", "hideTransitionOptions", "modalEnterAnimation", "modalLeaveAnimation", "appendTo", "maskMotionOptions", "motionOptions"], outputs: ["onShow", "onHide", "onImageError"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i8.TranslatePipe, name: "translate" }] });
26395
27104
  }
26396
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FrameworkDocsComponent, decorators: [{
27105
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FrameworkDocsComponent, decorators: [{
26397
27106
  type: Component,
26398
27107
  args: [{ selector: 'wuic-framework-docs', standalone: true, imports: [CommonModule, FormsModule, ButtonModule, InputTextModule, TagModule, TooltipModule, ImageModule, TranslateModule], template: "<div class=\"fw-docs\" [class.dark]=\"dark()\">\n <aside class=\"sidebar\">\n <div class=\"brand\">\n <h2>{{ 'docs.title' | translate }}</h2>\n <p>{{ 'docs.subtitle' | translate }}</p>\n </div>\n\n <div class=\"search\">\n <input pInputText type=\"text\" [ngModel]=\"query()\" (ngModelChange)=\"query.set($event || '')\" [placeholder]=\"t('docs.search_placeholder','Search feature, tag, keyword')\" />\n <button pButton type=\"button\" icon=\"pi pi-sun\" *ngIf=\"dark()\" (click)=\"toggleTheme()\"></button>\n <button pButton type=\"button\" icon=\"pi pi-moon\" *ngIf=\"!dark()\" (click)=\"toggleTheme()\"></button>\n </div>\n\n <div class=\"nav-groups\">\n @for (group of filteredGroups(); track group.id) {\n <section>\n <h3>{{ group.title }}</h3>\n <ul>\n @for (item of group.items; track item.slug) {\n <li>\n <a [class.active]=\"item.slug === currentSlug()\" [href]=\"'#/docs/' + item.slug\" (click)=\"openPage(item.slug); $event.preventDefault()\">\n <i class=\"pi\" [ngClass]=\"item.icon || 'pi-file'\"></i>\n <span>{{ item.title }}</span>\n </a>\n </li>\n }\n </ul>\n </section>\n }\n </div>\n </aside>\n\n <main class=\"content\" #contentScroller *ngIf=\"currentPage() as page\">\n <nav class=\"breadcrumbs\">{{ breadcrumbs().join(' / ') }}</nav>\n\n <header>\n <h1>{{ page.title }}</h1>\n <p>{{ page.description }}</p>\n <div class=\"tags\">\n <p-tag *ngFor=\"let tag of page.tags\" [value]=\"tag\"></p-tag>\n <p-tag\n [value]=\"page.status\"\n [severity]=\"page.status === 'beta' ? 'warn' : 'success'\"\n [pTooltip]=\"page.status === 'beta' ? 'Feature in beta: API e comportamento possono cambiare senza preavviso' : ''\"\n tooltipPosition=\"top\"></p-tag>\n <p-tag *ngIf=\"page.tier === 'pro'\" value=\"Pro only\" styleClass=\"tier-tag tier-tag-pro\"></p-tag>\n <p-tag *ngIf=\"!page.tier || page.tier === 'dev-and-pro'\" value=\"Dev & Pro\" styleClass=\"tier-tag tier-tag-base\"></p-tag>\n </div>\n </header>\n\n <div class=\"body-grid\">\n <article>\n @for (section of page.sections; track section.id) {\n <section [attr.id]=\"section.id\" class=\"doc-section\">\n <h3>{{ section.title }}</h3>\n @for (part of sectionParts(section); track $index) {\n @if (part.kind === 'html') {\n <div class=\"section-html\" [innerHTML]=\"getHighlightedHtml(part.html)\"></div>\n } @else {\n @let sampleKey = getSampleKey(page.slug, section.id, part.sample.id);\n <div class=\"code-block\" [ngClass]=\"getSnippetLanguageClass(part.sample.language)\">\n <div class=\"code-header\">\n <div class=\"code-meta\">\n <span>{{ part.sample.title }}</span>\n <span class=\"snippet-lang\" [ngClass]=\"getSnippetLanguageClass(part.sample.language)\">\n <i class=\"pi\" [ngClass]=\"getSnippetLanguageIconClass(part.sample.language)\"></i>\n {{ getSnippetLanguageLabel(part.sample.language) }}\n </span>\n </div>\n <div class=\"code-actions\">\n @if (isSampleCollapsible(part.sample.code)) {\n <button\n pButton\n type=\"button\"\n size=\"small\"\n severity=\"secondary\"\n [icon]=\"isSampleExpanded(sampleKey) ? 'pi pi-chevron-up' : 'pi pi-chevron-down'\"\n [label]=\"isSampleExpanded(sampleKey) ? t('docs.collapse','Collapse') : (t('docs.expand','Expand') + ' (+' + getCollapsedHiddenLines(part.sample.code) + ')')\"\n (click)=\"toggleSampleExpanded(sampleKey)\">\n </button>\n }\n <button pButton type=\"button\" size=\"small\" icon=\"pi pi-copy\" [label]=\"t('docs.copy','Copy')\" (click)=\"copyCode(part.sample.code)\"></button>\n </div>\n </div>\n <pre><code [innerHTML]=\"getVisibleSampleHighlightedCode(part.sample.code, sampleKey, part.sample.language)\"></code></pre>\n </div>\n }\n }\n </section>\n }\n\n @if (page.slug === 'themes') {\n <section class=\"themes-catalog\">\n <h3>{{ 'docs.themes_catalog' | translate }}</h3>\n <div class=\"themes-catalog-grid\">\n @for (theme of allThemes; track theme.value) {\n <div class=\"themes-catalog-item\">\n <span class=\"theme-dot\" [style.backgroundColor]=\"theme.primaryHex\"></span>\n <span>{{ theme.label }}</span>\n </div>\n }\n </div>\n </section>\n }\n\n @if (page.images?.length) {\n <section class=\"screenshots\">\n <h3>{{ 'docs.feature_screenshots' | translate }}</h3>\n <div class=\"shots-grid\">\n @for (img of page.images; track img.id) {\n <figure>\n <p-image\n *ngIf=\"img.status === 'available'\"\n [preview]=\"true\"\n [src]=\"img.path\"\n [previewImageSrc]=\"img.path\"\n [alt]=\"img.caption\"\n />\n <div class=\"missing\" *ngIf=\"img.status !== 'available'\">{{ 'docs.snapshot_unavailable' | translate }}</div>\n <figcaption [innerHTML]=\"getHighlightedText(formatScreenshotCaption(img.caption))\"></figcaption>\n </figure>\n }\n </div>\n </section>\n }\n </article>\n\n <aside class=\"toc\">\n <h4>{{ 'docs.on_this_page' | translate }}</h4>\n <ul>\n @for (item of page.toc; track item.id) {\n <li><a [href]=\"'#' + item.id\" (click)=\"scrollToSection($event, item.id, item.title)\">{{ item.title }}</a></li>\n }\n </ul>\n </aside>\n </div>\n </main>\n</div>\n", styles: ["@charset \"UTF-8\";.fw-docs{--bg: linear-gradient(145deg, #f7f9ff 0%, #edf3ff 40%, #fefbf4 100%);--panel: rgba(255, 255, 255, .86);--text: #18304a;--muted: #4d6278;--accent: #0d6efd;--border: #dce7fb;height:calc(100vh - 70px);display:grid;grid-template-columns:320px 1fr;background:var(--bg);color:var(--text);overflow:hidden}.fw-docs.dark,:host-context(.theme-dark) .fw-docs,:host-context(.dark-theme) .fw-docs,:host-context([data-theme-mode=dark]) .fw-docs{--bg: linear-gradient(145deg, #121b2a 0%, #0f2233 55%, #222134 100%);--panel: rgba(22, 35, 55, .85);--text: #dbe8ff;--muted: #9eb4d2;--accent: #7eb2ff;--border: #294869}.sidebar{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:var(--panel);border-right:1px solid var(--border);padding:18px;position:sticky;top:0;height:calc(100vh - 70px);overflow:auto}.brand h1{margin:0;font-size:1.1rem}.brand p{margin:6px 0 14px;color:var(--muted);font-size:.88rem}.search{display:grid;grid-template-columns:1fr auto;gap:6px;margin-bottom:12px}.nav-groups h3{margin:14px 0 8px;font-size:.86rem;text-transform:uppercase;color:var(--muted);letter-spacing:.06em}.nav-groups ul{list-style:none;padding:0;margin:0}.nav-groups a{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:10px;color:inherit;text-decoration:none;cursor:pointer}.nav-groups a.active,.nav-groups a:hover{background:#0d6efd1f}.content{padding:26px 30px 36px;height:100%;overflow-y:scroll;scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--p-content-border-color, #94a3b8) 80%,transparent) transparent;scrollbar-gutter:stable}.content::-webkit-scrollbar,.sidebar::-webkit-scrollbar{width:10px;height:10px}.content::-webkit-scrollbar-track,.sidebar::-webkit-scrollbar-track{background:transparent}.content::-webkit-scrollbar-thumb,.sidebar::-webkit-scrollbar-thumb{background-color:color-mix(in srgb,var(--p-content-border-color, #94a3b8) 65%,transparent);border-radius:6px;border:2px solid transparent;background-clip:content-box}.content::-webkit-scrollbar-thumb:hover,.sidebar::-webkit-scrollbar-thumb:hover{background-color:var(--p-text-muted-color, #64748b)}.sidebar{scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--p-content-border-color, #94a3b8) 80%,transparent) transparent}.breadcrumbs{font-size:.85rem;color:var(--muted);margin-bottom:10px}header h2{margin:0;font-size:2rem}header p{margin:8px 0 12px;color:var(--muted)}.tags{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:20px}::ng-deep .tier-tag.p-tag{font-weight:700;letter-spacing:.02em}::ng-deep .tier-tag-base.p-tag{background:color-mix(in srgb,#6366f1 15%,var(--panel, #fff));color:color-mix(in srgb,#4338ca 85%,var(--text, #0f172a));border:1px solid color-mix(in srgb,#6366f1 45%,transparent)}::ng-deep .tier-tag-pro.p-tag{background:color-mix(in srgb,#f59e0b 18%,var(--panel, #fff));color:color-mix(in srgb,#b45309 85%,var(--text, #0f172a));border:1px solid color-mix(in srgb,#f59e0b 55%,transparent)}.body-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(320px,360px);gap:20px}article{background:var(--panel);border:1px solid var(--border);border-radius:16px;padding:20px}.doc-section{padding:6px 0 16px;border-bottom:1px dashed var(--border)}.doc-section:last-child{border-bottom:0}.doc-section h3{margin:8px 0}.section-html{color:var(--text)}:host ::ng-deep .section-html p{line-height:1.58}:host ::ng-deep .section-html table{width:100%;border-collapse:collapse;margin:16px 0 24px;border:1px solid var(--border);border-radius:8px;overflow:hidden;font-size:.92rem;box-shadow:0 1px 2px color-mix(in srgb,var(--text) 6%,transparent)}:host ::ng-deep .section-html thead{background:color-mix(in srgb,var(--accent) 14%,var(--panel))}:host ::ng-deep .section-html thead th{text-align:left;color:var(--text);font-weight:700;padding:12px 16px;border:1px solid var(--border);border-bottom:2px solid color-mix(in srgb,var(--accent) 35%,var(--border));text-transform:uppercase;font-size:.78rem;letter-spacing:.4px;white-space:nowrap}:host ::ng-deep .section-html tbody td{padding:11px 16px;border:1px solid color-mix(in srgb,var(--border) 80%,transparent);vertical-align:top;line-height:1.55;color:var(--text)}:host ::ng-deep .section-html tbody tr:nth-child(odd) td{background:color-mix(in srgb,var(--panel) 92%,transparent)}:host ::ng-deep .section-html tbody tr:nth-child(2n) td{background:color-mix(in srgb,var(--panel) 100%,transparent)}:host ::ng-deep .section-html tbody tr:hover td{background:color-mix(in srgb,var(--accent) 8%,var(--panel))}:host ::ng-deep .section-html table code{font-size:.85em;padding:1px 5px}:host ::ng-deep .section-html strong{color:var(--text);font-weight:600}:host ::ng-deep .section-html em{font-style:italic}:host ::ng-deep .section-html a.wuic-demo-link{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;margin:0 2px;background:color-mix(in srgb,var(--accent) 14%,var(--panel));border:1px solid color-mix(in srgb,var(--accent) 35%,var(--border));border-radius:6px;color:var(--accent, #4f46e5);font-size:.85rem;font-weight:600;text-decoration:none;line-height:1.2;transition:background .15s ease}:host ::ng-deep .section-html a.wuic-demo-link:before{content:\"\\25b6\";font-size:.7rem;color:var(--accent, #4f46e5)}:host ::ng-deep .section-html a.wuic-demo-link:hover{background:color-mix(in srgb,var(--accent) 22%,var(--panel));text-decoration:none}:host ::ng-deep .section-html :where(ul,ol){margin:12px 0 16px;padding-left:0}:host ::ng-deep .section-html ul{list-style:none}:host ::ng-deep .section-html ul>li{position:relative;margin:8px 0;padding:8px 12px 8px 34px;border:1px solid color-mix(in srgb,var(--border) 78%,transparent);border-radius:10px;background:color-mix(in srgb,var(--panel) 90%,transparent);line-height:1.52}:host ::ng-deep .section-html ul>li:nth-child(odd){background:color-mix(in srgb,var(--panel) 94%,transparent)}:host ::ng-deep .section-html ul>li:nth-child(2n){background:color-mix(in srgb,var(--accent) 5%,var(--panel));border-color:color-mix(in srgb,var(--accent) 18%,var(--border))}:host ::ng-deep .section-html ul>li:before{content:\"\";position:absolute;left:12px;top:50%;width:10px;height:10px;border-radius:999px;transform:translateY(-50%);background:radial-gradient(circle at 35% 35%,#fff 0 24%,color-mix(in srgb,var(--accent) 80%,#4aa3ff) 25% 100%);box-shadow:0 0 0 1px color-mix(in srgb,var(--accent) 32%,transparent)}:host ::ng-deep .section-html ol{list-style:none;counter-reset:wuic-doc-step}:host ::ng-deep .section-html ol>li{position:relative;counter-increment:wuic-doc-step;margin:8px 0;padding:8px 12px 8px 42px;border-left:3px solid color-mix(in srgb,var(--accent) 55%,transparent);background:color-mix(in srgb,var(--accent) 7%,var(--panel));border-radius:10px;line-height:1.52}:host ::ng-deep .section-html ol>li:nth-child(odd){background:color-mix(in srgb,var(--accent) 7%,var(--panel))}:host ::ng-deep .section-html ol>li:nth-child(2n){background:color-mix(in srgb,var(--accent) 11%,var(--panel));border-left-color:color-mix(in srgb,var(--accent) 72%,transparent)}:host ::ng-deep .section-html ol>li:before{content:counter(wuic-doc-step);position:absolute;left:10px;top:50%;width:22px;height:22px;border-radius:999px;transform:translateY(-50%);display:grid;place-items:center;font-size:.78rem;font-weight:700;color:#fff;background:linear-gradient(160deg,color-mix(in srgb,var(--accent) 80%,#4aa3ff),color-mix(in srgb,var(--accent) 95%,#125dcc));box-shadow:0 2px 10px color-mix(in srgb,var(--accent) 35%,transparent)}:host ::ng-deep .section-html li>:where(ul,ol){margin-top:8px}.code-block{margin:10px 0;border:1px solid var(--border);border-radius:12px;overflow:hidden}.code-header{display:flex;justify-content:space-between;align-items:center;background:#0d6efd14;padding:8px 10px}.code-block pre{margin:0;padding:12px;overflow:auto;background:#1e1e1e}.code-block code{display:block;font-family:Consolas,Cascadia Code,Fira Code,Courier New,monospace;font-size:.88rem;line-height:1.55;color:#d4d4d4;white-space:pre}.code-actions{display:inline-flex;gap:8px;align-items:center}.code-meta{display:inline-flex;align-items:center;gap:10px}.snippet-lang{display:inline-flex;align-items:center;justify-content:center;min-width:44px;height:22px;padding:0 8px;border-radius:999px;font-size:.72rem;font-weight:700;letter-spacing:.04em;border:1px solid transparent;background:color-mix(in srgb,var(--text) 8%,transparent);color:var(--text)}.snippet-lang.lang-html{background:#e36c0924;border-color:#e36c0952;color:#b25d0e}.snippet-lang.lang-json{background:#00808024;border-color:#00808052;color:#0e6b6b}.snippet-lang.lang-ts{background:#3178c624;border-color:#3178c652;color:#1f5f9f}.snippet-lang.lang-cs{background:#68217a24;border-color:#68217a52;color:#6a2b82}.snippet-lang i{font-size:.72rem}:host ::ng-deep .code-block code .tok-comment{color:#6a9955}:host ::ng-deep .code-block code .tok-string{color:#ce9178}:host ::ng-deep .code-block code .tok-number{color:#b5cea8}:host ::ng-deep .code-block code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block code .tok-type{color:#4ec9b0}:host ::ng-deep .code-block code .tok-fn{color:#dcdcaa}:host ::ng-deep .code-block code .tok-prop{color:#9cdcfe}:host ::ng-deep .code-block code .tok-op{color:#d4d4d4}:host ::ng-deep .code-block.lang-html code .tok-tag{color:#569cd6}:host ::ng-deep .code-block.lang-html code .tok-attr{color:#9cdcfe}:host ::ng-deep .code-block.lang-html code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-json code .tok-key{color:#9cdcfe}:host ::ng-deep .code-block.lang-json code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-json code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block.lang-json code .tok-number{color:#b5cea8}:host ::ng-deep .code-block.lang-ts code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block.lang-ts code .tok-type{color:#4ec9b0}:host ::ng-deep .code-block.lang-ts code .tok-fn{color:#dcdcaa}:host ::ng-deep .code-block.lang-ts code .tok-prop{color:#9cdcfe}:host ::ng-deep .code-block.lang-ts code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-ts code .tok-comment{color:#6a9955}:host ::ng-deep .code-block.lang-ts code .tok-number{color:#b5cea8}:host ::ng-deep .code-block.lang-cs code .tok-keyword{color:#569cd6}:host ::ng-deep .code-block.lang-cs code .tok-fn{color:#dcdcaa}:host ::ng-deep .code-block.lang-cs code .tok-prop{color:#9cdcfe}:host ::ng-deep .code-block.lang-cs code .tok-string{color:#ce9178}:host ::ng-deep .code-block.lang-cs code .tok-comment{color:#6a9955}:host ::ng-deep .code-block.lang-cs code .tok-number{color:#b5cea8}.screenshots .shots-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px;align-items:start}.screenshots figure{margin:0;display:flex;flex-direction:column;gap:8px}.screenshots img{width:100%;border-radius:12px;border:1px solid var(--border)}:host ::ng-deep .screenshots .p-image,:host ::ng-deep .screenshots .p-image img{width:100%;display:block}:host ::ng-deep .screenshots .p-image{aspect-ratio:16/9;overflow:hidden;border-radius:12px;border:1px solid var(--border);background:color-mix(in srgb,var(--panel) 90%,transparent)}:host ::ng-deep .screenshots .p-image img{width:100%;height:100%;object-fit:contain;object-position:center;border:0}:host ::ng-deep .screenshots .p-image .p-image-preview-mask,:host ::ng-deep .screenshots .p-image .p-image-mask{inset:0;width:100%;height:100%}.missing{border:1px dashed var(--border);border-radius:12px;padding:36px 10px;text-align:center;color:var(--muted)}:host ::ng-deep .p-image-mask,:host ::ng-deep .p-image-preview-mask{background:none!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important}:host ::ng-deep .p-image-toolbar{background:#0c1220!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important;filter:none!important;border:1px solid rgba(255,255,255,.14)!important;box-shadow:0 8px 24px #00000059!important}:host ::ng-deep .p-image-action{color:#eaf1ff!important;opacity:1!important;text-shadow:none!important}:host ::ng-deep .p-image-preview{background:#7eb2ff2e!important}::ng-deep .p-image-mask.p-component-overlay,::ng-deep .p-image-preview-mask.p-component-overlay{background:#0c121efa!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important}:host ::ng-deep .screenshots .p-image .p-image-mask,:host ::ng-deep .screenshots .p-image .p-image-preview-indicator{background:#0c121e42!important}.themes-catalog{margin-top:14px;padding:6px 0 16px;border-top:1px dashed var(--border)}.themes-catalog-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:10px}.themes-catalog-item{display:inline-flex;align-items:center;gap:.55rem;min-height:2.25rem;padding:.45rem .65rem;border:1px solid var(--border);border-radius:10px;background:color-mix(in srgb,var(--panel) 92%,transparent)}.theme-dot{width:.8rem;height:.8rem;border-radius:999px;border:1px solid color-mix(in srgb,var(--text) 25%,transparent);box-shadow:0 0 0 1px color-mix(in srgb,#ffffff 55%,transparent) inset;flex:0 0 auto}.toc{position:sticky;top:20px;max-height:calc(100vh - 120px);overflow-y:auto;background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:14px}.toc h4{margin:0 0 8px}.toc ul{margin:0;padding-left:18px;list-style-position:outside}.toc a{display:block;color:var(--text);text-decoration:none;line-height:1.25;overflow-wrap:anywhere;word-break:break-word}.toc li{margin:0 0 6px}.toc li:last-child{margin-bottom:0}@media(max-width:1360px)and (min-width:1101px){.body-grid{grid-template-columns:minmax(0,1fr) minmax(280px,320px)}}@media(max-width:1100px){.fw-docs{grid-template-columns:1fr;height:auto;overflow:visible}.sidebar{height:auto;position:static;border-right:0;border-bottom:1px solid var(--border)}.content{height:auto;overflow:visible}.body-grid{grid-template-columns:1fr}.toc{position:static}}\n"] }]
26399
27108
  }], ctorParameters: () => [{ type: i1.Router }, { type: i1.ActivatedRoute }], propDecorators: { contentScroller: [{
@@ -26402,4 +27111,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
26402
27111
  }] } });
26403
27112
 
26404
27113
  export { FrameworkDocsComponent };
26405
- //# sourceMappingURL=wuic-framework-lib-framework-docs.component-BGcJ7k1X.mjs.map
27114
+ //# sourceMappingURL=wuic-framework-lib-framework-docs.component-B6qNTs5H.mjs.map