wuic-framework-lib 1.0.19 → 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-C0OYuZoM.mjs → wuic-framework-lib-chart-list.component-B44k_wZe.mjs} +73 -13
  2. package/fesm2022/{wuic-framework-lib-code-editor.component-B8OQ3Z0k.mjs → wuic-framework-lib-code-editor.component-D1dy4Dl9.mjs} +5 -5
  3. package/fesm2022/{wuic-framework-lib-designer.component-DsWgWtFj.mjs → wuic-framework-lib-designer.component-CnOQbvEH.mjs} +82 -49
  4. package/fesm2022/{wuic-framework-lib-first-run-wizard.component-wR_LrvPK.mjs → wuic-framework-lib-first-run-wizard.component-C9lswoxG.mjs} +8 -8
  5. package/fesm2022/{wuic-framework-lib-framework-docs.component-eyzu9tdV.mjs → wuic-framework-lib-framework-docs.component-B6qNTs5H.mjs} +803 -110
  6. package/fesm2022/{wuic-framework-lib-pivot-builder.component-BFdZW5Ch.mjs → wuic-framework-lib-pivot-builder.component-DMk26H0M.mjs} +264 -16
  7. package/fesm2022/{wuic-framework-lib-prompt-dialog-fallback-host.component-B583WbYe.mjs → wuic-framework-lib-prompt-dialog-fallback-host.component-y3shST6g.mjs} +5 -5
  8. package/fesm2022/{wuic-framework-lib-report-designer.component-HJUpslLX.mjs → wuic-framework-lib-report-designer.component-BnngR2eL.mjs} +5 -5
  9. package/fesm2022/{wuic-framework-lib-report-viewer.component-El32zpzi.mjs → wuic-framework-lib-report-viewer.component-LGNRIQh2.mjs} +5 -5
  10. package/fesm2022/{wuic-framework-lib-scheduler-list.component-E8s_e533.mjs → wuic-framework-lib-scheduler-list.component-DbcFMwx5.mjs} +28 -5
  11. package/fesm2022/{wuic-framework-lib-spreadsheet-list-sf.component-CDn0qi_C.mjs → wuic-framework-lib-spreadsheet-list-sf.component-DXyRX0YK.mjs} +9 -9
  12. package/fesm2022/{wuic-framework-lib-workflow-designer.component-hL7oKqYO.mjs → wuic-framework-lib-workflow-designer.component-Bm719AWV.mjs} +9 -9
  13. package/fesm2022/{wuic-framework-lib-wuic-framework-lib-ABS_G7lO.mjs → wuic-framework-lib-wuic-framework-lib-CGCZvtKK.mjs} +1680 -411
  14. package/fesm2022/wuic-framework-lib.mjs +1 -1
  15. package/package.json +1 -1
  16. package/types/wuic-framework-lib.d.ts +340 -3
@@ -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 { r as getThemeOptions, T as TranslationManagerService, U as UserInfoService } from './wuic-framework-lib-wuic-framework-lib-ABS_G7lO.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-05-03T15:13:16.104Z",
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', 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}"
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
  ],
@@ -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
  }
@@ -6800,26 +6969,34 @@ const frameworkDocsContent = {
6800
6969
  },
6801
6970
  {
6802
6971
  "id": "sec_6",
6803
- "title": "md_props_bag: archetypes.list"
6972
+ "title": "md_props_bag: toolbar"
6804
6973
  },
6805
6974
  {
6806
6975
  "id": "sec_7",
6807
- "title": "Paging: md_pagesize and md_page_size_choice"
6976
+ "title": "md_props_bag: archetypes.list"
6808
6977
  },
6809
6978
  {
6810
6979
  "id": "sec_8",
6811
- "title": "Forced Virtualization on High Page Size"
6980
+ "title": "Paging: md_pagesize and md_page_size_choice"
6812
6981
  },
