@massimo-cassandro/minimo 1.28.5 → 1.29.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massimo-cassandro/minimo",
3
- "version": "1.28.5",
3
+ "version": "1.29.1",
4
4
  "description": "Light css framework & utilities",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -44,7 +44,8 @@
44
44
  "test sideEffects tree-shaking": "node ./test/sideEffects-tree-shaking/run-test.mjs",
45
45
  "checkJS": "npx tsc --noEmit -p jsconfig.json",
46
46
  "checkJS ./src": "npx tsc --noEmit --allowJs --checkJs --target ES2023 --module ESNext --allowSyntheticDefaultImports --moduleResolution bundler declarations.d.ts types/global.d.ts src/**/*.js",
47
- "check outdated": "echo '*** ROOT ***' && npm outdated --omit=peer && echo '*** DEMO ***' && npm --prefix ./demo outdated && echo '*** END ***'"
47
+ "check outdated": "echo '*** ROOT ***' && npm outdated --omit=peer && echo '*** DEMO ***' && npm --prefix ./demo outdated && echo '*** END ***'",
48
+ "npm upd": "echo '*** ROOT ***' && npm update && echo '*** DEMO ***' && npm --prefix ./demo update && echo '*** END ***'"
48
49
  },
49
50
  "license": "MIT",
50
51
  "author": "Massimo Cassandro",
@@ -92,8 +93,8 @@
92
93
  }
93
94
  },
