@massimo-cassandro/minimo 1.6.0 → 1.7.0

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.
Files changed (37) hide show
  1. package/index.js +10 -3
  2. package/package.json +4 -2
  3. package/src/components/autocomplete/autocomplete-engine.js +70 -64
  4. package/src/components/autocomplete/check-autocomplete.js +10 -2
  5. package/src/components/dismiss-alerts.js +9 -2
  6. package/src/components/form-reset-submit.js +12 -4
  7. package/src/components/inner-nav/inner-nav.js +5 -1
  8. package/src/components/modal-alert/customization-templates/custom-modal-alert.js +4 -0
  9. package/src/components/modal-alert/defaults.js +6 -3
  10. package/src/components/modal-alert/modal-alert.js +62 -21
  11. package/src/components/modal-popup/modal-popup.js +51 -29
  12. package/src/components/overlay/overlay.js +13 -1
  13. package/src/components/sf-macro/sf-macro.js +23 -12
  14. package/src/components/snackbar/snackbar.js +32 -17
  15. package/src/components/spinner/spinner.js +6 -2
  16. package/src/components/unsplash-page/fetch-unsplash-data.js +25 -2
  17. package/src/components/unsplash-page/img-breakpoints.js +11 -0
  18. package/src/components/unsplash-page/unsplash-page.js +64 -40
  19. package/src/css/forms/forms.css +4 -0
  20. package/src/utilities/better-text.js +80 -0
  21. package/src/utilities/classnames.js +14 -1
  22. package/src/utilities/cookies.js +28 -0
  23. package/src/utilities/create-id.js +19 -0
  24. package/src/utilities/dom-builder/dom-builder.js +84 -75
  25. package/src/utilities/dom-builder/domBuilderBasicSetup.js +12 -9
  26. package/src/utilities/dom-builder/parseDomString.js +31 -29
  27. package/src/utilities/enable-submit-btns.js +7 -1
  28. package/src/utilities/{escapeHTML.js → escape-html.js} +13 -0
  29. package/src/utilities/linked-dates.js +64 -0
  30. package/src/utilities/relative-date.js +153 -0
  31. package/src/utilities/sentence-case.js +5 -2
  32. package/src/utilities/strip-tags.js +8 -0
  33. package/src/utilities/title-case.js +6 -2
  34. package/src/utilities/truncate-string.js +22 -0
  35. package/src/utilities/validation-error-class.js +14 -7
  36. package/src/web-components/s-datatable-component/s-datatable-component.js +1 -0
  37. package/src/web-components/s-datatable-component/src/parse-cols.js +1 -0
package/index.js CHANGED
@@ -1,15 +1,22 @@
1
1
 
2
2
  // utilities
3
+ export { betterText } from './src/utilities/better-text.js';
3
4
  export { classnames, classnamesNull } from './src/utilities/classnames.js';
5
+ export { createId } from './src/utilities/create-id.js';
4
6
  export { domBuilder } from './src/utilities/dom-builder/dom-builder.js';
5
7
  export { enableSubmitBtns } from './src/utilities/enable-submit-btns.js';
6
- export { titleCase } from './src/utilities/title-case.js';
8
+ export { escapeHTML, unescapeHTML } from './src/utilities/escape-html.js';
9
+ export { getCookie, setCookie } from './src/utilities/cookies.js';
10
+ export { linkedDates } from './src/utilities/linked-dates.js';
11
+ export { relativeDate } from './src/utilities/relative-date.js';
7
12
  export { sentenceCase } from './src/utilities/sentence-case.js';
8
- export { escapeHTML, unescapeHTML } from './src/utilities/escapeHTML.js';
13
+ export { stripTags } from './src/utilities/strip-tags.js';
14
+ export { titleCase } from './src/utilities/title-case.js';
15
+ export { truncateString } from './src/utilities/truncate-string.js';
9
16
  export { ValidationError } from './src/utilities/validation-error-class.js';
