@massimo-cassandro/minimo 1.7.2 → 1.8.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/tokens/size.minimo.tokens.jsonc +16 -16
- package/index.js +3 -2
- package/package.json +1 -1
- package/src/components/TODO form-multiselect/form-multiselect.css +40 -0
- package/src/components/TODO form-multiselect/form-multiselect.js +83 -0
- package/src/components/TODO vanilla-cookie-consent/cookie-consent.css +20 -0
- package/src/components/TODO vanilla-cookie-consent/cookie-consent.js +76 -0
- package/src/components/TODO vanilla-cookie-consent/render-cookie-list.js +32 -0
- package/src/components/TODO vanilla-cookie-consent/src/cookie-list.js +85 -0
- package/src/components/TODO vanilla-cookie-consent/src/it-translation.js +38 -0
- package/src/components/TODO vanilla-cookie-consent/src/run-analytics.js +54 -0
- package/src/components/TODO vanilla-cookie-consent/src/run-recaptcha.js +71 -0
- package/src/components/modal-alert/TODO modal-alert-demo/demo.css +63 -0
- package/src/components/modal-alert/TODO modal-alert-demo/demo.js +155 -0
- package/src/components/slide-up-down-toggle/slide-up-down-toggle.js +237 -0
- package/src/components/slide-up-down-toggle/slide-up-down-toggle.module.css +97 -0
- package/src/components/unsplash-page/TODO unsplash-page-demo/README.md +194 -0
- package/src/components/unsplash-page/TODO unsplash-page-demo/changelog.md +5 -0
- package/src/components/unsplash-page/TODO unsplash-page-demo/unsplash-page/demo.js +15 -0
- package/src/components/unsplash-page/TODO unsplash-page-demo/unsplash-page/rollup.config.mjs +26 -0
- package/src/components/unsplash-page/TODO unsplash-page-demo/unsplash-page/unsplash-page-demo.min.js +3 -0
- package/src/web-components/TODO json-table/README.md +67 -0
- package/src/web-components/TODO json-table/TODO.md +12 -0
- package/src/web-components/TODO json-table/_work/OLD/docs/custom-props-list.md +69 -0
- package/src/web-components/TODO json-table/_work/OLD/script/create-css-custom-props-doc.mjs +113 -0
- package/src/web-components/TODO json-table/_work/OLD/script/src/custom-props-end.md +4 -0
- package/src/web-components/TODO json-table/_work/OLD/script/src/custom-props-start.md +30 -0
- package/src/web-components/TODO json-table/_work/OLD/src/css/icons.module.css +22 -0
- package/src/web-components/TODO json-table/_work/OLD/src/css/info.module.css +42 -0
- package/src/web-components/TODO json-table/_work/OLD/src/css/main-wrapper.module.css +4 -0
- package/src/web-components/TODO json-table/_work/OLD/src/css/table.module.css +154 -0
- package/src/web-components/TODO json-table/_work/OLD/src/css/utility.module.css +9 -0
- package/src/web-components/TODO json-table/_work/OLD/src/icons/false.svg +1 -0
- package/src/web-components/TODO json-table/_work/OLD/src/icons/search-arrows.svg +1 -0
- package/src/web-components/TODO json-table/_work/OLD/src/icons/true.svg +1 -0
- package/src/web-components/TODO json-table/_work/OLD/src/index.js +56 -0
- package/src/web-components/TODO json-table/_work/OLD/src/js/default-columns-data-types.js +172 -0
- package/src/web-components/TODO json-table/_work/OLD/src/js/defaults.js +156 -0
- package/src/web-components/TODO json-table/_work/OLD/src/js/info-section.js +31 -0
- package/src/web-components/TODO json-table/_work/OLD/src/js/listeners.js +17 -0
- package/src/web-components/TODO json-table/_work/OLD/src/js/main-builder.js +26 -0
- package/src/web-components/TODO json-table/_work/OLD/src/js/parse-params.js +121 -0
- package/src/web-components/TODO json-table/_work/OLD/src/js/set-column-classname.js +22 -0
- package/src/web-components/TODO json-table/_work/OLD/src/js/table-builder.js +64 -0
- package/src/web-components/TODO json-table/_work/OLD/src/js/table-foot.js +8 -0
- package/src/web-components/TODO json-table/_work/OLD/src/js/table-head.js +47 -0
- package/src/web-components/TODO json-table/_work/OLD/src/js/table-row.js +83 -0
- package/src/web-components/TODO json-table/_work/OLD/src/js/utilities.js +5 -0
- package/src/web-components/TODO json-table/_work/__json-table-react/index.js +4 -0
- package/src/web-components/TODO json-table/_work/__json-table-react/src/build-page-array.js +73 -0
- package/src/web-components/TODO json-table/_work/__json-table-react/src/columns-data-types.js +73 -0
- package/src/web-components/TODO json-table/_work/__json-table-react/src/defaults.js +50 -0
- package/src/web-components/TODO json-table/_work/__json-table-react/src/download-excel.js +156 -0
- package/src/web-components/TODO json-table/_work/__json-table-react/src/set-className.js +19 -0
- package/src/web-components/TODO json-table/_work/__json-table-react/src/set-download-filename.js +17 -0
- package/src/web-components/TODO json-table/_work/__json-table-react/src/table-to-csv.js +48 -0
- package/src/web-components/TODO json-table/_work/archived/table-to-csv.js +48 -0
- package/src/web-components/TODO json-table/_work/arrow.svg +3 -0
- package/src/web-components/TODO json-table/_work/da decidere/get-cell-data.js +18 -0
- package/src/web-components/TODO json-table/_work/download-button.js +29 -0
- package/src/web-components/TODO json-table/_work/download-excel.js +155 -0
- package/src/web-components/TODO json-table/_work/pagination-array-builder.js +73 -0
- package/src/web-components/TODO json-table/_work/pagination.js +37 -0
- package/src/web-components/TODO json-table/_work/set-download-filename.js +19 -0
- package/src/web-components/TODO json-table/_work/table-info.js +24 -0
- package/src/web-components/TODO json-table/demo/README.md +66 -0
- package/src/web-components/TODO json-table/demo/assets/link-simple.svg +1 -0
- package/src/web-components/TODO json-table/demo/demo-data/json-static-table-demo-data.js +512 -0
- package/src/web-components/TODO json-table/demo/demo.css +32 -0
- package/src/web-components/TODO json-table/demo/demo.js +24 -0
- package/src/web-components/TODO json-table/demo/js/demo-ajax-table.js +165 -0
- package/src/web-components/TODO json-table/demo/js/demo-static-table.js +180 -0
- package/src/web-components/TODO json-table/demo/webpack-modules/css-rules.mjs +122 -0
- package/src/web-components/TODO json-table/demo/webpack-modules/get-jsConfig-aliases.mjs +33 -0
- package/src/web-components/TODO json-table/demo/webpack-modules/svg-rules.mjs +108 -0
- package/src/web-components/TODO json-table/demo/webpack-modules/svgo.config.mjs +26 -0
- package/src/web-components/TODO json-table/demo/webpack.config.mjs +333 -0
- package/src/web-components/TODO json-table/docs/custom-props-list.md +74 -0
- package/src/web-components/TODO json-table/docs-builder/create-css-custom-props-doc.mjs +157 -0
- package/src/web-components/TODO json-table/docs-builder/src/custom-props-end.md +6 -0
- package/src/web-components/TODO json-table/docs-builder/src/custom-props-start.md +29 -0
- package/src/web-components/TODO json-table/src/TODO.md +1 -0
- package/src/web-components/TODO json-table/src/__json-table-react/build-page-array.js +73 -0
- package/src/web-components/TODO json-table/src/__json-table-react/columns-data-types.js +73 -0
- package/src/web-components/TODO json-table/src/__json-table-react/defaults.js +50 -0
- package/src/web-components/TODO json-table/src/__json-table-react/download-excel.js +156 -0
- package/src/web-components/TODO json-table/src/__json-table-react/set-className.js +19 -0
- package/src/web-components/TODO json-table/src/__json-table-react/set-download-filename.js +17 -0
- package/src/web-components/TODO json-table/src/__json-table-react/table-to-csv.js +48 -0
- package/src/web-components/TODO json-table/src/css/custom-media.css +16 -0
- package/src/web-components/TODO json-table/src/css/icons.module.css +22 -0
- package/src/web-components/TODO json-table/src/css/info-section.module.css +78 -0
- package/src/web-components/TODO json-table/src/css/main-builder.module.css +6 -0
- package/src/web-components/TODO json-table/src/css/table.module.css +172 -0
- package/src/web-components/TODO json-table/src/css/utility.module.css +9 -0
- package/src/web-components/TODO json-table/src/icons/arrow-down-bold.svg +1 -0
- package/src/web-components/TODO json-table/src/icons/false.svg +1 -0
- package/src/web-components/TODO json-table/src/icons/true.svg +1 -0
- package/src/web-components/TODO json-table/src/index.js +125 -0
- package/src/web-components/TODO json-table/src/js/default-columns-data-types.js +172 -0
- package/src/web-components/TODO json-table/src/js/defaults.js +510 -0
- package/src/web-components/TODO json-table/src/js/get-ajax-data.js +72 -0
- package/src/web-components/TODO json-table/src/js/info-section.js +39 -0
- package/src/web-components/TODO json-table/src/js/init-ajax-table.js +30 -0
- package/src/web-components/TODO json-table/src/js/init-static-table.js +18 -0
- package/src/web-components/TODO json-table/src/js/main-builder.js +87 -0
- package/src/web-components/TODO json-table/src/js/parse-data-row.js +123 -0
- package/src/web-components/TODO json-table/src/js/parse-params.js +118 -0
- package/src/web-components/TODO json-table/src/js/set-search-listener.js +62 -0
- package/src/web-components/TODO json-table/src/js/set-sort-listeners.js +115 -0
- package/src/web-components/TODO json-table/src/js/static-data-sorting.js +23 -0
- package/src/web-components/TODO json-table/src/js/table-builder.js +45 -0
- package/src/web-components/TODO json-table/src/js/table-row.js +43 -0
- package/src/web-components/TODO json-table/src/js/table-tfoot.js +9 -0
- package/src/web-components/TODO json-table/src/js/table-thead.js +91 -0
- package/src/web-components/TODO json-table/src/js/update-info.js +42 -0
- package/src/web-components/TODO json-table/src/js/utilities.js +5 -0
- package/src/web-components/s-datatable-component/s-datatable-component.js +14 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import mAlert from '../src/modal-alert.js';
|
|
2
|
+
|
|
3
|
+
const custom_defaults = {
|
|
4
|
+
|
|
5
|
+
//settings to be applied to all dialog types
|
|
6
|
+
globals: {
|
|
7
|
+
onOpen: () => {
|
|
8
|
+
// console.log('open'); // eslint-disable-line
|
|
9
|
+
document.body.classList.add('modal-alert-is-open');
|
|
10
|
+
},
|
|
11
|
+
onClose: () => {
|
|
12
|
+
// console.log('close'); // eslint-disable-line
|
|
13
|
+
document.body.classList.remove('modal-alert-is-open');
|
|
14
|
+
},
|
|
15
|
+
cssFile: null
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
success: {
|
|
19
|
+
title: 'Done!',
|
|
20
|
+
mes: null,
|
|
21
|
+
ok_btn_text: 'OK',
|
|
22
|
+
heading_class: 'text-success',
|
|
23
|
+
ok_btn_class: 'btn',
|
|
24
|
+
timer: 4000, // ms
|
|
25
|
+
},
|
|
26
|
+
error: {
|
|
27
|
+
title: 'An error has occurred',
|
|
28
|
+
mes: null,
|
|
29
|
+
ok_btn_text: 'OK',
|
|
30
|
+
heading_class: null,
|
|
31
|
+
text_class: null,
|
|
32
|
+
ok_btn_class: 'btn',
|
|
33
|
+
timer: null,
|
|
34
|
+
},
|
|
35
|
+
warning: {
|
|
36
|
+
title: 'Attention!',
|
|
37
|
+
mes: null,
|
|
38
|
+
ok_btn_text: 'OK',
|
|
39
|
+
heading_class: null,
|
|
40
|
+
text_class: null,
|
|
41
|
+
ok_btn_class: 'btn',
|
|
42
|
+
timer: null,
|
|
43
|
+
},
|
|
44
|
+
info: {
|
|
45
|
+
title: null,
|
|
46
|
+
mes: null,
|
|
47
|
+
ok_btn_text: 'OK',
|
|
48
|
+
heading_class: null,
|
|
49
|
+
text_class: null,
|
|
50
|
+
ok_btn_class: 'btn',
|
|
51
|
+
timer: null,
|
|
52
|
+
},
|
|
53
|
+
confirm: {
|
|
54
|
+
title: 'Do you confirm?',
|
|
55
|
+
mes: null,
|
|
56
|
+
ok_btn_text: 'OK',
|
|
57
|
+
cancel_btn_text: 'Cancel',
|
|
58
|
+
cancel_focus: true, // false to give focus to the ok button
|
|
59
|
+
heading_class: null,
|
|
60
|
+
text_class: null,
|
|
61
|
+
ok_btn_class: 'btn btn-outline',
|
|
62
|
+
cancel_btn_class: 'btn',
|
|
63
|
+
callback: null,
|
|
64
|
+
timer: null,
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
document.querySelector('.demo-success').addEventListener('click', () => {
|
|
69
|
+
mAlert({
|
|
70
|
+
type : 'success',
|
|
71
|
+
mes: 'Lorem ipsum dolor sit amet',
|
|
72
|
+
callback: () => alert('callback')
|
|
73
|
+
}, custom_defaults);
|
|
74
|
+
}, false);
|
|
75
|
+
|
|
76
|
+
document.querySelector('.demo-info').addEventListener('click', () => {
|
|
77
|
+
mAlert({
|
|
78
|
+
type : 'info',
|
|
79
|
+
title: 'Info',
|
|
80
|
+
mes: '<strong>Lorem ipsum</strong> dolor sit amet'
|
|
81
|
+
}, custom_defaults);
|
|
82
|
+
}, false);
|
|
83
|
+
|
|
84
|
+
document.querySelector('.demo-error').addEventListener('click', () => {
|
|
85
|
+
mAlert({
|
|
86
|
+
type : 'error'
|
|
87
|
+
}, custom_defaults);
|
|
88
|
+
}, false);
|
|
89
|
+
|
|
90
|
+
document.querySelector('.demo-error-extra-btn').addEventListener('click', () => {
|
|
91
|
+
mAlert({
|
|
92
|
+
type : 'error',
|
|
93
|
+
ok_btn_class: 'btn btn-error btn btn-outline',
|
|
94
|
+
extra_btn: '<button type="button" data-malert-result="extra-btn-result" class="btn btn-error extra-btn">Extra btn</button>',
|
|
95
|
+
extra_btn_selector: '.extra-btn',
|
|
96
|
+
extra_btn_focus: true,
|
|
97
|
+
callback: result => alert(result === 'extra-btn-result'? 'Extra button pressed' : 'OK button pressed')
|
|
98
|
+
}, custom_defaults);
|
|
99
|
+
}, false);
|
|
100
|
+
|
|
101
|
+
document.querySelector('.demo-warning').addEventListener('click', () => {
|
|
102
|
+
mAlert({
|
|
103
|
+
type : 'warning',
|
|
104
|
+
mes: '<p>Quo dolorum cumque quo placeat aperiam doloribus dolorum animi. Sunt et dolores quas autem. Porro inventore ratione. Ex facere doloremque. Consectetur enim dolor. Sint ut facere quidem et et dicta natus itaque.</p> <p>Odit in eos repellat exercitationem tempore blanditiis voluptate. Dignissimos est tempora qui ducimus at et expedita esse.</p>'
|
|
105
|
+
}, custom_defaults);
|
|
106
|
+
}, false);
|
|
107
|
+
|
|
108
|
+
document.querySelector('.demo-confirm').addEventListener('click', () => {
|
|
109
|
+
mAlert({
|
|
110
|
+
type : 'confirm',
|
|
111
|
+
callback: result => alert(`${result? 'OK' : 'Cancel'} button pressed`)
|
|
112
|
+
}, custom_defaults);
|
|
113
|
+
}, false);
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
// forms
|
|
117
|
+
// Confirm on form submit
|
|
118
|
+
document.getElementById('test-form').addEventListener('submit', e => {
|
|
119
|
+
let choiceIsNeeded = true;
|
|
120
|
+
|
|
121
|
+
if(choiceIsNeeded) {
|
|
122
|
+
e.preventDefault();
|
|
123
|
+
mAlert({
|
|
124
|
+
type : 'confirm',
|
|
125
|
+
title: 'A choice is needed',
|
|
126
|
+
mes: 'Some message...',
|
|
127
|
+
ok_btn_text: 'Continue submit',
|
|
128
|
+
cancel_btn_text: 'Stop',
|
|
129
|
+
callback: result => {
|
|
130
|
+
if(result) {
|
|
131
|
+
alert('submit');
|
|
132
|
+
e.target.submit();
|
|
133
|
+
} else {
|
|
134
|
+
alert('submit canceled');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}, custom_defaults);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
}, false);
|
|
141
|
+
|
|
142
|
+
// Error on form submit
|
|
143
|
+
document.getElementById('test-form2').addEventListener('submit', e => {
|
|
144
|
+
let isError = true;
|
|
145
|
+
|
|
146
|
+
if(isError) {
|
|
147
|
+
e.preventDefault();
|
|
148
|
+
mAlert({
|
|
149
|
+
type : 'error',
|
|
150
|
+
title: 'Oh my! There is an error...',
|
|
151
|
+
mes: 'Some message'
|
|
152
|
+
}, custom_defaults);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
}, false);
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import * as styles from './slide-up-down-toggle.module.css';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @module slide-up-down-toggle
|
|
7
|
+
*
|
|
8
|
+
* Collapse/expand animation driven by CSS — JS only toggles a data attribute
|
|
9
|
+
* and manages a lightweight wrapper element. No pixel measurement anywhere.
|
|
10
|
+
*
|
|
11
|
+
* ─── How it works ─────────────────────────────────────────────────────────────
|
|
12
|
+
*
|
|
13
|
+
* The CSS module uses `interpolate-size: allow-keywords` (height: auto
|
|
14
|
+
* transitions), `allow-discrete` (display: none transition), and
|
|
15
|
+
* `@starting-style` (entry animation from display: none). See the CSS file
|
|
16
|
+
* for a full explanation of each feature.
|
|
17
|
+
*
|
|
18
|
+
* ─── wrap: true (default) ─────────────────────────────────────────────────────
|
|
19
|
+
*
|
|
20
|
+
* JS wraps the target in a generated <div class="slide"> and animates that
|
|
21
|
+
* wrapper. The target element is completely untouched and can have any
|
|
22
|
+
* display value, padding, margin, or flex/grid layout.
|
|
23
|
+
*
|
|
24
|
+
* // target is free to be flex, have padding, etc.
|
|
25
|
+
* slideToggle(document.querySelector('#panel'));
|
|
26
|
+
*
|
|
27
|
+
* Initial collapsed state — add data-slide="closed" to the TARGET in HTML
|
|
28
|
+
* before any JS call. The first call transfers it to the generated wrapper:
|
|
29
|
+
*
|
|
30
|
+
* <div id="panel" data-slide="closed">
|
|
31
|
+
* <div class="d-flex p-3">…content…</div>
|
|
32
|
+
* </div>
|
|
33
|
+
*
|
|
34
|
+
* ─── wrap: false ──────────────────────────────────────────────────────────────
|
|
35
|
+
*
|
|
36
|
+
* JS adds the .slide class directly to the target. The target must be a plain
|
|
37
|
+
* block container with no padding, flex or grid of its own. All layout must
|
|
38
|
+
* live on an inner child element.
|
|
39
|
+
*
|
|
40
|
+
* <!-- target: plain wrapper, no padding -->
|
|
41
|
+
* <div id="panel" data-slide="closed">
|
|
42
|
+
* <!-- inner: all layout here -->
|
|
43
|
+
* <div class="d-flex p-3">…content…</div>
|
|
44
|
+
* </div>
|
|
45
|
+
*
|
|
46
|
+
* slideToggle(document.querySelector('#panel'), { wrap: false });
|
|
47
|
+
*
|
|
48
|
+
* ─── Options ──────────────────────────────────────────────────────────────────
|
|
49
|
+
*
|
|
50
|
+
* @typedef {{ wrap?: boolean, duration?: number, callback?: (() => void) | null }} SlideOptions
|
|
51
|
+
*
|
|
52
|
+
* wrap {boolean} default true — auto-generate wrapper (see above)
|
|
53
|
+
* duration {number} ms — overrides --slide-duration CSS var;
|
|
54
|
+
* if omitted the CSS value is used
|
|
55
|
+
* callback {() => void|null} default null — invoked after the transition ends
|
|
56
|
+
*
|
|
57
|
+
* ─── Return value ─────────────────────────────────────────────────────────────
|
|
58
|
+
*
|
|
59
|
+
* All three functions return a Promise<void> that resolves when the transition ends.
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Default option values — single source of truth used in every slide function.
|
|
64
|
+
* @type {Required<SlideOptions>}
|
|
65
|
+
*/
|
|
66
|
+
const DEFAULT_OPTIONS = {
|
|
67
|
+
wrap: true,
|
|
68
|
+
duration: /** @type {any} */ (undefined),
|
|
69
|
+
callback: null,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Maps each target element to its slide element (generated wrapper or the
|
|
74
|
+
* target itself). Checked on every call so the setup runs only once per target.
|
|
75
|
+
* @type {WeakMap<HTMLElement, HTMLElement>}
|
|
76
|
+
*/
|
|
77
|
+
const registry = new WeakMap();
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Returns the slide element for `target`, creating it on the first call.
|
|
81
|
+
*
|
|
82
|
+
* wrap: true — inserts a <div class="slide"> before the target, moves the
|
|
83
|
+
* target inside it, and transfers any existing data-slide value.
|
|
84
|
+
* Resolves after a requestAnimationFrame so the browser paints
|
|
85
|
+
* the wrapper's initial state before any transition-triggering
|
|
86
|
+
* attribute change. (offsetHeight forces layout but not a paint —
|
|
87
|
+
* only rAF guarantees a rendered frame as the transition origin.)
|
|
88
|
+
* wrap: false — adds the .slide class directly to the target. Resolves immediately
|
|
89
|
+
* since the element was already in the DOM.
|
|
90
|
+
*
|
|
91
|
+
* @param {HTMLElement} target
|
|
92
|
+
* @param {boolean} wrap
|
|
93
|
+
* @returns {Promise<HTMLElement>} the element that carries .slide and data-slide
|
|
94
|
+
*/
|
|
95
|
+
async function getSlideEl(target, wrap) {
|
|
96
|
+
if (registry.has(target)) return /** @type {HTMLElement} */ (registry.get(target));
|
|
97
|
+
|
|
98
|
+
let el;
|
|
99
|
+
if (wrap) {
|
|
100
|
+
el = document.createElement('div');
|
|
101
|
+
el.classList.add(styles.slide);
|
|
102
|
+
// transfer only 'closed' state to the wrapper; 'open' is the default for
|
|
103
|
+
// visible elements and must NOT be set — it would trigger @starting-style
|
|
104
|
+
// on insertion, causing an unwanted height:0 entry animation
|
|
105
|
+
if ('slide' in target.dataset) {
|
|
106
|
+
if (target.dataset.slide === 'closed') el.dataset.slide = 'closed';
|
|
107
|
+
delete target.dataset.slide;
|
|
108
|
+
}
|
|
109
|
+
target.replaceWith(el);
|
|
110
|
+
el.appendChild(target);
|
|
111
|
+
// two nested rAFs are required: the outer one fires before the first paint
|
|
112
|
+
// (wrapper inserted but not yet rendered), the inner one fires after that
|
|
113
|
+
// paint — only then does the browser have a committed "previous style" to
|
|
114
|
+
// use as the transition origin. A single rAF or offsetHeight is not enough
|
|
115
|
+
// because both still execute before the first paint completes.
|
|
116
|
+
await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)));
|
|
117
|
+
} else {
|
|
118
|
+
// read computed display BEFORE adding .slide, which would alter it
|
|
119
|
+
const startsHidden = window.getComputedStyle(target).display === 'none';
|
|
120
|
+
target.classList.add(styles.slide);
|
|
121
|
+
el = target;
|
|
122
|
+
|
|
123
|
+
if (startsHidden && !('slide' in target.dataset)) {
|
|
124
|
+
// remove inline display: none so .slide and its [data-slide] rules own visibility;
|
|
125
|
+
// CSS-based display: none from other rules may still conflict — wrap: true avoids this
|
|
126
|
+
if (target.style.display === 'none') {
|
|
127
|
+
target.style.removeProperty('display');
|
|
128
|
+
}
|
|
129
|
+
target.dataset.slide = 'closed';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
registry.set(target, el);
|
|
134
|
+
return el;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Waits for the `height` transition on `el` to complete.
|
|
139
|
+
* Called AFTER the state attribute has been set so the transition is already
|
|
140
|
+
* running; transitionend fires at the end of the animation duration.
|
|
141
|
+
* Optionally sets --slide-duration to override the CSS default.
|
|
142
|
+
*
|
|
143
|
+
* @param {HTMLElement} el
|
|
144
|
+
* @param {number | undefined} duration
|
|
145
|
+
* @returns {Promise<void>}
|
|
146
|
+
*/
|
|
147
|
+
async function awaitHeight(el, duration) {
|
|
148
|
+
if (duration !== undefined) {
|
|
149
|
+
el.style.setProperty('--slide-duration', `${duration}ms`);
|
|
150
|
+
}
|
|
151
|
+
await /** @type {Promise<void>} */ (new Promise(resolve => {
|
|
152
|
+
/** @param {TransitionEvent} e */
|
|
153
|
+
const handler = (e) => {
|
|
154
|
+
if (e.propertyName === 'height') {
|
|
155
|
+
el.removeEventListener('transitionend', handler);
|
|
156
|
+
resolve();
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
el.addEventListener('transitionend', handler);
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Tears down the slide setup for `target`.
|
|
165
|
+
*
|
|
166
|
+
* wrap was true — removes the generated wrapper and restores the target
|
|
167
|
+
* to its original position in the DOM.
|
|
168
|
+
* wrap was false — removes the .slide class and the data-slide attribute
|
|
169
|
+
* from the target.
|
|
170
|
+
*
|
|
171
|
+
* In both cases --slide-duration is removed from the slide element.
|
|
172
|
+
*
|
|
173
|
+
* @param {HTMLElement} target
|
|
174
|
+
*/
|
|
175
|
+
export function disposeSliding(target) {
|
|
176
|
+
if (!registry.has(target)) return;
|
|
177
|
+
const el = /** @type {HTMLElement} */ (registry.get(target));
|
|
178
|
+
|
|
179
|
+
el.style.removeProperty('--slide-duration');
|
|
180
|
+
|
|
181
|
+
if (el !== target) {
|
|
182
|
+
el.replaceWith(target); // remove wrapper, put target back
|
|
183
|
+
} else {
|
|
184
|
+
target.classList.remove(styles.slide);
|
|
185
|
+
delete target.dataset.slide;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
registry.delete(target);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Collapses `target`.
|
|
193
|
+
* CSS animates height to 0, then snaps display to none.
|
|
194
|
+
*
|
|
195
|
+
* @param {HTMLElement} target
|
|
196
|
+
* @param {SlideOptions} [options]
|
|
197
|
+
* @returns {Promise<void>}
|
|
198
|
+
*/
|
|
199
|
+
export async function slideUp(target, options = {}) {
|
|
200
|
+
const { wrap, duration, callback } = { ...DEFAULT_OPTIONS, ...options };
|
|
201
|
+
const el = await getSlideEl(target, wrap);
|
|
202
|
+
el.dataset.slide = 'closed';
|
|
203
|
+
await awaitHeight(el, duration);
|
|
204
|
+
callback?.();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Expands `target`.
|
|
209
|
+
* display snaps to block, then CSS animates height from 0 (via @starting-style) to auto.
|
|
210
|
+
*
|
|
211
|
+
* @param {HTMLElement} target
|
|
212
|
+
* @param {SlideOptions} [options]
|
|
213
|
+
* @returns {Promise<void>}
|
|
214
|
+
*/
|
|
215
|
+
export async function slideDown(target, options = {}) {
|
|
216
|
+
const { wrap, duration, callback } = { ...DEFAULT_OPTIONS, ...options };
|
|
217
|
+
const el = await getSlideEl(target, wrap);
|
|
218
|
+
el.dataset.slide = 'open';
|
|
219
|
+
await awaitHeight(el, duration);
|
|
220
|
+
callback?.();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Toggles the collapsed/expanded state of `target`.
|
|
225
|
+
* Reads the current data-slide value on the slide element to decide direction.
|
|
226
|
+
*
|
|
227
|
+
* @param {HTMLElement} target
|
|
228
|
+
* @param {SlideOptions} [options]
|
|
229
|
+
* @returns {Promise<void>}
|
|
230
|
+
*/
|
|
231
|
+
export async function slideToggle(target, options = {}) {
|
|
232
|
+
const { wrap } = { ...DEFAULT_OPTIONS, ...options };
|
|
233
|
+
const el = await getSlideEl(target, wrap);
|
|
234
|
+
return el.dataset.slide === 'closed'
|
|
235
|
+
? slideDown(target, options)
|
|
236
|
+
: slideUp(target, options);
|
|
237
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* CSS Module for slide-up-down-toggle.js
|
|
3
|
+
*
|
|
4
|
+
* Three modern CSS features work together to animate height without JS measurement:
|
|
5
|
+
*
|
|
6
|
+
* interpolate-size: allow-keywords
|
|
7
|
+
* Enables transitioning to/from `height: auto` — the browser resolves the
|
|
8
|
+
* intrinsic size, so JS never reads offsetHeight or scrollHeight.
|
|
9
|
+
*
|
|
10
|
+
* transition-behavior: allow-discrete (via `allow-discrete` in shorthand)
|
|
11
|
+
* Allows `display` to participate in a transition. On collapse: height
|
|
12
|
+
* animates to 0, then `display: none` snaps at the very end. On expand:
|
|
13
|
+
* `display: block` snaps at the very start, then height animates to auto.
|
|
14
|
+
*
|
|
15
|
+
* @starting-style
|
|
16
|
+
* Defines the initial state for elements entering from `display: none`.
|
|
17
|
+
* Without it, the browser has no "before" value to transition from and
|
|
18
|
+
* the expand animation would not play.
|
|
19
|
+
*
|
|
20
|
+
* ─── Usage ────────────────────────────────────────────────────────────────────
|
|
21
|
+
*
|
|
22
|
+
* wrap: true (default)
|
|
23
|
+
* JS generates a wrapper <div class="slide"> around the target.
|
|
24
|
+
* The target element is completely free — any display, padding, margin.
|
|
25
|
+
* No changes to HTML required; initial open state is the default.
|
|
26
|
+
* For an element that should start collapsed, add data-slide="closed" to
|
|
27
|
+
* the target BEFORE calling any slide function — JS transfers it to the wrapper.
|
|
28
|
+
*
|
|
29
|
+
* wrap: false
|
|
30
|
+
* JS adds .slide directly to the target element.
|
|
31
|
+
* The target must be a plain block wrapper (no padding, no flex/grid).
|
|
32
|
+
* All layout, padding and margin must live on an inner child element.
|
|
33
|
+
* Initial collapsed state: add data-slide="closed" directly to the target in HTML.
|
|
34
|
+
*
|
|
35
|
+
* ─── FOUC prevention ──────────────────────────────────────────────────────────
|
|
36
|
+
*
|
|
37
|
+
* Elements with data-slide="closed" are hidden via a :global rule below,
|
|
38
|
+
* so they start invisible even before JS adds the .slide class.
|
|
39
|
+
*
|
|
40
|
+
* ─── Duration override ────────────────────────────────────────────────────────
|
|
41
|
+
*
|
|
42
|
+
* Set --slide-duration on the element or pass `duration` in JS options:
|
|
43
|
+
* style="--slide-duration: 300ms"
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/* applied to the animation wrapper (generated or the target itself) */
|
|
47
|
+
.slide {
|
|
48
|
+
interpolate-size: allow-keywords;
|
|
49
|
+
/*
|
|
50
|
+
* !important overrides any external display: none (inline or CSS rule without !important).
|
|
51
|
+
* .slide[data-slide='closed'] below has higher specificity (0,2,0 vs 0,1,0) so its
|
|
52
|
+
* display: none !important still wins when the element is collapsed.
|
|
53
|
+
*/
|
|
54
|
+
/* stylelint-disable-next-line declaration-no-important */
|
|
55
|
+
display: block !important;
|
|
56
|
+
height: auto;
|
|
57
|
+
overflow: hidden;
|
|
58
|
+
transition:
|
|
59
|
+
height var(--slide-duration, 500ms) ease,
|
|
60
|
+
display var(--slide-duration, 500ms) allow-discrete;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* collapsed state: height animates to 0, then display snaps to none */
|
|
64
|
+
.slide[data-slide='closed'] {
|
|
65
|
+
/* stylelint-disable-next-line declaration-no-important */
|
|
66
|
+
display: none !important;
|
|
67
|
+
height: 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/*
|
|
71
|
+
* Entry state for the expand transition.
|
|
72
|
+
* When .slide transitions from display:none → display:block, the browser needs
|
|
73
|
+
* a starting value for height. @starting-style provides height:0 so the
|
|
74
|
+
* animation plays from zero to auto instead of snapping instantly.
|
|
75
|
+
*/
|
|
76
|
+
/*
|
|
77
|
+
* @starting-style targets only [data-slide='open'] — not :not([data-slide='closed']).
|
|
78
|
+
* The broader selector would also match newly inserted wrappers (which have no
|
|
79
|
+
* data-slide attribute), causing an unwanted entry animation from height:0 that
|
|
80
|
+
* conflicts with the first collapse call and makes the transition invisible.
|
|
81
|
+
* Using [data-slide='open'] restricts this to the expand animation triggered by
|
|
82
|
+
* slideDown(), which always sets data-slide="open" on a previously hidden element.
|
|
83
|
+
*/
|
|
84
|
+
@starting-style {
|
|
85
|
+
.slide[data-slide='open'] {
|
|
86
|
+
height: 0;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/*
|
|
91
|
+
* FOUC prevention — global, no class required.
|
|
92
|
+
* Hides elements with data-slide="closed" before JS has run and added .slide.
|
|
93
|
+
* Uses :global so CSS Modules does not hash this selector.
|
|
94
|
+
*/
|
|
95
|
+
:global([data-slide='closed']) {
|
|
96
|
+
display: none;
|
|
97
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Unsplash page
|
|
2
|
+
|
|
3
|
+
> This repository will soon be integrated with [minimo](https://github.com/massimo-cassandro/minimo)
|
|
4
|
+
|
|
5
|
+
**demo:** <https://massimo-cassandro.github.io/unsplash-page/demo>
|
|
6
|
+
|
|
7
|
+
Single page with random photo from [Unsplash](https://unsplash.com/) to display some messages (useful for error pages).
|
|
8
|
+
|
|
9
|
+
This component comes from some experiments I did with the Unsplash API. The initial purpose was to display more appealing error pages, but you can use it for any use.
|
|
10
|
+
|
|
11
|
+
For best results, it is advisable to display images from a photo collection prepared on Unsplash. The demos in this repository use my collection ["World"](https://unsplash.com/collections/3660951/world).
|
|
12
|
+
|
|
13
|
+
You need to create a server script to retrieve the JSON data of a random image from Unsplash (see [Get a Random Photo](https://unsplash.com/documentation#get-a-random-photo) on Unsplash API docs).
|
|
14
|
+
|
|
15
|
+
The script also implements [BlurHash](https://blurha.sh/), to show a placeholder of the image until it loads.
|
|
16
|
+
|
|
17
|
+
For more details and examples take a look at:
|
|
18
|
+
|
|
19
|
+
* <https://unsplash.com/developers>
|
|
20
|
+
* [A Random Image Slideshow With Unsplash and React](https://betterprogramming.pub/a-random-image-slideshow-with-unsplash-and-react-1b6aee698652)
|
|
21
|
+
* [Unsplash Random Photo 1](https://github.com/massimo-cassandro/area-test/tree/main/2023-03-unsplash-random-photo-1)
|
|
22
|
+
* [Random Unsplash Photos Slideshow](https://github.com/massimo-cassandro/area-test/tree/main/2023-05-unsplash-random-photo-2)
|
|
23
|
+
* [A Split Image Effect in React](https://medium.com/better-programming/a-split-image-effect-in-react-beb2baa3fe5f) and [split image](https://github.com/massimo-cassandro/area-test/tree/main/2023-07-split-image)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
The SVG icons used in the script are from [Phosphor Icon](https://phosphoricons.com/). At the moment it is not possible to use different ones without modifying the source code.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
Install from NPM:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm i -S @massimo-cassandro/unsplash-page blurhash
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Fro info about **blurhash**: <https://www.npmjs.com/package/blurhash>.
|
|
37
|
+
|
|
38
|
+
## Running a local demo
|
|
39
|
+
|
|
40
|
+
Launch a php server (php needed for data proxy), then go to the `demo` dir:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
php -S localhost:8000
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
and then open `http://localhost:8000/demo/`.
|
|
47
|
+
|
|
48
|
+
## Use
|
|
49
|
+
|
|
50
|
+
Demo: <https://massimo-cassandro.github.io/unsplash-page/demo/vanilla-js/index.html>
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
Add to your script:
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
import { unsplashPageJS } from '@massimo-cassandro/unsplash-page';
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
unsplashPageJS({
|
|
60
|
+
targetElement : document.getElementById('root'),
|
|
61
|
+
className : 'class',
|
|
62
|
+
unsplashDataUrl : 'url',
|
|
63
|
+
utmSource : 'source',
|
|
64
|
+
title : 'Message title',
|
|
65
|
+
text : 'This is the message <strong>text</strong>',
|
|
66
|
+
backLink : '<a href="link/to/home">Back to home</a>',
|
|
67
|
+
hidePhotoLink : true,
|
|
68
|
+
cssModules : false,
|
|
69
|
+
cssModulesObj : null
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
where:
|
|
74
|
+
|
|
75
|
+
* `className`, `unsplashDataUrl`, `utmSource` and `hidePhotoLink` correspond exactly to what is described in the React version
|
|
76
|
+
* `title`, `text` and `backLink` are like the previous ones, but in this case they are always strings
|
|
77
|
+
* `targetElement`: The DOM element in which to insert the necessary markup
|
|
78
|
+
* `cssModules`: Set to true if your bundler uses CSS Modules (default `false`)
|
|
79
|
+
* `cssModulesObj`: If you are using CSS Modules, assign to these parameter the imported styles object (see below).
|
|
80
|
+
|
|
81
|
+
### CSS
|
|
82
|
+
The JS file does not include any CSS, to maintain maximum flexibility of use.
|
|
83
|
+
|
|
84
|
+
Then you can plug your CSS into the html (like I did in the demo) or manage it with Rollup, Webpack etc.
|
|
85
|
+
|
|
86
|
+
If you are using CSS modules, you need to share the parsed CSS object with the script so that the processed class names can be used:
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
import styles from '@massimo-cassandro/unsplash-page/src/unsplash-page.module.css';
|
|
91
|
+
|
|
92
|
+
unsplashPageJS({
|
|
93
|
+
/* ... */
|
|
94
|
+
cssModules : true,
|
|
95
|
+
cssModulesObj : styles
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### SVG icons
|
|
100
|
+
|
|
101
|
+
SVG icons are imported into the script, both in the React and Vanilla JS versions.
|
|
102
|
+
|
|
103
|
+
In the latter, they need to be managed by your bundler to ensure they load correctly. In the demo, I used [@rollup/plugin-image](https://www.npmjs.com/package/@rollup/plugin-image) to import them as base64. Since they are very small, this seems to be the simplest solution.
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
## CSS customization
|
|
107
|
+
|
|
108
|
+
You can customize the CSS (fonts, colors, etc.) using some CSS custom properties.
|
|
109
|
+
|
|
110
|
+
You need to provide them as a separate CSS file to change the default settings.
|
|
111
|
+
|
|
112
|
+
Below you can see the list of properties set with their default values: simply reset the ones you want to change and ignore the others.
|
|
113
|
+
|
|
114
|
+
You can do this by setting variables in the `:root` element, or you can narrow their scope to the component container by assigning it a class (thru the `className` parameter) and then using it in you CSS.
|
|
115
|
+
|
|
116
|
+
For a complete list of all custom properties see `src/unsplash-page.modules.css`;
|
|
117
|
+
|
|
118
|
+
```css
|
|
119
|
+
/* needed if your script doesn't embed the css file */
|
|
120
|
+
@import '@massimo-cassandro/unsplash-page/src/unsplash-page.module.css';
|
|
121
|
+
|
|
122
|
+
.your-custom-class { /* or :root */
|
|
123
|
+
|
|
124
|
+
/* your css custom props */
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Using http error codes
|
|
129
|
+
|
|
130
|
+
Using HTTP Error Codes from your server can be a clever way to use this component.
|
|
131
|
+
|
|
132
|
+
First, get the error code from your server (404, 500, etc.), for example by setting it as a `data` attribute somewhere, then pass it to the script that implements UnsplashPage.
|
|
133
|
+
Matching the errorCode with an object of predefinited titles and text, can do the trick:
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
```javascript
|
|
137
|
+
const myErrorCode = +document.body.dataset.errorCode || 500;
|
|
138
|
+
|
|
139
|
+
const errorTypes = {
|
|
140
|
+
403: {
|
|
141
|
+
title: 'Oops… You can’t access this page',
|
|
142
|
+
// title: 'Ops… Non puoi accedere a questa pagina',
|
|
143
|
+
text: null
|
|
144
|
+
},
|
|
145
|
+
404: {
|
|
146
|
+
title: 'Oops… This page does not exist',
|
|
147
|
+
// title: 'Ops… Questa pagina non esiste',
|
|
148
|
+
text: null
|
|
149
|
+
},
|
|
150
|
+
500: {
|
|
151
|
+
title: 'Oops… An error has occurred',
|
|
152
|
+
// title: 'Ops… Si è verificato un errore',
|
|
153
|
+
text: null
|
|
154
|
+
},
|
|
155
|
+
503: {
|
|
156
|
+
title: 'Sorry, this site is temporarily under maintenance',
|
|
157
|
+
// title: 'Siamo spiacenti, il sito è temporaneamente in manutenzione',
|
|
158
|
+
text: null
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
unsplashPageJS({
|
|
163
|
+
/* ... */
|
|
164
|
+
title: errorTypes[myErrorCode].title,
|
|
165
|
+
text: errorTypes[myErrorCode].text,
|
|
166
|
+
backLink: myErrorCode !== 503? <a href="link/to/home">Back to home</a> : null
|
|
167
|
+
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Use with React
|
|
172
|
+
The previous version of UP contained a React version of the script, but, to simplify maintenance and achieve maximum flexibility of use, I now prefer to use a React wrapper of the Vanilla JS script: you can find a template of the file in the `react` folder.
|
|
173
|
+
|
|
174
|
+
The sample uses [React Router](https://reactrouter.com) to display error messages based on server results.
|
|
175
|
+
|
|
176
|
+
In the same folder, you can find the previous, unmantained, React version,
|
|
177
|
+
|
|
178
|
+
## Images breakpoints & sizes
|
|
179
|
+
|
|
180
|
+
Images are uploaded at different sizes using the [Unsplash API](https://unsplash.com/documentation#dynamically-resizable-images). Other than a few differences, CSS media query breakpoints are the same as those in [Bootstrap 5](https://getbootstrap.com/docs/5.3/layout/breakpoints/).
|
|
181
|
+
|
|
182
|
+
The following table reports the image size for each breakpoint:
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
| BS5 breakpoint | xs | sm | md | lg | xl | xxl |
|
|
186
|
+
|------------------------------|:-----:|:--------:|:---------:|:----------:|:-----------:|:------:|
|
|
187
|
+
| Page width | < 576 | 576 -767 | 768 - 991 | 992 - 1199 | 1200 - 1399 | >=1400 |
|
|
188
|
+
| img width | 576 | 768 | 992 | 1200 | 1400 | 1920 |
|
|
189
|
+
| img height (16/9) | 324 | 432 | 558 | 675 | 788 | 1080 |
|
|
190
|
+
| img height (4/3) | 432 | 576 | 744 | 900 | 1050 | 1440 |
|
|
191
|
+
| img height (9/21, portrait) | 1344 | | | | | |
|
|
192
|
+
| img height (21/9, landscape) | 247 | | | | | |
|
|
193
|
+
|
|
194
|
+
Sizes and breakpoints are defined in the `img-breakpoints.js` file.
|