94
95
  "devDependencies": {
95
- "@massimo-cassandro/eslint-config": "^3.0.14",
96
- "@massimo-cassandro/stylelint-config": "^2.3.14",
96
+ "@massimo-cassandro/eslint-config": "^3.0.16",
97
+ "@massimo-cassandro/stylelint-config": "^2.3.15",
97
98
  "@types/node": "^26.2.0",
98
99
  "jsdom": "^29.1.1",
99
100
  "style-dictionary": "^5.5.1"
@@ -1,428 +0,0 @@
1
- # `s-datatable` web-component per simple-datatables
2
-
3
- <https://fiduswriter.github.io/simple-datatables/documentation/>
4
-
5
- ## Installazione
6
-
7
- `simple-datatables` è una **peer dependency opzionale** di minimo: va installata
8
- solo nei progetti che usano questo componente.
9
-
10
- ```bash
11
- npm i simple-datatables
12
- ```
13
-
14
- ## Utilizzo da markup
15
-
16
- ```html
17
- <s-datatable
18
- json="/api/data.json"
19
- cols='[{"_heading":"Nome","_field":"name"}, ...]'
20
- perPage="10"
21
- ></s-datatable>
22
-
23
- <!-- Alternativa: dati inline via attributo (utile per SSR/Twig/Blade).
24
- Se presenti entrambi, `data` ha la precedenza su `json`. -->
25
- <s-datatable
26
- data='[{"name":"Mario","city":"Roma"}, ...]'
27
- cols='[{"_heading":"Nome","_field":"name"}, ...]'
28
- perPage="10"
29
- ></s-datatable>
30
- ```
31
-
32
- ## Utilizzo da script
33
-
34
- ```javascript
35
- const el = document.querySelector('s-datatable');
36
- // oppure: const el = document.createElement('s-datatable');
37
-
38
- el.init({
39
- json: '/api/data.json',
40
- cols: [{ _heading: 'Nome', _field: 'name' }, ...],
41
- perPage: 10
42
- });
43
-
44
- // Alternativa: dati inline (array di oggetti) al posto di json
45
- el.init({
46
- data: [{ name: 'Mario', city: 'Roma' }, ...],
47
- cols: [{ _heading: 'Nome', _field: 'name' }, ...],
48
- });
49
- // Se presenti entrambi, `data` ha la precedenza su `json`.
50
-
51
- // Se appena creato, appendere dopo init():
52
- document.body.appendChild(el);
53
- ```
54
-
55
- ## Utilizzo misto – attributi HTML + script
56
-
57
- I parametri non inclusi in `init()` vengono letti dall'attributo HTML corrispondente.
58
- Si possono quindi separare configurazione statica (`cols`, `perPage`) nel markup
59
- e dati dinamici (`json`) nello script:
60
-
61
- ```html
62
- <s-datatable
63
- id="table-utenti"
64
- cols='[{"_heading":"Nome","_field":"name"},{"_heading":"Città","_field":"city"}]'
65
- perPage="20"
66
- ></s-datatable>
67
- ```
68
-
69
- ```javascript
70
- el.init({ json: '/api/utenti.json' }); // → json da script, cols e perPage da attributo
71
- ```
72
-
73
- Ordine di precedenza: config (script) > attributo HTML > default interno.
74
-
75
- Per ignorare esplicitamente un attributo e ricadere sul default, passare `null` nel config:
76
-
77
- ```javascript
78
- el.init({ json: '/api/data.json', cols: null }); // → cols = null, l'attributo viene saltato
79
- ```
80
-
81
- ## Reazione ad eventi esterni
82
-
83
- Il componente ascolta tre CustomEvent che altri componenti possono emettere
84
- sul `document` (o su qualsiasi antenato del componente nel DOM):
85
-
86
- ### `datatable:search`
87
-
88
- Applica una ricerca su una o più colonne.
89
-
90
- `detail: { term: string, columns?: number[] }`
91
-
92
- ```javascript
93
- // Ricerca globale
94
- document.dispatchEvent(new CustomEvent('datatable:search', {
95
- detail: { term: 'mario' }
96
- }));
97
-
98
- // Ricerca su colonna 0 e 2
99
- document.dispatchEvent(new CustomEvent('datatable:search', {
100
- detail: { term: 'mario', columns: [0, 2] }
101
- }));
102
- ```
103
-
104
- ### `datatable:multisearch`
105
-
106
- Applica più filtri contemporaneamente (uno per colonna).
107
-
108
- `detail: { queries: Array<{ terms: string[], columns?: number[] }> }`
109
-
110
- ```javascript
111
- // Filtra colonna 1 per "mario" E colonna 3 per "roma"
112
- document.dispatchEvent(new CustomEvent('datatable:multisearch', {
113
- detail: {
114
- queries: [
115
- { terms: ['mario'], columns: [1] },
116
- { terms: ['roma'], columns: [3] }
117
- ]
118
- }
119
- }));
120
- ```
121
-
122
- ### `datatable:reload`
123
-
124
- Ricarica i dati da un nuovo URL JSON (o dallo stesso URL originale se `json` è omesso),
125
- opzionalmente con nuove colonne, dati inline e/o un filtro da applicare al termine del
126
- caricamento.
127
-
128
- `detail: { json?: string, data?: Array, cols?: Array, search?: object }`
129
-
130
- Il campo `search` permette di combinare reload e filtro in un unico evento, evitando
131
- race condition tra il fetch e la ricerca. Formati supportati:
132
-
133
- - `{ term, columns? }` → ricerca semplice
134
- - `{ queries: [{terms, columns?}] }` → ricerca multipla
135
-
136
- ```javascript
137
- // Reload stesso URL
138
- document.dispatchEvent(new CustomEvent('datatable:reload', { detail: {} }));
139
-
140
- // Nuovo endpoint + nuove colonne
141
- document.dispatchEvent(new CustomEvent('datatable:reload', {
142
- detail: {
143
- json: '/api/data-filtered.json',
144
- cols: [{ _heading: 'Nome', _field: 'name' }]
145
- }
146
- }));
147
-
148
- // Dati inline (senza fetch)
149
- document.dispatchEvent(new CustomEvent('datatable:reload', {
150
- detail: {
151
- data: [{ name: 'Mario', city: 'Roma' }],
152
- }
153
- }));
154
-
155
- // Reload + filtro contestuale applicato dopo il render
156
- document.dispatchEvent(new CustomEvent('datatable:reload', {
157
- detail: {
158
- json: '/api/utenti?anno=2024',
159
- search: { term: 'mario', columns: [0] }
160
- }
161
- }));
162
- ```
163
-
164
- ## Targeting – più istanze nella stessa pagina
165
-
166
- Aggiungere `id` al componente e `target` nel detail. Se `target` è assente,
167
- l'evento viene raccolto da **tutte** le istanze.
168
-
169
- ```html
170
- <s-datatable id="table-utenti" ...></s-datatable>
171
- ```
172
-
173
- ```javascript
174
- document.dispatchEvent(new CustomEvent('datatable:search', {
175
- detail: { term: 'mario', target: 'table-utenti' }
176
- }));
177
- ```
178
-
179
- ## Parametro `cols`
180
-
181
- Array di oggetti di configurazione delle colonne. Le chiavi con prefisso `_` sono
182
- proprietà del componente; le restanti vengono passate direttamente a simple-datatables
183
- (es. `sortable`, `searchable`, `type`, `hidden`, …).
184
-
185
- ```javascript
186
- cols: [
187
- {
188
- // ── Proprietà del componente (prefisso _) ─────────────────────────────
189
-
190
- _field, // Chiave del campo JSON. Obbligatorio.
191
- // Supporta notazione punto per valori annidati: 'owner.cognome'
192
-
193
- _heading, // Testo dell'intestazione di colonna (contenuto del <th>). Obbligatorio.
194
-
195
- _title, // Testo del tooltip sull'intestazione (attributo `title` dell'<abbr>).
196
-
197
- _render, // Template HTML per il contenuto della cella.
198
- // Accetta:
199
- // - stringa mustache-like: i segnaposto [[chiave]] vengono sostituiti
200
- // con i valori della riga (notazione punto: [[owner.nome]]).
201
- // I segnaposto null usano _renderNullAs; se il padre dell'oggetto è
202
- // null (es. Symfony restituisce owner: null) non viene emesso alcun
203
- // warning (il dato è semplicemente assente, non un errore di config).
204
- // - funzione (row) => string
205
-
206
- _renderMode, // Preset di visualizzazione predefiniti. Valori disponibili:
207
- // 'id' – colonna numerica, non ricercabile, allineata a destra
208
- // 'email' – va a capo prima e dopo la @
209
- // 'boolean' – icona spunta/croce con colore semantico
210
- // 'sf_datetime' – data/ora da oggetto Symfony ({ date, timezone })
211
-
212
- _sortValue, // Percorso del campo da usare per l'ORDINAMENTO al posto del contenuto
213
- // visualizzato. Utile quando la cella contiene HTML composto (_render).
214
- // Supporta notazione punto.
215
- // Implementato tramite l'attributo data-order sulla cella, letto
216
- // nativamente da simple-datatables.
217
- //
218
- // Esempio: cella con '[[owner.nome]] [[owner.cognome]]'
219
- // _sortValue: 'owner.cognome' → ordina per cognome
220
-
221
- _searchValue, // Percorso/i del campo da usare per la RICERCA al posto del contenuto
222
- // visualizzato. Accetta uno o più percorsi separati da spazio,
223
- // oppure una funzione (row) => string|number|boolean.
224
- // Supporta notazione punto.
225
- // Implementato tramite searchMethod di colonna (API simple-datatables):
226
- // il valore viene scritto in attributes['data-search'] della cella e
227
- // la searchMethod lo legge dall'OGGETTO DATI INTERNO di
228
- // simple-datatables (cellType: { data, text?, order?, attributes? }),
229
- // NON dal nodo DOM <td> (la libreria passa alla callback
230
- // `searchRow[index]`, non l'elemento renderizzato).
231
- // Ordine di lettura: attributes['data-search'] → text → data.
232
- // Il valore viene convertito con String(), quindi sono ammessi
233
- // numeri e booleani (es. `_searchValue: row => row.id`).
234
- // La ricerca è di tipo AND: tutti i termini digitati devono essere
235
- // presenti nel valore del campo (o dei campi) indicati.
236
- //
237
- // Normalizzazione: simple-datatables normalizza solo i termini
238
- // digitati (default `sensitivity: 'base'`, `ignorePunctuation: true`).
239
- // La searchMethod applica la stessa catena anche al contenuto della
240
- // cella (minuscole + NFD senza diacritici + rimozione punteggiatura),
241
- // così "citta" trova "Città" e "spa" trova "S.p.A.".
242
- // Se sulla colonna sono impostate `sensitivity` / `ignorePunctuation`,
243
- // vengono rispettate.
244
- //
245
- // Esempio: _searchValue: 'owner.cognome owner.nome'
246
- // cerca su entrambi i campi concatenati; "rossi mario" trova solo
247
- // le righe che contengono entrambe le parole, in qualsiasi ordine.
248
-
249
- _footerRender, // Callback o stringa per il contenuto della cella nel <tfoot>.
250
- // Il <tfoot> (una sola riga) viene aggiunto automaticamente se
251
- // almeno una colonna definisce questa proprietà; le colonne senza
252
- // _footerRender producono una <td> vuota.
253
- //
254
- // Accetta:
255
- // - funzione (data) => string
256
- // `data` è l'array dei record da considerare per il calcolo.
257
- // Il contenuto di `data` dipende dall'opzione `updateFooterOnPageChange`
258
- // del componente (default false = dataset filtrato completo,
259
- // indipendente dalla pagina corrente).
260
- // - stringa statica (es. etichetta fissa "Totale:")
261
- //
262
- // La cella eredita le classi CSS di allineamento della colonna
263
- // (cellClass / headerClass), in modo da risultare visivamente coerente.
264
- //
265
- // Aggiornamenti automatici:
266
- // - sempre: al cambio di filtro (ricerca / multisearch)
267
- // - solo se updateFooterOnPageChange: true: al cambio di pagina
268
- //
269
- // Esempio – somma di una colonna numerica:
270
- // _footerRender: (data) => {
271
- // const tot = data.reduce((s, r) => s + (r.importo ?? 0), 0);
272
- // return `<strong>${tot.toLocaleString('it-IT')} €</strong>`;
273
- // }
274
-
275
- // ── Proprietà native simple-datatables (senza prefisso _) ────────────
276
-
277
- type, // Tipo di dato: 'string' | 'number' | 'date' | 'boolean' | 'html' | 'other'
278
- sortable, // false per disabilitare l'ordinamento sulla colonna
279
- searchable, // false per escludere la colonna dalla ricerca globale
280
- hidden, // true per nascondere la colonna (esclusa anche dalla ricerca)
281
- sort, // 'asc' | 'desc' per ordinamento iniziale (solo su colonna singola)
282
- // ... tutte le altre opzioni columns di simple-datatables
283
- }
284
- ]
285
- ```
286
-
287
- ### Esempio completo con valori annidati
288
-
289
- ```javascript
290
- const cols = [
291
- {
292
- _field: 'commessa',
293
- _heading: 'Commessa / fornitore',
294
- _render: '[[commessa]]<br><small class="text-muted">[[fornitore]]</small>',
295
- },
296
- {
297
- _field: 'owner.cognome',
298
- _heading: 'Owner',
299
- _render: '[[owner.nome]] [[owner.cognome]]',
300
- _sortValue: 'owner.cognome', // ordina per cognome
301
- _searchValue: 'owner.cognome owner.nome', // cerca su cognome e nome (AND)
302
- },
303
- {
304
- _field: 'importi.0.importo', // valore annidato con indice array
305
- _heading: 'Importo',
306
- type: 'number',
307
- }
308
- ];
309
- ```
310
-
311
- ### Esempio con tfoot – somma e conteggio
312
-
313
- ```javascript
314
- // updateFooterOnPageChange: false (default) – il footer mostra il totale del set filtrato,
315
- // indipendente dalla pagina. Non si aggiorna al cambio pagina.
316
- const el = document.querySelector('s-datatable');
317
- el.init({
318
- json: '/api/commesse.json',
319
- cols: [
320
- {
321
- _field: 'commessa',
322
- _heading: 'Commessa',
323
- _footerRender: '<strong>Totale:</strong>', // etichetta fissa
324
- },
325
- {
326
- _field: 'importo',
327
- _heading: 'Importo',
328
- type: 'number',
329
- cellClass: 'text-end',
330
- headerClass: 'text-end',
331
- // data = tutti i record filtrati (intera ricerca, non solo la pagina corrente)
332
- _footerRender: (data) => {
333
- const tot = data.reduce((s, r) => s + (r.importo ?? 0), 0);
334
- return `<strong>${tot.toLocaleString('it-IT', { style: 'currency', currency: 'EUR' })}</strong>`;
335
- },
336
- },
337
- {
338
- _field: 'stato',
339
- _heading: 'Stato',
340
- _footerRender: (data) => `${data.length} record`,
341
- },
342
- ],
343
- });
344
-
345
- // updateFooterOnPageChange: true – il footer mostra il subtotale della sola pagina corrente.
346
- // Si aggiorna sia al cambio filtro sia al cambio pagina.
347
- el.init({
348
- json: '/api/commesse.json',
349
- updateFooterOnPageChange: true,
350
- cols: [ /* stesse colonne di sopra */ ],
351
- });
352
- ```
353
-
354
- Il `<tfoot>` viene aggiunto solo se almeno una colonna definisce `_footerRender`.
355
-
356
- Con `updateFooterOnPageChange: false` (default) la callback riceve l'intero set filtrato: i totali
357
- non cambiano navigando tra le pagine e si aggiornano solo quando cambia la ricerca.
358
-
359
- Con `updateFooterOnPageChange: true` la callback riceve i record della pagina corrente: utile per
360
- subtotali di pagina; il footer si aggiorna sia al cambio filtro sia al cambio pagina.
361
-
362
- ## Testo info
363
-
364
- Il componente sovrascrive il testo dell'area informativa (in basso a sinistra) per includere
365
- sempre il numero totale assoluto di record, distinto dal conteggio filtrato.
366
-
367
- Senza ricerca attiva:
368
-
369
- > Stai visualizzando le righe da 1 a 25, su un totale di 100.
370
-
371
- Con ricerca attiva:
372
-
373
- > Stai visualizzando le righe da 1 a 25 dei 50 record filtrati, su un totale di 100.
374
-
375
- Ricerca senza risultati:
376
-
377
- > Nessun risultato per la ricerca corrente (totale record: 100).
378
-
379
- Il testo si aggiorna automaticamente ad ogni cambio pagina, ricerca e multisearch.
380
-
381
- ## Parametri `topSlot` e `showInfoAtTop`
382
-
383
- Entrambi occupano l'area in alto a sinistra del datatable — lo spazio normalmente
384
- riservato al selettore "righe per pagina" (qui disabilitato). I due parametri si
385
- escludono: se `showInfoAtTop` è `true` (default), `topSlot` viene ignorato.
386
-
387
- ### `showInfoAtTop`
388
-
389
- Replica nell'area superiore lo stesso testo dell'area `.info` in basso,
390
- mantenuto in sincronia ad ogni cambio pagina o filtro. Il nodo viene nascosto
391
- automaticamente quando non ci sono record o la ricerca non produce risultati
392
- (evita di mostrare "Nessun risultato…" in due posti).
393
-
394
- ```javascript
395
- el.init({
396
- json: '/api/data.json',
397
- cols: [...],
398
- showInfoAtTop: true,
399
- });
400
- ```
401
-
402
- ### `topSlot`
403
-
404
- Inserisce un elemento DOM o markup HTML arbitrario, avvolto in un
405
- `<div class="datatable-top-slot">`. Accetta sia una stringa HTML che un `Element`
406
- già costruito (utile per mantenere un riferimento al nodo e aggiungervi listener).
407
- Non è leggibile da attributo HTML, solo via `init()`.
408
-
409
- ```javascript
410
- // Stringa HTML
411
- el.init({
412
- json: '/api/data.json',
413
- cols: [...],
414
- topSlot: '<button class="btn btn-sm btn-primary">Esporta CSV</button>',
415
- });
416
-
417
- // Elemento DOM
418
- const btn = document.createElement('button');
419
- btn.className = 'btn btn-sm btn-outline-secondary';
420
- btn.textContent = 'Nuova commessa';
421
- btn.addEventListener('click', () => { /* … */ });
422
-
423
- el.init({
424
- json: '/api/commesse.json',
425
- cols: [...],
426
- topSlot: btn,
427
- });
428
- ```