@massimo-cassandro/minimo 0.16.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.16.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",
@@ -354,28 +354,43 @@ Ricerca senza risultati:
354
354
 
355
355
  Il testo si aggiorna automaticamente ad ogni cambio pagina, ricerca e multisearch.
356
356
 
357
- ## Parametro `topSlot`
357
+ ## Parametri `topSlot` e `showInfoAtTop`
358
358
 
359
- Permette di inserire un elemento DOM o del markup HTML nell'area in alto a sinistra
360
- del datatable — lo spazio normalmente occupato dal selettore "righe per pagina"
361
- (qui disabilitato). È utile per aggiungere pulsanti contestuali, filtri aggiuntivi
362
- o qualsiasi controllo da affiancare alla search box.
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.
363
362
 
364
- L'elemento viene avvolto in un `<div class="datatable-top-slot">` e inserito
365
- come primo figlio dell'area superiore, prima della search box.
363
+ ### `showInfoAtTop`
366
364
 
367
- È configurabile solo via script (non da attributo HTML), perché accetta sia
368
- stringhe HTML che elementi DOM già costruiti.
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()`.
369
384
 
370
385
  ```javascript
371
386
  // Stringa HTML
372
387
  el.init({
373
388
  json: '/api/data.json',
374
389
  cols: [...],
375
- topSlot: '<button class="btn btn-sm btn-primary" onclick="...">Esporta CSV</button>',
390
+ topSlot: '<button class="btn btn-sm btn-primary">Esporta CSV</button>',
376
391
  });
377
392
 
378
- // Elemento DOM (utile quando si vuole mantenere un riferimento al nodo)
393
+ // Elemento DOM
379
394
  const btn = document.createElement('button');
380
395
  btn.className = 'btn btn-sm btn-outline-secondary';
381
396
  btn.textContent = 'Nuova commessa';
@@ -114,8 +114,17 @@ import caretRightIcon from '../../icons/caret-right.svg?inline';
114
114
  * Accetta:
115
115
  * - `Element`: un nodo DOM già costruito
116
116
  * - stringa HTML: parsata via innerHTML
117
+ * Ignorato se `showInfoAtTop` è true.
117
118
  * Non è leggibile da attributo HTML (solo via script).
118
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
+ *
119
128
  * @param {string[]} refs Elenco di percorsi URL da cui, se si proviene,
120
129
  * la pagina corrente viene ripristinata dal cookie
121
130
  * di sessione `sd-pag`. Se il referrer non è tra
@@ -760,24 +769,25 @@ class SimpleDatatableAdapter extends HTMLElement {
760
769
  });
761
770
 
762
771
  this._dt.on('datatable.init', () => {
772
+ // ── Icone paginazione ─────────────────────────────────────────────────
763
773
  const pagination = this.querySelector(`.${styles.paginationList}`);
764
- if (!pagination) return;
765
-
766
- const items = pagination.querySelectorAll(`.${styles.paginationListItem}`);
767
- if (!items.length) return;
768
-
769
- pagination.querySelector(
770
- `.${styles.paginationListItem}:first-child .${styles.paginationListItemBtn}`
771
- ).innerHTML = caretLeftIcon;
772
-
773
- pagination.querySelector(
774
- `.${styles.paginationListItem}:last-child .${styles.paginationListItemBtn}`
775
- ).innerHTML = caretRightIcon;
776
-
777
- pagination.querySelectorAll(`.${styles.paginationListItemBtn}:has(svg)`)
778
- .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
+ }
779
789
 
780
- // Ripristino pagina dal cookie di sessione (se referrer autorizzato)
790
+ // ── Ripristino pagina dal cookie di sessione (se referrer autorizzato) ─
781
791
  // const isRefUrl = this._isReferrer(), refsCallback = this._getParam('refsCallback', null);
782
792
  // if (isRefUrl) {
783
793
  if (this._isReferrer()) {
@@ -793,6 +803,7 @@ class SimpleDatatableAdapter extends HTMLElement {
793
803
  // refsCallback(isRefUrl);
794
804
  // }
795
805
 
806
+ // ── Filtro contestuale post-reload ────────────────────────────────────
796
807
  // Applica eventuale filtro contestuale passato via reload({ search: … })
797
808
  // Lo facciamo qui, dentro datatable.init, per essere certi che
798
809
  // simple-datatables abbia completato il render dei nuovi dati.
@@ -807,14 +818,11 @@ class SimpleDatatableAdapter extends HTMLElement {
807
818
  }
808
819
  }
809
820
 
810
- // Renderizza il tfoot (se almeno una colonna ha _footerRender)
821
+ // ── Tfoot, info, top slot ─────────────────────────────────────────────
822
+ // Eseguiti sempre, indipendentemente dalla presenza della paginazione.
811
823
  this._renderTfoot();
812
-
813
- // Aggiorna il testo info con il totale assoluto
824
+ this._injectTopSlot(); // va prima di _updateInfo per creare il nodo top-info
814
825
  this._updateInfo();
815
-
816
- // Inietta l'elemento nello slot top-sinistra (se configurato)
817
- this._injectTopSlot();
818
826
  });
819
827
 
820
828
  // Salva la pagina corrente nel cookie ad ogni cambio di pagina;
