@massimo-cassandro/minimo 0.8.8 → 0.9.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/README.md +19 -0
- package/design-tokens-src/components/snackbar.minimo.tokens.mjs +35 -0
- package/index.js +1 -0
- package/package.json +41 -3
- package/src/components/modal-alert/TODO.md +2 -0
- package/src/components/modal-alert/modal-alert.css +2 -3
- package/src/components/snackbar/snackbar.js +105 -0
- package/src/components/snackbar/snackbar.module.css +70 -0
- package/src/css/alerts.css +1 -1
- package/src/css/anchors.css +5 -1
- package/src/css/buttons/btn-close.css +4 -0
- package/src/css/buttons/buttons-primary.css +4 -1
- package/src/css/buttons/buttons-secondary.css +5 -1
- package/src/css/buttons/buttons.css +5 -1
- package/src/css/buttons/status-buttons.css +8 -4
- package/src/css/forms/forms.css +4 -0
- package/src/css/forms/input-group.css +5 -1
- package/src/css/forms/validation.css +4 -0
- package/src/css/text.css +5 -1
- package/src/utilities/dom-builder/dom-builder.js +184 -0
- package/src/utilities/dom-builder/domBuilderBasicSetup.js +75 -0
- package/src/utilities/dom-builder/parseDomString.js +105 -0
package/README.md
CHANGED
|
@@ -11,3 +11,22 @@ npm i @massimo-cassandro/minimo
|
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
> Note: **minimo** is designed to be used with WebPack using the configuration you can find in the [webpack-setup folder](./webpack-setup/) of this repository. If changes are made (for example, to how SVG files are imported), things may not work as expected
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## Utilities
|
|
17
|
+
|
|
18
|
+
### Dom builder
|
|
19
|
+
|
|
20
|
+
Builds dom elements from array of objects.
|
|
21
|
+
|
|
22
|
+
Only available as module script.
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
import { domBuilder } from '@massimo.cassandro/dom-builder';
|
|
26
|
+
|
|
27
|
+
domBuilder({ /* options */}); // see source code for details
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Classnames
|
|
31
|
+
|
|
32
|
+
TODO
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// tokens utitilizzati sia da snackaber che da toast
|
|
2
|
+
export default{
|
|
3
|
+
snackbar: {
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
font: {
|
|
11
|
+
size: {
|
|
12
|
+
$value: '{font.size.sm}',
|
|
13
|
+
$type: 'dimension'
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
border: {
|
|
18
|
+
radius: {
|
|
19
|
+
$value: '{radius.sm}',
|
|
20
|
+
$type: 'color'
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
text: {
|
|
24
|
+
color: {
|
|
25
|
+
$value: '{text.color}',
|
|
26
|
+
$type: 'color'
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
duration: {
|
|
30
|
+
$value: '5s',
|
|
31
|
+
$type: 'duration'
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
};
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@massimo-cassandro/minimo",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Light css framework",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Light css framework & utilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://github.com/massimo-cassandro/minimo#readme",
|
|
7
7
|
"bugs": {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"UPD-version": "npx update-version # --config=./dev-utilities.config.mjs",
|
|
20
20
|
"upd@m": "npx upd@m",
|
|
21
21
|
"npm-publish": "npm publish",
|
|
22
|
+
"webpack": "NODE_ENV=development webpack serve --config ./demo/demo-src/webpack.config.mjs",
|
|
22
23
|
"build-tokens": "node ./design-tokens-utilities/build-tokens.mjs --config ./tokens-config.mjs",
|
|
23
24
|
"check unresolved props": "node ./design-tokens-utilities/check-unresolved-custom-props.mjs --config ./tokens-config.mjs"
|
|
24
25
|
},
|
|
@@ -28,6 +29,16 @@
|
|
|
28
29
|
"access": "public"
|
|
29
30
|
},
|
|
30
31
|
"main": "./index.js",
|
|
32
|
+
"sideEffects": [
|
|
33
|
+
"*.css",
|
|
34
|
+
"*.js",
|
|
35
|
+
"*.svg",
|
|
36
|
+
"*.jpg",
|
|
37
|
+
"*.jpeg",
|
|
38
|
+
"*.png",
|
|
39
|
+
"*.webp",
|
|
40
|
+
"*.avif"
|
|
41
|
+
],
|
|
31
42
|
"files": [
|
|
32
43
|
"./index.js",
|
|
33
44
|
"src/**/*.{js,mjs,css,svg,md}",
|
|
@@ -36,9 +47,36 @@
|
|
|
36
47
|
"design-tokens-utilities/**/*.{mjs,md}"
|
|
37
48
|
],
|
|
38
49
|
"devDependencies": {
|
|
50
|
+
"@babel/core": "^7.29.0",
|
|
51
|
+
"@babel/preset-env": "^7.29.2",
|
|
52
|
+
"@csstools/postcss-global-data": "^4.0.0",
|
|
39
53
|
"@massimo-cassandro/dev-updater": "^3.0.3",
|
|
40
54
|
"@massimo-cassandro/eslint-config": "^3.0.6",
|
|
41
55
|
"@massimo-cassandro/stylelint-config": "^2.3.4",
|
|
42
|
-
"
|
|
56
|
+
"autoprefixer": "^10.5.0",
|
|
57
|
+
"babel-loader": "^10.1.1",
|
|
58
|
+
"css-loader": "^7.1.4",
|
|
59
|
+
"css-minimizer-webpack-plugin": "^8.0.0",
|
|
60
|
+
"cssnano": "^7.1.5",
|
|
61
|
+
"ejs-loader": "^0.5.0",
|
|
62
|
+
"html-loader": "^5.1.0",
|
|
63
|
+
"html-webpack-plugin": "^5.6.7",
|
|
64
|
+
"mini-css-extract-plugin": "^2.10.2",
|
|
65
|
+
"mini-svg-data-uri": "^1.4.4",
|
|
66
|
+
"postcss": "^8.5.10",
|
|
67
|
+
"postcss-custom-media": "^12.0.1",
|
|
68
|
+
"postcss-loader": "^8.2.1",
|
|
69
|
+
"postcss-preset-env": "^11.2.1",
|
|
70
|
+
"process": "^0.11.10",
|
|
71
|
+
"style-dictionary": "^5.4.0",
|
|
72
|
+
"style-loader": "^4.0.0",
|
|
73
|
+
"svg-url-loader": "^8.0.0",
|
|
74
|
+
"svgo": "^4.0.1",
|
|
75
|
+
"svgo-add-viewbox": "^3.1.0",
|
|
76
|
+
"svgo-loader": "^5.0.0",
|
|
77
|
+
"terser-webpack-plugin": "^5.4.0",
|
|
78
|
+
"webpack": "^5.106.2",
|
|
79
|
+
"webpack-cli": "^7.0.2",
|
|
80
|
+
"webpack-dev-server": "^5.2.3"
|
|
43
81
|
}
|
|
44
82
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* NB incluso nel css principale */
|
|
2
1
|
|
|
3
2
|
@import '@massimo-cassandro/modal-alert';
|
|
4
3
|
|
|
@@ -13,8 +12,8 @@
|
|
|
13
12
|
--malert-warning-color: var(--status-warning-color);
|
|
14
13
|
--malert-warning-color-fg: var(--status-warning-fg);
|
|
15
14
|
|
|
16
|
-
--malert-
|
|
17
|
-
--malert-
|
|
15
|
+
--malert-danger-color: var(--status-danger-color);
|
|
16
|
+
--malert-danger-color-fg: var(--status-danger-fg);
|
|
18
17
|
|
|
19
18
|
--malert-confirm-color: var(--status-warning-color);
|
|
20
19
|
--malert-confirm-color-fg: var(--status-warning-fg);
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { domBuilder, classnames } from '../../../index.js';
|
|
2
|
+
import * as styles from './core.module.css';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
References
|
|
6
|
+
* https://web.dev/learn/css/popover-and-dialog
|
|
7
|
+
* https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/popover
|
|
8
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/Popover_API
|
|
9
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/Popover_API/Using
|
|
10
|
+
|
|
11
|
+
* https://m3.material.io/components/snackbar/guidelines
|
|
12
|
+
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export function snackbar(message, options = {}){
|
|
16
|
+
|
|
17
|
+
options = {
|
|
18
|
+
|
|
19
|
+
status : null, // danger, warning, info, success
|
|
20
|
+
duration : 4000, // ms or false/null to avoid autoclose
|
|
21
|
+
close_btn : true, // true | false
|
|
22
|
+
|
|
23
|
+
// TODO snackbar action
|
|
24
|
+
action : null, // null or function
|
|
25
|
+
action_text : null, // null or string
|
|
26
|
+
|
|
27
|
+
...options
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
domBuilder([
|
|
31
|
+
{
|
|
32
|
+
className: classnames(styles.snackbar, options.status? styles[`status-${options.status}`] : null),
|
|
33
|
+
attrs: {
|
|
34
|
+
popover: 'manual',
|
|
35
|
+
role: options.action? 'alert' : 'status'
|
|
36
|
+
},
|
|
37
|
+
children: [
|
|
38
|
+
{
|
|
39
|
+
className: styles.text,
|
|
40
|
+
content: message
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
tag: 'button',
|
|
44
|
+
attrs: {
|
|
45
|
+
type: 'button',
|
|
46
|
+
},
|
|
47
|
+
className: classnames('btn-reset', styles.action),
|
|
48
|
+
content: options.action_text?? '_actions_',
|
|
49
|
+
condition: options.action && (typeof options.action === 'function'),
|
|
50
|
+
callback: el => {}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
tag: 'button',
|
|
54
|
+
className: classnames('btn-close', styles.closeBtn),
|
|
55
|
+
attrs: {
|
|
56
|
+
type: 'button',
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
], document.body);
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/*
|
|
66
|
+
export const NotificationCore = {
|
|
67
|
+
|
|
68
|
+
createNotificationElement(message, options = {}) {
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
const show = () => {
|
|
73
|
+
document.body.appendChild(el);
|
|
74
|
+
el.showPopover();
|
|
75
|
+
setTimeout(() => hide(), duration);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const hide = () => {
|
|
79
|
+
if (el.matches(':popover-open')) {
|
|
80
|
+
el.hidePopover();
|
|
81
|
+
el.remove(); // Pulizia del DOM
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return { el, show, hide };
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
toast
|
|
93
|
+
|
|
94
|
+
import { NotificationCore } from './notifications.js';
|
|
95
|
+
|
|
96
|
+
export const toast = (message, type = 'info') => {
|
|
97
|
+
const { el, show } = NotificationCore.createBase(message, { type, duration: 4000 });
|
|
98
|
+
|
|
99
|
+
el.classList.add('ui-toast');
|
|
100
|
+
|
|
101
|
+
show();
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
*/
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/* purgecss start ignore */
|
|
2
|
+
|
|
3
|
+
.snackbar {
|
|
4
|
+
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/* purgecss end ignore */
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
.toastWrapper {
|
|
17
|
+
position: fixed;
|
|
18
|
+
inset-block-end: 0;
|
|
19
|
+
inset-inline: 0;
|
|
20
|
+
z-index: 1;
|
|
21
|
+
|
|
22
|
+
display: grid;
|
|
23
|
+
gap: 1vh;
|
|
24
|
+
justify-content: center;
|
|
25
|
+
justify-items: stretch;
|
|
26
|
+
padding-block-end: 5vh;
|
|
27
|
+
|
|
28
|
+
pointer-events: none;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.toast {
|
|
32
|
+
--_bg-lightness: 90%;
|
|
33
|
+
--_travel-distance: 0;
|
|
34
|
+
|
|
35
|
+
max-inline-size: min(25ch, 90vw);
|
|
36
|
+
padding-block: .5ch;
|
|
37
|
+
padding-inline: 1ch;
|
|
38
|
+
|
|
39
|
+
font-size: var(--toast-font-size);
|
|
40
|
+
color: var(--toast-text-color);
|
|
41
|
+
background: hsl(0 0% var(--_bg-lightness) / 90%);
|
|
42
|
+
border-radius: var(--toast-border-radius);
|
|
43
|
+
animation:
|
|
44
|
+
fade-in .3s ease,
|
|
45
|
+
slide-in .3s ease,
|
|
46
|
+
fade-out .3s ease var(--toast-duration);
|
|
47
|
+
|
|
48
|
+
will-change: transform;
|
|
49
|
+
|
|
50
|
+
@media (--dark) {
|
|
51
|
+
--_bg-lightness: 20%;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@media (--motionOK) {
|
|
55
|
+
--_travel-distance: 5vh;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@keyframes fade-in {
|
|
60
|
+
from { opacity: 0; }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@keyframes fade-out {
|
|
64
|
+
to { opacity: 0; }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@keyframes slide-in {
|
|
68
|
+
from { transform: translateY(var(--_travel-distance, 10px)); }
|
|
69
|
+
}
|
|
70
|
+
*/
|
package/src/css/alerts.css
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
--------------------------
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
-
/* alert-
|
|
27
|
+
/* alert-danger (default) */
|
|
28
28
|
--_alert-bg-color: var(--status-danger-background-color);
|
|
29
29
|
--_alert-fg-color: var(--status-danger-fg);
|
|
30
30
|
--_alert-border-color: var(--status-danger-border-color);
|
package/src/css/anchors.css
CHANGED
|
@@ -11,8 +11,11 @@
|
|
|
11
11
|
--_button-border-color: var(--btn-primary-hover-border-color);
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
+
&:focus {
|
|
15
|
+
outline: none;
|
|
16
|
+
}
|
|
14
17
|
|
|
15
|
-
&:focus, &:active {
|
|
18
|
+
&:focus-visible, &:active {
|
|
16
19
|
outline: var(--btn-focus-outline-width) solid var(--btn-primary-focus-outline-color);
|
|
17
20
|
/* outline-offset: 4px; */
|
|
18
21
|
}
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
--_status-fg-color: var(--status-info-fg);
|
|
19
19
|
--_status-border-color: var(--status-info-border-color);
|
|
20
20
|
}
|
|
21
|
-
&.btn-danger
|
|
21
|
+
&.btn-danger {
|
|
22
22
|
--_status-bg-color: var(--status-danger-color);
|
|
23
23
|
--_status-fg-color: var(--status-danger-fg);
|
|
24
24
|
--_status-border-color: var(--status-danger-border-color);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
&.btn-success, &.btn-info, &.btn-danger
|
|
27
|
+
&.btn-success, &.btn-info, &.btn-danger {
|
|
28
28
|
--_status-hover-bg-color: color-mix(in srgb, var(--_status-bg-color) 30%, transparent);
|
|
29
29
|
--_status-hover-fg-color: var(--_status-bg-color);
|
|
30
30
|
--_status-hover-border-color: var(--_status-bg-color);
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
&.btn-success, &.btn-info, &.btn-danger, &.btn-
|
|
54
|
+
&.btn-success, &.btn-info, &.btn-danger, &.btn-warning {
|
|
55
55
|
|
|
56
56
|
--_button-bg-color: var(--_status-bg-color);
|
|
57
57
|
--_button-fg-color: var(--_status-fg-color);
|
|
@@ -74,7 +74,11 @@
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
&:focus
|
|
77
|
+
&:focus {
|
|
78
|
+
outline: none;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
&:focus-visible, &:active {
|
|
78
82
|
outline: var(--_status-focus-outline);
|
|
79
83
|
}
|
|
80
84
|
|
package/src/css/forms/forms.css
CHANGED
|
@@ -41,6 +41,10 @@
|
|
|
41
41
|
border-radius: var(--form-control-border-radius);
|
|
42
42
|
|
|
43
43
|
&:focus {
|
|
44
|
+
outline: none;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
&:focus-visible {
|
|
44
48
|
outline: var(--form-control-focus-outline-width) solid var(--form-control-focus-color);
|
|
45
49
|
outline-offset: var(--form-control-focus-outline-offset);
|
|
46
50
|
}
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
outline-offset: var(--form-validation-outline-offset);
|
|
19
19
|
|
|
20
20
|
&:focus {
|
|
21
|
+
outline: none;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
&:focus-visible {
|
|
21
25
|
/* background-color: var(--_status-color); */
|
|
22
26
|
outline: var(--form-control-focus-outline-width) solid var(--_status-color);
|
|
23
27
|
outline-offset: var(--form-control-focus-outline-offset);
|
package/src/css/text.css
CHANGED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { domBuilderBasicSetup } from './domBuilderBasicSetup.js';
|
|
2
|
+
import { parseDomString } from './parseDomString.js';
|
|
3
|
+
|
|
4
|
+
// TODO sintassi stringa su più righe in cui ogni riga corrisponde ad un elemento
|
|
5
|
+
// TODO sintassi stringa come il precedente, ma con possibilità di indicare una nidificazione (indent?)
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* domBuilder
|
|
9
|
+
* Costruisce una struttura DOM a partire da un array di configurazione.
|
|
10
|
+
*
|
|
11
|
+
* La funzione analizza un array di oggetti di configurazione per creare elementi HTML.
|
|
12
|
+
*
|
|
13
|
+
* **Formato della Struttura di Configurazione (structureArray):**
|
|
14
|
+
*
|
|
15
|
+
* L'array `structureArray` può contenere stringhe (con sintassi abbreviata) e/o oggetti di configurazione:
|
|
16
|
+
*
|
|
17
|
+
* ```javascript
|
|
18
|
+
* structure = [
|
|
19
|
+
* '#mainContainer.container',
|
|
20
|
+
* 'p#main-info.info.active{data-id:123,role=button} Lorem ipsum',
|
|
21
|
+
* {
|
|
22
|
+
* tag: 'div' | ['.divClass', 'h2#id', 'table.class1', 'thead', 'tr.class2.class3(attr1: attrValue)'], // anche tagName
|
|
23
|
+
* className: 'xxx' | ['class1', 'class2'], // anche `class`
|
|
24
|
+
* id: 'element-id',
|
|
25
|
+
* attrs: [attr_name, attr_value] | [[...], [...]] | {name: value},
|
|
26
|
+
* content: 'xxx' | 123 | function | Element,
|
|
27
|
+
* condition: true | false,
|
|
28
|
+
* callback: el => ...,
|
|
29
|
+
* children: [...]
|
|
30
|
+
* },
|
|
31
|
+
* 'div.class[data-xxx: value]',
|
|
32
|
+
* '.another-div',
|
|
33
|
+
* 'p#paragraph''
|
|
34
|
+
* '...',
|
|
35
|
+
* { ... }
|
|
36
|
+
* ]
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* id e classi possono essere specificati sia chiave dell'oggetto di configurazione che all'interno dell'oggetto `attrs`
|
|
40
|
+
* In caso di conflitto, prevalgono quelli indicati al livello principale.
|
|
41
|
+
*
|
|
42
|
+
* È inoltre possibile utilizzare una versione "abbreviata" utilizzando una stringa costruita secondo la sintassi di `parseDomString`.
|
|
43
|
+
* Vedi `parseDomString` per altre in.formazioni.
|
|
44
|
+
*
|
|
45
|
+
* Questo tipo di sintassi non prevede la possibilità di definire `content`, `callback`, `condition` e `children`: in questi casi è necessario utilizzare la sintassi di base.
|
|
46
|
+
*
|
|
47
|
+
* La sintassi abbreviata può essere utilizzata anche con l'elemento `tag` della sintassi ad oggetti.
|
|
48
|
+
* È possibile definirlo come array in cui ogni elemento corrisponde al genitore del successivo. Eventuali altre proprietà dell'oggetto
|
|
49
|
+
* (className, callback, ecc.) vengono applicate solo all'ultimo elemento dell'array.
|
|
50
|
+
* In caso di conflitto tra classi, id o attributi indicati nella stringa e quelli indicati nell'oggetto
|
|
51
|
+
* prevalgono questi ultimi.
|
|
52
|
+
*
|
|
53
|
+
* @function domBuilder
|
|
54
|
+
* @param {Array<Object|string>} [structureArray=[]] - Array di configurazione degli elementi. Può contenere stringhe (con sintassi abbreviata secondo `parseDomString`) e/o oggetti di configurazione.
|
|
55
|
+
* @param {string|Array<string>} [structureArray[].tag='div'] - Nome del tag HTML (es. 'div', 'p') o array di tag nidificati (es. ['div.container', 'section', 'article']).
|
|
56
|
+
* @param {string|Array<string>} [structureArray[].tagName='div'] - Alias di tag
|
|
57
|
+
* @param {string|Array<string>} [structureArray[].className] - Classe/i CSS: stringa singola o array di classi, in quest'ultimo caso, i valori *falsy* vengopno rimossi.
|
|
58
|
+
* @param {string|Array<string>} [structureArray[].class] - Alias di className.
|
|
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.
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
export function domBuilder(structureArray = [], parent, options = {}) {
|
|
73
|
+
|
|
74
|
+
options = {
|
|
75
|
+
emptyParent: false,
|
|
76
|
+
...options
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
let mainElement = null, el, grand_parent = null;
|
|
80
|
+
|
|
81
|
+
if(parent && options.emptyParent) {
|
|
82
|
+
parent.innerHTML = '';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
structureArray.forEach(item => {
|
|
86
|
+
|
|
87
|
+
if(item != null && typeof item === 'string' && item !== '') {
|
|
88
|
+
item = parseDomString(item);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// console.log(item);
|
|
92
|
+
|
|
93
|
+
if (item != null && (item.condition ?? true)) {
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
// se tag è un array, vengono creati una serie di elementi nidificati
|
|
97
|
+
// l'ultimo è quello a cui vengono applicate le altre proprietà dell'oggetto in esame
|
|
98
|
+
if (Array.isArray(item.tag)) {
|
|
99
|
+
|
|
100
|
+
grand_parent = parent;
|
|
101
|
+
|
|
102
|
+
item.tag.forEach((tagItem, idx) => {
|
|
103
|
+
|
|
104
|
+
const isLast = idx === item.tag.length - 1;
|
|
105
|
+
const parsedItem = parseDomString(tagItem);
|
|
106
|
+
|
|
107
|
+
// nel caso dell'ultimo elemento, merge con le opzioni dell'oggetto
|
|
108
|
+
// con precedenza alle proprietà di quest'ultimo
|
|
109
|
+
if(isLast) {
|
|
110
|
+
item.attrs = {...parsedItem.attrs??{}, ...item.attrs??{}};
|
|
111
|
+
}
|
|
112
|
+
el = domBuilderBasicSetup(
|
|
113
|
+
document.createElement(parsedItem.tag || 'div'),
|
|
114
|
+
{...parsedItem, ...(isLast? item??{} : {})}
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
if (!isLast) { // l'ultimo elemento è gestito dalla procedura "standard"
|
|
118
|
+
if (parent) {
|
|
119
|
+
parent.appendChild(el);
|
|
120
|
+
}
|
|
121
|
+
parent = el;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
} else {
|
|
127
|
+
el = domBuilderBasicSetup(
|
|
128
|
+
document.createElement(item.tag ?? 'div'),
|
|
129
|
+
item
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
if (item.content != null) {
|
|
135
|
+
|
|
136
|
+
let content = '';
|
|
137
|
+
if (typeof item.content === 'function') {
|
|
138
|
+
content = item.content();
|
|
139
|
+
|
|
140
|
+
} else if (item.content != null) {
|
|
141
|
+
content = String(item.content);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (content instanceof Element) {
|
|
145
|
+
el.appendChild(content);
|
|
146
|
+
|
|
147
|
+
} else {
|
|
148
|
+
el.innerHTML = content;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
if (item.children != null && !Array.isArray(item.children)) {
|
|
154
|
+
// eslint-disable-next-line no-console
|
|
155
|
+
console.error('Error: `item.children` must be an array → ' + item.children);
|
|
156
|
+
}
|
|
157
|
+
if (item.children && Array.isArray(item.children)) {
|
|
158
|
+
domBuilder(item.children, el, {emptyParent: false});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (mainElement == null) {
|
|
162
|
+
mainElement = el;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (parent) {
|
|
166
|
+
parent.appendChild(el);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// TODO callback che includano azioni relatuvi ai children dell'elemento potrebbero bìnon essere eseguiti in assenza di parent
|
|
170
|
+
if (item.callback && typeof item.callback === 'function') {
|
|
171
|
+
item.callback(el);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (grand_parent) {
|
|
175
|
+
parent = grand_parent;
|
|
176
|
+
}
|
|
177
|
+
} // end if ((item.condition ?? true) && item != null)
|
|
178
|
+
|
|
179
|
+
}); // end forEach
|
|
180
|
+
|
|
181
|
+
return mainElement;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* Esegue le operazione di base del domBuilder: assegnazione classi, id ecc...
|
|
4
|
+
*/
|
|
5
|
+
export function domBuilderBasicSetup (element, domBuilderItem) {
|
|
6
|
+
|
|
7
|
+
// classi
|
|
8
|
+
// possibili denominazioni: class o className
|
|
9
|
+
// se presente className, class viene ignorato
|
|
10
|
+
if (domBuilderItem.class && !domBuilderItem.className) {
|
|
11
|
+
domBuilderItem.className = domBuilderItem.class;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// se item.attrs è un array singolo viene trattato come singolo elemento
|
|
15
|
+
if (domBuilderItem.attrs && Array.isArray(domBuilderItem.attrs) && !Array.isArray(domBuilderItem.attrs[0])) {
|
|
16
|
+
domBuilderItem.attrs = [ domBuilderItem.attrs ];
|
|
17
|
+
|
|
18
|
+
// item.attrs può essere un oggetto del tipo {attr_name: attr_value}
|
|
19
|
+
} else if (typeof domBuilderItem.attrs === 'object' && !Array.isArray(domBuilderItem.attrs) && domBuilderItem.attrs !== null) {
|
|
20
|
+
domBuilderItem.attrs = Object.entries(domBuilderItem.attrs);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// gli attributi booleani non vengono aggiunti se il loro valore è false
|
|
24
|
+
const boolAttrs = [
|
|
25
|
+
'allowfullscreen',
|
|
26
|
+
'async',
|
|
27
|
+
'autocomplete',
|
|
28
|
+
'autofocus',
|
|
29
|
+
'autoplay',
|
|
30
|
+
'border',
|
|
31
|
+
'checked',
|
|
32
|
+
'compact',
|
|
33
|
+
'contenteditable',
|
|
34
|
+
'controls',
|
|
35
|
+
'default',
|
|
36
|
+
'loop',
|
|
37
|
+
'defer',
|
|
38
|
+
'disabled',
|
|
39
|
+
'formnovalidate',
|
|
40
|
+
'hidden',
|
|
41
|
+
'inert',
|
|
42
|
+
'ismap',
|
|
43
|
+
'multiple',
|
|
44
|
+
'muted',
|
|
45
|
+
'novalidate',
|
|
46
|
+
'open',
|
|
47
|
+
'readonly',
|
|
48
|
+
'required',
|
|
49
|
+
'reversed',
|
|
50
|
+
'selected',
|
|
51
|
+
'spellcheck',
|
|
52
|
+
'translate'
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
(domBuilderItem.attrs ?? []).forEach(attr => {
|
|
58
|
+
if (attr[1] != null && !(boolAttrs.includes(attr[0]) && attr[1]=== false)) {
|
|
59
|
+
element.setAttribute(attr[0], String(attr[1]));
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// assegnazioen classi e id (dopo attrs in modo che le proprietà a livello di oggetto prevalgano)
|
|
64
|
+
if(domBuilderItem.className) {
|
|
65
|
+
element.className = Array.isArray(domBuilderItem.className)
|
|
66
|
+
? domBuilderItem.className.filter(Boolean).join(' ')
|
|
67
|
+
: domBuilderItem.className;
|
|
68
|
+
}
|
|
69
|
+
if(domBuilderItem.id) {
|
|
70
|
+
element.id = domBuilderItem.id;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return element;
|
|
74
|
+
|
|
75
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analizza una stringa per estrarre il nome del tag, l'ID, le classi e gli attributi.
|
|
3
|
+
*
|
|
4
|
+
* * Il formato della stringa atteso è:
|
|
5
|
+
*
|
|
6
|
+
* `tag#id.classe1.classe2.classeN(attributo1: valore1, attributo2=valore2) Lorem ipsum`
|
|
7
|
+
*
|
|
8
|
+
* In cui:
|
|
9
|
+
* * `tag` è il nome dell'elemento, se non specificato si assume che sia un `div`
|
|
10
|
+
* * `id` è l'id, facoltativo, da assegnare all'elemento, deve essere preeceduto da `#` e deve essere posizionato subito dopo il tag
|
|
11
|
+
* * `classe1, classe2...` sono le classi, facoltative, devono essere precedute da `.`
|
|
12
|
+
* * `(...)` è il blocco degli attributi, facoltativo. Oltre che dalle parentesi tonde, questa parte può essere delimitata dalle quadre e dalle graffe.
|
|
13
|
+
* Ogni coppia _nome-valore_ va separata con `:` o `=`. Se presente solo il nome, si assume che il valore sia `true`.
|
|
14
|
+
* * `Lorem ipsum` indica un eventuale contenuto testuale, separato dal blocco precedente da uno spazio
|
|
15
|
+
*
|
|
16
|
+
* Restituisce un oggetto *child* domBuilder
|
|
17
|
+
*
|
|
18
|
+
* Esempio:
|
|
19
|
+
* > p#main-info.info.active{data-id:123,role=button} text content
|
|
20
|
+
*
|
|
21
|
+
* oppure
|
|
22
|
+
* > input#search-field[type=text,disabled]
|
|
23
|
+
*
|
|
24
|
+
* @param {string} domString La stringa DOM Builder da analizzare.
|
|
25
|
+
* @returns {{tagName: string, id: string, classes: string[], attributes: {[key: string]: string}}}
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export function parseDomString(domString) {
|
|
29
|
+
|
|
30
|
+
// versione con groups
|
|
31
|
+
// const regex = new RegExp(
|
|
32
|
+
// /^(?<tag>[a-zA-Z]+)?/.source + // tag
|
|
33
|
+
// /(?:#(?<id>[a-zA-Z0-9_-]+))?/.source + // id
|
|
34
|
+
// /(?<classes>(?:\.[a-zA-Z0-9_-]+)*)?/.source + // classi
|
|
35
|
+
// /(?<attrs>[([{].*?[)\]}])?/.source + // attrs
|
|
36
|
+
// /(?: +(?<content>.*))?$/.source // content
|
|
37
|
+
// );
|
|
38
|
+
|
|
39
|
+
const regex = new RegExp(
|
|
40
|
+
/^([a-zA-Z]+)?/.source + // tag
|
|
41
|
+
/(?:#([a-zA-Z0-9_-]+))?/.source + // id
|
|
42
|
+
/((?:\.[a-zA-Z0-9_-]+)*)?/.source + // classi
|
|
43
|
+
/([([{].*?[)\]}])?/.source + // attrs
|
|
44
|
+
/(?: +(.*))?$/.source // content
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const matches = domString.match(regex);
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if (!matches) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Nuovi gruppi di cattura (match array):
|
|
55
|
+
// [0]: intera stringa
|
|
56
|
+
// [1]: Tag
|
|
57
|
+
// [2]: ID
|
|
58
|
+
// [3]: Classi
|
|
59
|
+
// [4]: Attributi (raw)
|
|
60
|
+
// [5]: content
|
|
61
|
+
|
|
62
|
+
const tag = (matches[1] || 'div').toLowerCase(),
|
|
63
|
+
id = matches[2]?.trim() || null,
|
|
64
|
+
classes = matches[3] ? matches[3].slice(1).split('.').map(c => c.trim()) : [],
|
|
65
|
+
rawAttrs = matches[4]?.trim() || null,
|
|
66
|
+
content = matches[5]?.trim() || null
|
|
67
|
+
;
|
|
68
|
+
|
|
69
|
+
// versione con uso di `groups`
|
|
70
|
+
// const tag = (matches.groups.tag || 'div').toLowerCase(),
|
|
71
|
+
// id = matches.groups.id?.trim() || null,
|
|
72
|
+
// classes = matches.groups.classes? matches[3].slice(1).split('.').map(c => c.trim()) : [],
|
|
73
|
+
// rawAttrs = matches.groups.attrs?.trim() || null,
|
|
74
|
+
// content = matches.groups.content?.trim() || null
|
|
75
|
+
// ;
|
|
76
|
+
|
|
77
|
+
const attrs = {};
|
|
78
|
+
if (rawAttrs) {
|
|
79
|
+
const attrsContent = rawAttrs.substring(1, rawAttrs.length - 1);
|
|
80
|
+
const attrPairs = attrsContent.split(/\s*,\s*/);
|
|
81
|
+
|
|
82
|
+
attrPairs.forEach(pair => {
|
|
83
|
+
if (!pair) return;
|
|
84
|
+
if (pair.includes('=') || pair.includes(':')) {
|
|
85
|
+
const parts = pair.split(/[:=]/, 2),
|
|
86
|
+
name = parts[0]?.trim(),
|
|
87
|
+
value = parts[1] != null ? parts[1].trim() : true;
|
|
88
|
+
|
|
89
|
+
attrs[name] = value;
|
|
90
|
+
|
|
91
|
+
} else {
|
|
92
|
+
|
|
93
|
+
attrs[pair.trim()] = true;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
tag: tag,
|
|
100
|
+
id: id,
|
|
101
|
+
className: classes?.join(' '),
|
|
102
|
+
attrs: attrs,
|
|
103
|
+
content: content
|
|
104
|
+
};
|
|
105
|
+
}
|