laif-ds 1.0.2-app-data-grid.76 → 1.0.2-app-data-grid.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CHANGELOG.md +43 -0
- package/dist/_virtual/index7.js +2 -5
- package/dist/_virtual/index8.js +5 -2
- package/dist/agent-docs/adoption-report.json +2 -2
- package/dist/agent-docs/components/AppDataGrid.md +7 -7
- package/dist/agent-docs/components-list.md +19 -0
- package/dist/agent-docs/manifest.json +3 -3
- package/dist/components/ui/app-data-grid/app-data-grid.js +332 -317
- package/dist/components/ui/app-data-grid/grid-filter-operators.js +31 -29
- package/dist/components/ui/app-data-grid/grid-i18n.js +2 -1
- package/dist/components/ui/app-data-grid/parts/grid-filter-editor.js +42 -42
- package/dist/components/ui/app-data-grid/parts/grid-toolbar-slots.js +100 -80
- package/dist/components/ui/gantt/components/Chart/Chart.js +1 -1
- package/dist/index.d.ts +117 -12
- package/dist/node_modules/eventemitter3/index2.js +1 -1
- package/package.json +1 -1
package/dist/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
### 🚀 Added
|
|
11
11
|
|
|
12
|
+
- **AppDataGrid**: Nuovo slot **`toolbar.resetFilters`** (`GridToolbarResetSlot` = `GridToolbarSlot` + `onReset?: () => void`) e nuova chiave i18n **`filters.restoreDefaults`** (inglese `"Restore defaults"`, italiano `"Ripristina i predefiniti"` in `stories/app-data-grid-i18n-it.ts`). La griglia rende un `<button data-grid-toolbar-reset-filters>` con l'icona `RotateCcw` e la veste condivisa `gridToolbarTriggerClass`, **subito dopo la ricerca e prima del grilletto «Filtri»** quando quest'ultimo c'è. ⚠️ **LA VISIBILITÀ È DEL CONSUMER, E NON È UNA COMODITÀ: È L'UNICA FORMA POSSIBILE.** La griglia non può calcolare «i filtri correnti differiscono dai default della pagina», perché i default non le arrivano mai — `initialState` è un SEME letto una volta (il `useMemo` con dipendenze `[]` in `engine/use-grid-engine.ts`, non tenuto in nessun ref e mai confrontato con niente) e `preset` porta la vista **ripristinata dell'utente**, non i default. Quindi la risposta è la **presenza di `onReset`**: senza `onReset` e senza `render` il nodo non si rende, esattamente come per `filters` e `actions`. ⚠️ **E LA GRIGLIA NON AGGIUNGE NESSUN GATE SUL CONTEGGIO DEI FILTRI**, che sarebbe la scorciatoia ovvia e sarebbe **sbagliata in un caso reale**: `gridActiveFilterCount(…) > 0` nasconderebbe il bottone quando l'utente ha **tolto** un filtro dichiarato dalla pagina — cioè proprio quando il ripristino ha lavoro da fare. ⚠️ **AMBITO DICHIARATO: FILTRI E RICERCA.** Ordinamento, taglia di pagina, larghezze, ordine e visibilità delle colonne devono **sopravvivere**, e siccome `query/preset` e `view/preset` **sostituiscono in blocco**, chi implementa `onReset` deve costruire un preset che porta i **default** per `filters`/`search` e i valori **correnti** per tutto il resto — un preset parziale non esiste. Il ritorno a `pagination.pageIndex = 0` è inevitabile e corretto: è ciò che fa qualunque cambio di filtro. ⚠️ **NON SOSTITUISCE «Ripristina vista predefinita»** nel menu azioni, che è la via d'uscita TOTALE da una vista salvata illeggibile: i due comandi coesistono e hanno ambiti diversi. ⚠️ **`render` SI RENDE COMUNQUE**: su quel ramo la condizione «differisce dai default» è per intero del consumer — la presenza di `onReset` governa il SOLO nodo che la griglia disegna. ⚠️ **IL FUOCO È GESTITO, E MISURATO.** Il bottone rimuove la condizione della propria esistenza: premuto, il consumer smette di passare `onReset` e il nodo si smonta. Tenerlo montato e `disabled` sarebbe il comando morto che questa corsia vieta, quindi al clic il fuoco si sposta sul **campo di ricerca** — l'altro comando che restringe i dati, il vicino sinistro, e l'unico bersaglio reso SEMPRE. **Non** è il grilletto «Filtri»: quello esiste solo se lo slot `filters` è dichiarato, e nella configurazione più diffusa (`filterMode: "row"`) non c'è affatto — era la ragione per cui la v1 aveva dichiarato il limite come inevitabile, e la ragione era sbagliata perché il bersaglio c'era. La play `IlRipristinoChiedeENonAgisce` **legge `document.activeElement` dopo il click** e pretende il campo di ricerca. Chi passa `render` gestisce il fuoco del proprio nodo da sé. ⚠️ **NON SI RIUSA `i18n.reset`**: quella stringa nomina il bottone di `GridFilterChips` che **svuota** i filtri applicati, questa nomina il gesto che li riporta a ciò che la pagina ha **dichiarato** — con default non vuoti i due esiti divergono, e in italiano «Azzera» e «Ripristina i predefiniti» non sono la stessa frase. ⚠️ **Il cambiamento è additivo e retrocompatibile**: due chiavi opzionali nuove, nessun consumer esistente compila diversamente e nessun nodo nuovo appare senza che qualcuno lo dichiari. L'unico `tsc` che diventa rosso è interno al repo (`gridI18nIt` è tipato `typeof defaultGridI18n` e il suo `filters` è un literal fresco), ed è il meccanismo voluto: la chiave non si spedisce non tradotta. ⚠️ **`GridToolbarResetSlot` NON è esportato dal barrel**, come i fratelli `GridToolbarSlot` e `GridToolbarDisclosureSlot`: `GridToolbarSlots` — che è esportato — basta per annotare, e nessun nome nuovo entra in `scripts/codemod/rename-map.json`. **Cinque story nuove** in `AppDataGrid/Guscio (C3)`, di cui due sulla POSIZIONE e **una sulla configurazione del consumer reale** (`filterMode="row"` **senza** slot `filters`, dove il grilletto «Filtri» non esiste affatto e l'ordine misurato è ricerca → ripristino → «Mostra la riga filtri»): senza quella, l'ordine sarebbe stato misurato solo rispetto a un nodo che il consumer non rende mai
|
|
13
|
+
|
|
12
14
|
- **AppDataGrid**: Nuova story `AppDataGrid/Completa → ScenaDipinta` — le **cinque personalizzazioni del chiamante accese nello stesso posto**, che il repo esponeva già una per una e non mostrava mai insieme: **header in HTML custom** (le due intestazioni di gruppo e quella foglia di «Stato», via `header: ReactNode` + `headerLabel`), **celle in HTML custom** (`cell` — avatar con le iniziali derivate dalla ragione sociale, e la barra di avanzamento), **celle colorate** (`cellClassName` a funzione, una tinta per ciascuno dei quattro stati), **righe colorate** (`rowClassName` a funzione, sul settore) e **colonne colorate** (`headerClassName` + `cellClassName` statica sulle due superfici di «Totale»). **Nessuna prop nuova**: sono cinque canali che il contratto ha già, e la story è una dimostrazione, non un allargamento di superficie. ⚠️ **UNA COLONNA PER DIMOSTRAZIONE, e non è pignoleria**: tinta di colonna e tinta per valore sulla stessa colonna sarebbero due `bg-*` nello stesso `cn()` e `tailwind-merge` ne terrebbe **una**, quindi la scena mostrerebbe quattro cose dichiarandone cinque e nessuna play potrebbe dire quale ha vinto — da qui la mappa, che è anche l'ordine delle **sei** colonne: `Commessa` (110) in chiaro con `rowHeader`, `Cliente` (170) cella custom, `Referente` (130) in chiaro, `Stato` (150) header custom **e** celle colorate, `Avanzamento` (150) cella custom, `Totale` (120) colonna colorata. ⚠️ **Header custom e tinta per valore CONVIVONO sulla stessa colonna** e la regola non lo vieta: quella riguarda due `bg-*` nello stesso `cn()`, non un `<th>` e dei `<td>` — sono due elementi, due prop, e due asserzioni separate. ⚠️ **`Referente` non è riempitivo**: è l'unica colonna senza tinta propria, cioè l'**unico** posto da cui si vede il fondo della **riga** — ed è per questo che la play misura lì il colore risolto. ⚠️ **SEI COLONNE PER 830px, E IL NUMERO È MISURATO**: con barra laterale e pannello addon aperti — come la story si apre — il viewport della griglia misura **850px** di `clientWidth`. La prima stesura aveva sette colonne per **1120px** dichiarati, onorati esatti dal `<colgroup>`, e proprio per questo `Totale` e `Consegna` cadevano **fuori**: la dimostrazione della colonna colorata si vedeva solo scorrendo, e una vetrina che chiede di scorrere per vedere una delle cinque cose che dichiara non le sta mostrando. Da qui la somma sotto la misura e `Consegna` — la seconda linea di base, la meno portante — via, verificato in Chrome: nessun contenitore scrollabile in orizzontale e il bordo destro di `Totale` dentro quello della griglia. ⚠️ **«In pausa» è `slate-200` e non `zinc-100`**, per una ragione che sta a schermo e non nei numeri: `zinc-100` è misurabilmente diverso da `amber-50` — l'asserzione sulle quattro stringhe distinte passava — ma accanto alla tinta di riga si legge come la stessa carta crema, cioè due tinte distinte che sembravano una. ⚠️ **NIENTE COLONNE PINNATE E NIENTE `checkable` in questa scena**, per due limiti già misurati e già scritti nel repo: lo sfondo opaco di una cella pinnata copre un `bg-*` che arrivi da `rowClassName` (lacuna dichiarata in `AppDataGrid.md` e nel commento di `parts/grid-row.tsx`), e la tinta della selezione è uno stile **inline** su ogni cella, che vince su qualunque utilità (`adg-r19`) — accendere l'una o l'altra renderebbe ambigua la dimostrazione proprio nel punto che deve dimostrare. ⚠️ **LA PRECEDENZA FRA LE TRE TINTE È MISURATA SUL COLORE RISOLTO**, non dedotta dalla cascata: su una riga tinta la play legge `getComputedStyle().backgroundColor` della cella di `Totale` e di quella di `Referente` e asserisce che sono **diversi**, e che la seconda è `rgba(0, 0, 0, 0)` — cioè che la cella in chiaro lascia passare il fondo della `<tr>` invece di ridipingerlo. La sonda è scritta **nel verso giusto**: se Tailwind non emettesse le classi della story i due colori sarebbero uguali e l'asserzione andrebbe **rossa**, non verde. ⚠️ **SEI FALSIFICAZIONI ESEGUITE**, ognuna riportata al verde subito dopo e ognuna rossa sul criterio suo: togliere `cellClassName` dalla colonna colorata (`expected +0 to be 12`); sostituire la tinta di colonna con una classe che Tailwind **non può emettere** — che lascia **verde** il criterio sulle classi e rossa la sonda del colore (`expected 'rgba(0, 0, 0, 0)' not to be 'rgba(0, 0, 0, 0)'`), cioè prova che quella sonda misura il CSS e non il markup; azzerare `rowClassName`; togliere il marcatore dell'header foglia; togliere quello dell'header di gruppo; e azzerare la tinta per valore (`expected 'truncate px-3 text-left' to contain 'bg-emerald-100 text-emerald-900'`). ⚠️ **La play non scrive numeri a mano**: il conteggio atteso delle celle custom è `dataRows(grid).length` letto dal DOM, e stato e settore di ogni riga si riagganciano ai dati per `data-grid-row` (da qui `getRowId={(riga) => riga.id}`, il codice e non l'indice), con `rigaDipintaDi` che **solleva** se una riga del DOM non si ritrova nei dati invece di misurare la riga sbagliata in silenzio
|
|
13
15
|
|
|
14
16
|
- **AppDataGrid**: New story `AppDataGrid/Completa → EtichettaDiGruppoNellaColonnaBloccata` — the signature scene with `groupLabelPlacement="first-column"`, the **only grouping prop of the contract the signature scene never passed at all**. It is not a duplicate of the three `AppDataGrid/Raggruppamento (P9)` stories that already cover the prop: those run on synthetic datasets **without `checkable`** (an exclusion declared there for an arithmetic constraint — with the checkbox column `aria-colcount` would be 15, not 14), without a `<tfoot>` and without the three-level header tree, so the composition the mockup actually shows — the label in the first **pinned** column, beside the checkbox cell also pinned at `left: 0` — was measured nowhere. It is the exact complement of `Raggruppata`: there `settore` carries the label and `commessa` is empty, here the inverse, and either play alone would stay green on a component that always puts the label in the same column. It also asserts the label MOVED rather than duplicated (label, toggle and count count 1 each in the group row and 1 each inside the `commessa` cell), that no column was hidden (`provaLAlberoDelleColonne` — fourteen leaves, three levels), and that the toggle still WORKS from there while the `<tfoot>` does not move. ⚠️ **Falsification executed**: dropping `etichettaDiGruppo="first-column"` from the render turns `npm run spec -- app-data-grid-completa` red on **this story only**, with «expected '' to contain 'Edilizia'». ⚠️ **And it retires a FALSE sentence** that stood in `Raggruppata`'s play — «the only way to move it would be to hide the grouping column: the rendered leaves would drop from fourteen to thirteen». That has not been true since `groupLabelPlacement` existed: the component rewrites only `group.columnId` and leaves `engine.groupByColumnIds` intact, so no column is hidden
|
|
@@ -27,6 +29,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
27
29
|
|
|
28
30
|
### 🐛 Fixed
|
|
29
31
|
|
|
32
|
+
- **AppDataGrid — ⚠️ IL BADGE FANTASMA: SVUOTARE UNA CELLA DELLA RIGA ZERO ORA TOGLIE IL FILTRO, NON SOLO IL
|
|
33
|
+
VALORE.** Con `filterMode="row"`, scrivere in una cella-filtro e poi **cancellare** lasciava il badge vivo in
|
|
34
|
+
`state.query.filters` con `value: ""`. La query ripartiva correttamente senza quel vincolo — `usableBadges`
|
|
35
|
+
(`grid-filter-compile.ts`) scarta i badge senza valore — ma **le tre superfici del filtro derivano tutte da
|
|
36
|
+
`state.query.filters`**, quindi la colonna restava segnata come filtrata: bordo `border-d-primary` e
|
|
37
|
+
`data-grid-filter-cell-applied="true"` sulla casella, imbuto sull'intestazione (`filteredColumnIds`), e
|
|
38
|
+
`gridActiveFilterCount` che continuava a contarlo. **E il fantasma era irrimovibile dalla cella**: la «×» si
|
|
39
|
+
rende solo `hasValue`, quindi spariva insieme al valore — l'utente vedeva una colonna marcata come filtrata,
|
|
40
|
+
non la vedeva filtrare, e non aveva nessun gesto in quella cella per spegnerla. Nei consumer che persistono
|
|
41
|
+
la vista il badge finiva **sul server** come `{"value":"","operator":"like"}`, cioè tornava a ogni ingresso in
|
|
42
|
+
pagina. Misurato sull'app viva il 2026-09-22. ⚠️ **I VUOTI SONO QUATTRO, NON UNO**, uno per ramo di editor:
|
|
43
|
+
`""` (testo), `undefined` (numero: un campo numerico svuotato emette `undefined`, non `Number("")`), `""`
|
|
44
|
+
(date/datetime, via `fromDate(undefined)`) e `[]` (lista multipla). Una correzione che guardasse il solo `""`
|
|
45
|
+
ne mancherebbe tre. ⚠️ **IL PREDICATO È UNO SOLO E VIVE ACCANTO A CIÒ CHE DECIDE LA «×»**: il nuovo
|
|
46
|
+
`isGridFilterValueEmpty` (`grid-filter-operators.ts`, interno alla corsia — **non esce dal barile**, per non
|
|
47
|
+
aggiungere una voce a `scripts/check-codemod-rename-map.mjs`) è esattamente `hasValue` negato, e
|
|
48
|
+
`parts/grid-filter-editor.tsx` ora lo chiama invece di ricalcolarlo: tenerli separati era garantirsi che un
|
|
49
|
+
giorno divergessero, ed è la divergenza a essere il difetto. ⚠️ **`false` E `0` NON SONO VUOTI**: «è falso» su
|
|
50
|
+
un booleano e «= 0» su un numerico sono filtri, e un `!value` li sbaglierebbe entrambi. ⚠️ **GLI OPERATORI
|
|
51
|
+
SENZA VALORE SONO SALVI**: `eq_null`, `n_eq_null`, `checked`, `unchecked` (`GRID_VALUELESS_OPERATORS`)
|
|
52
|
+
filtrano proprio senza valore, e la guardia li esclude esplicitamente. ⚠️ **SI INSTRADA SUL PERCORSO DI
|
|
53
|
+
RIMOZIONE CHE ESISTEVA GIÀ**, `clearFilterRowBadge` — coi suoi due rami per i due proprietari dello stato
|
|
54
|
+
(`onRemoveFilter` coi filtri sollevati, l'azione `query/filters` con la slice interna, che passa da `restart`
|
|
55
|
+
e rifà la lista da pagina 0) — invece di scrivere un secondo modo di togliere un badge; `changeFilterRowValue`
|
|
56
|
+
è stata spostata **dopo** quella dichiarazione, o la sua dipendenza cadrebbe nella zona morta del `const`.
|
|
57
|
+
⚠️ **IL PANNELLO NON CAMBIA, ED È DELIBERATO**: `parts/grid-filter-panel.tsx` scrive per un'altra strada
|
|
58
|
+
(`onUpdate(badge.id, …)`) perché «Aggiungi filtro» crea un badge col solo operatore, e quel mezzo-badge deve
|
|
59
|
+
restare vivo e visibile — per questo la correzione sta nel gestore della Riga Zero e **non** in `usableBadges`
|
|
60
|
+
o in `gridActiveFilterCount`, che la romperebbero. ⚠️ **LA LISTA MULTIPLA GUADAGNA UNA RIMOZIONE CHE NON
|
|
61
|
+
AVEVA**: il «pulisci» del primitivo emette `[]`, che ora è una rimozione — il costo dichiarato in
|
|
62
|
+
`grid-filter-editor.tsx` («il badge resta vivo e senza valore») è pagato, e il commento riscritto. ⚠️ **COSTO
|
|
63
|
+
RESIDUO, DICHIARATO**: chi sceglie un operatore non predefinito, scrive, e poi cancella con backspace perde il
|
|
64
|
+
badge **e con lui l'operatore scelto** — riscrivendo, il badge rinasce da `createGridFilterBadge` con
|
|
65
|
+
l'operatore di default. È lo stesso comportamento che la «×» ha sempre avuto, ma la «×» è un gesto deliberato
|
|
66
|
+
e il backspace no. Cinque story nuove lo misurano (`R29:` in `app-data-grid-riga-zero.stories.tsx` e
|
|
67
|
+
`app-data-grid-atomo-filtro.stories.tsx`): il gesto su testo e su numero, `0` e «è falso» che restano filtri,
|
|
68
|
+
«è vuoto» che sopravvive, e la lista multipla svuotata che rimuove.
|
|
69
|
+
|
|
30
70
|
- **AppDataGrid — ⚠️ SECONDA PASSATA DEL DEBOUNCE: ORA ASPETTA ANCHE IL FILTRAGGIO, E IN ENTRAMBE LE
|
|
31
71
|
MODALITÀ.** La voce qui sotto aveva coalescito la sola **emissione**: la richiesta al server partiva una
|
|
32
72
|
volta, ma la tabella continuava a rifiltrare **a ogni tasto** — e in modalità **client**, dove nessuna
|
|
@@ -256,6 +296,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
256
296
|
|
|
257
297
|
### 🔧 Changed
|
|
258
298
|
|
|
299
|
+
- **AppDataGrid**: ⚠️ **BREAKING — il default di `filterMode` passa da `"panel"` a `"row"`** (il destructuring di `AppDataGrid` in `src/components/ui/app-data-grid/app-data-grid.tsx`). Chi non passa `filterMode` ora ottiene la **Riga Zero**: una QUARTA `<tr>` di `<thead>` (`tr[data-grid-filter-row]`), montata **al primo paint** perché il toggle nasce aperto (`useState(true)`), più il bottone **«Mostra/Nascondi la riga filtri»** (`[data-grid-toolbar-filter-row-toggle]`) nel gruppo sinistro della toolbar. **Il `<thead>` cresce di 36px** e chi budgeta l'altezza deve passare `gridViewportHeight({ …, hasFilterRow: filterMode === "row" && open })` leggendo `open` da `onFilterRowOpenChange` — un budget calcolato con `hasFilterRow: false` ora promette 36px che la griglia occupa. ⚠️ **NON È UN CAMBIO DI TIPI: `tsc` resta verde e nessun call-site smette di compilare** — il cambiamento è di COMPORTAMENTO A SCHERMO, quindi si scopre nel DOM e non nel compilatore. ⚠️ **E IL CONTO VERO, MISURATO ESEGUENDO E NON DEDOTTO, È PIÙ ALTO DI QUELLO CHE IL REPO SI ASPETTAVA: 11 scene rosse, non 14 asserzioni in due file.** `yarn run spec` passa da `1 failed | 800 passed (801)` a `12 failed | 789 passed` e torna a `1 failed | 800 passed` — l'unico rosso, prima e dopo, è il preesistente `app-sidebar.stories.tsx > Expand On Hover`. ⚠️ **E QUELLE 11 SONO LE ROSSE *NON* PREVISTE**, perché i tre siti che l'indagine aveva enumerato — gli helper `thead tr` di `app-data-grid-header-levels.stories.tsx` (13 asserzioni) e `app-data-grid-integrazione.stories.tsx` (1), più `ultimaRigaDiThead` in `app-data-grid-grouping.stories.tsx`, il caso che non sarebbe diventato rosso — **sono stati curati nello stesso passo del flip e quindi non hanno mai suonato**: il conto qui sotto è ciò che è emerso ESEGUENDO, oltre a quello. Le rosse si dividono in tre famiglie e **nessuna delle tre era stata prevista**: **(a) un QUARTO helper della stessa classe, che l'inventario statico non aveva trovato** — `intestazioniFoglia` in `app-data-grid-guscio-registro.stories.tsx` (4 scene R16), che prendeva l'ULTIMA `thead tr` per averne le FOGLIE e, trovando la riga zero, **SOLLEVAVA** invece di confrontare due liste vuote: la sua guardia di non-vacuità ha fatto esattamente il mestiere per cui era stata scritta; **(b) chi budgeta la cornice con `hasFilterRow: false`** — `app-data-grid-cornice.stories.tsx` (dieci righe intere diventano nove), `app-data-grid-esiti.stories.tsx` (otto righe sopra il footer diventano sette), `app-data-grid-virtualization.stories.tsx` (i 36px entrano nello `scrollHeight` del viewport e sforano la tolleranza di due righe); **(c) — la famiglia che nessuna previsione conteneva — chi misura il FILO fra le righe di intestazione**, `app-data-grid-header-levels.stories.tsx` e `app-data-grid-density-fullscreen.stories.tsx`, perché `haUnaRigaSotto` (`parts/grid-header.tsx`) conta la Riga Zero come una riga sotto, per una decisione firmata il 2026-09-10, e quindi la riga delle foglie ACQUISTA il filo che quelle scene asserivano assente. Le asserzioni su `[data-grid-header-row]` e su `thead th` restano **immuni per costruzione**: la riga zero porta un marcatore proprio (`parts/grid-filter-row.tsx`) e le sue celle sono `<td>`. **Nessuna scena è stata pinnata in blocco**: 5 siti aggiornati (4 helper portati da `thead tr` a `[data-grid-header-row]` — la cura è una riga per file, perché quei file misurano i LIVELLI di intestazione e non la riga zero — più la scena del filo di `header-levels`, riscritta sulla verità della modalità nuova) e **6 scene pinnate a `filterMode="panel"` esplicito**, una per una e con la ragione scritta accanto al pin: `BordoHeaderConDueLivelli` (density-fullscreen), `AltezzaCorniceMonotona` (cornice), `FooterDeiTotali` (esiti), `CambioDensitaMantieneLeMisure` (virtualization), `ConfineDelBloccoPinnato` (banda-tabella), `FiltriSoloFuori` (completa). ⚠️ **DUE DI QUEI PIN NON SONO RUMORE TOLTO DI MEZZO, SONO COPERTURA CHE SI SAREBBE PERSA**: `BordoHeaderConDueLivelli` è ora l'UNICA scena che pinna il ramo FALSO di `haUnaRigaSotto` («l'ultima riga di intestazione non porta il filo, perché sotto c'è il corpo»), e il ramo VERO — che fino a oggi non aveva **nessuna** copertura eseguibile, perché entrambe le scene del filo montavano in `"panel"` — è pinnato dalla scena aggiornata di `header-levels`. ⚠️ **E DUE ERANO FILI TESI CHE HANNO SUONATO COME SCRITTO**: `ConfineDelBloccoPinnato` dichiarava già nel proprio commento che il conto «regge per quel motivo, non per fortuna», e `FiltriSoloFuori` enumera sette selettori di superfici di filtro «se un giorno una di esse diventasse resa per difetto, la riga che la nomina diventa rossa e dice QUALE» — è successo, e ha detto quale. ⚠️ **REVOCA UNA DECISIONE FIRMATA**, e la revoca sta qui perché chi aveva letto quella va avvisato: il TSDoc di `filterMode` dichiarava «IL DEFAULT E' `"panel"` ED E' UNA DECISIONE, non un ripiego di comodo», con la ragione — le scene esistenti che contano le righe di `<thead>` diventerebbero rosse senza che nessuno le abbia toccate. **La ragione era ed è vera, e la previsione era giusta nel meccanismo e sbagliata nei numeri**: diceva «TRE in nove punti» in header-levels e «altrettanto» in integrazione (sono 13 e 1), ometteva il caso che non diventa rosso (`grouping`) e ometteva del tutto le famiglie (b) e (c). È il **peso** della ragione a essere cambiato: il pannello era la modalità già consegnata quando la Riga Zero era nuova, e oggi la Riga Zero è la modalità che le applicazioni montano. Il prezzo si paga una volta, nelle scene, e non ogni volta in ogni call-site.
|
|
300
|
+
- **AppDataGrid**: `filterMode="panel"` è **work in progress**. Il pannello inventario **funziona, resta nell'unione `GridFilterMode` e resta passabile**, e `GridFilterPanel` resta esportato da entrambi i barili: **niente è stato rimosso e nessun call-site smette di compilare**. Quello che cambia è che non ci si arriva più per difetto e che la modalità non riceve lavoro finché la Riga Zero non è chiusa — la stessa distinzione, e la stessa forma, della voce `GridFilterPanel` in `src/components/documents/components-list.md`, dove la nuova voce sta **accanto** alla sua. ⚠️ **LA MARCATURA È DICHIARATIVA E NON ATTIVA, ed è scritta nei tre posti che un consumer legge davvero**: il TSDoc di `filterMode` (`src/components/ui/app-data-grid/app-data-grid.types.ts`), la riga di tabella di `filterMode` in `src/components/documents/components/AppDataGrid.md`, e la voce nuova in `components-list.md`. **Nessun `console.warn` su `filterMode="panel"`, e l'omissione è una scelta misurata**: le scene che montano il pannello per MISURARE il contratto (`app-data-grid-riga-zero.stories.tsx`) sono copertura corretta, e un warn le trasformerebbe in rumore a ogni run — a maggior ragione ora che sei scene lo passano esplicitamente per pinnare la geometria senza riga zero. La porta resta aperta: un warn è additivo. ⚠️ **È REVERSIBILE CON UNA RIGA**: rimettere `filterMode = "panel"` nel destructuring di `AppDataGrid` riporta tutto com'era, perché nessun tipo è cambiato e nessun export si è mosso, e le sei scene pinnate restano corrette in entrambi i versi. ⚠️ **IL TIPO NON È STATO TOCCATO**: `GridFilterMode = "row" | "panel"` è invariato, e `filterMode` resta appesa in coda a `GridShellFields` per non spostare le righe di `onQueryChange?: never` e `paginated?: never`, citate per numero da `scripts/check-grid-coexistence-matrix.mjs`.
|
|
301
|
+
|
|
259
302
|
- **AppDataGrid**: The **25 «intermediate» story files** — every one except the three showcase files (`AppDataGrid/Mockup`, `AppDataGrid/Completa`, `AppDataGrid/Confronto`) — moved from `title: "AppDataGrid/<name>"` to `title: "CI-CD/AppDataGrid/<name>"`, so the regression specs collect under one `CI-CD` sidebar node and the showcase keeps `AppDataGrid/` to itself. ⚠️ **Each one also pins `id:` in its own meta, and that is the whole point of the change being safe.** A CSF component id is DERIVED from the title (`toId`), so the retitle alone would have moved the URL of every story in those files — and those URLs are cited OUTSIDE the source: 12 files under `.laif/tasks/to-validate` (tasks awaiting human signature, naming the page to open for a criterion not yet signed) and 11 append-only run logs, which are not rewritable, so any id change would leave permanently dead links in the audit trail. `id?: ComponentId` is the documented clause for exactly this (`node_modules/storybook/dist/csf/index.d.ts:876`, «used for URLs. By default is inferred from sanitizing the title»); Storybook in use is 10.0.8. ⚠️ **MEASURED on Storybook's own `/index.json`, before and after — not deduced from the source**: `appdatagrid-*` stories 319 → **319**; ids that disappeared (broken URLs) **0**; unexpected new ids **0**; stories whose title changed at an **unchanged id** **305** (the remaining 14 are the three showcase files); total index 725 → **725**. Citations to intermediate specs that no longer resolve: **2 before, the same 2 after** — `atomo-di-filtro-c7--inventario` and `selezione-c6--barra-di-selezione` were ALREADY dangling before the rename, so the retitle broke nothing. `npm run spec` stays **102 files / 725 tests, 0 failed** (it filters by file path, not by title) and all **14** `scripts/check-*.mjs` gates exit 0 (they read paths and sources; none reads a title). ⚠️ **The id values were COPIED from the index, never retyped**: one of the twenty-five is `appdatagrid-densità-e-schermo-intero-p6`, accent included — retyping it would have normalised the accent and silently broken a URL that works today. ⚠️ **Accepted consequence: title and URL now diverge for good** — a future title rename will silently keep the pinned id, which is why every one of the 25 metas carries the reason inline and points at the «Il nodo CI-CD» section of `docs/analysis/datatable-v2/PIANO-COMPLETAMENTO.md`. ⚠️ **The seven `Manuale` stories move too, and it is stated rather than hidden**: they are the deliberately play-less bench for human task validation, and in CSF a story cannot override its meta's title, so keeping them under `AppDataGrid/` would have meant splitting files — not done. Their URLs are unchanged, so every citation by id still resolves; only their sidebar location moved
|
|
260
303
|
- **AppDataGrid**: ⚠️ **What the reorganisation deliberately did NOT do, after an audit that measured it.** All 25 intermediate files were audited one by one (25 recon passes, then three adversarial skeptics on every proposal to cut) and **none is deletable**: 23 came back «keep» outright and the 2 «reduce» proposals were refuted **3 votes out of 3**. The reason is structural and measurable — 24 of the 25 are the only place in the repo where at least one public prop, DOM selector or exported function is exercised (`infinite`, `filterControls`, `validateGridColumnTree`, `readGridViewPreset`, `th[scope="row"]`, the `data-grid-column-registry-*` family) — and, decisively, **deleting a story turns nothing red**: `npm run check` does not run the plays, and `spec` is chained to neither `check` nor `pr-checks.yml` for two reasons the repo itself already states in `package.json` (`_comment:spec`). The lane has also already paid for one pruning (`dtv2-f1-potatura-e-spacchettamento-story` deleted ten stories and with them the source three later tasks needed). Still open and NOT addressed here: the «show/hide specs» switch (`tags: ["spec"]` plus a `sidebar.filters` filter in `.storybook/manager.ts`), which must go in a separate PR because `scripts/check-datatable-freeze.mjs` scopes lane purity on `NEW_TABLE_PATHS`, which covers the story folder but not `.storybook/`
|
|
261
304
|
- **DataTable**: Deprecated **in the documentation only**, in favour of `AppDataGrid` — the entry is in `src/components/documents/components-list.md` under «Deprecated Components» and nothing is removed: `DataTable` is still exported and every existing call-site keeps compiling, `grep -rn '@deprecated' src/components/ui/tables/data-table/ | wc -l` returns 0 on purpose (a JSDoc tag would trip the consumer lint rules that ban deprecated APIs), the two components import nothing from each other and can be mounted on the same page, and `scripts/codemod/rename-map.json` records name by name which old export maps to which new one and which parts of the new surface have no old equivalent
|
package/dist/_virtual/index7.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
|
|
3
|
-
import { __require as e } from "../node_modules/classnames/index.js";
|
|
4
|
-
var s = e();
|
|
5
|
-
const o = /* @__PURE__ */ r(s);
|
|
2
|
+
var e = { exports: {} };
|
|
6
3
|
export {
|
|
7
|
-
|
|
4
|
+
e as __module
|
|
8
5
|
};
|
package/dist/_virtual/index8.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
|
|
2
|
+
import { getDefaultExportFromCjs as r } from "./_commonjsHelpers.js";
|
|
3
|
+
import { __require as e } from "../node_modules/classnames/index.js";
|
|
4
|
+
var s = e();
|
|
5
|
+
const o = /* @__PURE__ */ r(s);
|
|
3
6
|
export {
|
|
4
|
-
|
|
7
|
+
o as default
|
|
5
8
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"generatedAt": "2026-09-
|
|
3
|
+
"generatedAt": "2026-09-22T15:37:26.856Z",
|
|
4
4
|
"package": {
|
|
5
5
|
"name": "laif-ds",
|
|
6
|
-
"version": "1.0.2-app-data-grid.
|
|
6
|
+
"version": "1.0.2-app-data-grid.79"
|
|
7
7
|
},
|
|
8
8
|
"summary": {
|
|
9
9
|
"scannedFiles": 487,
|
|
@@ -8,7 +8,7 @@ A new data grid, built to be **independent of `DataTable`** — it imports nothi
|
|
|
8
8
|
> this component (see `components-list.md`), but the two coexist: nothing is removed, every existing
|
|
9
9
|
> call-site keeps compiling, and migration is call-site by call-site.
|
|
10
10
|
> What works today: real rows, real sorting (mouse + keyboard, with `aria-sort`), a real client-side search filter (on the columns that declare `searchable: true` — **opt-in since 2026-09-10**, as in the frozen table), the frozen public contracts, a measured emitter contract, paginated mode, opt-in **virtualization** (P3), **column widths, resizing and left-pinning with horizontal overflow** (P4), and **multi-level headers with the derived `id`/`headers` accessibility chain** (P5).
|
|
11
|
-
> What P6-P10 added, by phase: the designed **density and full-screen** experience, with the provisional `showDensityControl` switch gone (P6); the **lifted filter panel and chips**, whose state lives above the grid in `useGridFilters` and works with no grid in the tree, plus the **filter atom** — the control is derived from `filterType`, so a list is filtered from a list and a date from the picker — and the **288px overlay inventory** the panel became (P7); **infinite scroll and skeleton rows**, which coexist with the data (P8); client-side **row grouping** on one or more columns, with `aggregatedCell` filling the group rows (P9); and **saved views**, applied in one tick through `preset`/`onPresetApplied` (P10). **Riga Zero (C9)** then added the second home for the filters: `filterMode="row"` renders a FOURTH `<tr>` of `<thead>` with one control per column, reusing the Fase 7 atom and the one badge constructor, so the row and the inventory panel produce the same state; `"panel"`
|
|
11
|
+
> What P6-P10 added, by phase: the designed **density and full-screen** experience, with the provisional `showDensityControl` switch gone (P6); the **lifted filter panel and chips**, whose state lives above the grid in `useGridFilters` and works with no grid in the tree, plus the **filter atom** — the control is derived from `filterType`, so a list is filtered from a list and a date from the picker — and the **288px overlay inventory** the panel became (P7); **infinite scroll and skeleton rows**, which coexist with the data (P8); client-side **row grouping** on one or more columns, with `aggregatedCell` filling the group rows (P9); and **saved views**, applied in one tick through `preset`/`onPresetApplied` (P10). **Riga Zero (C9)** then added the second home for the filters: `filterMode="row"` renders a FOURTH `<tr>` of `<thead>` with one control per column, reusing the Fase 7 atom and the one badge constructor, so the row and the inventory panel produce the same state. **`"row"` is the default since 2026-09-22** — whoever passes no `filterMode` gets the zero row; `"panel"` is still in the union and still passable, but it is **work in progress** and no longer the mode this component steers you to.
|
|
12
12
|
> What the completion plan has added since, by phase: **left AND right pinning with the measured sticky band** and the single `align` derivation (Fase 2); the **multi-level header matrix** hardened against the group that covers zero leaves (Fase 3); **`gridResultScope`, the single source of every scope figure** — paginator, chips and filter trigger read the same object, and on the client branch the paginator is a read-out (Fase 4); and, from **Fase 5**, the three **outcomes without data rendered INSIDE the table** (a `<td>` covering every layout column, ghost column included), an **error distinguishable from an empty result** with its own surface, its own i18n strings and a «Retry» control (`error` + `onRetry`), the **`<tfoot>` of column totals** (`aggregate` per column, sticky at the useful bottom, aligned by the same single derivation), and the rule that **above a body with no data the frame is silent**: no totals, no paginator, no scope figure; the **page-scope selection bar** — count in page, a monetary aggregate as a render-prop, bulk actions that carry the number in their own name («Delete 10 rows»), a destructive variant and a way out — which writes its clause into the one permanent live region instead of opening a second one, together with the group tri-state (Fase 6); the **filter atom**, where both the control and the operator list are derived from `filterType` instead of a closed `switch`, and the panel it lives in became the 288px overlay inventory (Fase 7); **`GridFilterControl`**, the string-keyed registry that makes an application-declared filter first class through the obligatory `toFragment` + `describe` pair, so a filter with no column lands in the toolbar instead of being pruned (Fase 8, R15); and **Riga Zero** (Fase 9), described in the paragraph above. **Fase 10 adds no behaviour**: it is the delivery — the `AppDataGrid/Completa` scene, remounted on the mockup's own columns and rendered with the real engine (**fourteen leaf columns**, measured as `aria-colcount="14"` on the rendered table: twelve of data plus the checkbox column and the row menu; three header levels; five cell renderers declared through `cell` — status badge, initials avatar, progress bar, `it-IT` amounts, `gg/mm/aaaa` dates; group aggregates through `aggregatedCell` and the `<tfoot>` count and average through `aggregate`), next to the frozen `AppDataGrid/Mockup`; the list of deviations from that mockup, **thirty-four rows** each carrying `accettato`, `aperto` or `chiuso`; and the single measured registry of the fifteen requirements, all in `docs/analysis/datatable-v2/PIANO-COMPLETAMENTO.md`. ⚠️ **Fase 10 closes nothing on the component, and the remount closed nothing either**: **nine** of those thirty-four rows are still open — **seven of the component** (rows 13, 14, 19, 20, 22, 27, 34), one of governance (row 10, the chip row of §11.3-A, which no signed decision covers) and one of the story (row 28, the missing initial sort, open because nobody has written why). The seven: the group label falls in the grouping column rather than the pinned one (row 13) and reads «Edilizia 78 righe» rather than «Edilizia (78)» (row 14) — ⚠️ **row 14 is listed above, and in the COMPONENT it is closed**: `adg-m5` made the parentheses unconditional in `parts/grid-group-row.tsx`, and no prop governs them; it keeps its `aperto` only because the count mirrors the plan's table. **Row 13 stays genuinely open**: the prop exists (`groupLabelPlacement="first-column"`) but the signature scene does not pass it; the status line is `sr-only` (row 20); the selection bar's count phrase is a fixed template (row 22); and the toolbar order is **half closed** (row 27) — `adg-m3` gave it the mockup's two groups and «Esporta» before «Schermo intero», so what is left is ONE swap away: WHERE the filters slot goes, which is a decision and not a change (see «The toolbar order, and the one decision still open»); ⚠️ **Rows 19 and 34 are listed above, and in the COMPONENT they are closed — `adg-m2` closed BOTH in one edit, because they were one root**: a single undefined `scopeTotal` switched off the visible figure (19) and made `aria-rowcount` fall back to the rendered rows (34); the paginated scene now reads «Righe 1-10 di 128» with `aria-rowcount="128"` over ten rendered rows, measured live. They keep their `aperto` in the count above only because that count mirrors the plan's table, whose reclassification is pending — the two numbers are not a disagreement about the component. The remedy is NOT a new prop — `resultTotal` exists only on `GridClientProps` (`app-data-grid.types.ts:1088`), the branch declaring `paginated?: never` (line 982), so passing it beside `paginated` is `error TS2322`, measured with `tsc --noEmit`. The scope is **DERIVED** instead: a grid that paginates itself already holds the figure. See «The scope on a self-paginating grid». ⚠️ The filter trigger carrying no count is **not** among them: that is row 18, and it is `accettato`. ⚠️ Nor are **the trailing skeleton rows**, and the reason is the one that took them off the list (row 24, reclassified `accettato` with the measure in hand): they are not missing — **they are the R4/P8 invariant on the `infinite` branch**, where an appending page keeps `replacingRows` false, so `dataRowCount` stays full while `skeletonRowCount` is positive and the skeletons ACCOMPANY the data instead of replacing it. They replace it only outside that branch. The signature scene is paginated, so the trailing pair is unreachable **by construction** there, not absent — and it is proved by two stories, `app-data-grid-infinite.stories.tsx :: GliScheletriStannoDOPOIDati` and `:: NessunVuotoNeiTreStati`. ⚠️ Nor is **the viewport 64px shorter than its content with three header levels** — that is row 25, **closed by `adg-m1`**, and both halves of the old sentence are now false: the input exists (`headerLevels` on `GridViewportHeightInput`, with `headerHeight * headerLevels` in `gridViewportHeight`, and `gridFrameHeight` no longer dropping inherited fields in silence) and the measured delta is **0** — `scrollHeight` 502 against `clientHeight` 502, where it was 64, with zero rows covered by the `<tfoot>`. ⚠️ But the remedy is a PACT WITH THE CALLER, not a behaviour of the component: see «Two height formulas» for why the full signature is written out there. ⚠️ **`chiuso` is a THIRD outcome value, not an `accettato` in disguise**, and rows 15, 17 and 25 carry it: they left the OPEN rows without joining the accepted ones. The difference is the whole point — «accepted» means the divergence from the mockup is still there and we are keeping it; «closed» means it is GONE and is nobody's work any more. The thirty-four therefore split **22 `accettato` + 9 `aperto` + 3 `chiuso`**, and this blockquote enumerates only the nine. Which task closed what is written in the table's own `chiuso da <task>`, and is deliberately NOT repeated here. The count above is the plan's own tally at the moment it was measured, and `docs/analysis/datatable-v2/PIANO-COMPLETAMENTO.md` is the file that re-derives it: this paragraph does not, so that two documents cannot disagree about the same arithmetic.
|
|
13
13
|
>
|
|
14
14
|
> **Reach for `AppDataGrid` in new work.** Migrating a call site is mostly a rename — `scripts/codemod/rename-map.json` records name by name which old export maps to which new one; see "Coexistence with `DataTable`" below.
|
|
@@ -62,7 +62,7 @@ Three contracts are **copied from `DataTable`, never imported**, so that migrati
|
|
|
62
62
|
| `onRowSelectionChange` | `(selection) => void` | `undefined` | Fires on every selection change. |
|
|
63
63
|
| `onCheckedRowsChange` | `(rows: TData[]) => void` | `undefined` | The selected rows, already resolved to data. |
|
|
64
64
|
| `rowClassName` | `string \| ((row: GridRow<TData>) => string)` | `undefined` | Per-row classes. The callback form is resolved internally. |
|
|
65
|
-
| `toolbar` | `GridToolbarSlots` | `undefined` | **Fase 3, live.** The shell, opt-in **per slot**: `search`, `fullscreen`, `filters`, `columns`, `actions`. ⚠️ **`density` is NOT in that list from `adg-c12`** — the key was deleted, not moved to the opt-in family, and `toolbar={{ density: {} }}` is a `TS2353`. The "always rendered" family is therefore **two** keys, not three. Each key is a **configuration object** whose PRESENCE turns the slot on and whose optional `render` replaces the node the grid would draw. `search`/`density`/`fullscreen` are rendered **always** — declaring them only lets you substitute the node, and omitting them does not switch them off (the P6 rule is not retracted behind a prop). `filters`/`columns`/`actions` are **absent by default**, and the
|
|
65
|
+
| `toolbar` | `GridToolbarSlots` | `undefined` | **Fase 3, live.** The shell, opt-in **per slot**: `search`, `fullscreen`, `filters`, `resetFilters`, `columns`, `actions`. ⚠️ **`density` is NOT in that list from `adg-c12`** — the key was deleted, not moved to the opt-in family, and `toolbar={{ density: {} }}` is a `TS2353`. The "always rendered" family is therefore **two** keys, not three. Each key is a **configuration object** whose PRESENCE turns the slot on and whose optional `render` replaces the node the grid would draw. `search`/`density`/`fullscreen` are rendered **always** — declaring them only lets you substitute the node, and omitting them does not switch them off (the P6 rule is not retracted behind a prop). `filters`/`resetFilters`/`columns`/`actions` are **absent by default**, and the four use four **different** conditions: `columns` needs the key **and** a writable visibility map (see `onColumnVisibilityChange`); `filters` needs the key to carry `render` or `onOpen`; `resetFilters` needs the key to carry `render` or `onReset`, because **the grid cannot tell whether the command has any effect** — page defaults never reach it (`initialState` is a seed read once, `preset` carries the user's restored view), so «differs from the defaults» is the consumer's answer and the presence of `onReset` IS that answer; `actions` renders only its `render`, with no default node. ⚠️ **`resetFilters` restores FILTERS AND SEARCH ONLY** — sorting, page size, column widths/order/visibility must survive, and since `query/preset` and `view/preset` replace wholesale, whoever implements `onReset` has to build a preset carrying the **defaults** for `filters`/`search` and the **current** values for everything else; `pageIndex` returning to 0 is inevitable and correct. It renders **after the search and before the `filters` trigger**, and it does **not** replace «Restore default view» in the actions menu: that one is the total way out of an unreadable saved view. ⚠️ **Not to be confused with the top-level `actions` PROP**, which from `adg-m3` lands in the SAME `.ml-auto` group, immediately BEFORE this slot: that one is the compat rename of `DataTableProps.actions` and the grid dresses its buttons itself; this one is the consumer's own node and the grid draws nothing for it. A slot is deliberately not a `ReactNode`: `ReactNode` admits `boolean` and `string`, so `toolbar={{ search: false }}` would compile and switch search off. `search: false`, `search: "si"` and `search: { render: () => null }` are all compiler errors, proven by the **three** probes in `type-probes/shell-slots.probe.tsx` rather than by this sentence — the first two guard the **property type**, the third the **return type** (`search`/`density`/`fullscreen` use `GridToolbarAlwaysSlot`, whose `render` returns `ReactElement`, not `ReactElement | null`). ⚠️ **The type closes the form, not the intention, and that is a declared limit:** `() => null`does not compile, but`() => <></>` does and paints nothing, so a substitute node that renders nothing stays writable — excluding it would mean inspecting what a function returns, which a type does not do. |
|
|
66
66
|
| `selection` | `GridSelectionSlots` | `undefined` | **Live from Fase 6.** Configures the **page-selection bar** — the band that appears **above** the scroll area (right after the filter band, the mockup's order) as soon as at least one row is ticked, and that does not exist at all while none is. ⚠️ **It used to appear UNDER it; it moved on 2026-09-03**, and the move retires a named promise — see "Page selection" below. `mode` is the one-member union `"page"` because selection is page-only (no promotion to the whole result set), which makes that decision a compile error instead of a note. `actions` is `readonly GridSelectionAction[]` — the bulk actions rendered inside the bar (`label` **without** a number, optional `icon`, `destructive?: boolean` mapped to the shared `Button`'s `destructive` variant, and `onSelect(rowIds)` receiving the **page** ids). `summary` replaces the bar's **totals band** — not the bar, and not the count: the count is the bar's accessible name via `aria-labelledby`, so a slot that replaced it would leave the group unnamed. **`summary` still passes no arguments, and that is now a decision rather than an open question**: the two things it could pass — the selected rows and their aggregates — the consumer already has from `onCheckedRowsChange` (or the `rowSelection` it controls) and from its own `aggregate` functions, so passing them here would open a second channel for the same two numbers. |
|
|
67
67
|
| `pagination` | `GridPaginationSlots` | `undefined` | **LIVE from Fase 4.** `pagination.pageSize` turns the paginator ON **and** seeds the live page size. **The precedence is decided** and resolved in ONE place (at the `useGridEngine` call): `initialState.pagination.pageSize` > `pagination.pageSize` > the top-level `pageSize` prop. **On the CLIENT branch the paginator is a read-out, not a control**: `paginated` is `never` there and the types carry no page channel, so it prints the range and the rows clause from the declared `resultTotal` and renders NO page numbers, NO first/prev/next/last and NO size select — those commands could change nothing but the wording. **It does not coexist with `groupBy`**: the render path excludes it, because grouping × pagination is the pair declared unrepresentable by `paginated?: never` (line 982) on the branch that admits `groupBy`. |
|
|
68
68
|
| `initialFullscreen` | `boolean` | `false` | **Fase 3, live.** Seeds the fullscreen state at the **first** render, so a call site that mounts with it finds `role="dialog"` immediately rather than after an effect. It is an `initial*`, not a controlled `fullscreen`: it seeds and then stops governing, so no consumer has to resynchronise a state it does not own. |
|
|
@@ -75,7 +75,7 @@ Three contracts are **copied from `DataTable`, never imported**, so that migrati
|
|
|
75
75
|
| `onRetry` | `() => void` | `undefined` | **Fase 5.** What to do when the reader asks to retry after a failed load. Governs the «Retry» control **only** — whether the error outcome renders is `error`'s job: absent, the error outcome is a message without a button, which is the honest shape when the caller has nothing to relaunch. Lives on **BOTH branches** of the union — a request fails on the server branch too. One click invokes it **exactly once**; the grid keeps no attempt counter, because it holds no state. |
|
|
76
76
|
| `filterControls` | `GridFilterControlRegistry` | `undefined` | **Fase 8 (R15).** The registry of the filters the APPLICATION declares — `Readonly<Record<string, GridFilterControl>>`, keyed by `GridFilterControl["type"]`. The grid does **not** render a registry entry's `Control`: the control that writes a custom filter's value is rendered by the application, where the filter is born — `createGridCustomFilterBadge` is the only place `describe` and `toFragment` are ever called, and from there the badge carries its own text and fragment. The grid uses the registry for ONE measurable thing: recognising a `kind: "custom"` badge whose `customKey` is **not** a key of the registry, and marking its chip with `data-grid-toolbar-custom-filter-unknown` instead of showing it like the others — a badge that did not come from the registry is a defect, and it is made noisy rather than plausible. Absent, no chip is marked. Lives on **BOTH branches** of the union. |
|
|
77
77
|
| `onRemoveFilter` | `(id: string) => void` | `undefined` | **Fase 8 (R15).** Removes ONE filter without a column, by badge `id` — not by `customKey`, since two custom filters of the same type share the registry key and only the `id` tells them apart. **It is the door that turns the dashed toolbar group on:** without a handler the group is not rendered at all, because each custom filter's «×» is a real, named `<button>` and a button without a handler is a dead command. It is deliberately **not** on the `toolbar.filters` slot: filters without a column exist independently of opening the panel, so the group renders next to the «Filters» trigger and OUTSIDE its ternary — a consumer who does not declare the slot, or who replaces the trigger with their own `render()`, still gets the group. **Executable precondition: it also needs `filters` lifted — and since R28 that holds for ONE of its two readers, not both.** ⚠️ This row used to say «the grid has no way to remove a badge from its own internal slice — the `query/filters` action exists in the reducer and nothing dispatches it». That is FALSE since R28: `clearFilterRowBadge` (`app-data-grid.tsx`) dispatches exactly `query/filters` with the badge filtered out, and it is the door behind the Zero Row's clear «×». The sentence is corrected, not kept. The two readers now split: for the **dashed group of column-less filters** the precondition still holds, because that group is a pure passthrough with no branch that writes the internal slice, so `onRemoveFilter` without `filters` would give a «×» that announces itself, takes focus, fires the handler and changes nothing on screen — which is why it renders only when BOTH are present. For the **Zero Row's «×»** it does not: `clearFilterRowBadge` branches on where the data lives, so with filters NOT lifted it removes the badge from the internal slice itself, and the condition governing that «×» is «internal slice OR `onRemoveFilter` wired», which is strictly weaker. ⚠️ **And with `filters` NOT lifted, `onRemoveFilter` is never called even if you wired it** — the Zero Row's «×» writes the internal slice directly and does not route through your handler. So a caller that wires it expecting to observe every removal will miss those: the handler is the door for the LIFTED case only. (The two paths branch on different conditions — `clearFilterRowBadge` on where the data lives, `applyFilterRowPatch` on which callback is wired — which is a known open item, not a contract.) Whoever lifts the filters passes `filters.badges` to `filters` and `filters.removeBadge` here — the same `useGridFilters` instance. Lives on **BOTH branches** of the union. |
|
|
78
|
-
| `filterMode` | `"row" \| "panel"` | `"
|
|
78
|
+
| `filterMode` | `"row" \| "panel"` | `"row"` | **Riga Zero.** WHERE the filters live. `"row"` (**the default since 2026-09-22**) is the ZERO ROW — a FOURTH `<tr>` of `<thead>`, one control per column, inside the columns. `"panel"` is the inventory panel: an overlay anchored to the toolbar's «Filters» trigger. ⚠️ **`filterMode="panel"` IS WORK IN PROGRESS.** The panel WORKS, stays in the `GridFilterMode` union, stays passable, and `GridFilterPanel` stays exported from both barrels: **nothing was removed and no call-site stops compiling.** What changed is that you no longer land there by default, and that the mode receives no work until the Riga Zero is closed — the same distinction, and the same shape, as the `GridFilterPanel` entry in `components-list.md`. The marking is **declarative, not active**: there is no `console.warn` on `filterMode="panel"`, and the omission is a choice — the scenes that mount the panel to MEASURE its contract are correct coverage, and a warn would turn them into noise on every run. ⚠️⚠️ **THIS CELL ARGUED THE OPPOSITE, and should be read knowing it:** it said «**The default is `"panel"` and it is a decision, not convenience:** the zero row is the new mode and the panel the one already delivered and already measured, so a default on the other one would turn red every existing scene that counts `<thead>` rows without anyone having touched it. Whoever wants the row asks for it.» **REVOKED on 2026-09-22.** The prediction was **right about the mechanism and wrong about the numbers**: 13 assertions in `app-data-grid-header-levels.stories.tsx` and 1 in `app-data-grid-integrazione.stories.tsx`, and both were settled by changing ONE helper per file from `thead tr` to `[data-grid-header-row]` — those files measure header LEVELS, not the zero row — with no scene pinned to `filterMode="panel"` to keep them green. It also omitted the case that does NOT go red: `app-data-grid-grouping.stories.tsx`, where `ultimaRigaDiThead` took the LAST `thead tr` and would have started taking the zero row instead of the leaf row. The reason was and still is true; its **weight** is what changed — the panel was the already-delivered mode when the Riga Zero was new, and today the Riga Zero is the mode applications mount. The price is paid once, in the scenes, not every time in every call-site. ⚠️ **IT REVERSES WITH ONE LINE**: put `filterMode = "panel"` back in the `AppDataGrid` destructuring, because no type changed and no export moved. **It is not a switch.** The library renders no control that moves between the two modes: that choice belongs to whoever MOUNTS the component (a story, an application setting), because a switch inside the grid would mean the grid owns a state the caller already declares as a prop — two sources for one thing. ⚠️ **In `"row"` the row exists only while the toggle is OPEN — changed on 2026-09-04 (R13).** Until then the row was rendered ALWAYS and the «Hide filter row» button only took its CONTROLS out of sight, because unmounting the `<tr>` drops every body row by `gridFilterRowHeight` px while the frame stays put — the same pixel under the pointer changing command. That price was PAID on purpose: the residue (a 36px row with nothing in it, plus a chip band put there to stand in for it) cost more than the jump, and the button's name promises to remove the row. Reserve the space with `gridViewportHeight({ …, hasFilterRow: filterMode === "row" && open })`, reading `open` from `onFilterRowOpenChange`; deriving it from the MODE alone now promises 36px that a closed row does not occupy. Lives on **BOTH branches** of the union. |
|
|
79
79
|
| `onAddFilter` | `(badge: GridBadgeFilter) => void` | `undefined` | **Riga Zero.** A cell of the zero row creates a filter for a column that had none. The badge arrives **already built** by `createGridFilterBadge` — the one and only constructor, the same the inventory panel uses — so wire it straight to `useGridFilters().addBadge`, the very function you also hand to `GridFilterPanel.onAdd`: the two surfaces then share one store and «both roads produce the same state» is a fact of the wiring, not a coincidence between two implementations. **Required whenever `filters` is lifted**, and the grid enforces it by omission: with lifted filters the grid has no way to write them itself (the reducer would be overwritten by the prop on every render), so without this callback the cells of the zero row stay EMPTY — the row is still there, the height still reserved, but no control is offered rather than a named, focusable target that fires a handler and changes nothing. With filters NOT lifted it is unnecessary: the grid writes its own slice. Lives on **BOTH branches** of the union. |
|
|
80
80
|
| `onUpdateFilter` | `(id: string, patch: Partial<GridBadgeFilter>) => void` | `undefined` | **Riga Zero.** A cell of the zero row changes the operator or the value of a filter that already exists. Same pairing as `onAddFilter` — wire it to `useGridFilters().updateBadge`, the same function `GridFilterPanel.onUpdate` gets — and the same rule: required whenever `filters` is lifted, or the cells stay empty. Lives on **BOTH branches** of the union. |
|
|
81
81
|
| `statusRowVisible` | `boolean` | `false` | **M4.** Renders the status line ON SCREEN as well. `[data-grid-live-region]` has existed since P8 and carries `sr-only`: it says «128 rows. 0 filters active. 0 rows selected» and only a screen reader receives it. Turned on, the SAME string — no second sentence, no second text node — is rendered on screen inside a frame carrying a megaphone icon, so **a reviewer can read the text a screen reader would receive instead of approving the a11y on trust**. **Off by default, and that is not caution:** `sr-only` is `position: absolute`, so today's node is NOT a flex item of the root — it contributes neither height nor the frame gap — while turning it on by default would add an in-flow child to every existing scene. **It does NOT open a second live region**, and that invariant is what the prop must not touch: the frame carries no `role` and no `aria-live`, the ICON sits OUTSIDE the region, and the region keeps one text node and zero element children. Copying the mockup's announcement literally — a `role="status"` container holding the icon plus a `<span>` — would take the region count to TWO and have the sentence announced in pieces. **Declared limit:** `gridFrameHeight` has no addend for this line, so on a bounded frame the root gains an in-flow child the formula does not reserve; whoever budgets the height of the whole component adds it themselves. Lives on **BOTH branches** of the union. |
|
|
@@ -381,12 +381,12 @@ Stable hooks, independent of the i18n language:
|
|
|
381
381
|
| `[data-grid-group-cell="<id>"]` | A cell of a group row — one per visible leaf column, never one spanning many |
|
|
382
382
|
| `[data-grid-group-label]` | The grouping value. It lands in the **grouping column's** cell by default, or in the **first rendered column** with `groupLabelPlacement="first-column"` (M5) — the chevron and the count travel with it, always inside the same cell |
|
|
383
383
|
| `[data-grid-group-count]` | The number of data rows under the group, at any depth. ⚠️ **Read the ATTRIBUTE, not the text:** the attribute is the bare numeral (`78`), while the text reads `(78)` — the parentheses are literal in the node, wrapping the numeral only, as in the mockup. The word (`grouping.rowCount`: «rows» / «righe») follows inside an `sr-only` span, so the accessible name is the SUM of the visible number and the word. **No `aria-label` on this node**, deliberately: it would replace the visible number instead of extending it (WCAG 2.5.3 Label in Name) |
|
|
384
|
-
| `tr[data-grid-filter-row]` | **The ZERO ROW** — the FOURTH `<tr>` of `<thead>`, rendered
|
|
384
|
+
| `tr[data-grid-filter-row]` | **The ZERO ROW** — the FOURTH `<tr>` of `<thead>`, rendered in `filterMode="row"`, **which is the DEFAULT since 2026-09-22, so this is the ORDINARY case**: a grid that passes no `filterMode` has this node. In that mode it exists only while the toggle is OPEN. **Not rendered at all while the toggle is closed** — since R13 hiding the row unmounts it, so «the row is hidden» is measured as the ABSENCE of this node and there is no state attribute left to read. ⚠️ **Match it as `tr[data-grid-filter-row]`, never bare**: a badge row inside the inventory panel is a `<div>` carrying `data-grid-filter-row="<columnId>"`, and with both surfaces mounted a bare selector takes whichever comes first in the DOM |
|
|
385
385
|
| `[data-grid-filter-cell="<id>"]` | A cell of the zero row — one per leaf column plus `"__select"` when `checkable`, in the order of the columns, **never** with `colSpan`. Carries `data-grid-filter-cell-label` (the column's name) and `data-grid-filter-cell-applied="true"\|"false"`. Until `adg-r14` these were also the nodes the «N filters out of view» count was measured on; that indicator has been REMOVED from the toolbar and nothing reads their rects any more |
|
|
386
386
|
| `[data-grid-filter-cell-tick="<id>"]` | The zero row's **column mark**: a 1px-wide, `h-3` (12px) span pinned to the cell's LEFT edge and vertically CENTRED, `aria-hidden` and `pointer-events-none`. Present on every filter cell except the first and except a right-pinned block's edge cell. ⚠️ **It is a node and not a `box-shadow`:** an `inset` shadow covers the box's whole side, so the mark came out as tall as the cell and welded the zero row to the headers. Its height is asserted, not just its presence, because a full-height mark would satisfy a probe that only counts nodes |
|
|
387
387
|
| `[data-grid-filter-cell-trigger="<id>"]` | The one target of a filter cell: a real `<button>`, the cell's only tab stop, opening the Fase 7 filter atom in a popover. Carries `data-filter-truncated="true"\|"false"` — **always present**, so asserting its PRESENCE is green on a cell that truncates nothing — plus `title` and an `sr-only` node with the whole text **only when truncated**. **No glyph marks the cut at any width** — the ellipsis is the CSS one, drawn by `truncate` on the text span. Its `aria-label` names the operator IN FULL in both branches, even when the visible text takes the short form |
|
|
388
388
|
| `[data-grid-filter-cell-panel="<id>"]` | The popover of a filter cell. Radix **portals it to `document.body`**, so it is not inside the grid element. Inside: `[data-grid-filter-cell-operator="<id>"]` (the operator `<select>`, also carrying `data-grid-filter-cell-type`) and the atom's editor, named `filter-row-<id>` |
|
|
389
|
-
| `[data-grid-toolbar-filter-row-toggle]` | The «Show / Hide filter row» button, rendered in the toolbar with `filterMode="row"
|
|
389
|
+
| `[data-grid-toolbar-filter-row-toggle]` | The «Show / Hide filter row» button, rendered in the toolbar with `filterMode="row"` — **the DEFAULT since 2026-09-22, so it is in the toolbar of an ordinary grid**, not only of one that opted in. `aria-expanded` says whether the ZERO ROW is MOUNTED — since R13 closing it removes the `<tr>` entirely, and this button is the only way back, so it is rendered in both states |
|
|
390
390
|
| `[data-grid-toolbar]` | The toolbar row (Fase 3 **named** it; the row itself predates the phase and P6 renders it always, so it is present even at a minimal call site). `flex flex-wrap items-center gap-2` — `items-center` and no longer `items-end`, because from `adg-m3` the search cell is one line tall like the others. **From `adg-m3` it has TWO groups**: the direct children up to the filter-row indicator (what NARROWS the data), then one `.ml-auto` wrapper holding «columns», the `actions` prop's own buttons (`[data-grid-actions-row]`, from `adg-m3`), the `toolbar.actions` slot and the full-screen toggle, in that order (what changes the VIEW or takes the data out). ⚠️ **`ml-auto` only does work because the search lost `flex-1`**, and the measured four-way table is in the `play` of `AppDataGrid/Confronto`: with the field growing, `ml-auto` present or absent gives the identical layout. So a probe on the group's RIGHT edge alone is vacuous — the group ends flush right in three of the four combinations. Measure the GAP before the group instead: `gap-2` is 8px, and an auto margin that works makes it more |
|
|
391
391
|
| `[data-grid-toolbar-columns]` | The «columns» trigger — opt-in via `toolbar.columns`, and rendered ONLY when the visibility map can actually be written (see `onColumnVisibilityChange`) |
|
|
392
392
|
| `[data-grid-toolbar-columns-panel]` | The popover content holding `GridColumnRegistryPanel`, owned by the grid. Portaled **inside `[data-app-data-grid]`**, not to `<body>` — at full screen a body-level panel sits outside the sentinel cycle and outside `aria-modal`, so its checkboxes are neither tab-reachable nor announced |
|
|
@@ -492,7 +492,7 @@ What it renders, in DOM order:
|
|
|
492
492
|
| 3 | ~~`[data-grid-density-control]`~~ — **removed from the toolbar by `adg-c12`**; the grid renders no density control | — |
|
|
493
493
|
| 4 | the filters slot — `[data-grid-filter-panel-toggle]` with `render`, `[data-grid-toolbar-filters]` with `onOpen` | left |
|
|
494
494
|
| 5 | `[data-grid-toolbar-custom-filters]` — the dashed group, only with lifted filters AND `onRemoveFilter` | left |
|
|
495
|
-
| 6 | `[data-grid-toolbar-filter-row-toggle]` —
|
|
495
|
+
| 6 | `[data-grid-toolbar-filter-row-toggle]` — in `filterMode="row"`, i.e. **by default** since 2026-09-22 | left |
|
|
496
496
|
| 7 | `[data-grid-toolbar-columns]` | right (`.ml-auto`) |
|
|
497
497
|
| 8 | `[data-grid-actions-row]` — the `actions` prop's buttons: ONE inline, `[data-grid-actions-trigger]` from two up | right |
|
|
498
498
|
| 9 | the `toolbar.actions` slot — the consumer's own content, no default node | right |
|
|
@@ -1662,7 +1662,7 @@ infinite={{hasMore:true}}` over ten rows: the content (360px) is shorter than th
|
|
|
1662
1662
|
### Riga Zero — the filters inside the columns (C9)
|
|
1663
1663
|
|
|
1664
1664
|
`filterMode="row"` moves the filters from the overlay inventory into the table: a **fourth `<tr>` of
|
|
1665
|
-
`<thead>`**, one control under each column.
|
|
1665
|
+
`<thead>`**, one control under each column. **`"row"` is the DEFAULT since 2026-09-22** — it used to be `"panel"`, and the reversal is written up in the `filterMode` row of the props table. The switch between the two lives
|
|
1666
1666
|
in the **caller**, never in the grid.
|
|
1667
1667
|
|
|
1668
1668
|
⚠️ **Telling the zero row apart from the header row is done by a HORIZONTAL rule, and the vertical marks are
|
|
@@ -158,6 +158,25 @@ The following components are marked as deprecated and should not be used in new
|
|
|
158
158
|
|
|
159
159
|
- **GridFilterPanel** - ~~Use the Riga Zero filter row instead (`filterMode="row"`)~~
|
|
160
160
|
|
|
161
|
+
⚠️ **AND THE SAME THING THROUGH THE OTHER DOOR — `AppDataGrid` with `filterMode="panel"` is WORK
|
|
162
|
+
IN PROGRESS.** Since 2026-09-22 **`"row"` is the DEFAULT** (`src/components/ui/app-data-grid/app-data-grid.tsx`,
|
|
163
|
+
the `AppDataGrid` destructuring), so a grid that passes no `filterMode` gets the Riga Zero.
|
|
164
|
+
`"panel"` still works, stays in the `GridFilterMode` union and stays passable. What changed is that
|
|
165
|
+
you no longer land there by default, and that the mode **receives no work until the Riga Zero is
|
|
166
|
+
closed**. ⚠️ **THE MARKING IS DECLARATIVE, NOT ACTIVE, and the omission is a decision**: there is no
|
|
167
|
+
`console.warn` on `filterMode="panel"`, because the scenes that mount the panel in order to MEASURE
|
|
168
|
+
its contract (`app-data-grid-riga-zero.stories.tsx`) are correct coverage, and a warn would turn
|
|
169
|
+
them into noise on every run. The door stays open — a warn is additive — but it is not there today.
|
|
170
|
+
⚠️ **IT REVERSES WITH ONE LINE**: put `filterMode = "panel"` back in the destructuring and
|
|
171
|
+
everything is as it was — no type changed, no export moved, and the scenes pinned to
|
|
172
|
+
`filterMode="panel"` stay correct either way.
|
|
173
|
+
|
|
174
|
+
⚠️ **NOTE ON THIS CATALOG**: the paragraph above is deliberately NOT its own bullet. Every bullet
|
|
175
|
+
here is read as a COMPONENT NAME by `scripts/generate-agent-manifest.mjs`, and
|
|
176
|
+
`scripts/validate-agent-docs.mjs` then demands a `components/<Name>.md` for it — so a bullet named
|
|
177
|
+
after a PROP VALUE fails the release build with «Componenti catalogati senza documentazione».
|
|
178
|
+
Measured on 2026-09-22: it took the `Release (beta)` run down before the publish step.
|
|
179
|
+
|
|
161
180
|
⚠️ **Deprecated, not removed** — same distinction as `DataTable` above. `GridFilterPanel` is still
|
|
162
181
|
exported as a value and as a type from both barrels (`organisms/index.ts` and
|
|
163
182
|
`ui/app-data-grid/index.ts`), and every existing call-site keeps compiling. What changed is that
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.1.0",
|
|
3
|
-
"generatedAt": "2026-09-
|
|
3
|
+
"generatedAt": "2026-09-22T15:37:26.326Z",
|
|
4
4
|
"package": {
|
|
5
5
|
"name": "laif-ds",
|
|
6
|
-
"version": "1.0.2-app-data-grid.
|
|
6
|
+
"version": "1.0.2-app-data-grid.79"
|
|
7
7
|
},
|
|
8
8
|
"stats": {
|
|
9
9
|
"documentedComponentCount": 97,
|
|
@@ -6549,5 +6549,5 @@
|
|
|
6549
6549
|
]
|
|
6550
6550
|
}
|
|
6551
6551
|
],
|
|
6552
|
-
"checksum": "
|
|
6552
|
+
"checksum": "262fea22430a13fa9c94db3cf32f81a8c65c62268b67c5d9bb9030858b0076bc"
|
|
6553
6553
|
}
|