@massimo-cassandro/minimo 0.15.0 → 0.16.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": "0.15.0",
3
+ "version": "0.16.1",
4
4
  "description": "Light css framework & utilities",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/massimo-cassandro/minimo#readme",
@@ -13,6 +13,9 @@
13
13
  border: 0 !important;
14
14
  clip-path: rect(0 0 0 0) !important;
15
15
  }
16
+ .visibility-hidden {
17
+ visibility: hidden;
18
+ }
16
19
  /* purgecss end ignore */
17
20
 
18
21
  .overflow-hidden {
@@ -222,6 +222,32 @@ cols: [
222
222
  // cerca su entrambi i campi concatenati; "rossi mario" trova solo
223
223
  // le righe che contengono entrambe le parole, in qualsiasi ordine.
224
224
 
225
+ _footerRender, // Callback o stringa per il contenuto della cella nel <tfoot>.
226
+ // Il <tfoot> (una sola riga) viene aggiunto automaticamente se
227
+ // almeno una colonna definisce questa proprietà; le colonne senza
228
+ // _footerRender producono una <td> vuota.
229
+ //
230
+ // Accetta:
231
+ // - funzione (data) => string
232
+ // `data` è l'array dei record da considerare per il calcolo.
233
+ // Il contenuto di `data` dipende dall'opzione `updateFooterOnPageChange`
234
+ // del componente (default false = dataset filtrato completo,
235
+ // indipendente dalla pagina corrente).
236
+ // - stringa statica (es. etichetta fissa "Totale:")
237
+ //
238
+ // La cella eredita le classi CSS di allineamento della colonna
239
+ // (cellClass / headerClass), in modo da risultare visivamente coerente.
240
+ //
241
+ // Aggiornamenti automatici:
242
+ // - sempre: al cambio di filtro (ricerca / multisearch)
243
+ // - solo se updateFooterOnPageChange: true: al cambio di pagina
244
+ //
245
+ // Esempio – somma di una colonna numerica:
246
+ // _footerRender: (data) => {
247
+ // const tot = data.reduce((s, r) => s + (r.importo ?? 0), 0);
248
+ // return `<strong>${tot.toLocaleString('it-IT')} €</strong>`;
249
+ // }
250
+
225
251
  // ── Proprietà native simple-datatables (senza prefisso _) ────────────
226
252
 
227
253
  type, // Tipo di dato: 'string' | 'number' | 'date' | 'boolean' | 'html' | 'other'
@@ -257,3 +283,122 @@ const cols = [
257
283
  }
258
284
  ];
259
285
  ```
286
+
287
+ ### Esempio con tfoot – somma e conteggio
288
+
289
+ ```javascript
290
+ // updateFooterOnPageChange: false (default) – il footer mostra il totale del set filtrato,
291
+ // indipendente dalla pagina. Non si aggiorna al cambio pagina.
292
+ const el = document.querySelector('s-datatable');
293
+ el.init({
294
+ json: '/api/commesse.json',
295
+ cols: [
296
+ {
297
+ _field: 'commessa',
298
+ _heading: 'Commessa',
299
+ _footerRender: '<strong>Totale:</strong>', // etichetta fissa
300
+ },
301
+ {
302
+ _field: 'importo',
303
+ _heading: 'Importo',
304
+ type: 'number',
305
+ cellClass: 'text-end',
306
+ headerClass: 'text-end',
307
+ // data = tutti i record filtrati (intera ricerca, non solo la pagina corrente)
308
+ _footerRender: (data) => {
309
+ const tot = data.reduce((s, r) => s + (r.importo ?? 0), 0);
310
+ return `<strong>${tot.toLocaleString('it-IT', { style: 'currency', currency: 'EUR' })}</strong>`;
311
+ },
312
+ },
313
+ {
314
+ _field: 'stato',
315
+ _heading: 'Stato',
316
+ _footerRender: (data) => `${data.length} record`,
317
+ },
318
+ ],
319
+ });
320
+
321
+ // updateFooterOnPageChange: true – il footer mostra il subtotale della sola pagina corrente.
322
+ // Si aggiorna sia al cambio filtro sia al cambio pagina.
323
+ el.init({
324
+ json: '/api/commesse.json',
325
+ updateFooterOnPageChange: true,
326
+ cols: [ /* stesse colonne di sopra */ ],
327
+ });
328
+ ```
329
+
330
+ Il `<tfoot>` viene aggiunto solo se almeno una colonna definisce `_footerRender`.
331
+
332
+ Con `updateFooterOnPageChange: false` (default) la callback riceve l'intero set filtrato: i totali
333
+ non cambiano navigando tra le pagine e si aggiornano solo quando cambia la ricerca.
334
+
335
+ Con `updateFooterOnPageChange: true` la callback riceve i record della pagina corrente: utile per
336
+ subtotali di pagina; il footer si aggiorna sia al cambio filtro sia al cambio pagina.
337
+
338
+ ## Testo info
339
+
340
+ Il componente sovrascrive il testo dell'area informativa (in basso a sinistra) per includere
341
+ sempre il numero totale assoluto di record, distinto dal conteggio filtrato.
342
+
343
+ Senza ricerca attiva:
344
+
345
+ > Stai visualizzando le righe da 1 a 25, su un totale di 100.
346
+
347
+ Con ricerca attiva:
348
+
349
+ > Stai visualizzando le righe da 1 a 25 dei 50 record filtrati, su un totale di 100.
350
+
351
+ Ricerca senza risultati:
352
+
353
+ > Nessun risultato per la ricerca corrente (totale record: 100).
354
+
355
+ Il testo si aggiorna automaticamente ad ogni cambio pagina, ricerca e multisearch.
356
+
357
+ ## Parametri `topSlot` e `showInfoAtTop`
358
+
359
+ Entrambi occupano l'area in alto a sinistra del datatable — lo spazio normalmente
360
+ riservato al selettore "righe per pagina" (qui disabilitato). I due parametri si
361
+ escludono: se `showInfoAtTop` è `true`, `topSlot` viene ignorato.
362
+
363
+ ### `showInfoAtTop`
364
+
365
+ Replica nell'area superiore lo stesso testo dell'area `.info` in basso,
366
+ mantenuto in sincronia ad ogni cambio pagina o filtro. Il nodo viene nascosto
367
+ automaticamente quando non ci sono record o la ricerca non produce risultati
368
+ (evita di mostrare "Nessun risultato…" in due posti).
369
+
370
+ ```javascript
371
+ el.init({
372
+ json: '/api/data.json',
373
+ cols: [...],
374
+ showInfoAtTop: true,
375
+ });
376
+ ```
377
+
378
+ ### `topSlot`
379
+
380
+ Inserisce un elemento DOM o markup HTML arbitrario, avvolto in un
381
+ `<div class="datatable-top-slot">`. Accetta sia una stringa HTML che un `Element`
382
+ già costruito (utile per mantenere un riferimento al nodo e aggiungervi listener).
383
+ Non è leggibile da attributo HTML, solo via `init()`.
384
+
385
+ ```javascript
386
+ // Stringa HTML
387
+ el.init({
388
+ json: '/api/data.json',
389
+ cols: [...],
390
+ topSlot: '<button class="btn btn-sm btn-primary">Esporta CSV</button>',
391
+ });
392
+
393
+ // Elemento DOM
394
+ const btn = document.createElement('button');
395
+ btn.className = 'btn btn-sm btn-outline-secondary';
396
+ btn.textContent = 'Nuova commessa';
397
+ btn.addEventListener('click', () => { /* … */ });
398
+
399
+ el.init({
400
+ json: '/api/commesse.json',
401
+ cols: [...],
402
+ topSlot: btn,
403
+ });
404
+ ```
@@ -9,7 +9,7 @@ import caretRightIcon from '../../icons/caret-right.svg?inline';
9
9
 
10
10
 
11
11
  /**
12
- * Impementazione di simple-datatable (https://fiduswriter.github.io/simple-datatables/documentation/)
12
+ * Implementazione di simple-datatable (https://fiduswriter.github.io/simple-datatables/documentation/)
13
13
  * per generare una tabella da dati json e da un oggetto `cols` di configurazione
14
14
  *
15
15
  * ─────────────────────────────────────────────────────────────────────────────
@@ -42,6 +42,16 @@ import caretRightIcon from '../../icons/caret-right.svg?inline';
42
42
  * - funzione `(row) => string`
43
43
  * - se sono impostate sia `_cellRender` che l'opzione `render` di simple-datatable
44
44
  * quest'ultima viene annullata per evitare potenziali conflitti
45
+ * @property {string|Function} [_footerRender] Contenuto della cella <td> nel <tfoot> per questa colonna.
46
+ * Accetta:
47
+ * - funzione `(data) => string` dove `data` è l'array dei
48
+ * record visibili (filtrati dalla ricerca corrente, ma non
49
+ * limitati alla pagina attuale): ideale per somme, medie, conteggi.
50
+ * - stringa statica (es. etichetta fissa "Totale:")
51
+ * Le colonne senza `_footerRender` producono una <td> vuota.
52
+ * Il <tfoot> viene aggiunto solo se almeno una colonna lo definisce.
53
+ * La cella eredita le classi CSS (allineamento) della colonna.
54
+ *
45
55
  * @property {string|Function} [_sortValue] Percorso del campo da usare per l'ORDINAMENTO al posto
46
56
  * del contenuto visualizzato, oppure una funzione
47
57
  * `(row) => string|number` per casi complessi
@@ -89,6 +99,32 @@ import caretRightIcon from '../../icons/caret-right.svg?inline';
89
99
  * colonna con `_renderNullAs`.
90
100
  * Default: `\u2014` (em dash)
91
101
  *
102
+ * @param {boolean} updateFooterOnPageChange Se true, la callback `_footerRender` riceve i soli record
103
+ * della pagina corrente (subtotale di pagina). Se false
104
+ * (default), riceve l'intero set filtrato indipendentemente
105
+ * dalla paginazione (totale complessivo).
106
+ * Quando è false il footer si aggiorna solo al cambio di
107
+ * filtro, non al cambio pagina.
108
+ *
109
+ * @param {Element|string} topSlot Elemento DOM o stringa HTML da inserire nell'area in alto
110
+ * a sinistra del datatable (lo spazio del selettore righe/pagina,
111
+ * qui disabilitato). Utile per pulsanti contestuali, filtri
112
+ * aggiuntivi o qualsiasi controllo da affiancare alla search box.
113
+ * Viene avvolto in un `<div class="datatable-top-slot">`.
114
+ * Accetta:
115
+ * - `Element`: un nodo DOM già costruito
116
+ * - stringa HTML: parsata via innerHTML
117
+ * Ignorato se `showInfoAtTop` è true.
118
+ * Non è leggibile da attributo HTML (solo via script).
119
+ *
120
+ * @param {boolean} showInfoAtTop Se true, mostra nell'area superiore (spazio top-sinistra)
121
+ * lo stesso testo dell'area `.info` in basso, mantenuto in
122
+ * sync ad ogni cambio pagina/filtro. Il nodo viene nascosto
123
+ * automaticamente quando non ci sono record o la ricerca
124
+ * non produce risultati. Ha precedenza su `topSlot`:
125
+ * se entrambi sono definiti, `topSlot` viene ignorato.
126
+ * Default: false.
127
+ *
92
128
  * @param {string[]} refs Elenco di percorsi URL da cui, se si proviene,
93
129
  * la pagina corrente viene ripristinata dal cookie
94
130
  * di sessione `sd-pag`. Se il referrer non è tra
@@ -597,6 +633,10 @@ class SimpleDatatableAdapter extends HTMLElement {
597
633
  })
598
634
  }));
599
635
 
636
+ // Conserva i dati grezzi e le definizioni di colonna per il tfoot
637
+ this._rawData = rawData;
638
+ this._footerCols = cols;
639
+
600
640
  this.columns = cols.map((col_settings, idx) => {
601
641
  col_settings = parseCols(col_settings);
602
642
 
@@ -729,24 +769,25 @@ class SimpleDatatableAdapter extends HTMLElement {
729
769
  });
730
770
 
731
771
  this._dt.on('datatable.init', () => {
772
+ // ── Icone paginazione ─────────────────────────────────────────────────
732
773
  const pagination = this.querySelector(`.${styles.paginationList}`);
733
- if (!pagination) return;
734
-
735
- const items = pagination.querySelectorAll(`.${styles.paginationListItem}`);
736
- if (!items.length) return;
737
-
738
- pagination.querySelector(
739
- `.${styles.paginationListItem}:first-child .${styles.paginationListItemBtn}`
740
- ).innerHTML = caretLeftIcon;
741
-
742
- pagination.querySelector(
743
- `.${styles.paginationListItem}:last-child .${styles.paginationListItemBtn}`
744
- ).innerHTML = caretRightIcon;
745
-
746
- pagination.querySelectorAll(`.${styles.paginationListItemBtn}:has(svg)`)
747
- .forEach(btn => btn.classList.add(styles.hasSvg));
774
+ if (pagination) {
775
+ const items = pagination.querySelectorAll(`.${styles.paginationListItem}`);
776
+ if (items.length) {
777
+ pagination.querySelector(
778
+ `.${styles.paginationListItem}:first-child .${styles.paginationListItemBtn}`
779
+ ).innerHTML = caretLeftIcon;
780
+
781
+ pagination.querySelector(
782
+ `.${styles.paginationListItem}:last-child .${styles.paginationListItemBtn}`
783
+ ).innerHTML = caretRightIcon;
784
+
785
+ pagination.querySelectorAll(`.${styles.paginationListItemBtn}:has(svg)`)
786
+ .forEach(btn => btn.classList.add(styles.hasSvg));
787
+ }
788
+ }
748
789
 
749
- // Ripristino pagina dal cookie di sessione (se referrer autorizzato)
790
+ // ── Ripristino pagina dal cookie di sessione (se referrer autorizzato) ─
750
791
  // const isRefUrl = this._isReferrer(), refsCallback = this._getParam('refsCallback', null);
751
792
  // if (isRefUrl) {
752
793
  if (this._isReferrer()) {
@@ -762,6 +803,7 @@ class SimpleDatatableAdapter extends HTMLElement {
762
803
  // refsCallback(isRefUrl);
763
804
  // }
764
805
 
806
+ // ── Filtro contestuale post-reload ────────────────────────────────────
765
807
  // Applica eventuale filtro contestuale passato via reload({ search: … })
766
808
  // Lo facciamo qui, dentro datatable.init, per essere certi che
767
809
  // simple-datatables abbia completato il render dei nuovi dati.
@@ -775,15 +817,254 @@ class SimpleDatatableAdapter extends HTMLElement {
775
817
  this._dt.search(ps.term, ps.columns);
776
818
  }
777
819
  }
820
+
821
+ // ── Tfoot, info, top slot ─────────────────────────────────────────────
822
+ // Eseguiti sempre, indipendentemente dalla presenza della paginazione.
823
+ this._renderTfoot();
824
+ this._injectTopSlot(); // va prima di _updateInfo per creare il nodo top-info
825
+ this._updateInfo();
778
826
  });
779
827
 
780
- // Salva la pagina corrente nel cookie ad ogni cambio di pagina
828
+ // Salva la pagina corrente nel cookie ad ogni cambio di pagina;
829
+ // aggiorna il tfoot solo se updateFooterOnPageChange è attivo.
781
830
  this._dt.on('datatable.page', page => {
782
831
  this._savePageCookie(page);
832
+ if (this._getParam('updateFooterOnPageChange', false)) this._renderTfoot();
833
+ this._updateInfo();
783
834
  });
784
835
 
836
+ // Aggiorna il tfoot ad ogni cambio di filtro (ricerca / multisearch).
837
+ // Il sort non altera il set filtrato: il footer non deve cambiare.
838
+ this._dt.on('datatable.search', () => { this._renderTfoot(); this._updateInfo(); });
839
+ this._dt.on('datatable.multisearch', () => { this._renderTfoot(); this._updateInfo(); });
840
+
785
841
  } // end _render
786
842
 
843
+
844
+ // ─── Footer (tfoot) ──────────────────────────────────────────────────────────
845
+
846
+ /**
847
+ * Costruisce o aggiorna la riga <tfoot> della tabella.
848
+ *
849
+ * Chiamato dopo il render iniziale, ad ogni cambio di filtro (ricerca /
850
+ * multisearch) e — se `updateFooterOnPageChange` è true — anche ad ogni cambio pagina.
851
+ *
852
+ * Dataset passato alla callback `_footerRender(data)`:
853
+ *
854
+ * updateFooterOnPageChange: false (default)
855
+ * → l'intero set filtrato, indipendente dalla pagina corrente.
856
+ * I totali non cambiano navigando tra le pagine.
857
+ * Se non c'è ricerca attiva, viene passato il dataset completo.
858
+ *
859
+ * updateFooterOnPageChange: true
860
+ * → solo i record della pagina corrente.
861
+ * Utile per subtotali di pagina.
862
+ *
863
+ * `searchData` di simple-datatables è un array di indici nel dataset
864
+ * originale, disponibile (e non vuoto) solo quando `dt.searching === true`.
865
+ * Quando la ricerca è attiva ma non trova nulla, `searchData` è `[]`:
866
+ * in quel caso la callback riceve un array vuoto, non il dataset completo.
867
+ */
868
+ _renderTfoot() {
869
+ const cols = this._footerCols;
870
+ const rawData = this._rawData;
871
+
872
+ if (!cols?.length || !rawData) return;
873
+
874
+ const hasFooter = cols.some(c => c._footerRender != null);
875
+ if (!hasFooter) return;
876
+
877
+ const table = this.querySelector('table');
878
+ if (!table) return;
879
+
880
+ const dt = this._dt;
881
+ const updateFooterOnPageChange = this._getParam('updateFooterOnPageChange', false);
882
+
883
+ let visibleData;
884
+
885
+ if (updateFooterOnPageChange) {
886
+ // Dati della pagina corrente: ogni "pagina" in dt.pages è un array
887
+ // di oggetti riga; ciascuno ha una proprietà data-index che punta
888
+ // all'indice nel dataset originale.
889
+ const pageRows = dt.pages?.[dt.currentPage - 1] ?? [];
890
+ visibleData = pageRows
891
+ .map(row => rawData[row['data-index']])
892
+ .filter(Boolean);
893
+ } else {
894
+ // Dataset filtrato completo (ignorando la paginazione).
895
+ // dt.searching è true solo quando c'è una ricerca attiva;
896
+ // in quel caso searchData contiene gli indici dei match (può essere []).
897
+ visibleData = dt.searching
898
+ ? dt.searchData.map(idx => rawData[idx]).filter(Boolean)
899
+ : rawData;
900
+ }
901
+
902
+ // Riutilizza tfoot esistente o ne crea uno nuovo
903
+ let tfoot = table.querySelector('tfoot');
904
+ if (!tfoot) {
905
+ tfoot = document.createElement('tfoot');
906
+ table.appendChild(tfoot);
907
+ }
908
+
909
+ const tr = document.createElement('tr');
910
+
911
+ cols.forEach(col => {
912
+ const td = document.createElement('td');
913
+
914
+ if (col._footerRender != null) {
915
+ if (typeof col._footerRender === 'function') {
916
+ td.innerHTML = col._footerRender(visibleData) ?? '';
917
+ } else {
918
+ // Stringa statica (es. etichetta fissa "Totale:")
919
+ td.innerHTML = col._footerRender;
920
+ }
921
+ }
922
+
923
+ // Propaga le classi di allineamento della colonna per coerenza visiva
924
+ const cellClass = col.cellClass ?? col.headerClass ?? '';
925
+ if (cellClass) td.className = cellClass;
926
+
927
+ tr.appendChild(td);
928
+ });
929
+
930
+ tfoot.replaceChildren(tr);
931
+ }
932
+
933
+ // ─── Info label ──────────────────────────────────────────────────────────────
934
+
935
+ /**
936
+ * Sovrascrive il testo dell'area `.info` generato da simple-datatables per
937
+ * includere sempre il totale assoluto dei record (indipendente dal filtro).
938
+ * Se `showInfoAtTop` è attivo, aggiorna in sincronia anche il nodo `.info`
939
+ * nell'area superiore (creato da `_injectTopSlot`).
940
+ *
941
+ * Calcolo di start/end: si conta il numero di righe renderizzate nel tbody
942
+ * (`dt.pages[currentPage - 1]`) invece di fare aritmetica su perPage,
943
+ * perché `dt.currentPage` può essere undefined al primo render.
944
+ *
945
+ * Comportamento:
946
+ * - Senza ricerca attiva:
947
+ * "Stai visualizzando le righe da {start} a {end}, su un totale di {total}."
948
+ * - Con ricerca attiva (risultati > 0):
949
+ * "Stai visualizzando le righe da {start} a {end} dei {filtered} record
950
+ * filtrati, su un totale di {total}."
951
+ * - Nessun risultato (ricerca attiva, 0 match):
952
+ * "Nessun risultato per la ricerca corrente (totale record: {total})."
953
+ *
954
+ * Il nodo top-info viene nascosto quando filtered = 0 o total = 0.
955
+ */
956
+ _updateInfo() {
957
+ const infoEl = this.querySelector(`.${styles.info}`);
958
+ const topInfoEl = this.querySelector('.datatable-top-info');
959
+
960
+ if (!infoEl && !topInfoEl) return;
961
+
962
+ const dt = this._dt;
963
+ const total = this._rawData?.length ?? 0;
964
+ const filtered = dt.searching ? dt.searchData.length : total;
965
+
966
+ let text;
967
+
968
+ if (total === 0 || (filtered === 0 && dt.searching)) {
969
+ // Nessun risultato — nasconde il nodo top-info se presente
970
+ if (topInfoEl) topInfoEl.hidden = true;
971
+
972
+ text = filtered === 0 && dt.searching
973
+ ? `Nessun risultato per la ricerca corrente (totale record: ${total}).`
974
+ : `Nessun record trovato.`;
975
+
976
+ if (infoEl) infoEl.textContent = text;
977
+ return;
978
+ }
979
+
980
+ // Calcola start/end dalla pagina corrente in dt.pages (più affidabile di
981
+ // currentPage * perPage, che può dare NaN se currentPage è undefined
982
+ // al primo render oppure quando non c'è paginazione).
983
+ const currentPage = dt.currentPage ?? 1;
984
+ const pageRows = dt.pages?.[currentPage - 1] ?? [];
985
+ const perPage = dt.options?.perPage ?? filtered;
986
+ const start = pageRows.length > 0
987
+ ? (currentPage - 1) * perPage + 1
988
+ : 1;
989
+ const end = pageRows.length > 0
990
+ ? start + pageRows.length - 1
991
+ : filtered;
992
+
993
+ if (dt.searching) {
994
+ text =
995
+ `Stai visualizzando le righe da ${start} a ${end} ` +
996
+ `dei ${filtered} record filtrati, su un totale di ${total}.`;
997
+ } else {
998
+ text =
999
+ `Stai visualizzando le righe da ${start} a ${end}, ` +
1000
+ `su un totale di ${total}.`;
1001
+ }
1002
+
1003
+ if (infoEl) infoEl.textContent = text;
1004
+ if (topInfoEl) { topInfoEl.textContent = text; topInfoEl.hidden = false; }
1005
+ }
1006
+
1007
+
1008
+ // ─── Top slot ────────────────────────────────────────────────────────────────
1009
+
1010
+ /**
1011
+ * Inietta nell'area in alto a sinistra del datatable il contenuto configurato.
1012
+ *
1013
+ * Modalità:
1014
+ *
1015
+ * showInfoAtTop: true (ha precedenza su topSlot)
1016
+ * Inserisce un nodo `.datatable-top-info` con la stessa stringa dell'area
1017
+ * `.info` in basso, mantenuta in sync da `_updateInfo`. Viene nascosto
1018
+ * automaticamente quando filtered = 0 o total = 0.
1019
+ * Il parametro `topSlot`, se presente, viene ignorato.
1020
+ *
1021
+ * topSlot: Element | string (usato solo se showInfoAtTop è false/assente)
1022
+ * Inserisce un elemento DOM o markup HTML arbitrario, avvolto in un
1023
+ * `<div class="datatable-top-slot">`.
1024
+ *
1025
+ * In entrambi i casi l'elemento viene inserito come primo figlio di `.topArea`,
1026
+ * prima della search box.
1027
+ *
1028
+ * Esempi:
1029
+ * el.init({ showInfoAtTop: true });
1030
+ *
1031
+ * el.init({ topSlot: '<button class="btn btn-sm btn-primary">Esporta</button>' });
1032
+ *
1033
+ * const btn = document.createElement('button');
1034
+ * btn.textContent = 'Nuovo';
1035
+ * el.init({ topSlot: btn });
1036
+ */
1037
+ _injectTopSlot() {
1038
+ const topArea = this.querySelector(`.${styles.topArea}`);
1039
+ if (!topArea) return;
1040
+
1041
+ const showInfoAtTop = this._getParam('showInfoAtTop', false);
1042
+
1043
+ if (showInfoAtTop) {
1044
+ // Crea il nodo info top (il testo verrà scritto da _updateInfo)
1045
+ const infoTop = document.createElement('div');
1046
+ infoTop.className = `datatable-top-info ${styles.info}`;
1047
+ infoTop.hidden = true; // nascosto finché _updateInfo non lo popola
1048
+ topArea.insertBefore(infoTop, topArea.firstChild);
1049
+ return;
1050
+ }
1051
+
1052
+ const topSlot = this._getParam('topSlot', null);
1053
+ if (topSlot == null) return;
1054
+
1055
+ const wrapper = document.createElement('div');
1056
+ wrapper.className = 'datatable-top-slot';
1057
+
1058
+ if (topSlot instanceof Element) {
1059
+ wrapper.appendChild(topSlot);
1060
+ } else {
1061
+ wrapper.innerHTML = topSlot;
1062
+ }
1063
+
1064
+ topArea.insertBefore(wrapper, topArea.firstChild);
1065
+ }
1066
+
1067
+
787
1068
  } // end component
788
1069
 
789
1070
 
@@ -110,6 +110,15 @@ export function parseCols(col_obj){
110
110
 
111
111
  }
112
112
  };
113
+
114
+ // TODO completare, aggiungere formattazione
115
+ } else if(col_obj._renderMode === 'numeric') {
116
+ return {
117
+
118
+ cellClass: 'text-end text-numeric',
119
+ headerClass: 'text-end',
120
+ };
121
+
113
122
  }
114
123
  } // end if not render
115
124