@massimo-cassandro/minimo 0.12.2 → 0.12.3

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": "0.12.2",
3
+ "version": "0.12.3",
4
4
  "description": "Light css framework & utilities",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/massimo-cassandro/minimo#readme",
@@ -186,11 +186,14 @@ cols: [
186
186
 
187
187
  _title, // Testo del tooltip sull'intestazione (attributo `title` dell'<abbr>).
188
188
 
189
- _renderTpl, // Template HTML per il contenuto della cella.
190
- // I segnaposto [[chiave]] vengono sostituiti con i valori della riga.
191
- // Supporta notazione punto: [[owner.nome]] [[owner.cognome]]
192
- // Se un valore è null (es. oggetto padre assente da Symfony),
193
- // il segnaposto viene sostituito con stringa vuota senza errori.
189
+ _render, // Template HTML per il contenuto della cella.
190
+ // Accetta:
191
+ // - stringa mustache-like: i segnaposto [[chiave]] vengono sostituiti
192
+ // con i valori della riga (notazione punto: [[owner.nome]]).
193
+ // I segnaposto null usano _renderNullAs; se il padre dell'oggetto è
194
+ // null (es. Symfony restituisce owner: null) non viene emesso alcun
195
+ // warning (il dato è semplicemente assente, non un errore di config).
196
+ // - funzione (row) => string
194
197
 
195
198
  _renderMode, // Preset di visualizzazione predefiniti. Valori disponibili:
196
199
  // 'id' – colonna numerica, non ricercabile, allineata a destra
@@ -199,7 +202,7 @@ cols: [
199
202
  // 'sf_datetime' – data/ora da oggetto Symfony ({ date, timezone })
200
203
 
201
204
  _sortValue, // Percorso del campo da usare per l'ORDINAMENTO al posto del contenuto
202
- // visualizzato. Utile quando la cella contiene HTML composto (_renderTpl).
205
+ // visualizzato. Utile quando la cella contiene HTML composto (_render).
203
206
  // Supporta notazione punto.
204
207
  // Implementato tramite l'attributo data-order sulla cella, letto
205
208
  // nativamente da simple-datatables.
@@ -238,12 +241,12 @@ const cols = [
238
241
  {
239
242
  _field: 'commessa',
240
243
  _heading: 'Commessa / fornitore',
241
- _renderTpl: '[[commessa]]<br><small class="text-muted">[[fornitore]]</small>',
244
+ _render: '[[commessa]]<br><small class="text-muted">[[fornitore]]</small>',
242
245
  },
243
246
  {
244
247
  _field: 'owner.cognome',
245
248
  _heading: 'Owner',
246
- _renderTpl: '[[owner.nome]] [[owner.cognome]]',
249
+ _render: '[[owner.nome]] [[owner.cognome]]',
247
250
  _sortValue: 'owner.cognome', // ordina per cognome
248
251
  _searchValue: 'owner.cognome owner.nome', // cerca su cognome e nome (AND)
249
252
  },
@@ -21,10 +21,25 @@ import caretRightIcon from '../../icons/caret-right.svg?inline';
21
21
  * @typedef {object} ColDefinition
22
22
  * @property {string} _heading Intestazione della colonna (obbligatorio).
23
23
  * @property {string} _field Chiave del campo nell'oggetto dato (obbligatorio).
24
- * @property {string} [_title] Testo del tooltip sull'intestazione.
25
- * @property {string} [_renderTpl] Template HTML per il contenuto della cella.
26
- * I segnaposto `[[key]]` vengono sostituiti con i
27
- * valori corrispondenti dell'oggetto riga.
24
+ * @property {string|Function} [_headingTitle] Testo del tooltip sull'intestazione (`<abbr title="…">`).
25
+ * Accetta:
26
+ * - stringa semplice (tooltip statico)
27
+ * - stringa mustache-like con segnaposto `[[key]]`
28
+ * (interpolati sui dati della prima riga disponibile;
29
+ * utile solo se il titolo varia per colonna, non per riga)
30
+ * - funzione `(row) => string` (row è null per le intestazioni)
31
+ * @property {string|Function} [_cellTitle] Testo del tooltip delle celle (attributo `title`).
32
+ * Accetta:
33
+ * - stringa semplice (tooltip statico uguale per tutte le righe)
34
+ * - stringa mustache-like con segnaposto `[[key]]`
35
+ * (i segnaposto vengono sostituiti con i valori della riga corrente)
36
+ * - funzione `(row) => string` (riceve l'oggetto riga corrente)
37
+ * @property {string|Function} [_render] Template HTML per il contenuto della cella.
38
+ * Accetta:
39
+ * - stringa mustache-like con segnaposto `[[key]]`
40
+ * (sostituiti con i valori dell'oggetto riga;
41
+ * i segnaposto non trovati usano `_renderNullAs`)
42
+ * - funzione `(row) => string`
28
43
  * @property {string|Function} [_sortValue] Percorso del campo da usare per l'ORDINAMENTO al posto
29
44
  * del contenuto visualizzato, oppure una funzione
30
45
  * `(row) => string|number` per casi complessi
@@ -39,7 +54,7 @@ import caretRightIcon from '../../icons/caret-right.svg?inline';
39
54
  * @property {string} [_renderNullAs] Stringa da mostrare al posto di valori null/undefined.
40
55
  * Default: `'&mdash;'` (trattino em).
41
56
  * Applicato al valore della cella e ai segnaposto
42
- * di `_renderTpl` che si risolvono a null.
57
+ * di `_render` che si risolvono a null.
43
58
  * @property {'id'|'email'|'sf_datetime'} [_renderMode]
44
59
  * Modalità di visualizzazione predefinita:
45
60
  * - `id` – colonna numerica, non ricercabile, allineata a destra
@@ -92,6 +107,60 @@ function getNestedValue(obj, path) {
92
107
  }
93
108
 
94
109
 
110
+ /**
111
+ * resolveTemplate
112
+ *
113
+ * Risolve un template in base al suo tipo:
114
+ * - Funzione: invocata con `row`, deve restituire una stringa.
115
+ * - Stringa mustache-like ([[key]]): i segnaposto vengono sostituiti con i
116
+ * valori corrispondenti dell'oggetto riga.
117
+ * - Stringa semplice: restituita così com'è (utile per tooltip statici).
118
+ *
119
+ * Quando `row` è null/undefined (caso intestazioni) le funzioni vengono
120
+ * comunque chiamate con null; la stringa mustache-like viene restituita
121
+ * letteralmente perché non ci sono dati da interpolare.
122
+ *
123
+ * @param {string|Function} tpl Template da risolvere.
124
+ * @param {object|null} row Oggetto riga corrente (null per le intestazioni).
125
+ * @param {object} [opts] Opzioni aggiuntive.
126
+ * @param {string} [opts.nullAs] Stringa da usare come fallback per i segnaposto
127
+ * che si risolvono a null (default: `''`).
128
+ * Se non fornita i segnaposto non trovati diventano ''.
129
+ * @param {number} [opts.warnColIdx] Se fornito, emette console.error quando una
130
+ * chiave foglia non esiste sul padre (indica un errore
131
+ * di configurazione, non un dato assente).
132
+ * @returns {string|null} Stringa risolta, oppure null se `tpl` è falsy.
133
+ */
134
+ function resolveTemplate(tpl, row, { nullAs = '', warnColIdx } = {}) {
135
+ if (!tpl) return null;
136
+
137
+ if (typeof tpl === 'function') {
138
+ return tpl(row) ?? null;
139
+ }
140
+
141
+ // Stringa mustache-like: interpola solo se row è disponibile
142
+ if (row != null && /\[\[.*?\]\]/.test(tpl)) {
143
+ return decodeURIComponent(tpl).replace(
144
+ /\[\[(.*?)\]\]/g,
145
+ (_, key) => {
146
+ const resolved = getNestedValue(row, key);
147
+ if (warnColIdx !== undefined) {
148
+ const pathParts = key.split('.');
149
+ const parentObj = pathParts.length > 1
150
+ ? getNestedValue(row, pathParts.slice(0, -1).join('.'))
151
+ : row;
152
+ if (parentObj != null && !Object.prototype.hasOwnProperty.call(parentObj, pathParts.at(-1))) {
153
+ // eslint-disable-next-line no-console
154
+ console.error(`[s-datatable] Chiave "${key}" non trovata nella struttura dati (colonna ${warnColIdx})`);
155
+ }
156
+ }
157
+ return resolved ?? nullAs;
158
+ }
159
+ );
160
+ }
161
+
162
+ return tpl;
163
+ }
95
164
 
96
165
  class SimpleDatatableAdapter extends HTMLElement {
97
166
  constructor() {
@@ -343,11 +412,12 @@ class SimpleDatatableAdapter extends HTMLElement {
343
412
  rawData = parsed.data;
344
413
  }
345
414
 
346
- this.headings = cols.map(item =>
347
- item._title
348
- ? `<abbr title="${item._title}">${item._heading}</abbr>`
349
- : item._heading
350
- );
415
+ this.headings = cols.map(item => {
416
+ const title = resolveTemplate(item._headingTitle, null);
417
+ return title
418
+ ? `<abbr title="${title}">${item._heading}</abbr>`
419
+ : item._heading;
420
+ });
351
421
 
352
422
  this.data = rawData.map((row, idx) => ({
353
423
  attributes: { 'data-jidx': idx },
@@ -370,36 +440,12 @@ class SimpleDatatableAdapter extends HTMLElement {
370
440
  : null;
371
441
 
372
442
 
373
- // _renderTpl: template per il rendering del valore
374
- // se stringa contiene le chiavi da renderizzare in formato mustache-like ([[key]])
375
- // se funzione ha come argomento la riga dati
376
- if (col_item._renderTpl) {
377
- if(typeof col_item._renderTpl === 'string') {
378
-
379
- value = decodeURIComponent(col_item._renderTpl).replace(
380
- /\[\[(.*?)\]\]/g,
381
- (match, key) => {
382
- const resolved = getNestedValue(row, key);
383
- const pathParts = key.split('.');
384
- const parentObj = pathParts.length > 1
385
- ? getNestedValue(row, pathParts.slice(0, -1).join('.'))
386
- : row;
387
-
388
- // Segnala solo se il padre esiste ma la chiave foglia manca
389
- // (errore di configurazione). Se il padre è null il dato è
390
- // semplicemente assente (es. Symfony restituisce owner: null).
391
- if (parentObj != null && !Object.prototype.hasOwnProperty.call(parentObj, pathParts.at(-1))) {
392
- // eslint-disable-next-line no-console
393
- console.error(`[s-datatable] Chiave "${key}" non trovata nella struttura dati (colonna ${col_idx})`);
394
- }
395
-
396
- return resolved ?? nullAs;
397
- }
398
- );
399
-
400
- } else if (typeof col_item._renderTpl === 'function') {
401
- value = col_item._renderTpl(row);
402
- }
443
+ // _render: template per il rendering del valore.
444
+ // Accetta stringa mustache-like ([[key]]) o funzione (row) => string.
445
+ // Delega a resolveTemplate, passando nullAs e l'indice di colonna
446
+ // per i warning di chiavi mancanti.
447
+ if (col_item._render) {
448
+ value = resolveTemplate(col_item._render, row, { nullAs, warnColIdx: col_idx });
403
449
 
404
450
  } else if (col_item._renderMode === 'sf_datetime' && value != null) {
405
451
  value = value.date.replace(' ', 'T') +
@@ -408,7 +454,7 @@ class SimpleDatatableAdapter extends HTMLElement {
408
454
 
409
455
  // Applica _renderNullAs al valore della cella se null/undefined
410
456
  // e la cella non ha già un template che gestisce la visualizzazione.
411
- if (value == null && !col_item._renderTpl) {
457
+ if (value == null && col_item._render == null) {
412
458
  value = nullAs;
413
459
  }
414
460
 
@@ -431,10 +477,21 @@ class SimpleDatatableAdapter extends HTMLElement {
431
477
  // - `data` → contenuto visualizzato (obbligatorio)
432
478
  // - `order` → valore alternativo per l'ordinamento (campo nativo)
433
479
  // - `attributes['data-search']` → stringa su cui opera searchMethod
434
- if (sortValue !== null || searchValue !== null) {
480
+ // - `attributes['title']` → tooltip della cella (_cellTitle)
481
+
482
+ // Risolvi _cellTitle: stringa semplice, mustache-like o funzione(row)
483
+ const cellTitle = resolveTemplate(col_item._cellTitle, row);
484
+
485
+ if (sortValue !== null || searchValue !== null || cellTitle !== null) {
435
486
  const cellObj = { data: value ?? '' };
436
487
  if (sortValue !== null) cellObj.order = sortValue;
437
- if (searchValue !== null) cellObj.attributes = { 'data-search': searchValue };
488
+
489
+ // Unifica data-search e title in un unico oggetto attributes
490
+ const attrs = {};
491
+ if (searchValue !== null) attrs['data-search'] = searchValue;
492
+ if (cellTitle !== null) attrs['title'] = cellTitle;
493
+ if (Object.keys(attrs).length) cellObj.attributes = attrs;
494
+
438
495
  return cellObj;
439
496
  }
440
497