6813
6982
  {
6814
6983
  "id": "sec_9",
6984
+ "title": "Forced Virtualization on High Page Size"
6985
+ },
6986
+ {
6987
+ "id": "sec_10",
6815
6988
  "title": "Events and Subscriptions (Host)"
6989
+ },
6990
+ {
6991
+ "id": "sec_11",
6992
+ "title": "`rowCustomSelect` — actual signature (gotcha)"
6816
6993
  }
6817
6994
  ],
6818
6995
  "sections": [
6819
6996
  {
6820
6997
  "id": "overview",
6821
6998
  "title": "Overview",
6822
- "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>",
6823
7000
  "codeSamples": [
6824
7001
  {
6825
7002
  "id": "code_1",
@@ -6843,19 +7020,49 @@ const frameworkDocsContent = {
6843
7020
  "id": "code_4",
6844
7021
  "title": "Snippet 4",
6845
7022
  "language": "json",
6846
- "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}"
6847
7024
  },
6848
7025
  {
6849
7026
  "id": "code_5",
6850
7027
  "title": "Snippet 5",
6851
- "language": "html",
6852
- "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;"
6853
7030
  },
6854
7031
  {
6855
7032
  "id": "code_6",
6856
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",
6857
7046
  "language": "ts",
6858
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>"
6859
7066
  }
6860
7067
  ]
6861
7068
  }
@@ -7112,14 +7319,47 @@ const frameworkDocsContent = {
7112
7319
  {
7113
7320
  "id": "sec_3",
7114
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"
7115
7330
  }
7116
7331
  ],
7117
7332
  "sections": [
7118
7333
  {
7119
7334
  "id": "overview",
7120
7335
  "title": "Overview",
7121
- "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>",
7122
- "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
+ ]
7123
7363
  }
7124
7364
  ],
7125
7365
  "images": [
@@ -7852,28 +8092,54 @@ const frameworkDocsContent = {
7852
8092
  },
7853
8093
  {
7854
8094
  "id": "sec_5",
7855
- "title": "Assisted Workflow: Clone Lookup + Default Filter + Lookup Hierarchy"
8095
+ "title": "Slim combo — extra fields via `mc_props_bag.slimCombo`"
7856
8096
  },
7857
8097
  {
7858
8098
  "id": "sec_6",
7859
- "title": "Client/Server Effects"
8099
+ "title": "Assisted Workflow: Clone Lookup + Default Filter + Lookup Hierarchy"
7860
8100
  },
7861
8101
  {
7862
8102
  "id": "sec_7",
8103
+ "title": "Client/Server Effects"
8104
+ },
8105
+ {
8106
+ "id": "sec_8",
7863
8107
  "title": "Operational Notes"
8108
+ },
8109
+ {
8110
+ "id": "sec_9",
8111
+ "title": "Auto-fetch on programmatic FK changes"
7864
8112
  }
7865
8113
  ],
7866
8114
  "sections": [
7867
8115
  {
7868
8116
  "id": "overview",
7869
8117
  "title": "Overview",
7870
- "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>",
7871
8119
  "codeSamples": [
7872
8120
  {
7873
8121
  "id": "code_1",
7874
8122
  "title": "Snippet 1",
7875
8123
  "language": "json",
7876
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.\""
7877
8143
  }
7878
8144
  ]
7879
8145
  }
@@ -8136,10 +8402,14 @@ const frameworkDocsContent = {
8136
8402
  },
8137
8403
  {
8138
8404
  "id": "sec_4",
8139
- "title": "Recommended Operational Flow"
8405
+ "title": "Element context menu"
8140
8406
  },
8141
8407
  {
8142
8408
  "id": "sec_5",
8409
+ "title": "Recommended Operational Flow"
8410
+ },
8411
+ {
8412
+ "id": "sec_6",
8143
8413
  "title": "Operational Notes"
8144
8414
  }
8145
8415
  ],
@@ -8147,7 +8417,7 @@ const frameworkDocsContent = {
8147
8417
  {
8148
8418
  "id": "overview",
8149
8419
  "title": "Overview",
8150
- "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>",
8151
8421
  "codeSamples": []
8152
8422
  }
8153
8423
  ],
@@ -8211,14 +8481,18 @@ const frameworkDocsContent = {
8211
8481
  },
8212
8482
  {
8213
8483
  "id": "sec_3",
8214
- "title": "Recommended Operational Flow"
8484
+ "title": "View Builder Tab — Aggregations and GROUP BY (Tier 1, 2026-05-08)"
8215
8485
  },
8216
8486
  {
8217
8487
  "id": "sec_4",
8218
- "title": "Action Buttons"
8488
+ "title": "Recommended Operational Flow"
8219
8489
  },
8220
8490
  {
8221
8491
  "id": "sec_5",
8492
+ "title": "Action Buttons"
8493
+ },
8494
+ {
8495
+ "id": "sec_6",
8222
8496
  "title": "Usage Notes"
8223
8497
  }
8224
8498
  ],