@@ -927,84 +935,123 @@ class SimpleDatatableAdapter extends HTMLElement {
927
935
  /**
928
936
  * Sovrascrive il testo dell'area `.info` generato da simple-datatables per
929
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.
930
944
  *
931
945
  * Comportamento:
932
946
  * - Senza ricerca attiva:
933
947
  * "Stai visualizzando le righe da {start} a {end}, su un totale di {total}."
934
- * - Con ricerca attiva:
935
- * "Stai visualizzando le righe da {start} a {end} dei {filtered} record filtrati,
936
- * 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}."
937
951
  * - Nessun risultato (ricerca attiva, 0 match):
938
952
  * "Nessun risultato per la ricerca corrente (totale record: {total})."
939
953
  *
940
- * Viene chiamato da datatable.init, datatable.page, datatable.search,
941
- * datatable.multisearch.
954
+ * Il nodo top-info viene nascosto quando filtered = 0 o total = 0.
942
955
  */
943
956
  _updateInfo() {
944
- const infoEl = this.querySelector(`.${styles.info}`);
945
- if (!infoEl) return;
957
+ const infoEl = this.querySelector(`.${styles.info}`);
958
+ const topInfoEl = this.querySelector('.datatable-top-info');
946
959
 
947
- const dt = this._dt;
948
- const total = this._rawData?.length ?? 0;
960
+ if (!infoEl && !topInfoEl) return;
949
961
 
950
- // Numero di record nel set corrente (filtrati o tutti)
962
+ const dt = this._dt;
963
+ const total = this._rawData?.length ?? 0;
951
964
  const filtered = dt.searching ? dt.searchData.length : total;
952
965
 
953
- if (filtered === 0 && dt.searching) {
954
- infoEl.textContent = `Nessun risultato per la ricerca corrente (totale record: ${total}).`;
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;
955
977
  return;
956
978
  }
957
979
 
958
- // Calcola start/end della pagina corrente
959
- const perPage = dt.options.perPage;
960
- const currentPage = dt.currentPage; // 1-based
961
- const start = (currentPage - 1) * perPage + 1;
962
- const end = Math.min(currentPage * perPage, filtered);
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;
963
992
 
964
993
  if (dt.searching) {
965
- infoEl.textContent =
994
+ text =
966
995
  `Stai visualizzando le righe da ${start} a ${end} ` +
967
996
  `dei ${filtered} record filtrati, su un totale di ${total}.`;
968
997
  } else {
969
- infoEl.textContent =
998
+ text =
970
999
  `Stai visualizzando le righe da ${start} a ${end}, ` +
971
1000
  `su un totale di ${total}.`;
972
1001
  }
1002
+
1003
+ if (infoEl) infoEl.textContent = text;
1004
+ if (topInfoEl) { topInfoEl.textContent = text; topInfoEl.hidden = false; }
973
1005
  }
974
1006
 
975
1007
 
976
1008
  // ─── Top slot ────────────────────────────────────────────────────────────────
977
1009
 
978
1010
  /**
979
- * Inietta un elemento DOM o del markup HTML nell'area in alto a sinistra
980
- * del datatable — lo spazio normalmente occupato dal selettore "righe per
981
- * pagina" (qui disabilitato con `perPageSelect: false`).
1011
+ * Inietta nell'area in alto a sinistra del datatable il contenuto configurato.
982
1012
  *
983
- * Il parametro `topSlot` accetta:
984
- * - un `Element` (già costruito fuori dal componente)
985
- * - una stringa HTML (viene parsata con `innerHTML`)
1013
+ * Modalità:
986
1014
  *
987
- * L'elemento viene inserito come primo figlio dell'area `.topArea`,
988
- * prima della search box, avvolto in un `<div class="datatable-top-slot">`.
989
- * Se `topSlot` non è definito, non viene inserito nulla.
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.
990
1020
  *
991
- * Esempio – stringa HTML:
992
- * el.init({
993
- * topSlot: '<button class="btn btn-sm btn-primary">Esporta CSV</button>',
994
- * });
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>' });
995
1032
  *
996
- * Esempio – elemento DOM:
997
1033
  * const btn = document.createElement('button');
998
- * btn.textContent = 'Nuova commessa';
1034
+ * btn.textContent = 'Nuovo';
999
1035
  * el.init({ topSlot: btn });
1000
1036
  */
1001
1037
  _injectTopSlot() {
1002
- const topSlot = this._getParam('topSlot', null);
1003
- if (topSlot == null) return;
1004
-
1005
1038
  const topArea = this.querySelector(`.${styles.topArea}`);
1006
1039
  if (!topArea) return;
1007
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
+
1008
1055
  const wrapper = document.createElement('div');
1009
1056
  wrapper.className = 'datatable-top-slot';
1010
1057
 
@@ -1014,7 +1061,6 @@ class SimpleDatatableAdapter extends HTMLElement {
1014
1061
  wrapper.innerHTML = topSlot;
1015
1062
  }
1016
1063
 
1017
- // Inserisce prima della search box (primo figlio esistente)
1018
1064
  topArea.insertBefore(wrapper, topArea.firstChild);
1019
1065
  }
1020
1066