10
17
 
11
18
  // components
12
- export { default as modalAlert } from './src/components/modal-alert/modal-alert.js';
19
+ export { modalAlert } from './src/components/modal-alert/modal-alert.js';
13
20
  export { dismissAlerts } from './src/components/dismiss-alerts.js';
14
21
  export { formResetSubmit } from './src/components/form-reset-submit.js';
15
22
  export { innerNav } from './src/components/inner-nav/inner-nav.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massimo-cassandro/minimo",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Light css framework & utilities",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -28,7 +28,9 @@
28
28
  "npm-publish": "npm publish",
29
29
  "upd@m": "npx upd@m",
30
30
  "build-tokens": "node ./design-tokens/utilities/build-tokens.mjs --config ./design-tokens/tokens-config.mjs",
31
- "check unresolved props": "node ./design-tokens/utilities/check-unresolved-custom-props.mjs --config ./design-tokens/tokens-config.mjs"
31
+ "check unresolved props": "node ./design-tokens/utilities/check-unresolved-custom-props.mjs --config ./design-tokens/tokens-config.mjs",
32
+ "checkJS": "npx tsc --noEmit -p jsconfig.json",
33
+ "checkJS ./src": "npx tsc --noEmit --allowJs --checkJs --target ES2023 --module ESNext --allowSyntheticDefaultImports --moduleResolution bundler declarations.d.ts src/**/*.js"
32
34
  },
33
35
  "license": "MIT",
34
36
  "author": "Massimo Cassandro",
@@ -5,21 +5,20 @@ import autoComplete from '@tarekraafat/autocomplete.js';
5
5
 
