@massimo-cassandro/minimo 1.22.1 → 1.23.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/package.json
CHANGED
package/src/css/forms/switch.css
CHANGED
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
/* TODO valutare versione con label a sinistra (form-check-reverse) */
|
|
3
3
|
|
|
4
4
|
/*
|
|
5
|
-
Markup (
|
|
5
|
+
Markup (same as bootstrap 5, plus the native `switch` attribute):
|
|
6
6
|
|
|
7
7
|
<div class="form-check form-switch">
|
|
8
8
|
<input class="form-check-input" type="checkbox" role="switch" switch id="my-switch">
|
|
9
|
-
<label class="form-
|
|
9
|
+
<label class="form-label" for="my-switch">Enable notifications</label>
|
|
10
10
|
</div>
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
`appearance: none`
|
|
12
|
+
The `switch` attribute (WebKit only for now) makes the browser render a real
|
|
13
|
+
native switch control, which can be styled through the `::track` and `::thumb`
|
|
14
|
+
pseudo-elements. Where it is not supported (or where the attribute is missing),
|
|
15
|
+
the `appearance: none` fallback is applied, with the thumb drawn as a
|
|
16
|
+
background-image.
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
.form-switch {
|
|
@@ -34,11 +35,12 @@ Dove non è supportato (o dove l'attributo è assente) si applica il fallback
|
|
|
34
35
|
appearance: none;
|
|
35
36
|
cursor: pointer;
|
|
36
37
|
background-color: var(--form-switch-background-color);
|
|
37
|
-
background-image:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
background-image:
|
|
39
|
+
radial-gradient(
|
|
40
|
+
circle closest-side,
|
|
41
|
+
var(--form-switch-cursor-background-color) 98%,
|
|
42
|
+
transparent
|
|
43
|
+
);
|
|
42
44
|
background-repeat: no-repeat;
|
|
43
45
|
background-position: var(--form-switch-thumb-inset) center;
|
|
44
46
|
background-size: var(--_switch-thumb-size) var(--_switch-thumb-size);
|
|
@@ -63,7 +65,8 @@ Dove non è supportato (o dove l'attributo è assente) si applica il fallback
|
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
.form-
|
|
68
|
+
.form-label {
|
|
69
|
+
padding-inline-start: 0 !important;
|
|
67
70
|
cursor: pointer;
|
|
68
71
|
user-select: none;
|
|
69
72
|
}
|
|
@@ -71,7 +74,7 @@ Dove non è supportato (o dove l'attributo è assente) si applica il fallback
|
|
|
71
74
|
&:has(.form-check-input:disabled) {
|
|
72
75
|
opacity: var(--form-control-disabled-opacity);
|
|
73
76
|
|
|
74
|
-
.form-check-input, .form-
|
|
77
|
+
.form-check-input, .form-label {
|
|
75
78
|
cursor: not-allowed;
|
|
76
79
|
}
|
|
77
80
|
}
|
|
@@ -18,8 +18,12 @@ import { parseDomString } from './parseDomString.js';
|
|
|
18
18
|
* A string or number is set as plain text (`textContent`) unless it contains `<`, in which case it is
|
|
19
19
|
* treated as markup: sanitized and inserted via the native Sanitizer API (`Element.setHTML`) where
|
|
20
20
|
* supported, falling back to raw `innerHTML` on browsers without it.
|
|
21
|
-
* @property {
|
|
22
|
-
*
|
|
21
|
+
* @property {string | number} [text] - Literal text node shorthand: inserts a plain `Text` node (never
|
|
22
|
+
* parsed as markup) in place of an element, so it can sit as a sibling of tags within `children`/`content`
|
|
23
|
+
* arrays. Mutually exclusive with `tag`/`content`/`children`: when set (and `tag` is absent), every other
|
|
24
|
+
* property except `condition` and `callback` is ignored.
|
|
25
|
+
* @property {boolean} [condition=true] - When false, the element (or text node) is skipped.
|
|
26
|
+
* @property {(function(HTMLElement|Text): void) | null} [callback] - Callback invoked after the element (or text node) is created.
|
|
23
27
|
* @property {Array<DomBuilderItem|string>} [children] - Configuration array for child elements. Accepts strings (shorthand per `parseDomString`) and/or configuration objects.
|
|
24
28
|
*/
|
|
25
29
|
|
|
@@ -53,6 +57,18 @@ import { parseDomString } from './parseDomString.js';
|
|
|
53
57
|
* ]
|
|
54
58
|
* ```
|
|
55
59
|
*
|
|
60
|
+
* To mix literal text and tags as siblings within the same element (e.g. `Lorem <strong>ipsum</strong> dolor`),
|
|
61
|
+
* use the `text` shorthand in `children`/`content`, since a plain string item is always parsed as a tag
|
|
62
|
+
* (see `parseDomString`), never as a text node:
|
|
63
|
+
*
|
|
64
|
+
* ```javascript
|
|
65
|
+
* children: [
|
|
66
|
+
* { text: 'Lorem ' },
|
|
67
|
+
* { tag: 'strong', content: 'ipsum' },
|
|
68
|
+
* { text: ' dolor' }
|
|
69
|
+
* ]
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
56
72
|
* IDs and classes can be specified either as top-level object keys or inside the `attrs` object.
|
|
57
73
|
* When both are present, top-level properties take precedence.
|
|
58
74
|
*
|
|
@@ -74,8 +90,9 @@ import { parseDomString } from './parseDomString.js';
|
|
|
74
90
|
* - `id` {string | null} - Unique element ID.
|
|
75
91
|
* - `attrs` {[string, *] | [string, *][] | Object<string, *>} - Attributes: a `[name, value]` pair, array of pairs, or `{name: value}` object.
|
|
76
92
|
* - `content` {string | number | Function | Element | DomBuilderItem[] | null} - Element content. A string/number is set as `textContent` unless it contains `<`, in which case it's sanitized and inserted as markup (`Element.setHTML`, falling back to `innerHTML`).
|
|
77
|
-
* - `
|
|
78
|
-
* - `
|
|
93
|
+
* - `text` {string | number} - Literal text node shorthand: inserts a plain `Text` node (never parsed as markup) as a sibling of other items, in place of an element. Mutually exclusive with `tag`/`content`/`children`.
|
|
94
|
+
* - `condition` {boolean} - When false, the element (or text node) is skipped. Default `true`.
|
|
95
|
+
* - `callback` {(function(HTMLElement|Text): void) | null} - Invoked after the element (or text node) is created.
|
|
79
96
|
* - `children` {Array<DomBuilderItem|string>} - Configuration array for child elements (same format, nested).
|
|
80
97
|
*
|
|
81
98
|
* IDs and classes can be specified either as top-level object keys or inside `attrs`; top-level properties take precedence.
|
|
@@ -109,7 +126,7 @@ export function domBuilder(structureArray = [], parent, options = {}) {
|
|
|
109
126
|
/** @type {HTMLElement | DocumentFragment | null} */
|
|
110
127
|
let target = useFragment ? document.createDocumentFragment() : (parent ?? null);
|
|
111
128
|
|
|
112
|
-
/** @type {Map<HTMLElement | DocumentFragment,
|
|
129
|
+
/** @type {Map<HTMLElement | DocumentFragment, Node>} tracks, for `insertMode: 'after'`, the last inserted sibling per parent node */
|
|
113
130
|
const afterAnchors = new Map();
|
|
114
131
|
|
|
115
132
|
/** @type {HTMLElement | null} */
|
|
@@ -135,6 +152,34 @@ export function domBuilder(structureArray = [], parent, options = {}) {
|
|
|
135
152
|
|
|
136
153
|
const safeItem = item; // const binding so TypeScript tracks non-null type in nested closures
|
|
137
154
|
|
|
155
|
+
// `{ text: '...' }` shorthand: a literal Text node, never parsed as markup, inserted in
|
|
156
|
+
// place of an element so it can sit as a sibling of tags within `children`/`content` arrays
|
|
157
|
+
if (safeItem.tag == null && (typeof safeItem.text === 'string' || typeof safeItem.text === 'number')) {
|
|
158
|
+
|
|
159
|
+
const textNode = document.createTextNode(String(safeItem.text));
|
|
160
|
+
|
|
161
|
+
if (target) {
|
|
162
|
+
if (options.insertMode === 'before') {
|
|
163
|
+
const anchor = /** @type {HTMLElement} */ (target);
|
|
164
|
+
anchor.parentNode?.insertBefore(textNode, anchor);
|
|
165
|
+
|
|
166
|
+
} else if (options.insertMode === 'after') {
|
|
167
|
+
const anchor = afterAnchors.get(/** @type {HTMLElement} */ (target)) ?? /** @type {HTMLElement} */ (target);
|
|
168
|
+
anchor.parentNode?.insertBefore(textNode, anchor.nextSibling);
|
|
169
|
+
afterAnchors.set(/** @type {HTMLElement} */ (target), textNode);
|
|
170
|
+
|
|
171
|
+
} else {
|
|
172
|
+
target.appendChild(textNode);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (safeItem.callback && typeof safeItem.callback === 'function') {
|
|
177
|
+
safeItem.callback(textNode);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
138
183
|
// when tag is an array, create a series of nested elements;
|
|
139
184
|
// the last one receives the remaining object properties
|
|
140
185
|
if (Array.isArray(safeItem.tag)) {
|
|
@@ -132,12 +132,21 @@ export function buildSelect({
|
|
|
132
132
|
|
|
133
133
|
id = id || randomId();
|
|
134
134
|
|
|
135
|
-
// normalizing
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
// normalizing options as an array of [value, text] pairs
|
|
136
|
+
/** @type {Array<[string|number, string]>} */
|
|
137
|
+
let optionsList = [];
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
if(options) {
|
|
140
|
+
if(!Array.isArray(options)) {
|
|
141
|
+
optionsList = /** @type {Array<[string|number, string]>} */ (Object.entries(options));
|
|
142
|
+
|
|
143
|
+
} else if(!Array.isArray(options[0])) {
|
|
144
|
+
optionsList = /** @type {Array<Record<string, string>>} */ (options)
|
|
145
|
+
.map(o => /** @type {[string|number, string]} */ (Object.values(o)));
|
|
146
|
+
|
|
147
|
+
} else {
|
|
148
|
+
optionsList = /** @type {Array<[string|number, string]>} */ (options);
|
|
149
|
+
}
|
|
141
150
|
}
|
|
142
151
|
|
|
143
152
|
|
|
@@ -157,18 +166,15 @@ export function buildSelect({
|
|
|
157
166
|
},
|
|
158
167
|
children: [
|
|
159
168
|
...(addEmptyOption? ['option[value:]'] : []),
|
|
160
|
-
...(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}))
|
|
170
|
-
: []
|
|
171
|
-
)
|
|
169
|
+
...optionsList.map(([value, text]) => ({
|
|
170
|
+
tag: 'option',
|
|
171
|
+
attrs: {
|
|
172
|
+
value: value,
|
|
173
|
+
// eslint-disable-next-line eqeqeq
|
|
174
|
+
selected: value == selectedValue
|
|
175
|
+
},
|
|
176
|
+
content: text
|
|
177
|
+
}))
|
|
172
178
|
],
|
|
173
179
|
callback: callback
|
|
174
180
|
}
|
|
@@ -186,6 +192,8 @@ export function buildSelect({
|
|
|
186
192
|
* @param {string | null} args.id - `id` attribute.
|
|
187
193
|
* @param {string | number | null} [args.value=1] - checkbox `value` attribute.
|
|
188
194
|
* @param {boolean} [args.checked=false] - checkbox `checked` attribute.
|
|
195
|
+
* @param {boolean} [args.switch=false] - When true, the checkbox is rendered as a switch
|
|
196
|
+
* (`.form-switch` wrapper, `role="switch"` and the native `switch` attribute).
|
|
189
197
|
* @param {string | null} [args.wrapperClass=null] - optional class to be added to the `.form-group` wrapper (used only when `addFormGroup` is true).
|
|
190
198
|
* @param {boolean} [args.addFormGroup=true] - When true, wraps the checkbox in a `.form-group` element.
|
|
191
199
|
* @param {boolean} [args.condition=true] - When false, the function returns `null` without building anything.
|
|
@@ -200,6 +208,7 @@ export function buildCheckbox({
|
|
|
200
208
|
id = null,
|
|
201
209
|
value = 1,
|
|
202
210
|
checked = false,
|
|
211
|
+
switch: isSwitch = false,
|
|
203
212
|
wrapperClass = null,
|
|
204
213
|
addFormGroup = true,
|
|
205
214
|
condition = true,
|
|
@@ -213,17 +222,24 @@ export function buildCheckbox({
|
|
|
213
222
|
}
|
|
214
223
|
|
|
215
224
|
/*
|
|
216
|
-
<div class="form-group">
|
|
225
|
+
<div class="form-group"> <- optional
|
|
217
226
|
<div class="form-check">
|
|
218
227
|
<input type="checkbox" id="..." name="..." class="form-check-input" value="1">
|
|
219
228
|
<label for="..." class="form-label">...</label>
|
|
220
229
|
</div>
|
|
221
230
|
</div>
|
|
231
|
+
|
|
232
|
+
<div class="form-group"> <- optional
|
|
233
|
+
<div class="form-check form-switch">
|
|
234
|
+
<input class="form-check-input" type="checkbox" role="switch" switch id="my-switch">
|
|
235
|
+
<label class="form-label" for="my-switch">My label</label>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
222
238
|
*/
|
|
223
239
|
id = id || randomId();
|
|
224
240
|
|
|
225
241
|
const tag = {
|
|
226
|
-
className: classnames('form-check', !addFormGroup && !help && wrapperClass),
|
|
242
|
+
className: classnames('form-check', isSwitch && 'form-switch', !addFormGroup && !help && wrapperClass),
|
|
227
243
|
children: [
|
|
228
244
|
{
|
|
229
245
|
tag: 'input',
|
|
@@ -234,7 +250,9 @@ export function buildCheckbox({
|
|
|
234
250
|
type: 'checkbox',
|
|
235
251
|
value: value,
|
|
236
252
|
name: name,
|
|
237
|
-
checked: checked
|
|
253
|
+
checked: checked,
|
|
254
|
+
// native switch control (WebKit only for now), with the css fallback elsewhere
|
|
255
|
+
...(isSwitch? {role: 'switch', switch: ''} : {})
|
|
238
256
|
},
|
|
239
257
|
callback: callback
|
|
240
258
|
},
|