@massimo-cassandro/minimo 1.5.13 → 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/design-tokens/_src/components/spinner.minimo.tokens.mjs +18 -0
- package/design-tokens/_src/headings.minimo.tokens.mjs +56 -0
- package/design-tokens/_src/link.minimo.tokens.mjs +41 -0
- package/design-tokens/_src/radius.minimo.tokens.mjs +44 -0
- package/design-tokens/_src/size.minimo.tokens.mjs +105 -0
- package/design-tokens/_src/status.minimo.tokens.mjs +92 -0
- package/design-tokens/_src/typography.minimo.tokens.mjs +123 -0
- package/design-tokens/_src/utility.minimo.tokens.mjs +8 -0
- package/design-tokens/_src/web-components/simple-datatable.minimo.tokens.mjs +47 -0
- package/{design-tokens-utilities → design-tokens/utilities}/build-tokens-src/config.mjs +1 -1
- package/{design-tokens-utilities → design-tokens/utilities}/build-tokens.mjs +1 -1
- package/{design-tokens-utilities → design-tokens/utilities}/check-unresolved-custom-props.mjs +2 -2
- package/index.js +5 -3
- package/package.json +13 -8
- 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/components/unsplash-page/unsplash-page.module.css +1 -1
- 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
- package/design-tokens-src/components/spinner.minimo.tokens.jsonc +0 -22
- package/design-tokens-src/headings.minimo.tokens.jsonc +0 -57
- package/design-tokens-src/link.minimo.tokens.jsonc +0 -41
- package/design-tokens-src/radius.minimo.tokens.jsonc +0 -45
- package/design-tokens-src/size.minimo.tokens.jsonc +0 -105
- package/design-tokens-src/status.minimo.tokens.jsonc +0 -93
- package/design-tokens-src/typography.minimo.tokens.jsonc +0 -129
- package/design-tokens-src/utility.minimo.tokens.jsonc +0 -8
- package/design-tokens-src/web-components/simple-datatable.minimo.tokens.jsonc +0 -50
- /package/{design-tokens-src → design-tokens/_src}/_readme.md +0 -0
- /package/{design-tokens-src → design-tokens/_src}/alerts.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/badge.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/btn-close.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/btn-color-themes.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/btn.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/color-accent.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/color-neutral.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/color-primary.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/color-secondary.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/components/sf-macro.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/components/snackbar.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/components/unsplash-page.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/flex-grid.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/form-validation.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/form.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/layout.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/modal-popup.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/shadows.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/switch.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/table.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/text.minimo.tokens.mjs +0 -0
- /package/{design-tokens-src → design-tokens/_src}/z-index.minimo.tokens.mjs +0 -0
- /package/design-tokens/{alerts.minimo.tokens.jsonc → tokens/alerts.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{badge.minimo.tokens.jsonc → tokens/badge.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{btn-close.minimo.tokens.jsonc → tokens/btn-close.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{btn-color-themes.minimo.tokens.jsonc → tokens/btn-color-themes.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{btn.minimo.tokens.jsonc → tokens/btn.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{color-accent.minimo.tokens.jsonc → tokens/color-accent.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{color-neutral.minimo.tokens.jsonc → tokens/color-neutral.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{color-primary.minimo.tokens.jsonc → tokens/color-primary.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{color-secondary.minimo.tokens.jsonc → tokens/color-secondary.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{components → tokens/components}/sf-macro.minimo.tokens.jsonc +0 -0
- /package/design-tokens/{components → tokens/components}/snackbar.minimo.tokens.jsonc +0 -0
- /package/design-tokens/{components → tokens/components}/spinner.minimo.tokens.jsonc +0 -0
- /package/design-tokens/{components → tokens/components}/unsplash-page.minimo.tokens.jsonc +0 -0
- /package/design-tokens/{flex-grid.minimo.tokens.jsonc → tokens/flex-grid.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{form-validation.minimo.tokens.jsonc → tokens/form-validation.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{form.minimo.tokens.jsonc → tokens/form.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{headings.minimo.tokens.jsonc → tokens/headings.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{layout.minimo.tokens.jsonc → tokens/layout.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{link.minimo.tokens.jsonc → tokens/link.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{modal-popup.minimo.tokens.jsonc → tokens/modal-popup.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{radius.minimo.tokens.jsonc → tokens/radius.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{shadows.minimo.tokens.jsonc → tokens/shadows.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{size.minimo.tokens.jsonc → tokens/size.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{status.minimo.tokens.jsonc → tokens/status.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{switch.minimo.tokens.jsonc → tokens/switch.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{table.minimo.tokens.jsonc → tokens/table.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{text.minimo.tokens.jsonc → tokens/text.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{typography.minimo.tokens.jsonc → tokens/typography.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{utility.minimo.tokens.jsonc → tokens/utility.minimo.tokens.jsonc} +0 -0
- /package/design-tokens/{web-components → tokens/web-components}/simple-datatable.minimo.tokens.jsonc +0 -0
- /package/design-tokens/{z-index.minimo.tokens.jsonc → tokens/z-index.minimo.tokens.jsonc} +0 -0
- /package/{design-tokens-utilities → design-tokens/utilities}/build-tokens-src/formats/css.mjs +0 -0
- /package/{design-tokens-utilities → design-tokens/utilities}/build-tokens-src/formats/penpot.mjs +0 -0
- /package/{design-tokens-utilities → design-tokens/utilities}/build-tokens-src/platforms.mjs +0 -0
- /package/{design-tokens-utilities → design-tokens/utilities}/build-tokens-src/transforms.mjs +0 -0
- /package/{design-tokens-utilities → design-tokens/utilities}/readme.md +0 -0
- /package/{design-tokens-utilities → design-tokens/utilities}/tokens-config-sample.mjs +0 -0
|
@@ -7,10 +7,17 @@ import * as styles from './modal-popup.module.css';
|
|
|
7
7
|
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
// TODO
|
|
10
|
+
// TODO test iframe mode
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
/** @type {HTMLDialogElement | undefined} */
|
|
13
|
+
let dialogEl;
|
|
14
|
+
/** @type {HTMLElement | undefined} */
|
|
15
|
+
let dialogContentEl;
|
|
13
16
|
|
|
17
|
+
/**
|
|
18
|
+
* @param {HTMLDialogElement} el
|
|
19
|
+
* @returns {void}
|
|
20
|
+
*/
|
|
14
21
|
function closeDialog(el) {
|
|
15
22
|
el.classList.remove(styles.on);
|
|
16
23
|
|
|
@@ -21,43 +28,55 @@ function closeDialog(el) {
|
|
|
21
28
|
|
|
22
29
|
/**
|
|
23
30
|
* modalPopup
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* -
|
|
28
|
-
* -
|
|
31
|
+
* Opens a popup dialog with the given content.
|
|
32
|
+
*
|
|
33
|
+
* Content can be provided as:
|
|
34
|
+
* - a domBuilder array, plain text, or HTML (via `content`)
|
|
35
|
+
* - content loaded via Ajax (via `ajaxUrl` and `ajaxCallback`)
|
|
36
|
+
* - an iframe (via `iframeUrl`)
|
|
29
37
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
38
|
+
* When multiple options conflict, the first applicable one in the list above takes precedence.
|
|
39
|
+
*
|
|
40
|
+
* @param {Object} params
|
|
41
|
+
* @param {string | null} [params.dialogExtraClassName=null] - Extra class added to the dialog element.
|
|
42
|
+
* @param {string | null} [params.contentExtraClassName=null] - Extra class added to the content wrapper.
|
|
43
|
+
* @param {string | null} [params.iframeUrl=null] - URL to load in an iframe.
|
|
44
|
+
* @param {string | any[] | null} [params.content=null] - Plain text, HTML, or a domBuilder array.
|
|
45
|
+
* @param {string | null} [params.ajaxUrl=null] - URL for Ajax content loading.
|
|
46
|
+
* @param {((data: *, el: Element) => void) | null} [params.ajaxCallback=null] - Called with the Ajax response and the content element.
|
|
47
|
+
* @param {((el: HTMLDialogElement) => void) | null} [params.closeCallback=null] - Called with the dialog element just before it is removed.
|
|
48
|
+
* @param {boolean} [params.addScrollbarPadding=false] - Adds right padding to compensate for the scrollbar.
|
|
49
|
+
* @param {string | null} [params.headerContent=null] - Header content: plain text or HTML.
|
|
50
|
+
* @param {string | null} [params.footerContent=null] - Footer content: plain text or HTML.
|
|
51
|
+
* @returns {HTMLDialogElement} The dialog element.
|
|
32
52
|
*/
|
|
33
|
-
|
|
34
53
|
export function modalPopup({
|
|
35
54
|
|
|
36
55
|
/** dialog extra class */
|
|
37
56
|
dialogExtraClassName = null,
|
|
38
57
|
|
|
39
|
-
/** extra classname added to
|
|
58
|
+
/** extra classname added to dialogInner */
|
|
40
59
|
contentExtraClassName = null,
|
|
41
60
|
|
|
42
61
|
/** iframe url */
|
|
43
62
|
iframeUrl = null,
|
|
44
63
|
|
|
45
|
-
/** content: plain text, html or
|
|
64
|
+
/** content: plain text, html or domBuilder array */
|
|
46
65
|
content = null,
|
|
47
66
|
|
|
48
|
-
/** url
|
|
49
|
-
*
|
|
50
|
-
*
|
|
67
|
+
/** Ajax url and callback.
|
|
68
|
+
* The callback is invoked with the response data and the content container element
|
|
69
|
+
* (`ajaxCallback(data, dialogContentEl)`)
|
|
51
70
|
*/
|
|
52
71
|
ajaxUrl = null,
|
|
53
72
|
ajaxCallback = null,
|
|
54
73
|
|
|
55
|
-
/**
|
|
56
|
-
*
|
|
57
|
-
* NB: `dialogEl`
|
|
74
|
+
/** optional callback invoked when the dialog closes;
|
|
75
|
+
* receives the dialog element (`closeCallback(dialogEl)`)
|
|
76
|
+
* NB: `dialogEl` is removed immediately after
|
|
58
77
|
*/
|
|
59
78
|
closeCallback = null,
|
|
60
|
-
addScrollbarPadding = false, //
|
|
79
|
+
addScrollbarPadding = false, // adds extra right padding to compensate for the scrollbar
|
|
61
80
|
|
|
62
81
|
/** header content: plain text or HTML */
|
|
63
82
|
headerContent = null,
|
|
@@ -86,6 +105,9 @@ export function modalPopup({
|
|
|
86
105
|
mode = 'iframe';
|
|
87
106
|
}
|
|
88
107
|
|
|
108
|
+
// capture for use in the ajax callback closure (ajaxUrl is non-null when mode === 'ajax')
|
|
109
|
+
const safeAjaxUrl = /** @type {string} */ (ajaxUrl);
|
|
110
|
+
|
|
89
111
|
domBuilder([
|
|
90
112
|
{
|
|
91
113
|
tag: 'dialog',
|
|
@@ -93,7 +115,7 @@ export function modalPopup({
|
|
|
93
115
|
attrs: {
|
|
94
116
|
'closedby': 'any'
|
|
95
117
|
},
|
|
96
|
-
callback: el => dialogEl = el,
|
|
118
|
+
callback: el => { dialogEl = /** @type {HTMLDialogElement} */ (el); },
|
|
97
119
|
children: [
|
|
98
120
|
{
|
|
99
121
|
tag: 'button',
|
|
@@ -121,7 +143,7 @@ export function modalPopup({
|
|
|
121
143
|
: null,
|
|
122
144
|
|
|
123
145
|
children: [
|
|
124
|
-
...(mode === 'domBuilder'? content : []),
|
|
146
|
+
...(mode === 'domBuilder'? /** @type {any[]} */ (content) : []),
|
|
125
147
|
{
|
|
126
148
|
condition: mode === 'iframe',
|
|
127
149
|
className: styles.iframe,
|
|
@@ -138,15 +160,15 @@ export function modalPopup({
|
|
|
138
160
|
try {
|
|
139
161
|
|
|
140
162
|
(async () => {
|
|
141
|
-
const response = await fetch(
|
|
163
|
+
const response = await fetch(safeAjaxUrl),
|
|
142
164
|
data = await response.json();
|
|
143
165
|
el.innerHTML = '';
|
|
144
|
-
ajaxCallback(data, dialogContentEl);
|
|
166
|
+
ajaxCallback?.(data, dialogContentEl);
|
|
145
167
|
})();
|
|
146
168
|
|
|
147
169
|
} catch(err) {
|
|
148
170
|
/* eslint-disable no-console */
|
|
149
|
-
console.error(
|
|
171
|
+
console.error(safeAjaxUrl);
|
|
150
172
|
console.error(err);
|
|
151
173
|
/* eslint-enable no-console */
|
|
152
174
|
}
|
|
@@ -165,15 +187,15 @@ export function modalPopup({
|
|
|
165
187
|
}
|
|
166
188
|
], document.body);
|
|
167
189
|
|
|
168
|
-
// snapshot: dialogEl
|
|
169
|
-
const thisDialog = dialogEl;
|
|
190
|
+
// snapshot: dialogEl could be overwritten by a subsequent modalPopup call
|
|
191
|
+
const thisDialog = /** @type {HTMLDialogElement} */ (dialogEl);
|
|
170
192
|
|
|
171
|
-
//
|
|
172
|
-
thisDialog.querySelector(`.${styles.closeButton}`)
|
|
193
|
+
// close button listener
|
|
194
|
+
thisDialog.querySelector(`.${styles.closeButton}`)?.addEventListener('click', () => {
|
|
173
195
|
closeDialog(thisDialog);
|
|
174
196
|
}, false);
|
|
175
197
|
|
|
176
|
-
// // Esc
|
|
198
|
+
// // Esc and backdrop click
|
|
177
199
|
// thisDialog.addEventListener('cancel', (e) => {
|
|
178
200
|
// e.preventDefault();
|
|
179
201
|
// closeDialog(thisDialog);
|
|
@@ -1,11 +1,23 @@
|
|
|
1
|
-
import './overlay.css';
|
|
1
|
+
import './overlay.css';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Appends an overlay `div` to the given context element.
|
|
5
|
+
* @param {Element} [context=document.body]
|
|
6
|
+
* @param {boolean} [scroll_lock=false] - When true, locks body scroll via `overflow-hidden`.
|
|
7
|
+
* @returns {void}
|
|
8
|
+
*/
|
|
3
9
|
export function overlay(context = document.body, scroll_lock = false) {
|
|
4
10
|
context.insertAdjacentHTML('beforeend', '<div class="overlay"/>');
|
|
5
11
|
if(scroll_lock) {
|
|
6
12
|
document.body.classList.add('overflow-hidden');
|
|
7
13
|
}
|
|
8
14
|
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Removes the overlay element from the given context and re-enables body scroll.
|
|
18
|
+
* @param {Element} [context=document.body]
|
|
19
|
+
* @returns {void}
|
|
20
|
+
*/
|
|
9
21
|
export function removeOverlay(context = document.body) {
|
|
10
22
|
context.querySelector(':scope > .overlay')?.remove();
|
|
11
23
|
document.body.classList.remove('overflow-hidden');
|
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
// import './sf-macro.css'; //
|
|
2
|
-
|
|
1
|
+
// import './sf-macro.css'; // import in the consuming project
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Initialises Symfony macro collection fields (repeatable fieldsets).
|
|
5
|
+
* @param {Object} [options={}]
|
|
6
|
+
* @param {string} [options.wrapper_selector='.sf-macro-wrapper'] - Selector for the outer wrapper element.
|
|
7
|
+
* @param {string} [options.row_selector='.sf-macro-riga'] - Selector for each repeatable row.
|
|
8
|
+
* @param {((newRow: Element | null, addBtn: Element | null) => void) | null} [options.add_callback=null] - Called after a row is added; receives the new row and the add button.
|
|
9
|
+
* @param {(() => void) | null} [options.del_callback=null] - Called after a row is removed.
|
|
10
|
+
* @param {boolean} [options.insertAtTop=false] - When true, new rows are inserted at the top.
|
|
11
|
+
* @returns {void}
|
|
12
|
+
*/
|
|
3
13
|
export function sf_macro({
|
|
4
14
|
wrapper_selector = '.sf-macro-wrapper',
|
|
5
15
|
row_selector = '.sf-macro-riga',
|
|
@@ -9,19 +19,20 @@ export function sf_macro({
|
|
|
9
19
|
}={}) {
|
|
10
20
|
|
|
11
21
|
document.querySelectorAll(wrapper_selector).forEach(fset => {
|
|
12
|
-
const macro_container = fset.querySelector('.sf-macro-container')
|
|
13
|
-
|
|
14
|
-
;
|
|
22
|
+
const macro_container = /** @type {HTMLElement | null} */ (fset.querySelector('.sf-macro-container'));
|
|
23
|
+
if (!macro_container) return;
|
|
24
|
+
const macro_template = macro_container.dataset.template ?? '';
|
|
15
25
|
|
|
16
|
-
// listeners
|
|
26
|
+
// event listeners
|
|
17
27
|
fset.addEventListener('click', e => {
|
|
28
|
+
const target = /** @type {HTMLElement} */ (e.target);
|
|
18
29
|
|
|
19
|
-
if(
|
|
30
|
+
if(target.closest('.sf-macro-riga-add')) {
|
|
20
31
|
|
|
21
32
|
const righe_macro = macro_container.querySelectorAll(row_selector).length;
|
|
22
33
|
|
|
23
34
|
macro_container.insertAdjacentHTML(insertAtTop? 'afterbegin' : 'beforeend',
|
|
24
|
-
macro_template.replace(/__indice\d?__/g, righe_macro + 1)
|
|
35
|
+
macro_template.replace(/__indice\d?__/g, String(righe_macro + 1))
|
|
25
36
|
);
|
|
26
37
|
|
|
27
38
|
if(add_callback && typeof add_callback === 'function') {
|
|
@@ -29,13 +40,13 @@ export function sf_macro({
|
|
|
29
40
|
insertAtTop
|
|
30
41
|
? macro_container.querySelector('.sf-macro-riga:first-child')
|
|
31
42
|
: macro_container.querySelector('.sf-macro-riga:last-child'),
|
|
32
|
-
|
|
43
|
+
target.closest('.sf-macro-riga-add')
|
|
33
44
|
);
|
|
34
45
|
}
|
|
35
46
|
|
|
36
47
|
|
|
37
|
-
} else if(
|
|
38
|
-
|
|
48
|
+
} else if(target.closest('.sf-macro-close-btn')) {
|
|
49
|
+
target.closest(row_selector)?.remove();
|
|
39
50
|
|
|
40
51
|
if(del_callback && typeof del_callback === 'function') {
|
|
41
52
|
del_callback();
|
|
@@ -43,5 +54,5 @@ export function sf_macro({
|
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
}, false);
|
|
46
|
-
});
|
|
57
|
+
});
|
|
47
58
|
}
|
|
@@ -14,9 +14,19 @@ References
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
// TODO snackbar action
|
|
17
|
-
// TODO
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
// TODO handle multiple simultaneous snackbars (no stacking)
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Displays a snackbar / toast notification using the Popover API.
|
|
21
|
+
* @param {string} message - The message to display.
|
|
22
|
+
* @param {Object} [options={}]
|
|
23
|
+
* @param {string | null} [options.status=null] - Status variant: `danger`, `warning`, `info`, or `success`.
|
|
24
|
+
* @param {number | false | null} [options.duration=4000] - Auto-close delay in ms; `false` or `null` disables auto-close.
|
|
25
|
+
* @param {boolean} [options.close_btn=true] - Whether to show the close button.
|
|
26
|
+
* @param {Function | null} [options.action=null] - Action callback.
|
|
27
|
+
* @param {string | null} [options.action_text=null] - Label for the action button.
|
|
28
|
+
* @returns {{ close: function(): void }}
|
|
29
|
+
*/
|
|
20
30
|
export function snackbar(message, options = {}){
|
|
21
31
|
|
|
22
32
|
options = {
|
|
@@ -31,20 +41,24 @@ export function snackbar(message, options = {}){
|
|
|
31
41
|
...options
|
|
32
42
|
};
|
|
33
43
|
|
|
34
|
-
|
|
44
|
+
/** @type {HTMLElement | null} */
|
|
45
|
+
let _popover = null;
|
|
46
|
+
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
47
|
+
let _timeoutID;
|
|
48
|
+
let _isRemoving = false;
|
|
35
49
|
|
|
36
50
|
const removePopover = () => {
|
|
37
51
|
if (_isRemoving || !_popover?.matches(':popover-open')) return;
|
|
38
|
-
_isRemoving = true; //
|
|
52
|
+
_isRemoving = true; // prevents double-removal if called multiple times during the close animation
|
|
39
53
|
|
|
40
54
|
if (_timeoutID) clearTimeout(_timeoutID);
|
|
41
55
|
|
|
42
|
-
_popover
|
|
56
|
+
_popover?.classList.add(styles.isHiding);
|
|
43
57
|
|
|
44
|
-
_popover
|
|
45
|
-
_popover
|
|
46
|
-
_popover
|
|
47
|
-
}, { once: true }); //
|
|
58
|
+
_popover?.addEventListener('animationend', () => {
|
|
59
|
+
_popover?.hidePopover();
|
|
60
|
+
_popover?.remove();
|
|
61
|
+
}, { once: true }); // 'once' prevents duplicate listeners; the flag guards against calls before animation ends
|
|
48
62
|
};
|
|
49
63
|
|
|
50
64
|
_popover = domBuilder([
|
|
@@ -52,7 +66,7 @@ export function snackbar(message, options = {}){
|
|
|
52
66
|
className: classnames(styles.snackbarOuter, options.status? styles[`status-${options.status}`] : null),
|
|
53
67
|
attrs: {
|
|
54
68
|
popover: 'manual',
|
|
55
|
-
role: (options.action || ['danger','warning'].includes(options.status)) ? 'alert' : 'status'
|
|
69
|
+
role: (options.action || (options.status != null && ['danger','warning'].includes(options.status))) ? 'alert' : 'status'
|
|
56
70
|
},
|
|
57
71
|
children: [
|
|
58
72
|
{
|
|
@@ -85,16 +99,17 @@ export function snackbar(message, options = {}){
|
|
|
85
99
|
}
|
|
86
100
|
], document.body);
|
|
87
101
|
|
|
88
|
-
_popover
|
|
102
|
+
_popover?.showPopover();
|
|
89
103
|
|
|
90
104
|
|
|
91
105
|
if(options.duration) {
|
|
92
|
-
|
|
106
|
+
const duration = options.duration; // const preserves narrowing inside closures
|
|
107
|
+
_timeoutID = setTimeout(removePopover, duration);
|
|
93
108
|
|
|
94
|
-
//
|
|
95
|
-
_popover
|
|
96
|
-
_popover
|
|
97
|
-
_timeoutID = setTimeout(removePopover,
|
|
109
|
+
// pause the auto-close timeout on hover
|
|
110
|
+
_popover?.addEventListener('mouseenter', () => clearTimeout(_timeoutID));
|
|
111
|
+
_popover?.addEventListener('mouseleave', () => {
|
|
112
|
+
_timeoutID = setTimeout(removePopover, duration);
|
|
98
113
|
});
|
|
99
114
|
|
|
100
115
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import * as styles from './spinner.module.css';
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
// TODO
|
|
3
|
+
// The spinner class depends on the spinner type in use; the corresponding CSS file must be included.
|
|
4
|
+
// TODO: system for configuring the spinner type to allow multiple spinner types in the same project
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Returns the HTML markup for a loading spinner.
|
|
8
|
+
* @returns {string}
|
|
9
|
+
*/
|
|
6
10
|
export function spinner() {
|
|
7
11
|
return `<div class="${styles.spinnerWrapper}"><div class="spinner">Loading...</div></div>`;
|
|
8
12
|
}
|
|
@@ -1,4 +1,25 @@
|
|
|
1
1
|
|
|
2
|
+
/**
|
|
3
|
+
* @typedef {Object} UnsplashPhoto
|
|
4
|
+
* @property {string} id
|
|
5
|
+
* @property {string} color
|
|
6
|
+
* @property {number} width
|
|
7
|
+
* @property {number} height
|
|
8
|
+
* @property {string} alt_description
|
|
9
|
+
* @property {string} date
|
|
10
|
+
* @property {string} base_url
|
|
11
|
+
* @property {string} unsplash_url
|
|
12
|
+
* @property {string} author
|
|
13
|
+
* @property {string} author_profile
|
|
14
|
+
* @property {string} blur_hash
|
|
15
|
+
* @property {string} image_description
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fetches and normalises Unsplash photo data from the given URL.
|
|
20
|
+
* @param {{ unsplash_data_url: string | null }} settings
|
|
21
|
+
* @returns {Promise<UnsplashPhoto | undefined>}
|
|
22
|
+
*/
|
|
2
23
|
export async function fetchUnsplashData(settings) {
|
|
3
24
|
|
|
4
25
|
const default_settings = {
|
|
@@ -13,11 +34,13 @@ export async function fetchUnsplashData(settings) {
|
|
|
13
34
|
throw new Error( 'Unsplash data url must be defined' );
|
|
14
35
|
}
|
|
15
36
|
|
|
37
|
+
const url = settings.unsplash_data_url; // const preserves narrowing inside the IIFE closure
|
|
38
|
+
|
|
16
39
|
const imgData = await (async () => {
|
|
17
|
-
const response = await fetch(
|
|
40
|
+
const response = await fetch(url);
|
|
18
41
|
if (!response.ok) {
|
|
19
42
|
/* eslint-disable no-console */
|
|
20
|
-
console.error('Ajax error on: ' +
|
|
43
|
+
console.error('Ajax error on: ' + url);
|
|
21
44
|
console.error(response);
|
|
22
45
|
/* eslint-enable no-console */
|
|
23
46
|
throw new Error( `Loading error: ${response.status}` );
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} Breakpoint
|
|
3
|
+
* @property {string} name - Breakpoint identifier.
|
|
4
|
+
* @property {string} mq - Media query string.
|
|
5
|
+
* @property {number} w - Image width in pixels.
|
|
6
|
+
* @property {number} h - Image height in pixels.
|
|
7
|
+
* @property {boolean} dpr2 - Whether to include a 2× DPR srcset entry.
|
|
8
|
+
* @property {string} [ar] - Aspect ratio (e.g. '16/9').
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** @type {Breakpoint[]} */
|
|
1
12
|
export const breakpoints = [
|
|
2
13
|
{
|
|
3
14
|
name: 'xxl',
|
|
@@ -4,6 +4,21 @@ import { breakpoints } from './img-breakpoints.js';
|
|
|
4
4
|
import { fetchUnsplashData } from './fetch-unsplash-data.js';
|
|
5
5
|
import { decode } from 'blurhash';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Initialises an Unsplash full-page photo with blurhash placeholder, responsive picture element,
|
|
9
|
+
* and attribution overlay.
|
|
10
|
+
* @param {Object} settings
|
|
11
|
+
* @param {HTMLElement | null} settings.targetElement - Container element where the photo will be rendered.
|
|
12
|
+
* @param {string | null} [settings.className=null] - Extra class added to the container.
|
|
13
|
+
* @param {string | null} settings.unsplashDataUrl - URL returning Unsplash photo JSON.
|
|
14
|
+
* @param {string | null} settings.utmSource - UTM source value for attribution links.
|
|
15
|
+
* @param {string | null} settings.title - Main heading text.
|
|
16
|
+
* @param {string | null} [settings.text=null] - Optional body text.
|
|
17
|
+
* @param {string | null} [settings.backLink=null] - Optional back-link HTML.
|
|
18
|
+
* @param {boolean} [settings.hidePhotoLink=true] - When true, the Unsplash link is shown only on hover.
|
|
19
|
+
* @param {Record<string, string> | null} [settings.cssModulesObj=null] - CSS Modules class map; when null, class names are used as-is.
|
|
20
|
+
* @returns {void}
|
|
21
|
+
*/
|
|
7
22
|
export function unsplashPageJS(settings) {
|
|
8
23
|
|
|
9
24
|
const default_settings = {
|
|
@@ -24,22 +39,26 @@ export function unsplashPageJS(settings) {
|
|
|
24
39
|
|
|
25
40
|
try {
|
|
26
41
|
|
|
42
|
+
const settingsMap = /** @type {Record<string, unknown>} */ (settings);
|
|
27
43
|
if(required_settings
|
|
28
|
-
.map(i =>
|
|
44
|
+
.map(i => settingsMap[i])
|
|
29
45
|
.filter(i => i !== null && i !== '').length !== required_settings.length) {
|
|
30
46
|
|
|
31
47
|
throw new Error( `I parametri ${required_settings.map(i => `\`${i}\``).join(',')} sono obbligatori` );
|
|
32
48
|
}
|
|
33
49
|
|
|
50
|
+
const targetElement = /** @type {HTMLElement} */ (settings.targetElement);
|
|
34
51
|
|
|
35
|
-
const cssClass =
|
|
52
|
+
const cssClass = /** @param {string} classname */ classname =>
|
|
53
|
+
settings.cssModulesObj? settings.cssModulesObj[classname] : classname;
|
|
36
54
|
|
|
37
|
-
|
|
38
|
-
const container =
|
|
55
|
+
targetElement.innerHTML = `<div class="${cssClass('upContainer') + (settings.className? ` ${settings.className}` : '')}"></div>`;
|
|
56
|
+
const container = /** @type {HTMLElement} */ (targetElement.querySelector('.' + cssClass('upContainer')));
|
|
39
57
|
container.innerHTML = `<div class="${cssClass('upLoaderWrapper')}"><div class="${cssClass('upLoader')}"></div></div>`;
|
|
40
58
|
|
|
41
59
|
fetchUnsplashData({unsplash_data_url: settings.unsplashDataUrl})
|
|
42
60
|
.then(photo => {
|
|
61
|
+
if (!photo) return;
|
|
43
62
|
|
|
44
63
|
const pixels = decode(photo.blur_hash, container.offsetWidth, container.offsetHeight);
|
|
45
64
|
|
|
@@ -48,9 +67,11 @@ export function unsplashPageJS(settings) {
|
|
|
48
67
|
canvas.width = container.offsetWidth;
|
|
49
68
|
canvas.height = container.offsetHeight;
|
|
50
69
|
const ctx = canvas.getContext('2d');
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
70
|
+
if (ctx) {
|
|
71
|
+
const imageData = ctx.createImageData(container.offsetWidth, container.offsetHeight);
|
|
72
|
+
imageData.data.set(pixels);
|
|
73
|
+
ctx.putImageData(imageData, 0, 0);
|
|
74
|
+
}
|
|
54
75
|
|
|
55
76
|
|
|
56
77
|
container.insertAdjacentElement('afterbegin', canvas);
|
|
@@ -100,42 +121,45 @@ export function unsplashPageJS(settings) {
|
|
|
100
121
|
'</picture>'
|
|
101
122
|
); // end insert <picture>
|
|
102
123
|
|
|
103
|
-
const img = container.querySelector('.' + cssClass('unsplashPhoto'));
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
<
|
|
124
|
+
const img = /** @type {HTMLImageElement | null} */ (container.querySelector('.' + cssClass('unsplashPhoto')));
|
|
125
|
+
if (img) {
|
|
126
|
+
img.onload = () => {
|
|
127
|
+
container.querySelector('.' + cssClass('upLoaderWrapper'))?.remove();
|
|
128
|
+
container.classList.add(cssClass('show'));
|
|
129
|
+
|
|
130
|
+
container.insertAdjacentHTML('beforeend', `<div class="${cssClass('upMessageBox')}">
|
|
131
|
+
<div class="${cssClass('upMessage')}">
|
|
132
|
+
<h1>${settings.title}</h1>
|
|
133
|
+
${settings.text? `<p>${settings.text}</p>` : ''}
|
|
134
|
+
${settings.backLink ? `<p class="${cssClass('upBackLink')}">${settings.backLink}</p>` : ''}
|
|
135
|
+
|
|
136
|
+
<div class="${cssClass('upArrowWrapper')}" role="button">
|
|
137
|
+
<img src="${arrowIcon}" alt="Icona freccia">
|
|
138
|
+
</div>
|
|
116
139
|
</div>
|
|
117
|
-
</div>
|
|
118
140
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
141
|
+
<div class="${cssClass('upCredits')}">
|
|
142
|
+
<em>${photo.image_description}</em>
|
|
143
|
+
<span>Photo
|
|
144
|
+
<a href="${`${photo.author_profile}?utmSource=${settings.utmSource}&utm_medium=referral`}">
|
|
145
|
+
${photo.author} / Unsplash
|
|
146
|
+
</a>
|
|
147
|
+
</span>
|
|
148
|
+
</div>
|
|
126
149
|
</div>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
</
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
150
|
+
<div class="${cssClass('unsplashPhotoLink')}${settings.hidePhotoLink? ` ${cssClass('upShowOnHover')}` : ''}">
|
|
151
|
+
<a href="${`${photo.unsplash_url}?utm_source=${settings.utmSource}&utm_medium=referral`}" target="_blank" rel="noopener noreferrer">
|
|
152
|
+
<img src="${imageIcon}" alt="Icona immagine" />
|
|
153
|
+
</a>
|
|
154
|
+
</div>`);
|
|
155
|
+
|
|
156
|
+
container.querySelector('.' + cssClass('upArrowWrapper'))?.addEventListener('click', e => {
|
|
157
|
+
const target = /** @type {HTMLElement} */ (e.target);
|
|
158
|
+
target.closest(`.${cssClass('upMessage')}`)?.classList.toggle(cssClass('hidden'));
|
|
159
|
+
}, false);
|
|
160
|
+
|
|
161
|
+
}; // end img.onload
|
|
162
|
+
}
|
|
139
163
|
|
|
140
164
|
}); // end .then
|
|
141
165
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
PERSONALIZZAZIONE
|
|
3
|
-
custom properties (valori di default, vedi /design-tokens
|
|
3
|
+
custom properties (valori di default, vedi /design-tokens/_src/components/unsplash-page.minimo.tokens.mjs):
|
|
4
4
|
|
|
5
5
|
Basic props:
|
|
6
6
|
--up-bg-color: hsl(0deg 0% 0% / .7);
|
package/src/css/forms/forms.css
CHANGED
|
@@ -148,6 +148,10 @@ input::-webkit-calendar-picker-trigger-icon { /* solo in chromium, non in safari
|
|
|
148
148
|
textarea.form-control {
|
|
149
149
|
flex: 1;
|
|
150
150
|
min-height: calc(3.5lh + var(--form-control-padding-block) * 2);
|
|
151
|
+
|
|
152
|
+
&.autosize {
|
|
153
|
+
field-sizing: content;
|
|
154
|
+
}
|
|
151
155
|
}
|
|
152
156
|
|
|
153
157
|
|
|
@@ -1,14 +1,27 @@
|
|
|
1
|
+
/** @typedef {string | null | undefined | false | 0} ClassValue */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Joins CSS class names, filtering out falsy and non-string values.
|
|
5
|
+
* Accepts strings, falsy values, and (nested) arrays of the same.
|
|
6
|
+
* @param {...(ClassValue | ClassValue[])} args
|
|
7
|
+
* @returns {string}
|
|
8
|
+
*/
|
|
1
9
|
export function classnames(...args) {
|
|
2
10
|
return args.flat().filter(x => {
|
|
3
11
|
if (x && typeof x !== 'string') {
|
|
4
12
|
// eslint-disable-next-line no-console
|
|
5
|
-
console.error('[classnames]
|
|
13
|
+
console.error('[classnames] non-string value ignored: ', ...args);
|
|
6
14
|
return false;
|
|
7
15
|
}
|
|
8
16
|
return Boolean(x);
|
|
9
17
|
}).join(' ');
|
|
10
18
|
}
|
|
11
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Like {@link classnames}, but returns `null` when the result is an empty string.
|
|
22
|
+
* @param {...(ClassValue | ClassValue[])} args
|
|
23
|
+
* @returns {string | null}
|
|
24
|
+
*/
|
|
12
25
|
export function classnamesNull(...args) {
|
|
13
26
|
return classnames(...args) || null;
|
|
14
27
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { stripTags } from './strip-tags.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates an id from a string (useful for generating automatic anchors from tag content).
|
|
5
|
+
* Strips HTML tags, normalizes accented characters, punctuation, and whitespace.
|
|
6
|
+
* @param {string | null | undefined} str
|
|
7
|
+
* @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
export function createId(str) {
|
|
10
|
+
return (stripTags(str) || '')
|
|
11
|
+
.toLowerCase()
|
|
12
|
+
.normalize('NFD')
|
|
13
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
14
|
+
.replace(/[\p{P}\p{S}]+/gu, '') // strips all Unicode punctuation and symbols
|
|
15
|
+
.replace(/\s+/g, '-')
|
|
16
|
+
.replace(/^-+|-+$/g, '')
|
|
17
|
+
.replace(/-+/g, '-')
|
|
18
|
+
.replace(/[^a-z0-9-]/g, '');
|
|
19
|
+
}
|