@@ -8226,7 +8500,7 @@ const frameworkDocsContent = {
8226
8500
  {
8227
8501
  "id": "overview",
8228
8502
  "title": "Overview",
8229
- "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>",
8230
8504
  "codeSamples": []
8231
8505
  }
8232
8506
  ],
@@ -9302,13 +9576,21 @@ const frameworkDocsContent = {
9302
9576
  {
9303
9577
  "id": "sec_3",
9304
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"
9305
9587
  }
9306
9588
  ],
9307
9589
  "sections": [
9308
9590
  {
9309
9591
  "id": "overview",
9310
9592
  "title": "Overview",
9311
- "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>",
9312
9594
  "codeSamples": [
9313
9595
  {
9314
9596
  "id": "code_1",
@@ -9333,6 +9615,24 @@ const frameworkDocsContent = {
9333
9615
  "title": "Snippet 4",
9334
9616
  "language": "ts",
9335
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;"
9336
9636
  }
9337
9637
  ]
9338
9638
  }
@@ -11782,26 +12082,34 @@ const frameworkDocsContent = {
11782
12082
  },
11783
12083
  {
11784
12084
  "id": "sec_6",
11785
- "title": "md_props_bag: archetypes.list"
12085
+ "title": "md_props_bag: toolbar"
11786
12086
  },
11787
12087
  {
11788
12088
  "id": "sec_7",
11789
- "title": "Pagination : md_pagesize et md_page_size_choice"
12089
+ "title": "md_props_bag: archetypes.list"
11790
12090
  },
11791
12091
  {
11792
12092
  "id": "sec_8",
11793
- "title": "Activation forcee de la virtualisation sur une taille de page elevee"
12093
+ "title": "Pagination : md_pagesize et md_page_size_choice"
11794
12094
  },
11795
12095
  {
11796
12096
  "id": "sec_9",
12097
+ "title": "Activation forcee de la virtualisation sur une taille de page elevee"
12098
+ },
12099
+ {
12100
+ "id": "sec_10",
11797
12101
  "title": "Evenements et subscriptions (host)"
12102
+ },
12103
+ {
12104
+ "id": "sec_11",
12105
+ "title": "`rowCustomSelect` — signature reelle (piege)"
11798
12106
  }
11799
12107
  ],
11800
12108
  "sections": [
11801
12109
  {
11802
12110
  "id": "overview",
11803
12111
  "title": "Overview",
11804
- "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>",
11805
12113
  "codeSamples": [
11806
12114
  {
11807
12115
  "id": "code_1",
@@ -11825,19 +12133,49 @@ const frameworkDocsContent = {
11825
12133
  "id": "code_4",
11826
12134
  "title": "Snippet 4",
11827
12135
  "language": "json",
11828
- "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}"
11829
12137
  },
11830
12138
  {
11831
12139
  "id": "code_5",
11832
12140
  "title": "Snippet 5",
11833
- "language": "html",
11834
- "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;"
11835
12143
  },
11836
12144
  {
11837
12145
  "id": "code_6",
11838
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",
11839
12159
  "language": "ts",
11840
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>"
11841
12179
  }
11842
12180
  ]
11843
12181
  }
@@ -12094,14 +12432,47 @@ const frameworkDocsContent = {
12094
12432
  {
12095
12433
  "id": "sec_3",
12096
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"
12097
12443
  }
12098
12444
  ],
12099
12445
  "sections": [
12100
12446
  {
12101
12447
  "id": "overview",
12102
12448
  "title": "Overview",
12103
- "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>",
12104
- "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
+ ]
12105
12476
  }
12106
12477
  ],
12107
12478
  "images": [
@@ -12834,28 +13205,54 @@ const frameworkDocsContent = {
12834
13205
  },
12835
13206
  {
12836
13207
  "id": "sec_5",
12837
- "title": "Flux assiste : Clone Lookup + Filtre par defaut + Hierarchie Lookup"
13208
+ "title": "Combo slim — champs supplementaires via `mc_props_bag.slimCombo`"
12838
13209
  },
12839
13210
  {
12840
13211
  "id": "sec_6",
12841
- "title": "Effets Client/Serveur"
13212
+ "title": "Flux assiste : Clone Lookup + Filtre par defaut + Hierarchie Lookup"
12842
13213
  },
12843
13214
  {
12844
13215
  "id": "sec_7",
13216
+ "title": "Effets Client/Serveur"
13217
+ },
13218
+ {
13219
+ "id": "sec_8",
12845
13220
  "title": "Notes operationnelles"
13221
+ },
13222
+ {
13223
+ "id": "sec_9",
13224
+ "title": "Auto-fetch sur changement programmatique de FK"
12846
13225
  }
12847
13226
  ],
12848
13227
  "sections": [
12849
13228
  {
12850
13229
  "id": "overview",
12851
13230
  "title": "Overview",
12852
- "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>",
12853
13232
  "codeSamples": [
12854
13233
  {
12855
13234
  "id": "code_1",
12856
13235
  "title": "Snippet 1",
12857
13236
  "language": "json",
12858
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.\""
12859
13256
  }
12860
13257
  ]
12861
13258
  }
@@ -13118,10 +13515,14 @@ const frameworkDocsContent = {
13118
13515
  },
13119
13516
  {
13120
13517
  "id": "sec_4",
13121
- "title": "Flux operationnel conseille"
13518
+ "title": "Menu contextuel des elements"
13122
13519
  },
13123
13520
  {
13124
13521
  "id": "sec_5",
13522
+ "title": "Flux operationnel conseille"
13523
+ },
13524
+ {
13525
+ "id": "sec_6",
13125
13526
  "title": "Notes operationnelles"
13126
13527
  }
13127
13528
  ],
@@ -13129,7 +13530,7 @@ const frameworkDocsContent = {
13129
13530
  {
13130
13531
  "id": "overview",
13131
13532
  "title": "Overview",
13132
- "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>",
13133
13534
  "codeSamples": []
13134
13535
  }
13135
13536
  ],
@@ -13193,14 +13594,18 @@ const frameworkDocsContent = {
13193
13594
  },
13194
13595
  {
13195
13596
  "id": "sec_3",
13196
- "title": "Flux operationnel conseille"
13597
+ "title": "Onglet View Builder — Agregations et GROUP BY (Tier 1, 2026-05-08)"
13197
13598
  },
13198
13599
  {
13199
13600
  "id": "sec_4",
13200
- "title": "Boutons d'action"
13601
+ "title": "Flux operationnel conseille"
13201
13602
  },
13202
13603
  {
13203
13604
  "id": "sec_5",
13605
+ "title": "Boutons d'action"
13606
+ },
13607
+ {
13608
+ "id": "sec_6",
13204
13609
  "title": "Notes d'utilisation"
13205
13610
  }
13206
13611
  ],
@@ -13208,7 +13613,7 @@ const frameworkDocsContent = {
13208
13613
  {
13209
13614
  "id": "overview",
13210
13615
  "title": "Overview",
13211
- "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>",
13212
13617
  "codeSamples": []
13213
13618
  }
13214
13619
  ],
@@ -14284,13 +14689,21 @@ const frameworkDocsContent = {
14284
14689
  {
14285
14690
  "id": "sec_3",
14286
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"
14287
14700
  }
14288
14701
  ],
14289
14702
  "sections": [
14290
14703
  {
14291
14704
  "id": "overview",
14292
14705
  "title": "Overview",
14293
- "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>",
14294
14707
  "codeSamples": [
14295
14708
  {
14296
14709
  "id": "code_1",
@@ -14305,16 +14718,34 @@ const frameworkDocsContent = {
14305
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>"
14306
14719
  },
14307
14720
  {
14308
- "id": "code_3",
14309
- "title": "Snippet 3",
14310
- "language": "ts",
14311
- "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>"
14312
14743
  },
14313
14744
  {
14314
- "id": "code_4",
14315
- "title": "Snippet 4",
14745
+ "id": "code_7",
14746
+ "title": "Snippet 7",
14316
14747
  "language": "ts",
14317
- "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;"
14318
14749
  }
14319
14750
  ]
14320
14751
  }
@@ -16775,26 +17206,34 @@ const frameworkDocsContent = {
16775
17206
  },
16776
17207
  {
16777
17208
  "id": "sec_6",
16778
- "title": "md_props_bag: archetypes.list"
17209
+ "title": "md_props_bag: toolbar"
16779
17210
  },
16780
17211
  {
16781
17212
  "id": "sec_7",
16782
- "title": "Paging: md_pagesize y md_page_size_choice"
17213
+ "title": "md_props_bag: archetypes.list"
16783
17214
  },
16784
17215
  {
16785
17216
  "id": "sec_8",
16786
- "title": "Forzado de virtualización en page size alto"
17217
+ "title": "Paging: md_pagesize y md_page_size_choice"
16787
17218
  },
16788
17219
  {
16789
17220
  "id": "sec_9",
17221
+ "title": "Forzado de virtualización en page size alto"
17222
+ },
17223
+ {
17224
+ "id": "sec_10",
16790
17225
  "title": "Eventos y subscriptions (host)"
17226
+ },
17227
+ {
17228
+ "id": "sec_11",
17229
+ "title": "`rowCustomSelect` — firma real (gotcha)"
16791
17230
  }
16792
17231
  ],
16793
17232
  "sections": [
16794
17233
  {
16795
17234
  "id": "overview",
16796
17235
  "title": "Overview",
16797
- "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>",
16798
17237
  "codeSamples": [
16799
17238
  {
16800
17239
  "id": "code_1",
@@ -16818,19 +17257,49 @@ const frameworkDocsContent = {
16818
17257
  "id": "code_4",
16819
17258
  "title": "Snippet 4",
16820
17259
  "language": "json",
16821
- "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}"
16822
17261
  },
16823
17262
  {
16824
17263
  "id": "code_5",
16825
17264
  "title": "Snippet 5",
16826
- "language": "html",
16827
- "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;"
16828
17267
  },
16829
17268
  {
16830
17269
  "id": "code_6",
16831
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",
16832
17283
  "language": "ts",
16833
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>"
16834
17303
  }
16835
17304
  ]
16836
17305
  }
@@ -17087,14 +17556,47 @@ const frameworkDocsContent = {
17087
17556
  {
17088
17557
  "id": "sec_3",
17089
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"
17090
17567
  }
17091
17568
  ],
17092
17569
  "sections": [
17093
17570
  {
17094
17571
  "id": "overview",
17095
17572
  "title": "Overview",
17096
- "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>",
17097
- "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
+ ]
17098
17600
  }
17099
17601
  ],
17100
17602
  "images": [
@@ -17827,28 +18329,54 @@ const frameworkDocsContent = {
17827
18329
  },
17828
18330
  {
17829
18331
  "id": "sec_5",
17830
- "title": "Flujo Asistido: Clonar Lookup + Filtro Default + Lookup Hierarchy"
18332
+ "title": "Combo slim — campos extra vía `mc_props_bag.slimCombo`"
17831
18333
  },
17832
18334
  {
17833
18335
  "id": "sec_6",
17834
- "title": "Efectos Cliente/Servidor"
18336
+ "title": "Flujo Asistido: Clonar Lookup + Filtro Default + Lookup Hierarchy"
17835
18337
  },
17836
18338
  {
17837
18339
  "id": "sec_7",
18340
+ "title": "Efectos Cliente/Servidor"
18341
+ },
18342
+ {
18343
+ "id": "sec_8",
17838
18344
  "title": "Notas operativas"
18345
+ },
18346
+ {
18347
+ "id": "sec_9",
18348
+ "title": "Auto-fetch sobre cambio programatico de FK"
17839
18349
  }
17840
18350
  ],
17841
18351
  "sections": [
17842
18352
  {
17843
18353
  "id": "overview",
17844
18354
  "title": "Overview",
17845
- "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>",
17846
18356
  "codeSamples": [
17847
18357
  {
17848
18358
  "id": "code_1",
17849
18359
  "title": "Snippet 1",
17850
18360
  "language": "json",
17851
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.\""
17852
18380
  }
17853
18381
  ]
17854
18382
  }
@@ -18111,10 +18639,14 @@ const frameworkDocsContent = {
18111
18639
  },
18112
18640
  {
18113
18641
  "id": "sec_4",
18114
- "title": "Flujo operativo recomendado"
18642
+ "title": "Menú contextual de los elementos"
18115
18643
  },
18116
18644
  {
18117
18645
  "id": "sec_5",
18646
+ "title": "Flujo operativo recomendado"
18647
+ },
18648
+ {
18649
+ "id": "sec_6",
18118
18650
  "title": "Notas operativas"
18119
18651
  }
18120
18652
  ],
@@ -18122,7 +18654,7 @@ const frameworkDocsContent = {
18122
18654
  {
18123
18655
  "id": "overview",
18124
18656
  "title": "Overview",
18125
- "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>",
18126
18658
  "codeSamples": []
18127
18659
  }
18128
18660
  ],
@@ -18186,14 +18718,18 @@ const frameworkDocsContent = {
18186
18718
  },
18187
18719
  {
18188
18720
  "id": "sec_3",
18189
- "title": "Flujo operativo recomendado"
18721
+ "title": "Pestaña View Builder — Agregaciones y GROUP BY (Tier 1, 2026-05-08)"
18190
18722
  },
18191
18723
  {
18192
18724
  "id": "sec_4",
18193
- "title": "Botones de acción"
18725
+ "title": "Flujo operativo recomendado"
18194
18726
  },
18195
18727
  {
18196
18728
  "id": "sec_5",
18729
+ "title": "Botones de acción"
18730
+ },
18731
+ {
18732
+ "id": "sec_6",
18197
18733
  "title": "Notas de uso"
18198
18734
  }
18199
18735
  ],
@@ -18201,7 +18737,7 @@ const frameworkDocsContent = {
18201
18737
  {
18202
18738
  "id": "overview",
18203
18739
  "title": "Overview",
18204
- "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>",
18205
18741
  "codeSamples": []
18206
18742
  }
18207
18743
  ],
@@ -19277,13 +19813,21 @@ const frameworkDocsContent = {
19277
19813
  {
19278
19814
  "id": "sec_3",
19279
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"
19280
19824
  }
19281
19825
  ],
19282
19826
  "sections": [
19283
19827
  {
19284
19828
  "id": "overview",
19285
19829
  "title": "Overview",
19286
- "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>",
19287
19831
  "codeSamples": [
19288
19832
  {
19289
19833
  "id": "code_1",
@@ -19308,6 +19852,24 @@ const frameworkDocsContent = {
19308
19852
  "title": "Snippet 4",
19309
19853
  "language": "ts",
19310
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;"
19311
19873
  }
19312
19874
  ]
19313
19875
  }
@@ -21768,26 +22330,34 @@ const frameworkDocsContent = {
21768
22330
  },
21769
22331
  {
21770
22332
  "id": "sec_6",
21771
- "title": "md_props_bag: archetypes.list"
22333
+ "title": "md_props_bag: toolbar"
21772
22334
  },
21773
22335
  {
21774
22336
  "id": "sec_7",
21775
- "title": "Paging: md_pagesize und md_page_size_choice"
22337
+ "title": "md_props_bag: archetypes.list"
21776
22338
  },
21777
22339
  {
21778
22340
  "id": "sec_8",
21779
- "title": "Erzwungene Virtualisierung bei hoher Seitengroesse"
22341
+ "title": "Paging: md_pagesize und md_page_size_choice"
21780
22342
  },
21781
22343
  {
21782
22344
  "id": "sec_9",
22345
+ "title": "Erzwungene Virtualisierung bei hoher Seitengroesse"
22346
+ },
22347
+ {
22348
+ "id": "sec_10",
21783
22349
  "title": "Events und Subscriptions (Host)"
22350
+ },
22351
+ {
22352
+ "id": "sec_11",
22353
+ "title": "`rowCustomSelect` — tatsachliche Signatur (Falle)"
21784
22354
  }
21785
22355
  ],
21786
22356
  "sections": [
21787
22357
  {
21788
22358
  "id": "overview",
21789
22359
  "title": "Overview",
21790
- "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>",
21791
22361
  "codeSamples": [
21792
22362
  {
21793
22363
  "id": "code_1",
@@ -21811,19 +22381,49 @@ const frameworkDocsContent = {
21811
22381
  "id": "code_4",
21812
22382
  "title": "Snippet 4",
21813
22383
  "language": "json",
21814
- "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}"
21815
22385
  },
21816
22386
  {
21817
22387
  "id": "code_5",
21818
22388
  "title": "Snippet 5",
21819
- "language": "html",
21820
- "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;"
21821
22391
  },
21822
22392
  {
21823
22393
  "id": "code_6",
21824
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",
21825
22407
  "language": "ts",
21826
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>"
21827
22427
  }
21828
22428
  ]
21829
22429
  }
@@ -22080,14 +22680,47 @@ const frameworkDocsContent = {
22080
22680
  {
22081
22681
  "id": "sec_3",
22082
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"
22083
22691
  }
22084
22692
  ],
22085
22693
  "sections": [
22086
22694
  {
22087
22695
  "id": "overview",
22088
22696
  "title": "Overview",
22089
- "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>",
22090
- "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
+ ]
22091
22724
  }
22092
22725
  ],
22093
22726
  "images": [
@@ -22820,28 +23453,54 @@ const frameworkDocsContent = {
22820
23453
  },
22821
23454
  {
22822
23455
  "id": "sec_5",
22823
- "title": "Assistierter Workflow: Clone Lookup + Standard-Filter + Lookup-Hierarchie"
23456
+ "title": "Slim-Combo — Zusatzfelder via `mc_props_bag.slimCombo`"
22824
23457
  },
22825
23458
  {
22826
23459
  "id": "sec_6",
22827
- "title": "Client-/Server-Effekte"
23460
+ "title": "Assistierter Workflow: Clone Lookup + Standard-Filter + Lookup-Hierarchie"
22828
23461
  },
22829
23462
  {
22830
23463
  "id": "sec_7",
23464
+ "title": "Client-/Server-Effekte"
23465
+ },
23466
+ {
23467
+ "id": "sec_8",
22831
23468
  "title": "Betriebshinweise"
23469
+ },
23470
+ {
23471
+ "id": "sec_9",
23472
+ "title": "Auto-Fetch bei programmatischen FK-Aenderungen"
22832
23473
  }
22833
23474
  ],
22834
23475
  "sections": [
22835
23476
  {
22836
23477
  "id": "overview",
22837
23478
  "title": "Overview",
22838
- "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>",
22839
23480
  "codeSamples": [
22840
23481
  {
22841
23482
  "id": "code_1",
22842
23483
  "title": "Snippet 1",
22843
23484
  "language": "json",
22844
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.\""
22845
23504
  }
22846
23505
  ]
22847
23506
  }
@@ -23104,10 +23763,14 @@ const frameworkDocsContent = {
23104
23763
  },
23105
23764
  {
23106
23765
  "id": "sec_4",
23107
- "title": "Empfohlener operativer Ablauf"
23766
+ "title": "Kontextmenue der Elemente"
23108
23767
  },
23109
23768
  {
23110
23769
  "id": "sec_5",
23770
+ "title": "Empfohlener operativer Ablauf"
23771
+ },
23772
+ {
23773
+ "id": "sec_6",
23111
23774
  "title": "Betriebshinweise"
23112
23775
  }
23113
23776
  ],
@@ -23115,7 +23778,7 @@ const frameworkDocsContent = {
23115
23778
  {
23116
23779
  "id": "overview",
23117
23780
  "title": "Overview",
23118
- "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>",
23119
23782
  "codeSamples": []
23120
23783
  }
23121
23784
  ],
@@ -23179,14 +23842,18 @@ const frameworkDocsContent = {
23179
23842
  },
23180
23843
  {
23181
23844
  "id": "sec_3",
23182
- "title": "Empfohlener operativer Ablauf"
23845
+ "title": "View-Builder-Tab — Aggregationen und GROUP BY (Tier 1, 2026-05-08)"
23183
23846
  },
23184
23847
  {
23185
23848
  "id": "sec_4",
23186
- "title": "Aktionsbuttons"
23849
+ "title": "Empfohlener operativer Ablauf"
23187
23850
  },
23188
23851
  {
23189
23852
  "id": "sec_5",
23853
+ "title": "Aktionsbuttons"
23854
+ },
23855
+ {
23856
+ "id": "sec_6",
23190
23857
  "title": "Nutzungshinweise"
23191
23858
  }
23192
23859
  ],
@@ -23194,7 +23861,7 @@ const frameworkDocsContent = {
23194
23861
  {
23195
23862
  "id": "overview",
23196
23863
  "title": "Overview",
23197
- "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>",
23198
23865
  "codeSamples": []
23199
23866
  }
23200
23867
  ],
@@ -24270,13 +24937,21 @@ const frameworkDocsContent = {
24270
24937
  {
24271
24938
  "id": "sec_3",
24272
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"
24273
24948
  }
24274
24949
  ],
24275
24950
  "sections": [
24276
24951
  {
24277
24952
  "id": "overview",
24278
24953
  "title": "Overview",
24279
- "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>",
24280
24955
  "codeSamples": [
24281
24956
  {
24282
24957
  "id": "code_1",
@@ -24301,6 +24976,24 @@ const frameworkDocsContent = {
24301
24976
  "title": "Snippet 4",
24302
24977
  "language": "ts",
24303
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;"
24304
24997
  }
24305
24998
  ]
24306
24999
  }
@@ -26406,10 +27099,10 @@ class FrameworkDocsComponent {
26406
27099
  }
26407
27100
  return work;
26408
27101
  }
26409
- 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 });
26410
- 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" }] });
26411
27104
  }
26412
- 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: [{
26413
27106
  type: Component,
26414
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"] }]
26415
27108
  }], ctorParameters: () => [{ type: i1.Router }, { type: i1.ActivatedRoute }], propDecorators: { contentScroller: [{
@@ -26418,4 +27111,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
26418
27111
  }] } });
26419
27112
 
26420
27113
  export { FrameworkDocsComponent };
26421
- //# sourceMappingURL=wuic-framework-lib-framework-docs.component-eyzu9tdV.mjs.map
27114
+ //# sourceMappingURL=wuic-framework-lib-framework-docs.component-B6qNTs5H.mjs.map