laif-ds 1.0.2-app-data-grid.76 → 1.0.2-app-data-grid.77

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 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
@@ -1,8 +1,8 @@
1
1
  "use client";
2
- import { getDefaultExportFromCjs as r } from "./_commonjsHelpers.js";
3
- import { __require as e } from "../node_modules/style-to-js/cjs/index.js";
4
- var t = e();
5
- const a = /* @__PURE__ */ r(t);
2
+ import { getDefaultExportFromCjs as e } from "./_commonjsHelpers.js";
3
+ import { __require as t } from "../node_modules/eventemitter3/index2.js";
4
+ var r = t();
5
+ const m = /* @__PURE__ */ e(r);
6
6
  export {
7
- a as default
7
+ m as default
8
8
  };
@@ -1,8 +1,8 @@
1
1
  "use client";
2
- import { getDefaultExportFromCjs as e } from "./_commonjsHelpers.js";
3
- import { __require as r } from "../node_modules/extend/index.js";
4
- var t = r();
5
- const x = /* @__PURE__ */ e(t);
2
+ import { getDefaultExportFromCjs as r } from "./_commonjsHelpers.js";
3
+ import { __require as e } from "../node_modules/style-to-js/cjs/index.js";
4
+ var t = e();
5
+ const a = /* @__PURE__ */ r(t);
6
6
  export {
7
- x as default
7
+ a as default
8
8
  };
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { getDefaultExportFromCjs as e } from "./_commonjsHelpers.js";
3
- import { __require as t } from "../node_modules/eventemitter3/index2.js";
4
- var r = t();
5
- const m = /* @__PURE__ */ e(r);
3
+ import { __require as r } from "../node_modules/extend/index.js";
4
+ var t = r();
5
+ const x = /* @__PURE__ */ e(t);
6
6
  export {
7
- m as default
7
+ x as default
8
8
  };
@@ -1,8 +1,5 @@
1
1
  "use client";
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);
2
+ var e = { exports: {} };
6
3
  export {
7
- o as default
4
+ e as __module
8
5
  };
@@ -1,5 +1,8 @@
1
1
  "use client";
2
- var e = { exports: {} };
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
- e as __module
7
+ o as default
5
8
  };
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-09-22T08:46:36.530Z",
3
+ "generatedAt": "2026-09-22T14:04:02.646Z",
4
4
  "package": {
5
5
  "name": "laif-ds",
6
- "version": "1.0.2-app-data-grid.76"
6
+ "version": "1.0.2-app-data-grid.77"
7
7
  },
8
8
  "summary": {
9
9
  "scannedFiles": 487,
@@ -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 three use three **different** conditions: `columns` needs the key **and** a writable visibility map (see `onColumnVisibilityChange`); `filters` needs the key to carry `render` or `onOpen`; `actions` renders only its `render`, with no default node. ⚠️ **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. |
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. |
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": "1.1.0",
3
- "generatedAt": "2026-09-22T08:46:35.988Z",
3
+ "generatedAt": "2026-09-22T14:04:02.345Z",
4
4
  "package": {
5
5
  "name": "laif-ds",
6
- "version": "1.0.2-app-data-grid.76"
6
+ "version": "1.0.2-app-data-grid.77"
7
7
  },
8
8
  "stats": {
9
9
  "documentedComponentCount": 97,
@@ -6549,5 +6549,5 @@
6549
6549
  ]
6550
6550
  }
6551
6551
  ],
6552
- "checksum": "f8ac7cce8e9d40a3d02d1cfab5956c01d3301bd9a523afac254cabeadfbcf28c"
6552
+ "checksum": "99477e72a8d34669d4ca0ee03d04d90111d82c1c68fb763a9fc53b75854b1d8a"
6553
6553
  }