@massimo-cassandro/minimo 0.11.0 → 0.11.2

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.
File without changes
package/index.js CHANGED
@@ -1,4 +1,14 @@
1
+
2
+ // utilities
1
3
  export { classnames } from './src/utilities/classnames.js';
2
4
  export { domBuilder } from './src/utilities/dom-builder/dom-builder.js';
3
5
  export { enableSubmitBtns } from './src/utilities/enable-submit-btns.js';
6
+
7
+ // components
4
8
  export { default as modalAlert } from './src/components/modal-alert/modal-alert.js';
9
+ export { dismissAlerts } from './src/components/dismiss-alerts.js';
10
+ export { formResetSubmit } from './src/components/form-reset-submit.js';
11
+ export { innerNav } from './src/components/inner-nav/inner-nav.js';
12
+ export { overlay, removeOverlay } from './src/components/overlay/overlay.js';
13
+ export { sf_macro } from './src/components/sf-macro/sf-macro.js';
14
+ export { snackbar } from './src/components/snackbar/snackbar.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massimo-cassandro/minimo",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "description": "Light css framework & utilities",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/massimo-cassandro/minimo#readme",
@@ -30,16 +30,7 @@
30
30
  "access": "public"
31
31
  },
32
32
  "main": "./index.js",
33
- "sideEffects": [
34
- "*.css",
35
- "*.js",
36
- "*.svg",
37
- "*.jpg",
38
- "*.jpeg",
39
- "*.png",
40
- "*.webp",
41
- "*.avif"
42
- ],
33
+ "sideEffects": false,
43
34
  "files": [
44
35
  "./index.js",
45
36
  "src/**/*.{js,mjs,css,svg,md}",
@@ -1,6 +1,7 @@
1
- // dismiss alerts
2
- document.querySelectorAll('[data-dismiss]').forEach(item => {
3
- item.addEventListener('click', () => {
4
- item.closest('.' + item.dataset.dismiss).remove();
5
- }, false);
6
- });
1
+ export function dismissAlerts(){
2
+ document.querySelectorAll('[data-dismiss]').forEach(item => {
3
+ item.addEventListener('click', () => {
4
+ item.closest('.' + item.dataset.dismiss).remove();
5
+ }, false);
6
+ });
7
+ }
@@ -0,0 +1,38 @@
1
+ // import './form.css'; //incorporato nel css principale
2
+ import { enableSubmitBtns } from '../../index.js';
3
+
4
+ export function formResetSubmit(){
5
+
6
+ document.querySelectorAll('form').forEach( form => {
7
+
8
+ // form.querySelectorAll('[type="submit"]').forEach(btn =>{
9
+ // btn.addEventListener('click', () => {
10
+ // form.classList.add('was-validated');
11
+ // }, false);
12
+ // });
13
+
14
+ form.addEventListener('submit', e => {
15
+
16
+ if(!form.hasAttribute('data-no-was-validated')) {
17
+ form.classList.add('was-validated');
18
+ }
19
+
20
+ form.querySelectorAll('.is-invalid, .is-valid').forEach(item => {
21
+ item.classList.remove('is-invalid', 'is-valid');
22
+ });
23
+
24
+ if(!form.checkValidity()) {
25
+ e.preventDefault();
26
+ enableSubmitBtns();
27
+
28
+ } else {
29
+ if(!form.hasAttribute('data-no-disabling') && !form.hasAttribute('data-disable-submit')) { /* 'data-disable-submit' per compatibilitò con verioni precedenti */
30
+ form.querySelectorAll('[type="submit"]').forEach(btn => {
31
+ btn.disabled = true;
32
+ });
33
+ }
34
+ }
35
+ }, false);
36
+ });
37
+ }
38
+
@@ -7,13 +7,16 @@
7
7
 
8
8
  import './inner-nav.css';
9
9
 
10
- let nav_primary = document.querySelector('.inner-nav-main');
11
- if (nav_primary) {
12
- // nav_primary.classList.add('text-right', 'd-print-none');
13
- let nav = nav_primary.innerHTML;
10
+ export function innerNav(){
14
11
 
15
- document.querySelectorAll('.inner-nav').forEach(item => {
16
- item.innerHTML = nav;
17
- // item.classList.add('text-right', 'd-print-none');
18
- });
12
+ const nav_primary = document.querySelector('.inner-nav-main');
13
+ if (nav_primary) {
14
+ // nav_primary.classList.add('text-right', 'd-print-none');
15
+ let nav = nav_primary.innerHTML;
16
+
17
+ document.querySelectorAll('.inner-nav').forEach(item => {
18
+ item.innerHTML = nav;
19
+ // item.classList.add('text-right', 'd-print-none');
20
+ });
21
+ }
19
22
  }
@@ -1,27 +1,27 @@
1
- # `s-datatable` web-component for simple-datatatble
1
+ # `s-datatable` web-component per simple-datatables
2
2
 
3
- </https://fiduswriter.github.io/simple-datatables/documentation/>
3
+ <https://fiduswriter.github.io/simple-datatables/documentation/>
4
4
 
5
5
 
6
6
  ## Utilizzo da markup
7
7
 
8
8
  ```html
9
- <s-datatable
10
- json="/api/data.json"
11
- cols='[{"_heading":"Nome","_field":"name"}, ...]'
12
- perPage="10"
13
- ></s-datatable>
14
-
15
- <!-- Alternativa: dati inline via attributo (utile per SSR/Twig/Blade).
16
- Se presenti entrambi, `data` ha la precedenza su `json`. -->
17
- <s-datatable
18
- data='[{"name":"Mario","city":"Roma"}, ...]'
19
- cols='[{"_heading":"Nome","_field":"name"}, ...]'
20
- perPage="10"
21
- ></s-datatable>
22
- ```
23
-
24
- ## Utilizzo da script
9
+ <s-datatable
10
+ json="/api/data.json"
11
+ cols='[{"_heading":"Nome","_field":"name"}, ...]'
12
+ perPage="10"
13
+ ></s-datatable>
14
+
15
+ <!-- Alternativa: dati inline via attributo (utile per SSR/Twig/Blade).
16
+ Se presenti entrambi, `data` ha la precedenza su `json`. -->
17
+ <s-datatable
18
+ data='[{"name":"Mario","city":"Roma"}, ...]'
19
+ cols='[{"_heading":"Nome","_field":"name"}, ...]'
20
+ perPage="10"
21
+ ></s-datatable>
22
+ ```
23
+
24
+ ## Utilizzo da script
25
25
 
26
26
  ```javascript
27
27
  const el = document.querySelector('s-datatable');
@@ -43,23 +43,25 @@ el.init({
43
43
  // Se appena creato, appendere dopo init():
44
44
  document.body.appendChild(el);
45
45
  ```
46
-
46
+
47
47
  ## Utilizzo misto – attributi HTML + script
48
48
 
49
- I parametri non inclusi in init() vengono letti dall'attributo HTML corrispondente. Si possono quindi separare configurazione statica (`cols`, `perPage`) nel markup e dati dinamici (json) nello script:
49
+ I parametri non inclusi in `init()` vengono letti dall'attributo HTML corrispondente.
50
+ Si possono quindi separare configurazione statica (`cols`, `perPage`) nel markup
51
+ e dati dinamici (`json`) nello script:
50
52
 
51
- ```html
53
+ ```html
52
54
  <s-datatable
53
55
  id="table-utenti"
54
56
  cols='[{"_heading":"Nome","_field":"name"},{"_heading":"Città","_field":"city"}]'
55
57
  perPage="20"
56
58
  ></s-datatable>
57
59
  ```
58
-
60
+
59
61
  ```javascript
60
62
  el.init({ json: '/api/utenti.json' }); // → json da script, cols e perPage da attributo
61
63
  ```
62
-
64
+
63
65
  Ordine di precedenza: config (script) > attributo HTML > default interno.
64
66
 
65
67
  Per ignorare esplicitamente un attributo e ricadere sul default, passare `null` nel config:
@@ -67,121 +69,188 @@ Per ignorare esplicitamente un attributo e ricadere sul default, passare `null`
67
69
  ```javascript
68
70
  el.init({ json: '/api/data.json', cols: null }); // → cols = null, l'attributo viene saltato
69
71
  ```
70
-
72
+
71
73
  ## Reazione ad eventi esterni
72
74
 
73
- Il componente ascolta tre CustomEvent che altri componenti possono emettere sul `document` (o su qualsiasi antenato del componente nel DOM):
74
-
75
+ Il componente ascolta tre CustomEvent che altri componenti possono emettere
76
+ sul `document` (o su qualsiasi antenato del componente nel DOM):
77
+
75
78
  ### `datatable:search`
76
79
 
77
80
  Applica una ricerca su una o più colonne.
78
81
 
79
82
  `detail: { term: string, columns?: number[] }`
80
83
 
81
- Esempio – ricerca globale:
82
-
83
84
  ```javascript
85
+ // Ricerca globale
84
86
  document.dispatchEvent(new CustomEvent('datatable:search', {
85
87
  detail: { term: 'mario' }
86
88
  }));
87
- ```
88
89
 
89
- Esempio – ricerca su colonna 0 e 2:
90
-
91
- ```javascript
92
- document.dispatchEvent(new CustomEvent('datatable:search', {
93
- detail: { term: 'mario', columns: [0, 2] }
94
- }));
90
+ // Ricerca su colonna 0 e 2
91
+ document.dispatchEvent(new CustomEvent('datatable:search', {
92
+ detail: { term: 'mario', columns: [0, 2] }
93
+ }));
95
94
  ```
96
95
 
97
96
  ### `datatable:multisearch`
98
-
97
+
99
98
  Applica più filtri contemporaneamente (uno per colonna).
100
99
 
101
100
  `detail: { queries: Array<{ terms: string[], columns?: number[] }> }`
102
-
103
- Esempio – filtra colonna 1 per "mario" E colonna 3 per "roma":
104
101
 
105
102
  ```javascript
103
+ // Filtra colonna 1 per "mario" E colonna 3 per "roma"
106
104
  document.dispatchEvent(new CustomEvent('datatable:multisearch', {
107
105
  detail: {
108
106
  queries: [
109
- { terms: ['gino'], columns: [1] },
107
+ { terms: ['mario'], columns: [1] },
110
108
  { terms: ['roma'], columns: [3] }
111
109
  ]
112
110
  }
113
111
  }));
114
112
  ```
115
-
113
+
116
114
  ### `datatable:reload`
117
115
 
118
- Ricarica i dati da un nuovo URL JSON (o dallo stesso URL originale se json è omesso), opzionalmente con nuove colonne o dati inline.
116
+ Ricarica i dati da un nuovo URL JSON (o dallo stesso URL originale se `json` è omesso),
117
+ opzionalmente con nuove colonne, dati inline e/o un filtro da applicare al termine del
118
+ caricamento.
119
119
 
120
- `detail: { json?: string, data?: Array, cols?: Array }`
120
+ `detail: { json?: string, data?: Array, cols?: Array, search?: object }`
121
121
 
122
- Esempio – reload stesso URL:
123
-
124
- ```javascript
125
- document.dispatchEvent(new CustomEvent('datatable:reload', {
126
- detail: {}
127
- }));
128
- ```
129
-
130
- Esempio – nuovo endpoint + nuove colonne:
122
+ Il campo `search` permette di combinare reload e filtro in un unico evento, evitando
123
+ race condition tra il fetch e la ricerca. Formati supportati:
131
124
 
125
+ - `{ term, columns? }` → ricerca semplice
126
+ - `{ queries: [{terms, columns?}] }` → ricerca multipla
132
127
 
133
128
  ```javascript
129
+ // Reload stesso URL
130
+ document.dispatchEvent(new CustomEvent('datatable:reload', { detail: {} }));
131
+
132
+ // Nuovo endpoint + nuove colonne
134
133
  document.dispatchEvent(new CustomEvent('datatable:reload', {
135
134
  detail: {
136
135
  json: '/api/data-filtered.json',
137
136
  cols: [{ _heading: 'Nome', _field: 'name' }]
138
137
  }
139
138
  }));
140
- ```
141
-
142
- Esempio – dati inline (senza fetch):
143
139
 
144
- ```javascript
140
+ // Dati inline (senza fetch)
145
141
  document.dispatchEvent(new CustomEvent('datatable:reload', {
146
142
  detail: {
147
143
  data: [{ name: 'Mario', city: 'Roma' }],
148
- cols: [{ _heading: 'Nome', _field: 'name' }]
144
+ }
145
+ }));
146
+
147
+ // Reload + filtro contestuale applicato dopo il render
148
+ document.dispatchEvent(new CustomEvent('datatable:reload', {
149
+ detail: {
150
+ json: '/api/utenti?anno=2024',
151
+ search: { term: 'mario', columns: [0] }
149
152
  }
150
153
  }));
151
154
  ```
152
-
153
155
 
154
- ## Targeting – se nella pagina ci sono più istanze di s-datatable
156
+ ## Targeting – più istanze nella stessa pagina
157
+
158
+ Aggiungere `id` al componente e `target` nel detail. Se `target` è assente,
159
+ l'evento viene raccolto da **tutte** le istanze.
155
160
 
161
+ ```html
162
+ <s-datatable id="table-utenti" ...></s-datatable>
163
+ ```
156
164
 
157
- Aggiungere l'attributo `id` al componente e includere `target` nel detail:
158
-
159
- `<s-datatable id="table-utenti" ...></s-datatable>`
160
-
161
165
  ```javascript
162
166
  document.dispatchEvent(new CustomEvent('datatable:search', {
163
167
  detail: { term: 'mario', target: 'table-utenti' }
164
168
  }));
165
169
  ```
166
170
 
167
- Se `target` è assente, l'evento viene raccolto da TUTTE le istanze.
171
+ ## Parametro `cols`
168
172
 
169
- ## Esempio parametro `cols`
173
+ Array di oggetti di configurazione delle colonne. Le chiavi con prefisso `_` sono
174
+ proprietà del componente; le restanti vengono passate direttamente a simple-datatables
175
+ (es. `sortable`, `searchable`, `type`, `hidden`, …).
170
176
 
171
177
  ```javascript
172
- {
173
-
174
- cols: [
175
- {
176
- _field // chiave campo json (anche composta es: `key1.key2`),
177
- _heading // testo th
178
- _title // testo `title` del `th`
179
- _renderMode // preset rendering predefiniti
180
- _renderTpl // stringa mustache-like per la composizione della cella (chiavi indicate forma `[[chiave]]`)
181
- type // parametri simple-datatable (senza `_`)
182
- searchable // ...
183
- sortable // ...
184
- // ...
185
- }
186
- ]
187
- }
178
+ cols: [
179
+ {
180
+ // ── Proprietà del componente (prefisso _) ─────────────────────────────
181
+
182
+ _field, // Chiave del campo JSON. Obbligatorio.
183
+ // Supporta notazione punto per valori annidati: 'owner.cognome'
184
+
185
+ _heading, // Testo dell'intestazione di colonna (contenuto del <th>). Obbligatorio.
186
+
187
+ _title, // Testo del tooltip sull'intestazione (attributo `title` dell'<abbr>).
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.
194
+
195
+ _renderMode, // Preset di visualizzazione predefiniti. Valori disponibili:
196
+ // 'id' – colonna numerica, non ricercabile, allineata a destra
197
+ // 'email' – va a capo prima e dopo la @
198
+ // 'boolean' – icona spunta/croce con colore semantico
199
+ // 'sf_datetime' – data/ora da oggetto Symfony ({ date, timezone })
200
+
201
+ _sortValue, // Percorso del campo da usare per l'ORDINAMENTO al posto del contenuto
202
+ // visualizzato. Utile quando la cella contiene HTML composto (_renderTpl).
203
+ // Supporta notazione punto.
204
+ // Implementato tramite l'attributo data-order sulla cella, letto
205
+ // nativamente da simple-datatables.
206
+ //
207
+ // Esempio: cella con '[[owner.nome]] [[owner.cognome]]'
208
+ // _sortValue: 'owner.cognome' → ordina per cognome
209
+
210
+ _searchValue, // Percorso/i del campo da usare per la RICERCA al posto del contenuto
211
+ // visualizzato. Accetta uno o più percorsi separati da spazio.
212
+ // Supporta notazione punto.
213
+ // Implementato tramite searchMethod di colonna (API simple-datatables)
214
+ // e attributo data-search sulla cella.
215
+ // La ricerca è di tipo AND: tutti i termini digitati devono essere
216
+ // presenti nel valore del campo (o dei campi) indicati.
217
+ //
218
+ // Esempio: _searchValue: 'owner.cognome owner.nome'
219
+ // cerca su entrambi i campi concatenati; "rossi mario" trova solo
220
+ // le righe che contengono entrambe le parole, in qualsiasi ordine.
221
+
222
+ // ── Proprietà native simple-datatables (senza prefisso _) ────────────
223
+
224
+ type, // Tipo di dato: 'string' | 'number' | 'date' | 'boolean' | 'html' | 'other'
225
+ sortable, // false per disabilitare l'ordinamento sulla colonna
226
+ searchable, // false per escludere la colonna dalla ricerca globale
227
+ hidden, // true per nascondere la colonna (esclusa anche dalla ricerca)
228
+ sort, // 'asc' | 'desc' per ordinamento iniziale (solo su colonna singola)
229
+ // ... tutte le altre opzioni columns di simple-datatables
230
+ }
231
+ ]
232
+ ```
233
+
234
+ ### Esempio completo con valori annidati
235
+
236
+ ```javascript
237
+ const cols = [
238
+ {
239
+ _field: 'commessa',
240
+ _heading: 'Commessa / fornitore',
241
+ _renderTpl: '[[commessa]]<br><small class="text-muted">[[fornitore]]</small>',
242
+ },
243
+ {
244
+ _field: 'owner.cognome',
245
+ _heading: 'Owner',
246
+ _renderTpl: '[[owner.nome]] [[owner.cognome]]',
247
+ _sortValue: 'owner.cognome', // ordina per cognome
248
+ _searchValue: 'owner.cognome owner.nome', // cerca su cognome e nome (AND)
249
+ },
250
+ {
251
+ _field: 'importi.0.importo', // valore annidato con indice array
252
+ _heading: 'Importo',
253
+ type: 'number',
254
+ }
255
+ ];
256
+ ```
@@ -25,6 +25,15 @@ import caretRightIcon from '../../icons/caret-right.svg?inline';
25
25
  * @property {string} [_renderTpl] Template HTML per il contenuto della cella.
26
26
  * I segnaposto `[[key]]` vengono sostituiti con i
27
27
  * valori corrispondenti dell'oggetto riga.
28
+ * @property {string} [_sortValue] Percorso del campo da usare per l'ordinamento al posto
29
+ * del contenuto visualizzato (es. 'owner.cognome').
30
+ * Supporta valori annidati con la notazione punto.
31
+ * Implementato tramite `data-order` sull'attributo della cella.
32
+ * @property {string} [_searchValue] Percorso del campo da usare per la ricerca al posto
33
+ * del contenuto visualizzato (es. 'owner.cognome owner.nome').
34
+ * Supporta valori annidati con la notazione punto.
35
+ * Implementato tramite una funzione `search` personalizzata
36
+ * sulla colonna (API simple-datatables).
28
37
  * @property {'id'|'email'|'boolean'|'sf_datetime'} [_renderMode]
29
38
  * Modalità di visualizzazione predefinita:
30
39
  * - `id` – colonna numerica, non ricercabile, allineata a destra
@@ -332,6 +341,12 @@ class SimpleDatatableAdapter extends HTMLElement {
332
341
  cells: cols.map((col_item, col_idx) => {
333
342
  let value = getNestedValue(row, col_item._field);
334
343
 
344
+ // _sortValue: valore alternativo per l'ordinamento
345
+ // simple-datatables lo legge da cell.attributes['data-order']
346
+ const sortValue = col_item._sortValue
347
+ ? (getNestedValue(row, col_item._sortValue) ?? '')
348
+ : null;
349
+
335
350
  if (col_item._renderTpl) {
336
351
  value = decodeURIComponent(col_item._renderTpl).replace(
337
352
  /\[\[(.*?)\]\]/g,
@@ -358,6 +373,29 @@ class SimpleDatatableAdapter extends HTMLElement {
358
373
  (value.timezone === 'UTC' ? 'Z' : '');
359
374
  }
360
375
 
376
+ // _searchValue: valore alternativo per la ricerca
377
+ // Viene scritto come testo nascosto nella cella così la funzione
378
+ // search personalizzata della colonna può leggerlo direttamente
379
+ // dal contenuto, senza dipendere da attributi non supportati.
380
+ const searchValue = col_item._searchValue
381
+ ? col_item._searchValue.split(' ')
382
+ .map(path => getNestedValue(row, path.trim()) ?? '')
383
+ .filter(Boolean)
384
+ .join(' ')
385
+ : null;
386
+
387
+ // Se sono definiti _sortValue o _searchValue, restituisce un
388
+ // oggetto cella strutturato; altrimenti il valore grezzo.
389
+ // Se sono definiti _sortValue o _searchValue, restituisce un
390
+ // oggetto cella strutturato con attributi data-order / data-search
391
+ // che simple-datatables può leggere in modo affidabile.
392
+ if (sortValue !== null || searchValue !== null) {
393
+ const attrs = {};
394
+ if (sortValue !== null) attrs['data-order'] = sortValue;
395
+ if (searchValue !== null) attrs['data-search'] = searchValue;
396
+ return { text: value ?? '', attributes: attrs };
397
+ }
398
+
361
399
  return value;
362
400
  })
363
401
  }));
@@ -365,7 +403,7 @@ class SimpleDatatableAdapter extends HTMLElement {
365
403
  this.columns = cols.map((col_settings, idx) => {
366
404
  col_settings = parseCols(col_settings);
367
405
 
368
- return {
406
+ const colObj = {
369
407
  ...(Object.fromEntries(
370
408
  Object.entries(col_settings).filter(([key]) => !key.startsWith('_'))
371
409
  )),
@@ -374,6 +412,25 @@ class SimpleDatatableAdapter extends HTMLElement {
374
412
  searchable: col_settings.searchable ?? true,
375
413
  sortable: col_settings.sortable ?? true,
376
414
  };
415
+
416
+ // _searchValue: funzione searchMethod personalizzata che opera su
417
+ // cell.attributes['data-search'] invece che sul testo visualizzato.
418
+ // L'API simple-datatables prevede: (terms, cell, row, colIdx, source)
419
+ // - terms: array di stringhe (uno per parola cercata)
420
+ // - cell: oggetto cella dai dati originali (con .text e .attributes)
421
+ // La funzione deve restituire true se la riga è da includere.
422
+ // Comportamento AND: tutti i termini devono essere presenti (come
423
+ // nella demo https://fiduswriter.github.io/simple-datatables/demos/22-and-search/).
424
+ if (col_settings._searchValue) {
425
+ colObj.searchMethod = (terms, cell) => {
426
+ const haystack = (
427
+ cell.attributes?.['data-search'] ?? cell.text ?? ''
428
+ ).toLowerCase();
429
+ return terms.every(term => haystack.includes(term.toLowerCase()));
430
+ };
431
+ }
432
+
433
+ return colObj;
377
434
  });
378
435
 
379
436
  } catch (err) {
@@ -1,36 +0,0 @@
1
- // import './form.css'; //incorporato nel css principale
2
-
3
- import { enableSubmitBtns } from '../../index.js';
4
-
5
- document.querySelectorAll('form').forEach( form => {
6
-
7
- // form.querySelectorAll('[type="submit"]').forEach(btn =>{
8
- // btn.addEventListener('click', () => {
9
- // form.classList.add('was-validated');
10
- // }, false);
11
- // });
12
-
13
- form.querySelectorAll('.is-invalid, .is-valid').forEach(item => {
14
- item.classList.remove('is-invalid', 'is-valid');
15
- });
16
-
17
- form.addEventListener('submit', e => {
18
-
19
- if(!form.hasAttribute('data-no-was-validated')) {
20
- form.classList.add('was-validated');
21
- }
22
-
23
- if(!form.checkValidity()) {
24
- e.preventDefault();
25
- enableSubmitBtns();
26
-
27
- } else {
28
- if(!form.hasAttribute('data-no-disabling') && !form.hasAttribute('data-disable-submit')) { /* 'data-disable-submit' per compatibilitò con verioni precedenti */
29
- form.querySelectorAll('[type="submit"]').forEach(btn => {
30
- btn.disabled = true;
31
- });
32
- }
33
- }
34
- }, false);
35
- });
36
-