6
6
  export const ac_default_params = {
7
7
  placeholder: 'Inserisci tre o più caratteri',
8
- ac_url: null, // url di ricerca, deve avere slash iniziali e finali
8
+ ac_url: null, // search URL; must have leading and trailing slashes
9
9
 
10
- // se true, non viene concatenata la stringa di ricerca all'url (utile se si usa un json)
10
+ // when true, the search query string is not appended to the URL (useful with a static JSON)
11
11
  test_mode: false,
12
12
 
13
13
  /*
14
- funzione che riceve il risultato del fetch ajax e restituisce un array
15
- di oggetti nella forma
14
+ Function that receives the fetch result and returns an array of objects in the form:
16
15
  {
17
- id <== id dell'elemento
18
- val <== valore da visualizzare come risultato della selezione
19
- list_display <== stringa visualizzata nell'elenco
16
+ id <== element id
17
+ val <== value shown as the selection result
18
+ list_display <== string shown in the results list
20
19
  }
21
20
 
22
- esempio:
21
+ example:
23
22
  data => data.map(item => {
24
23
  return {
25
24
  id: item.id,
@@ -27,86 +26,88 @@ export const ac_default_params = {
27
26
  list_display: `#${item.id} ${item.agenzia} (${item.network})`+
28
27
  (item.ragioneSociale? `<br><small>${item.ragioneSociale}</small>` : '—')
29
28
 
30
- // opzionale:
31
- __xxx__: item (o altri dati custom)
29
+ // optional:
30
+ __xxx__: item (or other custom data)
32
31
  };
33
32
  });
34
33
  */
34
+ // @ts-ignore — parameter type inferred from library callback
35
35
  fetch_result_function: data => data,
36
36
 
37
- // elemento o funzione che restituisce l'elemento
37
+ // field element or function returning the element
38
38
  autocomplete_field: null,
39
39
 
40
- // selettore dell'elemento contenitore di autocomplete_field e degli altri elementi
40
+ // selector of the container element that wraps autocomplete_field and related elements
41
41
  autocomplete_parent_selector: '.form-group',
42
42
 
43
- // oggetto opzione di parametri da accodare all'url di ricerca in modalità get
44
- // i valori delle chiavi possono essere singole stringhe o numeri oppure array
45
- // es. {param1: 'val1', param2: ['val2'. 'val3']}
43
+ // extra query parameters to append to the search URL (GET mode)
44
+ // values can be a single string/number or an array
45
+ // e.g. {param1: 'val1', param2: ['val2', 'val3']}
46
46
  extra_query_params: {},
47
47
 
48
- // name e id dell'elemento hidden su cui registrare l'id selezionato
49
- // il `name` non viene utilizzato se l'elemento è già presente
50
- // se nessuno tra hidden_id, hidden_field e select_id è presente, l'id del valore selezionato non viene gestito
48
+ // name and id of the hidden input that stores the selected id
49
+ // `name` is ignored if the element already exists
50
+ // if none of hidden_id, hidden_field, or select_id is set, the selected id is not managed
51
51
  hidden_name: null,
52
52
  hidden_id: null,
53
53
 
54
- // elemento hidden; se impostato e se presenti, `hidden_name` e `hidden_id` non vengono presi in considerazione
54
+ // hidden field element; when set, `hidden_name` and `hidden_id` are ignored
55
55
  hidden_field: null,
56
56
 
57
- // se presente utilizza l'elemento select (già esistente) indicato
58
- // se impostato, `hidden_name` e `hidden_id` vengono ignorati
59
- // l'elemento select è considerato di tipo `multiple` se `select_multiple == true`
60
- // nel caso di select multiple non è possibile inserire due voci con lo stesso id
57
+ // uses an existing <select> element with the given id
58
+ // when set, `hidden_name` and `hidden_id` are ignored
59
+ // treated as multiple if `select_multiple === true`
60
+ // in multiple mode, duplicate ids are not allowed
61
61
  select_id: null,
62
62
  select_multiple: true,
63
63
 
64
- // id dell'elemento in cui generare i badge delle opzioni selezionate
65
- // Solo se `select_id` è impostato, per visualizzazione ed editing delle voci scelte.
66
- // Se non presente, viene ignorato ma è necessario predisporre autonomanente
67
- // la procedura di editing
68
- // NB: solo per select multiple
64
+ // id of the element used to render selected-option badges
65
+ // only applies when `select_id` is set (multiple mode)
66
+ // if absent, badge rendering must be handled externally
69
67
  badges_container_id: null,
70
68
 
71
- // callback invocato quando un badge viene rimosso
72
- // viene invocato con argomenti l'id e la voce (il testo del badge) dell'elemento rimosso
69
+ // callback invoked when a badge is removed;
70
+ // receives the id and label text of the removed item
73
71
  badges_remove_callback: null,
74
72
 
75
- // funzione personalizzata per la costruzione dei badge
76
- // viene invocata con argomenti `event.detail.selection.value`, e l'oggetto `params`.
77
- // Il primo corrisponde all'oggetto ritornato da `fetch_result_function`
78
- // se `null`, viene utilizzato il markup di default
79
- // NB: al momento, nel caso di elementi preregistrati, il primo argomento di badges_builder
80
- // contiene solo gli elementi `id`, `val`
81
- // NB: è necessario che il badge abbia classe `ac-badge` e attributo `data-id`.
82
- // La funzione deve restituire il markup completo del badge
73
+ // custom function for building badge markup
74
+ // receives `event.detail.selection.value` and the `params` object
75
+ // if null, the default markup is used
76
+ // NB: for pre-registered items the first argument only contains `id` and `val`
77
+ // NB: the badge must have class `ac-badge` and attribute `data-id`; must return complete badge HTML
83
78
  badges_builder: null,
84
79
 
85
- // classe assegnata allo span che contiene il testo del badge
80
+ // class assigned to the span containing the badge label
86
81
  badge_label_class: 'ac-badge-label',
87
82
 
88
- // classe assegnata al pulsante di rimozione del badge
83
+ // class assigned to the badge remove button
89
84
  badge_btn_class: 'ac-badge-btn',
90
85
 
91
86
 
92
- // callback autocomplete
93
- // se presente viene invocato con 5 argomenti: id, val, autocomplete field element, list_display (outerHTML) e row
94
- // NB: row viene istanziato solo dopo la selezione di un'opzione
87
+ // autocomplete callback
88
+ // when present, called with 5 arguments: id, val, autocomplete field element, list_display (outerHTML), and row
89
+ // NB: row is only available after an option has been selected
95
90
  callback: null,
96
91
 
97
- // classe aggiuntiva opzionale per la lista dei risultati
92
+ // optional extra class for the results list
98
93
  resultList_extra_class: null,
99
94
 
100
- // classe aggiuntiva opzionale per il wrapper esterno
95
+ // optional extra class for the outer wrapper
101
96
  wrapper_extra_class: null
102
97
  };
