@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.
- package/index.js +10 -3
- package/package.json +4 -2
- package/src/components/autocomplete/autocomplete-engine.js +70 -64
- package/src/components/autocomplete/check-autocomplete.js +10 -2
- package/src/components/dismiss-alerts.js +9 -2
- package/src/components/form-reset-submit.js +12 -4
- package/src/components/inner-nav/inner-nav.js +5 -1
- package/src/components/modal-alert/customization-templates/custom-modal-alert.js +4 -0
- package/src/components/modal-alert/defaults.js +6 -3
- package/src/components/modal-alert/modal-alert.js +62 -21
- package/src/components/modal-popup/modal-popup.js +51 -29
- package/src/components/overlay/overlay.js +13 -1
- package/src/components/sf-macro/sf-macro.js +23 -12
- package/src/components/snackbar/snackbar.js +32 -17
- package/src/components/spinner/spinner.js +6 -2
- package/src/components/unsplash-page/fetch-unsplash-data.js +25 -2
- package/src/components/unsplash-page/img-breakpoints.js +11 -0
- package/src/components/unsplash-page/unsplash-page.js +64 -40
- package/src/css/forms/forms.css +4 -0
- package/src/utilities/better-text.js +80 -0
- package/src/utilities/classnames.js +14 -1
- package/src/utilities/cookies.js +28 -0
- package/src/utilities/create-id.js +19 -0
- package/src/utilities/dom-builder/dom-builder.js +84 -75
- package/src/utilities/dom-builder/domBuilderBasicSetup.js +12 -9
- package/src/utilities/dom-builder/parseDomString.js +31 -29
- package/src/utilities/enable-submit-btns.js +7 -1
- package/src/utilities/{escapeHTML.js → escape-html.js} +13 -0
- package/src/utilities/linked-dates.js +64 -0
- package/src/utilities/relative-date.js +153 -0
- package/src/utilities/sentence-case.js +5 -2
- package/src/utilities/strip-tags.js +8 -0
- package/src/utilities/title-case.js +6 -2
- package/src/utilities/truncate-string.js +22 -0
- package/src/utilities/validation-error-class.js +14 -7
- package/src/web-components/s-datatable-component/s-datatable-component.js +1 -0
- 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 {
|
|
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 {
|
|
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 {
|
|
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.
|
|
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, //
|
|
8
|
+
ac_url: null, // search URL; must have leading and trailing slashes
|
|
9
9
|
|
|
10
|
-
//
|
|
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
|
-
|
|
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
|
|
18
|
-
val <==
|
|
19
|
-
list_display <==
|
|
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
|
-
|
|
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
|
-
//
|
|
31
|
-
__xxx__: item (
|
|
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
|
-
//
|
|
37
|
+
// field element or function returning the element
|
|
38
38
|
autocomplete_field: null,
|
|
39
39
|
|
|
40
|
-
//
|
|
40
|
+
// selector of the container element that wraps autocomplete_field and related elements
|
|
41
41
|
autocomplete_parent_selector: '.form-group',
|
|
42
42
|
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
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
|
|
49
|
-
//
|
|
50
|
-
//
|
|
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
|
-
//
|
|
54
|
+
// hidden field element; when set, `hidden_name` and `hidden_id` are ignored
|
|
55
55
|
hidden_field: null,
|
|
56
56
|
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
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
|
|
65
|
-
//
|
|
66
|
-
//
|
|
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
|
|
72
|
-
//
|
|
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
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
// NB:
|
|
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
|
-
//
|
|
80
|
+
// class assigned to the span containing the badge label
|
|
86
81
|
badge_label_class: 'ac-badge-label',
|
|
87
82
|
|
|
88
|
-
//
|
|
83
|
+
// class assigned to the badge remove button
|
|
89
84
|
badge_btn_class: 'ac-badge-btn',
|
|
90
85
|
|
|
91
86
|
|
|
92
|
-
// callback
|
|
93
|
-
//
|
|
94
|
-
// NB: row
|
|
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
|
-
//
|
|
92
|
+
// optional extra class for the results list
|
|
98
93
|
resultList_extra_class: null,
|
|
99
94
|
|
|
100
|
-
//
|
|
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}">×</button>` +
|
|
@@ -119,7 +120,7 @@ export default function (params = {}) {
|
|
|
119
120
|
params.autocomplete_field = params.autocomplete_field();
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
321
|
+
// badge click listener
|
|
318
322
|
badges_container?.addEventListener('click', e => {
|
|
319
|
-
const
|
|
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
|
-
|
|
324
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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:
|
|
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
|
-
|
|
4
|
-
|
|
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'; //
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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');
|
|
@@ -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
|
|
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, //
|
|
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
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
...(
|
|
22
|
-
...(custom_defaults.globals?? {}),
|
|
23
|
-
...(
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
96
|
-
const ok_btn = dialog.querySelector('.malert-ok'),
|
|
97
|
-
cancel_btn = dialog.querySelector('.malert-cancel'),
|
|
98
|
-
extra_btn =
|
|
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
|
-
|
|
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') {
|