@massimo-cassandro/minimo 1.6.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +4 -2
- 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 +58 -20
- 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/classnames.js +14 -1
- 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 +13 -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/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
|
@@ -1,26 +1,38 @@
|
|
|
1
1
|
import { domBuilderBasicSetup } from './domBuilderBasicSetup.js';
|
|
2
2
|
import { parseDomString } from './parseDomString.js';
|
|
3
3
|
|
|
4
|
-
// TODO
|
|
5
|
-
// TODO
|
|
4
|
+
// TODO multi-line string syntax where each line maps to an element
|
|
5
|
+
// TODO same as above with optional nesting via indentation
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} DomBuilderItem
|
|
9
|
+
* @property {string | string[]} [tag='div'] - HTML tag name or array of nested tags (each is parent of the next). Also `tagName`.
|
|
10
|
+
* @property {string | string[]} [tagName='div'] - Alias for `tag`.
|
|
11
|
+
* @property {string | string[]} [className] - CSS class(es): a single string or an array (falsy values filtered out).
|
|
12
|
+
* @property {string | string[]} [class] - Alias for `className`.
|
|
13
|
+
* @property {string | null} [id] - Unique element ID.
|
|
14
|
+
* @property {[string, *] | [string, *][] | Object<string, *>} [attrs] - Attributes: a `[name, value]` pair, array of pairs, or `{name: value}` object.
|
|
15
|
+
* @property {string | number | Function | Element | DomBuilderItem[] | null} [content] - Element content.
|
|
16
|
+
* @property {boolean} [condition=true] - When false, the element is skipped.
|
|
17
|
+
* @property {function(HTMLElement): void} [callback] - Callback invoked after the element is created.
|
|
18
|
+
* @property {DomBuilderItem[]} [children] - Configuration array for child elements.
|
|
19
|
+
*/
|
|
6
20
|
|
|
7
21
|
/**
|
|
8
22
|
* domBuilder
|
|
9
|
-
*
|
|
23
|
+
* Builds a DOM structure from a configuration array.
|
|
10
24
|
*
|
|
11
|
-
*
|
|
25
|
+
* Parses an array of configuration objects to create HTML elements.
|
|
12
26
|
*
|
|
13
|
-
* **
|
|
14
|
-
*
|
|
15
|
-
* L'array `structureArray` può contenere stringhe (con sintassi abbreviata) e/o oggetti di configurazione:
|
|
27
|
+
* **Configuration structure format (structureArray):**
|
|
16
28
|
*
|
|
17
29
|
* ```javascript
|
|
18
30
|
* structure = [
|
|
19
31
|
* '#mainContainer.container',
|
|
20
32
|
* 'p#main-info.info.active{data-id:123,role=button} Lorem ipsum',
|
|
21
33
|
* {
|
|
22
|
-
* tag: 'div' | ['.divClass', 'h2#id', 'table.class1', 'thead', 'tr.class2.class3(attr1: attrValue)'], //
|
|
23
|
-
* className: 'xxx' | ['class1', 'class2'], //
|
|
34
|
+
* tag: 'div' | ['.divClass', 'h2#id', 'table.class1', 'thead', 'tr.class2.class3(attr1: attrValue)'], // also tagName
|
|
35
|
+
* className: 'xxx' | ['class1', 'class2'], // also `class`
|
|
24
36
|
* id: 'element-id',
|
|
25
37
|
* attrs: [attr_name, attr_value] | [[...], [...]] | {name: value},
|
|
26
38
|
* content: 'xxx' | 123 | domBuilder Array | function | Element,
|
|
@@ -30,45 +42,33 @@ import { parseDomString } from './parseDomString.js';
|
|
|
30
42
|
* },
|
|
31
43
|
* 'div.class[data-xxx: value]',
|
|
32
44
|
* '.another-div',
|
|
33
|
-
* 'p#paragraph'
|
|
45
|
+
* 'p#paragraph',
|
|
34
46
|
* '...',
|
|
35
47
|
* { ... }
|
|
36
48
|
* ]
|
|
37
49
|
* ```
|
|
38
50
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
51
|
+
* IDs and classes can be specified either as top-level object keys or inside the `attrs` object.
|
|
52
|
+
* When both are present, top-level properties take precedence.
|
|
41
53
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
54
|
+
* A shorthand string syntax is also available, built according to `parseDomString` conventions.
|
|
55
|
+
* See `parseDomString` for details.
|
|
44
56
|
*
|
|
45
|
-
*
|
|
57
|
+
* The shorthand syntax does not support `content`, `callback`, `condition`, or `children`; use the object syntax for those.
|
|
46
58
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* (className, callback,
|
|
50
|
-
*
|
|
51
|
-
*
|
|
59
|
+
* The shorthand can also be used for the `tag` property in object syntax.
|
|
60
|
+
* When `tag` is an array, each element becomes the parent of the next one. Any other object properties
|
|
61
|
+
* (className, callback, etc.) are applied only to the last element in the array.
|
|
62
|
+
* When shorthand string and object-level properties conflict (classes, id, or attributes),
|
|
63
|
+
* object-level properties take precedence.
|
|
52
64
|
*
|
|
53
65
|
* @function domBuilder
|
|
54
|
-
* @param {Array<
|
|
55
|
-
* @param {
|
|
56
|
-
* @param {
|
|
57
|
-
* @param {
|
|
58
|
-
* @
|
|
59
|
-
* @param {string} [structureArray[].id] - ID univoco dell'elemento.
|
|
60
|
-
* @param {Array<[string, *]>|Array<Array<[string, *]>>|Object<string, *>} [structureArray[].attrs] - Attributi: coppia `[name, value]`, array di coppie o oggetto `{name: value}`.
|
|
61
|
-
* @param {string|number|Function|Element} [structureArray[].content] - Contenuto dell'elemento: stringa, numero, funzione che restituisce contenuto, o HTMLElement.
|
|
62
|
-
* @param {boolean} [structureArray[].condition=true] - Se false, l'elemento non viene generato.
|
|
63
|
-
* @param {Function} [structureArray[].callback] - Callback invocata quando l'elemento viene creato; riceve l'elemento come argomento.
|
|
64
|
-
* @param {Array<Object>} [structureArray[].children] - Array di configurazione per elementi figlio.
|
|
65
|
-
* @param {HTMLElement} [parent] - Elemento genitore al quale appendere la struttura.
|
|
66
|
-
* @param {Object} [options={}] - Opzioni di configurazione.
|
|
67
|
-
* @param {boolean} [options.emptyParent=true] - Se true, l'element parent indicato viene svuotato prima di procedere con ciclo builder
|
|
68
|
-
* @returns {HTMLElement|null} Il primo elemento creato, o null se nessun elemento è stato creato.
|
|
66
|
+
* @param {Array<DomBuilderItem|string>} [structureArray=[]] - Configuration array. Accepts strings (shorthand per `parseDomString`) and/or configuration objects.
|
|
67
|
+
* @param {HTMLElement} [parent] - Parent element to append the structure to.
|
|
68
|
+
* @param {Object} [options={}] - Configuration options.
|
|
69
|
+
* @param {boolean} [options.emptyParent=false] - When true, the parent element is emptied before building.
|
|
70
|
+
* @returns {HTMLElement|null} The first created element, or null if nothing was created.
|
|
69
71
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
72
72
|
export function domBuilder(structureArray = [], parent, options = {}) {
|
|
73
73
|
|
|
74
74
|
options = {
|
|
@@ -76,45 +76,55 @@ export function domBuilder(structureArray = [], parent, options = {}) {
|
|
|
76
76
|
...options
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
/** @type {HTMLElement | null} */
|
|
80
|
+
let mainElement = null;
|
|
81
|
+
/** @type {HTMLElement} */
|
|
82
|
+
let el;
|
|
83
|
+
/** @type {HTMLElement | null} */
|
|
84
|
+
let grand_parent = null;
|
|
80
85
|
|
|
81
86
|
if(parent && options.emptyParent) {
|
|
82
87
|
parent.innerHTML = '';
|
|
83
88
|
}
|
|
84
89
|
|
|
85
|
-
structureArray.forEach(
|
|
90
|
+
structureArray.forEach(inputItem => {
|
|
86
91
|
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
/** @type {DomBuilderItem | null} */
|
|
93
|
+
let item;
|
|
94
|
+
if (inputItem != null && typeof inputItem === 'string' && inputItem !== '') {
|
|
95
|
+
item = parseDomString(inputItem);
|
|
96
|
+
} else {
|
|
97
|
+
item = /** @type {DomBuilderItem} */ (inputItem);
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
// console.log(item);
|
|
92
101
|
|
|
93
102
|
if (item != null && (item.condition ?? true)) {
|
|
94
103
|
|
|
104
|
+
const safeItem = item; // const binding so TypeScript tracks non-null type in nested closures
|
|
95
105
|
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
if (Array.isArray(
|
|
106
|
+
// when tag is an array, create a series of nested elements;
|
|
107
|
+
// the last one receives the remaining object properties
|
|
108
|
+
if (Array.isArray(safeItem.tag)) {
|
|
99
109
|
|
|
100
|
-
grand_parent = parent;
|
|
110
|
+
grand_parent = parent ?? null;
|
|
111
|
+
const tags = /** @type {string[]} */ (safeItem.tag);
|
|
101
112
|
|
|
102
|
-
|
|
113
|
+
tags.forEach((tagItem, idx) => {
|
|
103
114
|
|
|
104
|
-
const isLast = idx ===
|
|
105
|
-
const parsedItem = parseDomString(tagItem);
|
|
115
|
+
const isLast = idx === tags.length - 1;
|
|
116
|
+
const parsedItem = parseDomString(tagItem) ?? { tag: 'div', id: undefined, className: '', attrs: {}, content: undefined };
|
|
106
117
|
|
|
107
|
-
//
|
|
108
|
-
// con precedenza alle proprietà di quest'ultimo
|
|
118
|
+
// for the last element, merge with the object's own options; object-level properties take precedence
|
|
109
119
|
if(isLast) {
|
|
110
|
-
|
|
120
|
+
safeItem.attrs = {...parsedItem.attrs ?? {}, ...safeItem.attrs ?? {}};
|
|
111
121
|
}
|
|
112
122
|
el = domBuilderBasicSetup(
|
|
113
123
|
document.createElement(parsedItem.tag || 'div'),
|
|
114
|
-
{...parsedItem, ...(isLast?
|
|
124
|
+
{...parsedItem, ...(isLast ? safeItem : {})}
|
|
115
125
|
);
|
|
116
126
|
|
|
117
|
-
if (!isLast) { //
|
|
127
|
+
if (!isLast) { // the last element is handled by the standard path below
|
|
118
128
|
if (parent) {
|
|
119
129
|
parent.appendChild(el);
|
|
120
130
|
}
|
|
@@ -125,40 +135,41 @@ export function domBuilder(structureArray = [], parent, options = {}) {
|
|
|
125
135
|
|
|
126
136
|
} else {
|
|
127
137
|
el = domBuilderBasicSetup(
|
|
128
|
-
document.createElement(
|
|
129
|
-
|
|
138
|
+
document.createElement(/** @type {string} */ (safeItem.tag) ?? 'div'),
|
|
139
|
+
safeItem
|
|
130
140
|
);
|
|
131
141
|
}
|
|
132
142
|
|
|
133
143
|
|
|
134
|
-
if (
|
|
144
|
+
if (safeItem.content != null) {
|
|
135
145
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
146
|
+
/** @type {string | Element | null} */
|
|
147
|
+
let content = null;
|
|
148
|
+
if (typeof safeItem.content === 'function') {
|
|
149
|
+
content = safeItem.content();
|
|
139
150
|
|
|
140
|
-
} else if (Array.isArray(
|
|
141
|
-
content = domBuilder(
|
|
151
|
+
} else if (Array.isArray(safeItem.content)) {
|
|
152
|
+
content = domBuilder(safeItem.content);
|
|
142
153
|
|
|
143
|
-
} else if (
|
|
144
|
-
content = String(
|
|
154
|
+
} else if (safeItem.content != null) {
|
|
155
|
+
content = String(safeItem.content);
|
|
145
156
|
}
|
|
146
157
|
|
|
147
158
|
if (content instanceof Element) {
|
|
148
159
|
el.appendChild(content);
|
|
149
160
|
|
|
150
|
-
} else {
|
|
161
|
+
} else if (content != null) {
|
|
151
162
|
el.innerHTML = content;
|
|
152
163
|
}
|
|
153
164
|
}
|
|
154
165
|
|
|
155
166
|
|
|
156
|
-
if (
|
|
167
|
+
if (safeItem.children != null && !Array.isArray(safeItem.children)) {
|
|
157
168
|
// eslint-disable-next-line no-console
|
|
158
|
-
console.error('Error: `item.children` must be an array → ' +
|
|
169
|
+
console.error('Error: `item.children` must be an array → ' + safeItem.children);
|
|
159
170
|
}
|
|
160
|
-
if (
|
|
161
|
-
domBuilder(
|
|
171
|
+
if (safeItem.children && Array.isArray(safeItem.children)) {
|
|
172
|
+
domBuilder(safeItem.children, el, {emptyParent: false});
|
|
162
173
|
}
|
|
163
174
|
|
|
164
175
|
if (mainElement == null) {
|
|
@@ -169,19 +180,17 @@ export function domBuilder(structureArray = [], parent, options = {}) {
|
|
|
169
180
|
parent.appendChild(el);
|
|
170
181
|
}
|
|
171
182
|
|
|
172
|
-
// TODO
|
|
173
|
-
if (
|
|
174
|
-
|
|
183
|
+
// TODO callbacks that act on the element's children may not run when no parent is set
|
|
184
|
+
if (safeItem.callback && typeof safeItem.callback === 'function') {
|
|
185
|
+
safeItem.callback(el);
|
|
175
186
|
}
|
|
176
187
|
|
|
177
188
|
if (grand_parent) {
|
|
178
189
|
parent = grand_parent;
|
|
179
190
|
}
|
|
180
|
-
}
|
|
191
|
+
}
|
|
181
192
|
|
|
182
|
-
});
|
|
193
|
+
});
|
|
183
194
|
|
|
184
195
|
return mainElement;
|
|
185
196
|
}
|
|
186
|
-
|
|
187
|
-
|
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
|
|
2
|
+
/** @typedef {import('./dom-builder.js').DomBuilderItem} DomBuilderItem */
|
|
3
|
+
|
|
2
4
|
/**
|
|
3
|
-
*
|
|
5
|
+
* Applies basic domBuilder configuration to an element: classes, id, and attributes.
|
|
6
|
+
* @param {HTMLElement} element - The element to configure.
|
|
7
|
+
* @param {DomBuilderItem} domBuilderItem - The domBuilder configuration object.
|
|
8
|
+
* @returns {HTMLElement}
|
|
4
9
|
*/
|
|
5
10
|
export function domBuilderBasicSetup (element, domBuilderItem) {
|
|
6
11
|
|
|
7
|
-
//
|
|
8
|
-
// possibili denominazioni: class o className
|
|
9
|
-
// se presente className, class viene ignorato
|
|
12
|
+
// class aliases: accept both `class` and `className`; `className` takes precedence
|
|
10
13
|
if (domBuilderItem.class && !domBuilderItem.className) {
|
|
11
14
|
domBuilderItem.className = domBuilderItem.class;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
//
|
|
17
|
+
// normalize attrs: a single flat array is treated as a single [name, value] pair
|
|
15
18
|
if (domBuilderItem.attrs && Array.isArray(domBuilderItem.attrs) && !Array.isArray(domBuilderItem.attrs[0])) {
|
|
16
19
|
domBuilderItem.attrs = [ domBuilderItem.attrs ];
|
|
17
20
|
|
|
18
|
-
//
|
|
21
|
+
// attrs can also be a plain object: {attr_name: attr_value}
|
|
19
22
|
} else if (typeof domBuilderItem.attrs === 'object' && !Array.isArray(domBuilderItem.attrs) && domBuilderItem.attrs !== null) {
|
|
20
23
|
domBuilderItem.attrs = Object.entries(domBuilderItem.attrs);
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
//
|
|
26
|
+
// boolean attributes are omitted when their value is explicitly false
|
|
24
27
|
const boolAttrs = [
|
|
25
28
|
'allowfullscreen',
|
|
26
29
|
'async',
|
|
@@ -54,13 +57,13 @@ export function domBuilderBasicSetup (element, domBuilderItem) {
|
|
|
54
57
|
|
|
55
58
|
|
|
56
59
|
|
|
57
|
-
(domBuilderItem.attrs ?? []).forEach(attr => {
|
|
60
|
+
(/** @type {[string, unknown][]} */ (domBuilderItem.attrs ?? [])).forEach(attr => {
|
|
58
61
|
if (attr[1] != null && !(boolAttrs.includes(attr[0]) && attr[1]=== false)) {
|
|
59
62
|
element.setAttribute(attr[0], String(attr[1]));
|
|
60
63
|
}
|
|
61
64
|
});
|
|
62
65
|
|
|
63
|
-
//
|
|
66
|
+
// assign classes and id after attrs so object-level properties take precedence
|
|
64
67
|
if(domBuilderItem.className) {
|
|
65
68
|
element.className = Array.isArray(domBuilderItem.className)
|
|
66
69
|
? domBuilderItem.className.filter(Boolean).join(' ')
|
|
@@ -1,48 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Parses a string to extract tag name, ID, classes, and attributes.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Expected string format:
|
|
5
5
|
*
|
|
6
|
-
* `tag#id.
|
|
6
|
+
* `tag#id.class1.class2.classN(attr1: val1, attr2=val2) text content`
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* * `tag`
|
|
10
|
-
* *
|
|
11
|
-
* *
|
|
12
|
-
* * `(...)`
|
|
13
|
-
*
|
|
14
|
-
* *
|
|
8
|
+
* Where:
|
|
9
|
+
* * `tag` is the element name; defaults to `div` if omitted
|
|
10
|
+
* * `#id` is the optional element id; must immediately follow the tag
|
|
11
|
+
* * `.class1, .class2...` are optional CSS classes, each preceded by `.`
|
|
12
|
+
* * `(...)` is the optional attribute block; can also use `[...]` or `{...}`.
|
|
13
|
+
* Each name–value pair is separated by `:` or `=`. A bare name (no value) defaults to `true`.
|
|
14
|
+
* * Text content follows after a space
|
|
15
15
|
*
|
|
16
|
-
*
|
|
16
|
+
* Returns a domBuilder child object.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
18
|
+
* Examples:
|
|
19
19
|
* > p#main-info.info.active{data-id:123,role=button} text content
|
|
20
20
|
*
|
|
21
|
-
* oppure
|
|
22
21
|
* > input#search-field[type=text,disabled]
|
|
23
22
|
*
|
|
24
|
-
* @param {string} domString
|
|
25
|
-
* @returns {{
|
|
23
|
+
* @param {string} domString - The domBuilder string to parse.
|
|
24
|
+
* @returns {{tag: string, id: string|null, className: string, attrs: Object<string, string|true>, content: string|null}|null}
|
|
26
25
|
*/
|
|
27
26
|
|
|
28
27
|
export function parseDomString(domString) {
|
|
29
28
|
|
|
30
|
-
//
|
|
29
|
+
// version using named capture groups (kept for reference)
|
|
31
30
|
// const regex = new RegExp(
|
|
32
31
|
// /^(?<tag>[a-zA-Z][a-zA-Z0-9-]*)?/.source + // tag
|
|
33
32
|
// /(?:#(?<id>[a-zA-Z0-9_-]+))?/.source + // id
|
|
34
|
-
// /(?<classes>(?:\.[a-zA-Z0-9_-]+)*)?/.source + //
|
|
33
|
+
// /(?<classes>(?:\.[a-zA-Z0-9_-]+)*)?/.source + // classes
|
|
35
34
|
// /(?<attrs>[([{].*?[)\]}])?/.source + // attrs
|
|
36
35
|
// /(?: +(?<content>.*))?$/.source // content
|
|
37
36
|
// );
|
|
38
37
|
|
|
39
38
|
|
|
40
|
-
// FIX
|
|
39
|
+
// FIX: does not work when id follows classes: <TAG>.<CLASS>#<ID>; id must precede classes
|
|
41
40
|
|
|
42
41
|
const regex = new RegExp(
|
|
43
|
-
/^([a-zA-Z][a-zA-Z0-9-]*)?/.source + // tag (
|
|
42
|
+
/^([a-zA-Z][a-zA-Z0-9-]*)?/.source + // tag (also accepts web component names)
|
|
44
43
|
/(?:#([a-zA-Z0-9_-]+))?/.source + // id
|
|
45
|
-
/((?:\.[a-zA-Z0-9_-]+)*)?/.source + //
|
|
44
|
+
/((?:\.[a-zA-Z0-9_-]+)*)?/.source + // classes
|
|
46
45
|
/([([{].*?[)\]}])?/.source + // attrs
|
|
47
46
|
/(?: +(.*))?$/.source // content
|
|
48
47
|
);
|
|
@@ -54,12 +53,12 @@ export function parseDomString(domString) {
|
|
|
54
53
|
return null;
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
//
|
|
58
|
-
// [0]:
|
|
59
|
-
// [1]:
|
|
60
|
-
// [2]:
|
|
61
|
-
// [3]:
|
|
62
|
-
// [4]:
|
|
56
|
+
// Capture group indices:
|
|
57
|
+
// [0]: full match
|
|
58
|
+
// [1]: tag
|
|
59
|
+
// [2]: id
|
|
60
|
+
// [3]: classes
|
|
61
|
+
// [4]: raw attributes
|
|
63
62
|
// [5]: content
|
|
64
63
|
|
|
65
64
|
const tag = (matches[1] || 'div').toLowerCase(),
|
|
@@ -69,7 +68,7 @@ export function parseDomString(domString) {
|
|
|
69
68
|
content = matches[5]?.trim() || null
|
|
70
69
|
;
|
|
71
70
|
|
|
72
|
-
//
|
|
71
|
+
// version using named capture groups (kept for reference)
|
|
73
72
|
// const tag = (matches.groups.tag || 'div').toLowerCase(),
|
|
74
73
|
// id = matches.groups.id?.trim() || null,
|
|
75
74
|
// classes = matches.groups.classes? matches[3].slice(1).split('.').map(c => c.trim()) : [],
|
|
@@ -77,6 +76,7 @@ export function parseDomString(domString) {
|
|
|
77
76
|
// content = matches.groups.content?.trim() || null
|
|
78
77
|
// ;
|
|
79
78
|
|
|
79
|
+
/** @type {Record<string, string | true>} */
|
|
80
80
|
const attrs = {};
|
|
81
81
|
if (rawAttrs) {
|
|
82
82
|
const attrsContent = rawAttrs.substring(1, rawAttrs.length - 1);
|
|
@@ -86,10 +86,12 @@ export function parseDomString(domString) {
|
|
|
86
86
|
if (!pair) return;
|
|
87
87
|
if (pair.includes('=') || pair.includes(':')) {
|
|
88
88
|
const parts = pair.split(/[:=]/, 2),
|
|
89
|
-
name = parts[0]?.trim(),
|
|
89
|
+
name = parts[0]?.trim() ?? '',
|
|
90
90
|
value = parts[1] != null ? parts[1].trim() : true;
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
if (name) {
|
|
93
|
+
attrs[name] = value;
|
|
94
|
+
}
|
|
93
95
|
|
|
94
96
|
} else {
|
|
95
97
|
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-enables all submit and non-disabled button elements within the given context.
|
|
3
|
+
* @param {Document | Element} [context=document]
|
|
4
|
+
* @returns {void}
|
|
5
|
+
*/
|
|
1
6
|
export function enableSubmitBtns(context = document) {
|
|
2
7
|
context.querySelectorAll('[type=submit], [type=button]:not([data-disabled])').forEach(btn => {
|
|
3
|
-
|
|
8
|
+
const button = /** @type {HTMLButtonElement | HTMLInputElement} */ (btn);
|
|
9
|
+
button.disabled = false;
|
|
4
10
|
});
|
|
5
11
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** @type {Record<string, string>} */
|
|
1
2
|
const ESCAPE_MAP = {
|
|
2
3
|
'&' : '&',
|
|
3
4
|
'"' : '"',
|
|
@@ -5,10 +6,22 @@ const ESCAPE_MAP = {
|
|
|
5
6
|
'<' : '<',
|
|
6
7
|
'>' : '>'
|
|
7
8
|
};
|
|
9
|
+
/** @type {Record<string, string>} */
|
|
8
10
|
const UNESCAPE_MAP = Object.fromEntries(Object.entries(ESCAPE_MAP).map(([k, v]) => [v, k]));
|
|
9
11
|
|
|
10
12
|
const ESCAPE_RE = /[&"'<>]/g;
|
|
11
13
|
const UNESCAPE_RE = /&|"|'|<|>/g;
|
|
12
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Escapes special HTML characters in a string.
|
|
17
|
+
* @param {string | null | undefined} str
|
|
18
|
+
* @returns {string}
|
|
19
|
+
*/
|
|
13
20
|
export const escapeHTML = str => str?.replace(ESCAPE_RE, m => ESCAPE_MAP[m]) ?? '';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Unescapes HTML entities back to their original characters.
|
|
24
|
+
* @param {string | null | undefined} str
|
|
25
|
+
* @returns {string}
|
|
26
|
+
*/
|
|
14
27
|
export const unescapeHTML = str => str?.replace(UNESCAPE_RE, m => UNESCAPE_MAP[m]) ?? '';
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Converts a string to sentence case (first letter uppercase, rest lowercase).
|
|
3
|
+
* @param {string | null | undefined} str
|
|
4
|
+
* @returns {string | null | undefined}
|
|
5
|
+
*/
|
|
3
6
|
export function sentenceCase(str) {
|
|
4
7
|
if(str) {
|
|
5
8
|
return str.charAt(0).toUpperCase() + str.substr(1).toLowerCase();
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Converts a string to title case (first letter of each word uppercase, rest lowercase).
|
|
3
|
+
* @param {string | null | undefined} str
|
|
4
|
+
* @returns {string | null | undefined}
|
|
5
|
+
*/
|
|
2
6
|
export function titleCase(str) {
|
|
3
7
|
|
|
4
8
|
if(str) {
|
|
@@ -11,4 +15,4 @@ export function titleCase(str) {
|
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
return str;
|
|
14
|
-
}
|
|
18
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
// throw new ValidationError('Sono presenti valori duplicati', campiErrore );
|
|
1
|
+
// usage:
|
|
2
|
+
// throw new ValidationError('Duplicate values found', errorFields);
|
|
4
3
|
|
|
5
4
|
/*
|
|
6
|
-
|
|
5
|
+
example:
|
|
7
6
|
|
|
8
7
|
const form = document.getElementById('form-__xxxx___');
|
|
9
8
|
form.addEventListener('submit', e => {
|
|
@@ -13,7 +12,7 @@ form.addEventListener('submit', e => {
|
|
|
13
12
|
form.classList.add('was-validated');
|
|
14
13
|
try {
|
|
15
14
|
if( ... ) {
|
|
16
|
-
throw new ValidationError('
|
|
15
|
+
throw new ValidationError('__message__', [errorFields, ...] );
|
|
17
16
|
}
|
|
18
17
|
} catch( error ) {
|
|
19
18
|
e.preventDefault();
|
|
@@ -35,10 +34,18 @@ form.addEventListener('submit', e => {
|
|
|
35
34
|
});
|
|
36
35
|
*/
|
|
37
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Custom error class for form validation failures.
|
|
39
|
+
* Extends the native Error with an array of invalid form fields.
|
|
40
|
+
*/
|
|
38
41
|
export class ValidationError extends Error {
|
|
42
|
+
/**
|
|
43
|
+
* @param {string} message - Error message.
|
|
44
|
+
* @param {Element[]} fields - Array of invalid form field elements.
|
|
45
|
+
*/
|
|
39
46
|
constructor(message, fields) {
|
|
40
|
-
super(message);
|
|
47
|
+
super(message);
|
|
41
48
|
this.name = 'ValidationError';
|
|
42
|
-
this.fields = fields;
|
|
49
|
+
this.fields = fields;
|
|
43
50
|
}
|
|
44
51
|
}
|