103
98
 
99
+ /**
100
+ * Initialises an autocomplete field using @tarekraafat/autocomplete.js.
101
+ * @param {Record<string, any>} [params={}]
102
+ * @returns {void}
103
+ */
104
104
  export default function (params = {}) {
105
105
 
106
106
  try {
107
107
 
108
108
  params = {...ac_default_params, ...params};
109
109
 
110
+ // @ts-ignore — circular reference: bparams defaults to params
110
111
  params.badges_builder ??= (result_obj, bparams = params) => `<span class="ac-badge badge rounded-pill text-bg-secondary" data-id="${result_obj.id}">` +
111
112
  `<span class="${bparams.badge_label_class}">${result_obj.val}</span>` +
112
113
  `<button type="button" class="${bparams.badge_btn_class}">&times;</button>` +
@@ -119,7 +120,7 @@ export default function (params = {}) {
119
120
  params.autocomplete_field = params.autocomplete_field();
120
121
  }
121
122
 
122
- // impostazione attributi autocomplete
123
+ // set autocomplete field attributes
123
124
  params.autocomplete_field.type = 'search';
124
125
  ['spellcheck=false', 'autocorrect=off', 'autocomplete=off', 'autocapitalize=off'].forEach(item => {
125
126
  const [attr, val] = item.split('=');
@@ -137,6 +138,7 @@ export default function (params = {}) {
137
138
  let extra_query_params = [];
138
139
  for(const i in params.extra_query_params) {
139
140
  if(Array.isArray(params.extra_query_params[i])) {
141
+ // @ts-ignore — extra_query_params values are untyped (string, number, or array)
140
142
  params.extra_query_params[i].forEach(item => {
141
143
  extra_query_params.push(`${i}[]=${item}`);
142
144
  });
@@ -158,8 +160,7 @@ export default function (params = {}) {
158
160
  }
159
161
 
160
162
  } else {
161
- // campo hidden
162
- // se non presente, viene generato
163
+ // hidden field — created if not already present
163
164
  if(params.hidden_field) {
164
165
  hidden_field = params.hidden_field;
165
166
 
@@ -180,6 +181,7 @@ export default function (params = {}) {
180
181
  diacritics: true,
181
182
  threshold: 3,
182
183
  data: {
184
+ // @ts-ignore — library callback; query type defined by autoComplete.js
183
185
  src: async (query) => {
184
186
  const ac_url = params.ac_url +
185
187
  (params.test_mode? '' : encodeURIComponent(query)) + extra_query_params_string;
@@ -205,6 +207,7 @@ export default function (params = {}) {
205
207
  class: params.resultList_extra_class,
206
208
 
207
209
  destination: '#' + params.autocomplete_field.id,
210
+ // @ts-ignore — library callback; types defined by autoComplete.js
208
211
  element: (list, data) => {
209
212
  if (!data.results.length) {
210
213
  // Create "No Results" message element
@@ -225,6 +228,7 @@ export default function (params = {}) {
225
228
  events: {
226
229
 
227
230
  input: {
231
+ // @ts-ignore — library event; type defined by autoComplete.js
228
232
  selection: (event) => {
229
233
  // console.log(event.detail.selection.value);
230
234
 
@@ -237,7 +241,7 @@ export default function (params = {}) {
237
241
  const option_element = new Option(selected_text, selected_id, true, true);
238
242
 
239
243
  if(params.select_multiple) {
240
- // impedisce i doppioni
244
+ // prevent duplicate entries
241
245
  const registered_option = select_field.querySelector(`option[value="${selected_id}"]`),
242
246
  new_badge = params.badges_builder(event.detail.selection.value, params);
243
247
 
@@ -250,18 +254,18 @@ export default function (params = {}) {
250
254
  badges_container.insertAdjacentHTML('beforeend', new_badge);
251
255
  }
252
256
 
253
- // replace del badge per avere sempre la versione più recente
257
+ // replace the existing badge to always show the latest version
254
258
  } else {
255
259
  registered_option.replaceWith(option_element);
256
260
  if(badges_container) {
257
261
  const prev_badge = badges_container.querySelector(`.ac-badge[data-id="${selected_id}"]`);
258
- prev_badge.replaceWith(
262
+ prev_badge?.replaceWith(
259
263
  new DOMParser().parseFromString(new_badge, 'text/html').body.childNodes[0]
260
264
  );
261
265
  }
262
266
  }
263
267
 
264
- } else { // select singolo
268
+ } else { // single select
265
269
  select_field.innerHTML = '';
266
270
  select_field.appendChild(option_element);
267
271
  }
@@ -281,9 +285,9 @@ export default function (params = {}) {
281
285
  }); // end autoComplete
282
286
 
283
287
  // TODO[epic=autocomplete]
284
- // migliorare, rendere più efficiente la chiamata del callback
288
+ // improve and make the callback invocation more efficient
285
289
 
286
- // reset hidden
290
+ // reset hidden field when the autocomplete value is cleared or mismatched
287
291
  const check_ac = () => {
288
292
  if(params.autocomplete_field.value === '' ||
289
293
  (params.autocomplete_field.dataset.sel !== undefined &&
@@ -314,16 +318,18 @@ export default function (params = {}) {
314
318
  check_ac();
315
319
  }, false);
316
320
 
317
- // listener su badges
321
+ // badge click listener
318
322
  badges_container?.addEventListener('click', e => {
319
- const btn = e.target.closest(`.${params.badge_btn_class}`);
323
+ const target = /** @type {HTMLElement} */ (e.target);
324
+ const btn = target.closest(`.${params.badge_btn_class}`);
320
325
 
321
326
  if(btn) {
322
- const badge = btn.closest('.ac-badge'),
323
- item_id = badge.dataset.id,
324
- item_text = badge.querySelector(':scope > span').innerText;
327
+ const badge = /** @type {HTMLElement | null} */ (btn.closest('.ac-badge'));
328
+ if (!badge) return;
329
+ const item_id = badge.dataset.id,
330
+ item_text = /** @type {HTMLElement | null} */ (badge.querySelector(':scope > span'))?.innerText ?? '';
325
331
  badge.remove();
326
- select_field.querySelector(`option[value="${item_id}"]`).remove();
332
+ select_field?.querySelector(`option[value="${item_id}"]`)?.remove();
327
333
 
328
334
  if(params.badges_remove_callback && typeof params.badges_remove_callback === 'function') {
329
335
 
@@ -332,17 +338,17 @@ export default function (params = {}) {
332
338
  }
333
339
  }, false);
334
340
 
335
- // aggiunta voci o badges di eventuali valori preregistrati
341
+ // render badges for any pre-registered select options
336
342
  if(select_field) {
337
343
 
338
344
  if(params.select_multiple && badges_container) {
339
- // eventuali attributi data dell'option vengono aggiunti
340
- // alla funzione `badges_builder`, con chiave `dataset`
345
+ // any data attributes on the option are forwarded to `badges_builder` under the `dataset` key
341
346
 
342
347
  select_field.querySelectorAll('option[selected]').forEach(option => {
348
+ const opt = /** @type {HTMLOptionElement} */ (option);
343
349
  badges_container.insertAdjacentHTML('beforeend',
344
350
 
345
- params.badges_builder({id: option.value, val: option.innerHTML, dataset: {...option.dataset}}, params)
351
+ params.badges_builder({id: opt.value, val: opt.innerHTML, dataset: {...opt.dataset}}, params)
346
352
  );
347
353
  });
348
354
  } else {
@@ -1,7 +1,15 @@
1
- // TODO trigger ac selection?
1
+ // TODO trigger ac selection
2
+
3
+ /**
4
+ * Resets both the autocomplete and hidden fields when the current value does not match
5
+ * the last confirmed selection.
6
+ * @param {HTMLInputElement | null | undefined} autocomplete_field
7
+ * @param {HTMLInputElement | null | undefined} hidden_field
8
+ * @returns {void}
9
+ */
2
10
  export function checkAutocomplete(autocomplete_field, hidden_field) {
3
11
  if(autocomplete_field && hidden_field) {
4
- if(autocomplete_field.value === '' || autocomplete_field.value.toLowerCase() !== autocomplete_field.dataset.sel.toLowerCase()) {
12
+ if(autocomplete_field.value === '' || autocomplete_field.value.toLowerCase() !== (autocomplete_field.dataset.sel ?? '').toLowerCase()) {
5
13
  autocomplete_field.value = '';
6
14
  hidden_field.value = '';
7
15
  }
@@ -1,7 +1,14 @@
1
+ /**
2
+ * Adds click listeners to all `[data-dismiss]` elements to remove their closest ancestor
3
+ * that matches the class specified in the `data-dismiss` attribute.
4
+ * Typically used to remove alert boxes.
5
+ * @returns {void}
6
+ */
1
7
  export function dismissAlerts(){
2
8
  document.querySelectorAll('[data-dismiss]').forEach(item => {
3
- item.addEventListener('click', () => {
4
- item.closest('.' + item.dataset.dismiss).remove();
9
+ const el = /** @type {HTMLElement} */ (item);
10
+ el.addEventListener('click', () => {
11
+ el.closest('.' + el.dataset.dismiss)?.remove();
5
12
  }, false);
6
13
  });
7
14
  }
@@ -1,6 +1,13 @@
1
- // import './form.css'; //incorporato nel css principale
1
+ // import './form.css'; // included in the main CSS entry point
2
2
  import { enableSubmitBtns } from '../../index.js';
3
3
 
4
+ /**
5
+ * Wires up submit validation and button-disabling behaviour on all forms.
6
+ * - Adds `was-validated` class on submit (unless `data-no-was-validated` is present).
7
+ * - Clears `.is-invalid` / `.is-valid` classes before each submission attempt.
8
+ * - Disables submit buttons on valid submission (unless `data-no-disabling` is present).
9
+ * @returns {void}
10
+ */
4
11
  export function formResetSubmit(){
5
12
 
6
13
  document.querySelectorAll('form').forEach( form => {
@@ -26,13 +33,14 @@ export function formResetSubmit(){
26
33
  enableSubmitBtns();
27
34
 
28
35
  } else {
29
- if(!form.hasAttribute('data-no-disabling') && !form.hasAttribute('data-disable-submit')) { /* 'data-disable-submit' per compatibilitò con verioni precedenti */
36
+ // 'data-disable-submit' kept for backwards compatibility
37
+ if(!form.hasAttribute('data-no-disabling') && !form.hasAttribute('data-disable-submit')) {
30
38
  form.querySelectorAll('[type="submit"]').forEach(btn => {
31
- btn.disabled = true;
39
+ const button = /** @type {HTMLButtonElement} */ (btn);
40
+ button.disabled = true;
32
41
  });
33
42
  }
34
43
  }
35
44
  }, false);
36
45
  });
37
46
  }
38
-
@@ -1,4 +1,4 @@
1
- // navigazione scheda / elenco
1
+ // tab / list navigation — copies the primary nav markup into all `.inner-nav` containers
2
2
  /*
3
3
  <p class="inner-nav-main">...</p>
4
4
  ...
@@ -7,6 +7,10 @@
7
7
 
8
8
  import './inner-nav.css';
9
9
 
10
+ /**
11
+ * Copies the inner HTML of `.inner-nav-main` into every `.inner-nav` element on the page.
12
+ * @returns {void}
13
+ */
10
14
  export function innerNav(){
11
15
 
12
16
  const nav_primary = document.querySelector('.inner-nav-main');
@@ -80,6 +80,10 @@ const marks = {
80
80
  confirm: confirmIcon
81
81
  };
82
82
 
83
+ /**
84
+ * @param {Record<string, *>} params
85
+ * @returns {void}
86
+ */
83
87
  export function mAlert(params) {
84
88
  modalAlert(params, custom_defaults, marks);
85
89
  }
@@ -1,7 +1,10 @@
1
+ /**
2
+ * Default configuration for all modal-alert dialog types.
3
+ * Type-specific blocks can override any `globals` setting.
4
+ */
1
5
  export const defaults = {
2
6
 
3
- // settings to be applied to all dialog types
4
- // can be reassigned on single types
7
+ // settings applied to all dialog types; can be overridden per type
5
8
  globals: {
6
9
  extra_class: null,
7
10
  onOpen: null,
@@ -44,7 +47,7 @@ export const defaults = {
44
47
  },
45
48
  confirm: {
46
49
  title: 'Confermi?',
47
- use_warning_icon: false, // se true usa l'icona warning
50
+ use_warning_icon: false, // when true, uses the warning icon instead of the confirm icon
48
51
  ok_btn_text: 'OK',
49
52
  cancel_btn_text: 'Annulla',
50
53
  cancel_focus: true, // false to give focus to the ok button
@@ -4,28 +4,62 @@ import {defaults} from './defaults.js';
4
4
  // https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement
5
5
 
6
6
  // TODO rewrite as async
7
- // TODO rivedere completamente, riscrivere con domBuilder
8
-
9
- export default function (params, custom_defaults = {}, marks = default_marks) {
7
+ // TODO refactor entirely, rewrite using domBuilder
8
+
9
+ /**
10
+ * @typedef {Object} ModalAlertParams
11
+ * @property {string} type - Dialog type: `success`, `error`, `warning`, `info`, or `confirm`.
12
+ * @property {string | null} [extra_class]
13
+ * @property {(() => void) | null} [onOpen]
14
+ * @property {(() => void) | null} [onClose]
15
+ * @property {string | null} [cssFile]
16
+ * @property {boolean} [animation]
17
+ * @property {boolean} [showMarks]
18
+ * @property {((arg?: *) => void) | null} [callback]
19
+ * @property {number | null} [timer]
20
+ * @property {string | null} [mes]
21
+ * @property {string | null} [heading_class]
22
+ * @property {string | null} [text_class]
23
+ * @property {string | null} [extra_btn]
24
+ * @property {string | null} [extra_btn_selector]
25
+ * @property {boolean} [extra_btn_focus]
26
+ * @property {string | null} [title]
27
+ * @property {string} [ok_btn_text]
28
+ * @property {string} [ok_btn_class]
29
+ * @property {string} [cancel_btn_text]
30
+ * @property {string} [cancel_btn_class]
31
+ * @property {boolean} [cancel_focus]
32
+ * @property {boolean} [use_warning_icon]
33
+ */
34
+
35
+ /**
36
+ * Renders and opens a modal alert dialog.
37
+ * @param {ModalAlertParams} params - Dialog parameters (merged with defaults by type).
38
+ * @param {Record<string, any>} [custom_defaults={}] - Per-project overrides for the default settings.
39
+ * @param {Record<string, string>} [marks=default_marks] - SVG icon map keyed by dialog type.
40
+ * @returns {void}
41
+ */
42
+
43
+ //TODO refactoring
44
+
45
+ export function modalAlert(params, custom_defaults = {}, marks = default_marks) {
10
46
 
11
47
  try {
12
48
 
13
49
  // garbage collection
14
- let dialog = document.querySelector('.modal-alert');
15
- if(dialog) {
16
- dialog.remove();
17
- }
50
+ document.querySelector('.modal-alert')?.remove();
18
51
 
52
+ const typedDefaults = /** @type {Record<string, any>} */ (defaults);
19
53
 
20
- params = {
21
- ...(defaults.globals?? {}),
22
- ...(custom_defaults.globals?? {}),
23
- ...(defaults[params.type]?? {}),
24
- ...(custom_defaults[params.type]?? {}),
54
+ params = /** @type {ModalAlertParams} */ ({
55
+ ...(typedDefaults.globals ?? {}),
56
+ ...(custom_defaults.globals ?? {}),
57
+ ...(typedDefaults[params.type] ?? {}),
58
+ ...(custom_defaults[params.type] ?? {}),
25
59
  ...params
26
- };
60
+ });
27
61
 
28
- // per compatibilità
62
+ // backwards compatibility: 'danger' is an alias for 'error'
29
63
  if(params.type === 'danger') {
30
64
  params.type = 'error';
31
65
  }
@@ -53,7 +87,7 @@ export default function (params, custom_defaults = {}, marks = default_marks) {
53
87
  );
54
88
  }
55
89
 
56
- // icona warning opzionale per confirm
90
+ // confirm type can optionally use the warning icon instead of the confirm icon
57
91
  let icon = marks[params.type];
58
92
  if(params.type === 'confirm' && params.use_warning_icon) {
59
93
  icon = marks.warning;
@@ -88,14 +122,16 @@ export default function (params, custom_defaults = {}, marks = default_marks) {
88
122
  </dialog>`
89
123
  );
90
124
 
91
- dialog = document.querySelector('.modal-alert');
125
+ const dialog = /** @type {HTMLDialogElement} */ (document.querySelector('.modal-alert'));
92
126
 
93
127
  dialog.showModal();
94
128
 
95
- // btn focus
96
- const ok_btn = dialog.querySelector('.malert-ok'),
97
- cancel_btn = dialog.querySelector('.malert-cancel'),
98
- extra_btn = (params.extra_btn && params.extra_btn_selector)? dialog.querySelector(params.extra_btn_selector) : null;
129
+ // button focus
130
+ const ok_btn = /** @type {HTMLElement | null} */ (dialog.querySelector('.malert-ok')),
131
+ cancel_btn = /** @type {HTMLElement | null} */ (dialog.querySelector('.malert-cancel')),
132
+ extra_btn = /** @type {HTMLElement | null} */ (
133
+ (params.extra_btn && params.extra_btn_selector)? dialog.querySelector(params.extra_btn_selector) : null
134
+ );
99
135
 
100
136
  if(extra_btn && params.extra_btn_focus) {
101
137
  extra_btn.focus();
@@ -111,9 +147,14 @@ export default function (params, custom_defaults = {}, marks = default_marks) {
111
147
  params.onOpen();
112
148
  }
113
149
 
150
+ /** @type {number | undefined} */
114
151
  let timeoutID;
115
152
 
116
- const dialogDismiss = (btn) => {
153
+ /**
154
+ * @param {HTMLElement | null} [btn]
155
+ * @returns {void}
156
+ */
157
+ const dialogDismiss = (btn = null) => {
117
158
  dialog.remove();
118
159
 
119
160
  if(params.onClose && typeof params.onClose === 'function') {