@massimo-cassandro/minimo 0.16.2 → 1.0.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.
@@ -71,6 +71,18 @@
71
71
  "$value": "{snackbar.text.color}"
72
72
  }
73
73
  },
74
+ "background": {
75
+ "color": {
76
+ "$type": "color",
77
+ "$value": "transparent"
78
+ }
79
+ },
80
+ "border": {
81
+ "color": {
82
+ "$type": "color",
83
+ "$value": "transparent"
84
+ }
85
+ },
74
86
  "hover": {
75
87
  "background": {
76
88
  "color": {
@@ -0,0 +1,132 @@
1
+ // -----------------------------------------------------------------------
2
+ // Generated file — do not edit manually.
3
+ // This file is produced by the design-token build pipeline.
4
+ // Source of truth: the token files in the project source directory.
5
+ // Re-generate with: node build-tokens.mjs --config <path/to/config>
6
+ // -----------------------------------------------------------------------
7
+ {
8
+ "up": {
9
+ "bg": {
10
+ "color": {
11
+ "$type": "color",
12
+ "$value": "hsl(0deg 0% 0% / .7)"
13
+ }
14
+ },
15
+ "text": {
16
+ "color": {
17
+ "$type": "color",
18
+ "$value": "#fff"
19
+ }
20
+ },
21
+ "line-height": {
22
+ "$type": "number",
23
+ "$value": "{line-height.sm}"
24
+ },
25
+ "font": {
26
+ "family": {
27
+ "$type": "fontFamily",
28
+ "$value": "{font.family}"
29
+ },
30
+ "size": {
31
+ "$type": "dimension",
32
+ "$value": "{font.size.base}"
33
+ },
34
+ "weight": {
35
+ "$type": "fontWeight",
36
+ "$value": "{font.weight.regular}"
37
+ },
38
+ "variation-settings": {
39
+ "$type": "string",
40
+ "$value": "unset"
41
+ }
42
+ },
43
+ "credits": {
44
+ "font": {
45
+ "size": {
46
+ "$type": "dimension",
47
+ "$value": "{font.size.xs}"
48
+ },
49
+ "weight": {
50
+ "$type": "fontWeight",
51
+ "$value": "{font.weight.regular}"
52
+ }
53
+ },
54
+ "line-height": {
55
+ "$type": "number",
56
+ "$value": "{line-height.xs}"
57
+ }
58
+ },
59
+ "h1": {
60
+ "color": {
61
+ "$type": "color",
62
+ "$value": "{up.text.color}"
63
+ },
64
+ "font": {
65
+ "size": {
66
+ "$type": "dimension",
67
+ "$value": "{font.size.xl}"
68
+ },
69
+ "weight": {
70
+ "$type": "fontWeight",
71
+ "$value": "{font.weight.bold}"
72
+ },
73
+ "variation-settings": {
74
+ "$type": "string",
75
+ "$value": "unset"
76
+ }
77
+ },
78
+ "line-height": {
79
+ "$type": "number",
80
+ "$value": "{line-height.xs}"
81
+ }
82
+ },
83
+ "icon": {
84
+ "box": {
85
+ "bg": {
86
+ "$type": "color",
87
+ "$value": "{up.link.color}"
88
+ }
89
+ },
90
+ "fill": {
91
+ "color": {
92
+ "$type": "color",
93
+ "$value": "#333"
94
+ }
95
+ },
96
+ "hover": {
97
+ "box": {
98
+ "bg": {
99
+ "$type": "color",
100
+ "$value": "#fff"
101
+ }
102
+ }
103
+ }
104
+ },
105
+ "link": {
106
+ "color": {
107
+ "$type": "color",
108
+ "$value": "#fc0"
109
+ },
110
+ "decoration": {
111
+ "$type": "string",
112
+ "$value": "none"
113
+ },
114
+ "hover": {
115
+ "color": {
116
+ "$type": "color",
117
+ "$value": "#fc0"
118
+ },
119
+ "decoration": {
120
+ "$type": "string",
121
+ "$value": "underline"
122
+ }
123
+ }
124
+ },
125
+ "loader": {
126
+ "color": {
127
+ "$type": "color",
128
+ "$value": "hsl(0deg 0% 100% / .6)"
129
+ }
130
+ }
131
+ }
132
+ }
@@ -52,12 +52,6 @@
52
52
  "$value": "clamp(2.2rem, .2308rem + 3.0769vw, 3rem)"
53
53
  }
54
54
  },
55
- "line-height": {
56
- "base": {
57
- "$type": "number",
58
- "$value": "1.5"
59
- }
60
- },
61
55
  "style": {
62
56
  "base": {
63
57
  "$type": "string",
@@ -98,5 +92,19 @@
98
92
  "$value": "900"
99
93
  }
100
94
  }
95
+ },
96
+ "line-height": {
97
+ "base": {
98
+ "$type": "number",
99
+ "$value": 1.5
100
+ },
101
+ "sm": {
102
+ "$type": "number",
103
+ "$value": 1.4
104
+ },
105
+ "xs": {
106
+ "$type": "number",
107
+ "$value": 1.25
108
+ }
101
109
  }
102
110
  }
@@ -1,5 +1,5 @@
1
1
 
2
- ## SD Types
2
+ # SD Types
3
3
 
4
4
  * **Color** (`$type` value: `color`)
5
5
  * Colors in Hex, RGB, HSL, or standard names. ("#00ff00" or "rgba(0,0,0,0.5)" )
@@ -15,6 +15,9 @@
15
15
  * Name of the font family or list (array). ("Inter", ["Helvetica", "Arial"] )
16
16
  * **Font Weight** (`fontWeight`)
17
17
  * Font weight (number or text alias). (700, "bold", "light" )
18
+
19
+ ## compounds:
20
+
18
21
  * **border** (`border`)
19
22
  * Compound: requires color, width, style. ({"color": "#000", "width": "1px", "style": "solid"} )
20
23
  * **Shadow** (`shadow`)
@@ -69,6 +69,18 @@ export default{
69
69
  $type: 'color'
70
70
  }
71
71
  },
72
+ background: {
73
+ color: {
74
+ $value: 'transparent',
75
+ $type: 'color'
76
+ }
77
+ },
78
+ border: {
79
+ color: {
80
+ $value: 'transparent',
81
+ $type: 'color'
82
+ }
83
+ },
72
84
 
73
85
  hover: {
74
86
  background: {
@@ -0,0 +1,141 @@
1
+ export default {
2
+ up: {
3
+ bg: {
4
+ color: {
5
+ $type: 'color',
6
+ $value: 'hsl(0deg 0% 0% / .7)'
7
+ }
8
+ },
9
+ text: {
10
+ color: {
11
+ $type: 'color',
12
+ $value: '#fff'
13
+
14
+ }
15
+ },
16
+ 'line-height': {
17
+ $type: 'number',
18
+ $value: '{line-height.sm}'
19
+ },
20
+ font: {
21
+ family: {
22
+ $type: 'fontFamily',
23
+ $value: '{font.family}'
24
+ },
25
+ size: {
26
+ $type: 'dimension',
27
+ $value: '{font.size.base}'
28
+
29
+ },
30
+ weight: {
31
+ $type: 'fontWeight',
32
+ $value: '{font.weight.regular}'
33
+ },
34
+ 'variation-settings': {
35
+ $type: 'string',
36
+ $value: 'unset'
37
+ }
38
+ }, // end base setup
39
+
40
+ // credit box
41
+ credits: {
42
+ font: {
43
+ size: {
44
+ $type: 'dimension',
45
+ $value: '{font.size.xs}'
46
+ },
47
+ weight: {
48
+ $type: 'fontWeight',
49
+ $value: '{font.weight.regular}'
50
+ },
51
+ },
52
+ 'line-height': {
53
+ $type: 'number',
54
+ $value: '{line-height.xs}'
55
+ },
56
+ }, // end credits
57
+
58
+ // title
59
+ h1: {
60
+ color: {
61
+ $type: 'color',
62
+ $value: '{up.text.color}'
63
+ },
64
+ font: {
65
+ size: {
66
+ $type: 'dimension',
67
+ $value: '{font.size.xl}'
68
+ },
69
+ weight: {
70
+ $type: 'fontWeight',
71
+ $value: '{font.weight.bold}'
72
+ },
73
+ 'variation-settings': {
74
+ $type: 'string',
75
+ $value: 'unset'
76
+ }
77
+ },
78
+ 'line-height': {
79
+ $type: 'number',
80
+ $value: '{line-height.xs}'
81
+ },
82
+ }, // end h1
83
+
84
+ // icons
85
+ icon: {
86
+ box: {
87
+ bg: {
88
+ $type: 'color',
89
+ $value: '{up.link.color}'
90
+ }
91
+ },
92
+ fill: {
93
+ color: {
94
+ $type: 'color',
95
+ $value: '#333'
96
+ }
97
+ },
98
+ hover: {
99
+ box: {
100
+ bg: {
101
+ $type: 'color',
102
+ $value: '#fff'
103
+ }
104
+ },
105
+ }
106
+ }, // end icons
107
+
108
+ // links
109
+ link: {
110
+ color: {
111
+ $type: 'color',
112
+ $value: '#fc0'
113
+ },
114
+ decoration: {
115
+ $type: 'string',
116
+ $value: 'none'
117
+ },
118
+ hover: {
119
+ color: {
120
+ $type: 'color',
121
+ $value: '#fc0'
122
+ },
123
+ decoration: {
124
+ $type: 'string',
125
+ $value: 'underline'
126
+ },
127
+
128
+ }
129
+
130
+ }, // end link
131
+
132
+ // loader
133
+ loader: {
134
+ color: {
135
+ $type: 'color',
136
+ $value: 'hsl(0deg 0% 100% / .6)'
137
+ }
138
+ } // end loader
139
+
140
+ } // end up
141
+ };
@@ -55,16 +55,6 @@
55
55
  },
56
56
 
57
57
 
58
- // Rhythm in Web Typography | Better Web Type <https://betterwebtype.com/articles/2018/10/15/rhythm-in-web-typography/>
59
- // The good line-height <https://www.thegoodlineheight.com/>
60
-
61
- "line-height": {
62
- "base": {
63
- "$value": "1.5",
64
- "$type": "number"
65
- }
66
- },
67
-
68
58
  "style": {
69
59
  "base": {
70
60
  "$value": "normal",
@@ -116,5 +106,24 @@
116
106
  "$description": ""
117
107
  }
118
108
  }
119
- }
109
+ }, // end font
110
+
111
+ // Rhythm in Web Typography | Better Web Type <https://betterwebtype.com/articles/2018/10/15/rhythm-in-web-typography/>
112
+ // The good line-height <https://www.thegoodlineheight.com/>
113
+
114
+ "line-height": {
115
+ "base": {
116
+ "$value": 1.5,
117
+ "$type": "number"
118
+ },
119
+ "sm": {
120
+ "$value": 1.4,
121
+ "$type": "number"
122
+ },
123
+ "xs": {
124
+ "$value": 1.25,
125
+ "$type": "number"
126
+ }
127
+
128
+ },
120
129
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massimo-cassandro/minimo",
3
- "version": "0.16.2",
3
+ "version": "1.0.1",
4
4
  "description": "Light css framework & utilities",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/massimo-cassandro/minimo#readme",
@@ -1,2 +1,3 @@
1
1
  da rivedere
2
2
  spostare custom props nel dictionary
3
+ eliminare customization-templates, gestire tutto da tokens
@@ -1,2 +1,3 @@
1
1
  riscrivere css con sizes
2
2
  rivedere js
3
+ importare css direttamente nel js
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  PERSONALIZZAZIONE
3
- custom properties (valori di default):
3
+ custom properties (valori di default, vedi /design-tokens-src/components/unsplash-page.minimo.tokens.mjs):
4
4
 
5
5
  Basic props:
6
6
  --up-bg-color: hsl(0deg 0% 0% / .7);
@@ -157,14 +157,14 @@
157
157
  }
158
158
 
159
159
  .upArrowWrapper {
160
- --arrow-size: 2rem;
161
- --arrow-position: calc(var(--arrow-size) * .25 * -1);
160
+ --_arrow-size: 2rem;
161
+ --_arrow-position: calc(var(--_arrow-size) * .25 * -1);
162
162
 
163
163
  position: absolute;
164
- bottom: var(--arrow-position);
165
- left: var(--arrow-position);
166
- width: var(--arrow-size);
167
- height: var(--arrow-size);
164
+ bottom: var(--_arrow-position);
165
+ left: var(--_arrow-position);
166
+ width: var(--_arrow-size);
167
+ height: var(--_arrow-size);
168
168
  color: var(--up-icon-fill-color, #333);
169
169
  cursor: w-resize;
170
170
  background-color: var(--up-icon-box-bg, var(--up-link-color, #fc0));
@@ -189,7 +189,7 @@
189
189
  &.hidden {
190
190
  translate: calc(-1 * (50dvw + 50% - 2rem));
191
191
  .upArrowWrapper {
192
- right: var(--arrow-position);
192
+ right: var(--_arrow-position);
193
193
  left: auto;
194
194
  cursor: e-resize;
195
195
  rotate: .5turn;
@@ -6,7 +6,7 @@ html, body {
6
6
  font-family: var(--font-family);
7
7
  font-size: var(--font-size-base);
8
8
  font-style: var(--font-style-base);
9
- line-height: var(--font-line-height-base);
9
+ line-height: var(--line-height-base);
10
10
  color: var(--text-color);
11
11
  accent-color: var(--accent-color);
12
12
  background-color: var(--body-background-color);
@@ -92,7 +92,6 @@
92
92
  --flex-grid-gap: 1.5rem;
93
93
  --font-family: sans-serif;
94
94
  --font-family-mono: 'Monaspace Neon Var', 'Monaspace Neon', 'Aptos Mono', 'Roboto Mono', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', 'Liberation Mono', 'Andale Mono', 'Courier New', monospace;
95
- --font-line-height-base: 1.5;
96
95
  --font-size-2xl: clamp(2rem, .7692rem + 1.9231vw, 2.5rem);
97
96
  --font-size-3xl: clamp(2.2rem, .2308rem + 3.0769vw, 3rem);
98
97
  --font-size-base: clamp(.9rem, .6538rem + .3846vw, 1rem);
@@ -182,6 +181,9 @@
182
181
  --headings-h4-font-size: var(--font-size-md);
183
182
  --headings-text-align: inherit;
184
183
  --layout-container-max-width: 80rem;
184
+ --line-height-base: 1.5;
185
+ --line-height-sm: 1.4;
186
+ --line-height-xs: 1.25;
185
187
  --link-active-background-color: transparent;
186
188
  --link-active-color: #51054c;
187
189
  --link-color: #0d12ae;
@@ -234,6 +236,8 @@
234
236
  --snackbar-border-radius: var(--radius-xs);
235
237
  --snackbar-btn-close-active-background-color: transparent;
236
238
  --snackbar-btn-close-active-icon-color: var(--snackbar-text-color);
239
+ --snackbar-btn-close-background-color: transparent;
240
+ --snackbar-btn-close-border-color: transparent;
237
241
  --snackbar-btn-close-focus-outline-color: color-mix(in srgb, var(--snackbar-text-color) 50%, transparent);
238
242
  --snackbar-btn-close-focus-outline-offset: .125rem;
239
243
  --snackbar-btn-close-focus-outline-width: .125rem;
@@ -287,6 +291,29 @@
287
291
  --text-negative: #fff;
288
292
  --text-selected-background-color: #ddd;
289
293
  --text-selected-color: var(--body-text);
294
+ --up-bg-color: hsl(0deg 0% 0% / .7);
295
+ --up-credits-font-size: var(--font-size-xs);
296
+ --up-credits-font-weight: var(--font-weight-regular);
297
+ --up-credits-line-height: var(--line-height-xs);
298
+ --up-font-family: var(--font-family);
299
+ --up-font-size: var(--font-size-base);
300
+ --up-font-variation-settings: unset;
301
+ --up-font-weight: var(--font-weight-regular);
302
+ --up-h1-color: var(--up-text-color);
303
+ --up-h1-font-size: var(--font-size-xl);
304
+ --up-h1-font-variation-settings: unset;
305
+ --up-h1-font-weight: var(--font-weight-bold);
306
+ --up-h1-line-height: var(--line-height-xs);
307
+ --up-icon-box-bg: var(--up-link-color);
308
+ --up-icon-fill-color: #333;
309
+ --up-icon-hover-box-bg: #fff;
310
+ --up-line-height: var(--line-height-sm);
311
+ --up-link-color: #fc0;
312
+ --up-link-decoration: none;
313
+ --up-link-hover-color: #fc0;
314
+ --up-link-hover-decoration: underline;
315
+ --up-loader-color: hsl(0deg 0% 100% / .6);
316
+ --up-text-color: #fff;
290
317
  --z-index-overlay: 99;
291
318
  --z-index-snackbar: 100;
292
319
  }
@@ -106,25 +106,6 @@ import caretRightIcon from '../../icons/caret-right.svg?inline';
106
106
  * Quando è false il footer si aggiorna solo al cambio di
107
107
  * filtro, non al cambio pagina.
108
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: true.
127
- *
128
109
  * @param {string[]} refs Elenco di percorsi URL da cui, se si proviene,
129
110
  * la pagina corrente viene ripristinata dal cookie
130
111
  * di sessione `sd-pag`. Se il referrer non è tra
@@ -233,6 +214,22 @@ class SimpleDatatableAdapter extends HTMLElement {
233
214
  this._pendingSearch = null;
234
215
  this._loadGeneration = 0;
235
216
 
217
+ /**
218
+ * Conteggio centralizzato delle righe attualmente visibili (post-filtro).
219
+ * Viene inizializzato al totale in datatable.init e aggiornato dall'evento
220
+ * datatable.search tramite dt.data?.data.length.
221
+ * È l'unica fonte di verità per _renderTfoot().
222
+ */
223
+ this._filteredCount = 0;
224
+
225
+ /**
226
+ * Array di record raw attualmente visibili (post-filtro, pre-paginazione).
227
+ * Popolato da onFilter al momento dell'evento datatable.search/multisearch,
228
+ * quando dt.searchData è garantito aggiornato.
229
+ * null = nessun filtro attivo → _renderTfoot usa rawData direttamente.
230
+ */
231
+ this._filteredData = null;
232
+
236
233
  // Handler bound per poterli rimuovere in disconnectedCallback
237
234
  this._onSearch = this._handleSearch.bind(this);
238
235
  this._onMultiSearch = this._handleMultiSearch.bind(this);
@@ -413,17 +410,11 @@ class SimpleDatatableAdapter extends HTMLElement {
413
410
  // 2. Attributo HTML
414
411
  const attr = this.getAttribute(name);
415
412
  if (attr !== null) {
416
- // Auto-parse booleani quando il defaultValue è boolean e il valore
417
- // è una delle stringhe canoniche true/false/0/1, oppure true/false.
418
- // Casi supportati:
419
- // showInfoAtTop (attributo senza valore → attr === '') → true
420
- // showInfoAtTop="true" / showInfoAtTop="1" → true
421
- // showInfoAtTop="false" / showInfoAtTop="0" → false
422
413
  // Auto-parse booleani solo quando il parametro è dichiaratamente booleano
423
414
  // (defaultValue === true | false). Copre tutti i formati HTML idiomatici:
424
- // showInfoAtTop (attributo senza valore → '') → true
425
- // showInfoAtTop="true" / showInfoAtTop="1" → true
426
- // showInfoAtTop="false" / showInfoAtTop="0" → false
415
+ // updateFooterOnPageChange (attributo senza valore → '') → true
416
+ // updateFooterOnPageChange="true" / updateFooterOnPageChange="1" → true
417
+ // updateFooterOnPageChange="false" / updateFooterOnPageChange="0" → false
427
418
  // I valori "1" e "0" NON vengono intercettati qui se defaultValue non è
428
419
  // boolean, per non interferire con parametri numerici (es. perPage="1").
429
420
  const trimmed = attr.trim();
@@ -733,10 +724,24 @@ class SimpleDatatableAdapter extends HTMLElement {
733
724
  pageTitle: 'Pagina {page}',
734
725
  perPage: 'Righe per pagina',
735
726
  noRows: 'Nessun record trovato',
736
- info: 'Stai visualizzando le righe da {start} a {end}, su un totale di {rows}',
727
+ info: 'Stai visualizzando le righe da {start} a {end}, su un totale di {rows} record trovati',
737
728
  noResults: 'Nessun risultato per la ricerca',
738
729
  },
739
730
 
731
+ template: (options, dom) => `<div class='${options.classes.top}'>`+
732
+ `<div class='${options.classes.info}'></div>` +
733
+ (options.searchable
734
+ ? `<div class='${options.classes.search}'>` +
735
+ `<input class='${options.classes.input}' placeholder='${options.labels.placeholder}' type='search' title='${options.labels.searchTitle}'${dom.id ? ` aria-controls="${dom.id}"` : ''}>` +
736
+ '</div>'
737
+ : '') +
738
+ '</div>' +
739
+ `<div class='${options.classes.container}'${options.scrollY.length ? ` style='height: ${options.scrollY}; overflow-Y: auto;'` : ""}></div>`+
740
+ `<div class='${options.classes.bottom}'>`+
741
+ `<nav class='${options.classes.pagination}'></nav>` +
742
+ '</div>'
743
+ ,
744
+
740
745
  classes: {
741
746
  wrapper : styles.datatableWrapper,
742
747
  container : 'table-responsive',
@@ -788,6 +793,9 @@ class SimpleDatatableAdapter extends HTMLElement {
788
793
  });
789
794
 
790
795
  this._dt.on('datatable.init', () => {
796
+ // Inizializza il conteggio filtrato al totale: al primo render non c'è
797
+ // ricerca attiva, quindi tutti i record sono visibili.
798
+ this._filteredCount = this._rawData?.length ?? 0;
791
799
  // ── Icone paginazione ─────────────────────────────────────────────────
792
800
  const pagination = this.querySelector(`.${styles.paginationList}`);
793
801
  if (pagination) {
@@ -837,11 +845,9 @@ class SimpleDatatableAdapter extends HTMLElement {
837
845
  }
838
846
  }
839
847
 
840
- // ── Tfoot, info, top slot ─────────────────────────────────────────────
841
- // Eseguiti sempre, indipendentemente dalla presenza della paginazione.
848
+ // ── Tfoot ─────────────────────────────────────────────────────────────
849
+ // Eseguito sempre, indipendentemente dalla presenza della paginazione.
842
850
  this._renderTfoot();
843
- this._injectTopSlot(); // va prima di _updateInfo per creare il nodo top-info
844
- this._updateInfo();
845
851
  });
846
852
 
847
853
  // Salva la pagina corrente nel cookie ad ogni cambio di pagina;
@@ -849,13 +855,43 @@ class SimpleDatatableAdapter extends HTMLElement {
849
855
  this._dt.on('datatable.page', page => {
850
856
  this._savePageCookie(page);
851
857
  if (this._getParam('updateFooterOnPageChange', false)) this._renderTfoot();
852
- this._updateInfo();
853
858
  });
854
859
 
855
860
  // Aggiorna il tfoot ad ogni cambio di filtro (ricerca / multisearch).
861
+ // L'evento datatable.search è la fonte di verità centralizzata per il conteggio
862
+ // delle righe filtrate: _filteredCount viene aggiornato qui e poi consumato da
863
+ // _renderTfoot(), eliminando letture inconsistenti di dt.data?.data.
856
864
  // Il sort non altera il set filtrato: il footer non deve cambiare.
857
- this._dt.on('datatable.search', () => { this._renderTfoot(); this._updateInfo(); });
858
- this._dt.on('datatable.multisearch', () => { this._renderTfoot(); this._updateInfo(); });
865
+ // datatable.search emette (term, searchData)
866
+ // datatable.multisearch emette (queries, searchData)
867
+ // searchData è l'array degli indici in dt.data.data (post-sort), NON in rawData.
868
+ // Per risalire ai record raw si usa l'attributo 'data-jidx' scritto su ogni riga
869
+ // in fase di caricamento (_load), che preserva la corrispondenza con rawData
870
+ // anche dopo che simple-datatables ha riordinato dt.data.data internamente.
871
+ // Quando term === '' (reset), searchData è [] ma va usato rawData completo.
872
+ const onFilter = (termOrQueries, searchData) => {
873
+ const rawData = this._rawData ?? [];
874
+ const dt = this._dt;
875
+ const isReset = termOrQueries === '' ||
876
+ (Array.isArray(termOrQueries) && termOrQueries.length === 0);
877
+
878
+ if (isReset) {
879
+ this._filteredData = null; // null → _renderTfoot usa rawData
880
+ } else {
881
+ // Mappa l'indice dt.data.data → indice rawData tramite data-jidx
882
+ this._filteredData = searchData
883
+ .map(dtIdx => {
884
+ const jidx = dt.data?.data[dtIdx]?.attributes?.['data-jidx'];
885
+ return jidx != null ? rawData[jidx] : undefined;
886
+ })
887
+ .filter(Boolean);
888
+ }
889
+
890
+ this._filteredCount = this._filteredData?.length ?? rawData.length;
891
+ this._renderTfoot();
892
+ };
893
+ this._dt.on('datatable.search', onFilter);
894
+ this._dt.on('datatable.multisearch', onFilter);
859
895
 
860
896
  } // end _render
861
897
 
@@ -868,20 +904,27 @@ class SimpleDatatableAdapter extends HTMLElement {
868
904
  * Chiamato dopo il render iniziale, ad ogni cambio di filtro (ricerca /
869
905
  * multisearch) e — se `updateFooterOnPageChange` è true — anche ad ogni cambio pagina.
870
906
  *
907
+ * Il conteggio delle righe filtrate viene letto da `this._filteredCount`,
908
+ * aggiornato centralmente dall'handler di `datatable.search` / `datatable.multisearch`.
909
+ * Il tfoot viene rimosso quando non ci sono righe filtrate.
910
+ *
871
911
  * Dataset passato alla callback `_footerRender(data)`:
872
912
  *
873
913
  * updateFooterOnPageChange: false (default)
874
914
  * → l'intero set filtrato, indipendente dalla pagina corrente.
875
915
  * I totali non cambiano navigando tra le pagine.
876
916
  * Se non c'è ricerca attiva, viene passato il dataset completo.
917
+ * I record filtrati vengono letti da `this._filteredData`, popolato
918
+ * in onFilter dagli argomenti dell'evento datatable.search/multisearch,
919
+ * che passano `searchData` (array di indici) direttamente senza dover
920
+ * leggere lo stato interno di simple-datatables.
921
+ * null = reset o nessun filtro → rawData completo.
877
922
  *
878
923
  * updateFooterOnPageChange: true
879
924
  * → solo i record della pagina corrente.
880
925
  * Utile per subtotali di pagina.
881
926
  *
882
- * `searchData` di simple-datatables è un array di indici nel dataset
883
- * originale, disponibile (e non vuoto) solo quando `dt.searching === true`.
884
- * Quando la ricerca è attiva ma non trova nulla, `searchData` è `[]`:
927
+ * Quando la ricerca non trova nulla, `_filteredData` è `[]`:
885
928
  * in quel caso la callback riceve un array vuoto, non il dataset completo.
886
929
  */
887
930
  _renderTfoot() {
@@ -896,26 +939,29 @@ class SimpleDatatableAdapter extends HTMLElement {
896
939
  const table = this.querySelector('table');
897
940
  if (!table) return;
898
941
 
899
- const dt = this._dt;
942
+ const dt = this._dt;
943
+ const filteredCount = this._filteredCount;
944
+
945
+ // Rimuove il tfoot quando non ci sono righe filtrate
946
+ if (filteredCount === 0) {
947
+ table.querySelector('tfoot')?.remove();
948
+ return;
949
+ }
950
+
900
951
  const updateFooterOnPageChange = this._getParam('updateFooterOnPageChange', false);
901
952
 
902
953
  let visibleData;
903
954
 
904
955
  if (updateFooterOnPageChange) {
905
- // Dati della pagina corrente: ogni "pagina" in dt.pages è un array
906
- // di oggetti riga; ciascuno ha una proprietà data-index che punta
907
- // all'indice nel dataset originale.
908
956
  const pageRows = dt.pages?.[dt.currentPage - 1] ?? [];
909
957
  visibleData = pageRows
910
958
  .map(row => rawData[row['data-index']])
911
959
  .filter(Boolean);
912
960
  } else {
913
- // Dataset filtrato completo (ignorando la paginazione).
914
- // dt.searching è true solo quando c'è una ricerca attiva;
915
- // in quel caso searchData contiene gli indici dei match (può essere []).
916
- visibleData = dt.searching
917
- ? dt.searchData.map(idx => rawData[idx]).filter(Boolean)
918
- : rawData;
961
+ // this._filteredData è popolato in onFilter dagli argomenti dell'evento
962
+ // datatable.search / datatable.multisearch (che passano searchData direttamente).
963
+ // null = nessun filtro attivo (o reset) → usiamo rawData completo.
964
+ visibleData = this._filteredData ?? rawData;
919
965
  }
920
966
 
921
967
  // Riutilizza tfoot esistente o ne crea uno nuovo
@@ -949,140 +995,6 @@ class SimpleDatatableAdapter extends HTMLElement {
949
995
  tfoot.replaceChildren(tr);
950
996
  }
951
997
 
952
- // ─── Info label ──────────────────────────────────────────────────────────────
953
-
954
- /**
955
- * Sovrascrive il testo dell'area `.info` generato da simple-datatables per
956
- * includere sempre il totale assoluto dei record (indipendente dal filtro).
957
- * Se `showInfoAtTop` è attivo, aggiorna in sincronia anche il nodo `.info`
958
- * nell'area superiore (creato da `_injectTopSlot`).
959
- *
960
- * Calcolo di start/end: si conta il numero di righe renderizzate nel tbody
961
- * (`dt.pages[currentPage - 1]`) invece di fare aritmetica su perPage,
962
- * perché `dt.currentPage` può essere undefined al primo render.
963
- *
964
- * Comportamento:
965
- * - Senza ricerca attiva:
966
- * "Stai visualizzando le righe da {start} a {end}, su un totale di {total}."
967
- * - Con ricerca attiva (risultati > 0):
968
- * "Stai visualizzando le righe da {start} a {end} dei {filtered} record
969
- * filtrati, su un totale di {total}."
970
- * - Nessun risultato (ricerca attiva, 0 match):
971
- * "Nessun risultato per la ricerca corrente (totale record: {total})."
972
- *
973
- * Il nodo top-info viene nascosto quando filtered = 0 o total = 0.
974
- */
975
- _updateInfo() {
976
- const infoEl = this.querySelector(`.${styles.info}`);
977
- const topInfoEl = this.querySelector('.datatable-top-info');
978
-
979
- if (!infoEl && !topInfoEl) return;
980
-
981
- const dt = this._dt;
982
- const total = this._rawData?.length ?? 0;
983
- const filtered = dt.searching ? dt.searchData.length : total;
984
-
985
- let text;
986
-
987
- if (total === 0 || (filtered === 0 && dt.searching)) {
988
- // Nessun risultato — nasconde il nodo top-info se presente
989
- if (topInfoEl) topInfoEl.hidden = true;
990
-
991
- text = filtered === 0 && dt.searching
992
- ? `Nessun risultato per la ricerca corrente (totale record: ${total}).`
993
- : `Nessun record trovato.`;
994
-
995
- if (infoEl) infoEl.textContent = text;
996
- return;
997
- }
998
-
999
- // Calcola start/end dalla pagina corrente in dt.pages (più affidabile di
1000
- // currentPage * perPage, che può dare NaN se currentPage è undefined
1001
- // al primo render oppure quando non c'è paginazione).
1002
- const currentPage = dt.currentPage ?? 1;
1003
- const pageRows = dt.pages?.[currentPage - 1] ?? [];
1004
- const perPage = dt.options?.perPage ?? filtered;
1005
- const start = pageRows.length > 0
1006
- ? (currentPage - 1) * perPage + 1
1007
- : 1;
1008
- const end = pageRows.length > 0
1009
- ? start + pageRows.length - 1
1010
- : filtered;
1011
-
1012
- if (dt.searching) {
1013
- text =
1014
- `Stai visualizzando le righe da ${start} a ${end} ` +
1015
- `dei ${filtered} record filtrati, su un totale di ${total}.`;
1016
- } else {
1017
- text =
1018
- `Stai visualizzando le righe da ${start} a ${end}, ` +
1019
- `su un totale di ${total}.`;
1020
- }
1021
-
1022
- if (infoEl) infoEl.textContent = text;
1023
- if (topInfoEl) { topInfoEl.textContent = text; topInfoEl.hidden = false; }
1024
- }
1025
-
1026
-
1027
- // ─── Top slot ────────────────────────────────────────────────────────────────
1028
-
1029
- /**
1030
- * Inietta nell'area in alto a sinistra del datatable il contenuto configurato.
1031
- *
1032
- * Modalità:
1033
- *
1034
- * showInfoAtTop: true (ha precedenza su topSlot)
1035
- * Inserisce un nodo `.datatable-top-info` con la stessa stringa dell'area
1036
- * `.info` in basso, mantenuta in sync da `_updateInfo`. Viene nascosto
1037
- * automaticamente quando filtered = 0 o total = 0.
1038
- * Il parametro `topSlot`, se presente, viene ignorato.
1039
- *
1040
- * topSlot: Element | string (usato solo se showInfoAtTop è false/assente)
1041
- * Inserisce un elemento DOM o markup HTML arbitrario, avvolto in un
1042
- * `<div class="datatable-top-slot">`.
1043
- *
1044
- * In entrambi i casi l'elemento viene inserito come primo figlio di `.topArea`,
1045
- * prima della search box.
1046
- *
1047
- * Esempi:
1048
- * el.init({ showInfoAtTop: true });
1049
- *
1050
- * el.init({ topSlot: '<button class="btn btn-sm btn-primary">Esporta</button>' });
1051
- *
1052
- * const btn = document.createElement('button');
1053
- * btn.textContent = 'Nuovo';
1054
- * el.init({ topSlot: btn });
1055
- */
1056
- _injectTopSlot() {
1057
- const topArea = this.querySelector(`.${styles.topArea}`);
1058
- if (!topArea) return;
1059
-
1060
- const showInfoAtTop = this._getParam('showInfoAtTop', true);
1061
-
1062
- if (showInfoAtTop) {
1063
- // Crea il nodo info top (il testo verrà scritto da _updateInfo)
1064
- const infoTop = document.createElement('div');
1065
- infoTop.className = `datatable-top-info ${styles.info}`;
1066
- infoTop.hidden = true; // nascosto finché _updateInfo non lo popola
1067
- topArea.insertBefore(infoTop, topArea.firstChild);
1068
- return;
1069
- }
1070
-
1071
- const topSlot = this._getParam('topSlot', null);
1072
- if (topSlot == null) return;
1073
-
1074
- const wrapper = document.createElement('div');
1075
- wrapper.className = 'datatable-top-slot';
1076
-
1077
- if (topSlot instanceof Element) {
1078
- wrapper.appendChild(topSlot);
1079
- } else {
1080
- wrapper.innerHTML = topSlot;
1081
- }
1082
-
1083
- topArea.insertBefore(wrapper, topArea.firstChild);
1084
- }
1085
-
1086
998
 
1087
999
  } // end component
1088
1000
 
@@ -20,8 +20,7 @@
20
20
  justify-content: flex-end;
21
21
  padding-block-end: var(--size-xs);
22
22
 
23
- /* stylelint-disable-next-line selector-class-pattern */
24
- &:has(:global(.datatable-top-info)), &:has(:global(.datatable-top-slot)) {
23
+ &:has(.info), &:has(:global(.datatable-top-slot)) {
25
24
  justify-content: space-between;
26
25
  }
27
26
  }
@@ -137,7 +136,7 @@
137
136
  gap: var(--size-md);
138
137
  align-content: center;
139
138
  align-items: center;
140
- justify-content: space-between;
139
+ justify-content: flex-end;
141
140
  margin-block-start: var(--size-xs);
142
141
  }
143
142
  .info {