@massimo-cassandro/minimo 0.10.11 → 0.11.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.
Files changed (32) hide show
  1. package/design-tokens/components/snackbar.minimo.tokens.jsonc +106 -12
  2. package/design-tokens/shadows.minimo.tokens.jsonc +19 -0
  3. package/design-tokens/z-index.minimo.tokens.jsonc +4 -0
  4. package/design-tokens-src/_readme.md +1 -1
  5. package/design-tokens-src/badge.minimo.tokens.mjs +36 -0
  6. package/design-tokens-src/btn-close.minimo.tokens.mjs +101 -0
  7. package/design-tokens-src/components/snackbar.minimo.tokens.mjs +113 -21
  8. package/design-tokens-src/flex-grid.minimo.tokens.mjs +18 -0
  9. package/design-tokens-src/shadows.minimo.tokens.mjs +16 -0
  10. package/design-tokens-src/z-index.minimo.tokens.mjs +14 -0
  11. package/design-tokens-utilities/build-tokens-src/transforms.mjs +2 -1
  12. package/index.js +10 -0
  13. package/package.json +2 -11
  14. package/src/components/dismiss-alerts.js +7 -6
  15. package/src/components/form-reset-submit.js +38 -0
  16. package/src/components/inner-nav/inner-nav.js +11 -8
  17. package/src/components/snackbar/snackbar.js +56 -62
  18. package/src/components/snackbar/snackbar.module.css +79 -49
  19. package/src/css/buttons/btn-close.css +3 -2
  20. package/src/css/forms/forms.css +1 -0
  21. package/src/custom-properties.css +22 -4
  22. package/src/web-components/s-datatable-component/docs.md +187 -0
  23. package/src/web-components/s-datatable-component/s-datatable-component.js +517 -0
  24. package/src/web-components/{simple-datatable/simple-datatable.module.css → s-datatable-component/s-datatable-component.module.css} +1 -1
  25. package/src/web-components/{simple-datatable → s-datatable-component}/src/parse-cols.js +1 -1
  26. package/design-tokens-src/badge.minimo.tokens.jsonc +0 -36
  27. package/design-tokens-src/btn-close.minimo.tokens.jsonc +0 -101
  28. package/design-tokens-src/flex-grid.minimo.tokens.jsonc +0 -18
  29. package/design-tokens-src/gradients-shadowes.minimo.tokens.jsonc +0 -4
  30. package/design-tokens-src/z-index.minimo.tokens.jsonc +0 -8
  31. package/src/components/forms.js +0 -36
  32. package/src/web-components/simple-datatable/simple-datatable.js +0 -218
@@ -12,94 +12,88 @@ References
12
12
 
13
13
  */
14
14
 
15
+ // TODO snackbar action
16
+ // TODO gestione snackbar multiple (NO stacking)
17
+
18
+
15
19
  export function snackbar(message, options = {}){
16
20
 
17
21
  options = {
18
22
 
19
23
  status : null, // danger, warning, info, success
20
- duration : 4000, // ms or false/null to avoid autoclose
24
+ duration : 6000, // ms or false/null to avoid autoclose
21
25
  close_btn : true, // true | false
22
26
 
23
- // TODO snackbar action
24
27
  action : null, // null or function
25
28
  action_text : null, // null or string
26
29
 
27
30
  ...options
28
31
  };
29
32
 
30
- domBuilder([
33
+ let _popover, _timeoutID;
34
+
35
+ const removePopover = () => {
36
+ if (_popover?.matches(':popover-open')) {
37
+ if (_timeoutID) clearTimeout(_timeoutID);
38
+
39
+ _popover.classList.add(styles.isHiding);
40
+
41
+ _popover.addEventListener('animationend', () => {
42
+ _popover.hidePopover();
43
+ _popover.remove();
44
+ }, { once: true });
45
+ }
46
+ };
47
+
48
+ _popover = domBuilder([
31
49
  {
32
- className: classnames(styles.snackbar, options.status? styles[`status-${options.status}`] : null),
50
+ className: classnames(styles.snackbarOuter, options.status? styles[`status-${options.status}`] : null),
33
51
  attrs: {
34
52
  popover: 'manual',
35
- role: options.action? 'alert' : 'status'
53
+ role: (options.action || ['danger','warning'].includes(options.status)) ? 'alert' : 'status'
36
54
  },
37
55
  children: [
38
56
  {
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?? '_action-text_',
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
- },
57
+ className: styles.snackbar,
58
+ children: [
59
+ {
60
+ // className: styles.text,
61
+ content: message
62
+ },
63
+ {
64
+ condition: options.action != null && (typeof options.action === 'function'),
65
+ tag: 'button',
66
+ attrs: {
67
+ type: 'button',
68
+ },
69
+ className: classnames('btn-reset', styles.action),
70
+ content: options.action_text?? '_action-text_',
71
+ },
72
+ {
73
+ tag: 'button',
74
+ className: classnames('btn-close', styles.closeBtn),
75
+ attrs: {
76
+ type: 'button',
77
+ },
78
+ callback: el => el.addEventListener('click', () => removePopover())
79
+ }
80
+ ]
58
81
  }
59
82
  ]
60
83
  }
61
84
  ], document.body);
62
85
 
63
- }
64
-
65
- /*
66
- export const NotificationCore = {
67
-
68
- createNotificationElement(message, options = {}) {
69
-
86
+ _popover.showPopover();
70
87
 
71
88
 
72
- const show = () => {
73
- document.body.appendChild(el);
74
- el.showPopover();
75
- setTimeout(() => hide(), duration);
76
- };
89
+ if(options.duration) {
90
+ _timeoutID = setTimeout(removePopover, options.duration);
77
91
 
78
- const hide = () => {
79
- if (el.matches(':popover-open')) {
80
- el.hidePopover();
81
- el.remove(); // Pulizia del DOM
82
- }
83
- };
92
+ // mette in pausa il timeout sull'hover
93
+ _popover.addEventListener('mouseenter', () => clearTimeout(_timeoutID));
94
+ _popover.addEventListener('mouseleave', () => {
95
+ _timeoutID = setTimeout(removePopover, options.duration);
96
+ });
84
97
 
85
- return { el, show, hide };
86
98
  }
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
- */
99
+ }
@@ -1,75 +1,105 @@
1
1
  /* purgecss start ignore */
2
2
 
3
- .snackbar {
3
+ .snackbarOuter {
4
+ position: fixed;
5
+ inset-block: auto var(--size-md);
6
+ /* inset-inline: 0 auto; */
7
+ z-index: var(--z-index-snackbar);
8
+ inline-size: 20dvw;
9
+ min-inline-size: min(100%, 37ch);
10
+ padding: var(--snackbar-outer-padding-block) var(--snackbar-outer-padding-inline);
11
+ margin-inline: auto;
12
+ background-color: var(--snackbar-outer-background-color);
13
+ border: var(--snackbar-outer-border);
14
+ border-radius: var(--snackbar-outer-border-radius);
15
+ box-shadow: var(--snackbar-outer-drop-shadow);
16
+ animation: slide-in .3s cubic-bezier(.34, 1.56, .64, 1) forwards;
17
+
18
+ &.isHiding {
19
+ animation: slide-out .25s ease-in forwards;
20
+ }
4
21
 
5
22
  }
6
23
 
7
- .text {}
8
- .action {}
9
- .closeBtn {}
24
+ .snackbar {
25
+ display: flex;
26
+ gap: var(--size-xs);
27
+ align-items: center;
28
+ justify-content: space-between;
29
+ padding: var(--size-xs) var(--size-sm);
30
+ font-size: var(--snackbar-font-size);
31
+ color: var(--snackbar-text-color);
32
+ background-color: var(--snackbar-background-color);
33
+ border-radius: var(--snackbar-border-radius);
34
+
35
+ &::backdrop {
36
+ display: none;
37
+ }
10
38
 
39
+ * {
40
+ overflow-wrap: break-word;
41
+ }
42
+ }
11
43
 
12
- /* purgecss end ignore */
44
+ /* .text {} */
13
45
 
46
+ .action {
47
+ margin-inline-start: auto;
48
+ }
49
+ .closeBtn {
50
+ --_icon-bar-width: var(--snackbar-btn-close-size); /* --_icon-bar-width is defined in buttons/btn-close.css */
14
51
 
15
- /*
52
+ --btn-close-background-color: var(--snackbar-btn-close-background-color);
53
+ --btn-close-icon-color: var(--snackbar-btn-close-icon-color);
54
+ --btn-close-icon-bar-size: 2px;
16
55
 
17
56
 
57
+ --btn-close-hover-background-color: var(--snackbar-btn-close-hover-background-color);
58
+ --btn-close-hover-border-color: var(--snackbar-btn-close-hover-border-color);
59
+ --btn-close-hover-icon-color: var(--snackbar-btn-close-hover-icon-color);
18
60
 
61
+ --btn-close-focus-outline-color: var(--snackbar-btn-close-focus-outline-color);
62
+ --btn-close-focus-outline-offset: var(--snackbar-btn-close-focus-outline-offset);
63
+ --btn-close-focus-outline-width: var(--snackbar-btn-close-focus-outline-width);
19
64
 
65
+ --btn-close-active-background-color: var(--snackbar-btn-close-active-background-color);
66
+ --btn-close-active-icon-color: var(--snackbar-btn-close-active-icon-color);
20
67
 
21
- .toastWrapper {
22
- position: fixed;
23
- inset-block-end: 0;
24
- inset-inline: 0;
25
- z-index: 1;
26
68
 
27
- display: grid;
28
- gap: 1vh;
29
- justify-content: center;
30
- justify-items: stretch;
31
- padding-block-end: 5vh;
69
+ border: none;
32
70
 
33
- pointer-events: none;
71
+ &:active {
72
+ scale: .6;
73
+ }
34
74
  }
35
75
 
36
- .toast {
37
- --_bg-lightness: 90%;
38
- --_travel-distance: 0;
39
-
40
- max-inline-size: min(25ch, 90vw);
41
- padding-block: .5ch;
42
- padding-inline: 1ch;
43
-
44
- font-size: var(--toast-font-size);
45
- color: var(--toast-text-color);
46
- background: hsl(0 0% var(--_bg-lightness) / 90%);
47
- border-radius: var(--toast-border-radius);
48
- animation:
49
- fade-in .3s ease,
50
- slide-in .3s ease,
51
- fade-out .3s ease var(--toast-duration);
52
-
53
- will-change: transform;
54
-
55
- @media (--dark) {
56
- --_bg-lightness: 20%;
76
+ @keyframes slide-in {
77
+ from {
78
+ opacity: 0;
79
+ translate: 0 calc(100% + var(--size-md));
57
80
  }
58
-
59
- @media (--motionOK) {
60
- --_travel-distance: 5vh;
81
+ to {
82
+ opacity: 1;
83
+ translate: 0 0;
61
84
  }
62
85
  }
63
86
 
64
- @keyframes fade-in {
65
- from { opacity: 0; }
87
+ @keyframes slide-out {
88
+ from {
89
+ opacity: 1;
90
+ translate: 0 0;
91
+ }
92
+ to {
93
+ opacity: 0;
94
+ translate: 0 calc(100% + var(--size-md));
95
+ }
66
96
  }
67
97
 
68
- @keyframes fade-out {
69
- to { opacity: 0; }
98
+ @media (prefers-reduced-motion: reduce) {
99
+ .snackbarOuter,
100
+ .snackbarOuter.isHiding {
101
+ animation: none;
102
+ }
70
103
  }
71
104
 
72
- @keyframes slide-in {
73
- from { transform: translateY(var(--_travel-distance, 10px)); }
74
- }
75
- */
105
+ /* purgecss end ignore */
@@ -24,14 +24,15 @@
24
24
  &::before, &::after {
25
25
  position: absolute;
26
26
  top: 50%;
27
- left: 0; /* calc(var(--btn-close-icon-bar-size) / 2); */
27
+ left: 50%;
28
28
  display: block;
29
29
  width: var(--_icon-bar-width);
30
30
  height: var(--btn-close-icon-bar-size);
31
31
  content: '';
32
32
  background: currentColor;
33
33
  transform-origin: center;
34
- translate: var(--btn-close-padding) -50%;
34
+ /* translate: var(--btn-close-padding) -50%; */
35
+ translate: -50% -50%;
35
36
  }
36
37
  &::before {
37
38
  rotate: 45deg;
@@ -3,6 +3,7 @@
3
3
  /* TODO multiselect */
4
4
  /* TODO classe grid specifica per i form ?? (.form-grid?) */
5
5
  /* TODO customizable select */
6
+ /* TODO form-check: rifare come flex, separare? */
6
7
 
7
8
  .form-group:has([required]):not(:has([disabled])) {
8
9
 
@@ -12,13 +12,14 @@
12
12
  --badge-lg-font-weight: var(--badge-font-weight);
13
13
  --body-background-color: #fff;
14
14
  --body-text: var(--text-color);
15
+ --box-shadow-base: 0 0 .75rem 0 rgb(0 0 0 / .25);
15
16
  --btn-border-radius: var(--form-control-border-radius);
16
17
  --btn-border-width: var(--form-control-border-width);
17
18
  --btn-close-active-background-color: var(--btn-close-background-color);
18
19
  --btn-close-active-icon-color: var(--link-active-color);
19
20
  --btn-close-background-color: var(--body-background-color);
20
21
  --btn-close-border-color: var(--btn-close-icon-color);
21
- --btn-close-border-radius: 3.125rem;
22
+ --btn-close-border-radius: 50%;
22
23
  --btn-close-border-width: .125rem;
23
24
  --btn-close-focus-outline-color: var(--link-focus-outline-color);
24
25
  --btn-close-focus-outline-offset: .125rem;
@@ -227,10 +228,26 @@
227
228
  --size-xl: 2rem;
228
229
  --size-xs: .5rem;
229
230
  --size-xxs: .25rem;
230
- --snackbar-border-radius: var(--radius-sm);
231
- --snackbar-duration: 5s;
231
+ --snackbar-background-color: var(--secondary-100);
232
+ --snackbar-border-radius: var(--radius-xs);
233
+ --snackbar-btn-close-active-background-color: transparent;
234
+ --snackbar-btn-close-active-icon-color: var(--snackbar-text-color);
235
+ --snackbar-btn-close-focus-outline-color: color-mix(in srgb, var(--snackbar-text-color) 50%, transparent);
236
+ --snackbar-btn-close-focus-outline-offset: .125rem;
237
+ --snackbar-btn-close-focus-outline-width: .125rem;
238
+ --snackbar-btn-close-hover-background-color: color-mix(in srgb, var(--snackbar-text-color) 40%, transparent);
239
+ --snackbar-btn-close-hover-border-color: transparent;
240
+ --snackbar-btn-close-hover-icon-color: var(--snackbar-background-color);
241
+ --snackbar-btn-close-icon-color: var(--snackbar-text-color);
242
+ --snackbar-btn-close-size: 1rem;
232
243
  --snackbar-font-size: var(--font-size-sm);
233
- --snackbar-text-color: var(--text-color);
244
+ --snackbar-outer-background-color: var(--secondary-100);
245
+ --snackbar-outer-border: none;
246
+ --snackbar-outer-border-radius: var(--radius-xs);
247
+ --snackbar-outer-drop-shadow: var(--box-shadow-base);
248
+ --snackbar-outer-padding-block: 0;
249
+ --snackbar-outer-padding-inline: 0;
250
+ --snackbar-text-color: var(--secondary-100-fg);
234
251
  --spinner-color: var(--primary-100);
235
252
  --spinner-size: var(--size-lg);
236
253
  --spinner-stroke-width: .125rem;
@@ -266,4 +283,5 @@
266
283
  --text-selected-background-color: #ddd;
267
284
  --text-selected-color: var(--body-text);
268
285
  --z-index-overlay: 99;
286
+ --z-index-snackbar: 100;
269
287
  }
@@ -0,0 +1,187 @@
1
+ # `s-datatable` web-component for simple-datatatble
2
+
3
+ </https://fiduswriter.github.io/simple-datatables/documentation/>
4
+
5
+
6
+ ## Utilizzo da markup
7
+
8
+ ```html
9
+ <s-datatable
10
+ json="/api/data.json"
11
+ cols='[{"_heading":"Nome","_field":"name"}, ...]'
12
+ perPage="10"
13
+ ></s-datatable>
14
+
15
+ <!-- Alternativa: dati inline via attributo (utile per SSR/Twig/Blade).
16
+ Se presenti entrambi, `data` ha la precedenza su `json`. -->
17
+ <s-datatable
18
+ data='[{"name":"Mario","city":"Roma"}, ...]'
19
+ cols='[{"_heading":"Nome","_field":"name"}, ...]'
20
+ perPage="10"
21
+ ></s-datatable>
22
+ ```
23
+
24
+ ## Utilizzo da script
25
+
26
+ ```javascript
27
+ const el = document.querySelector('s-datatable');
28
+ // oppure: const el = document.createElement('s-datatable');
29
+
30
+ el.init({
31
+ json: '/api/data.json',
32
+ cols: [{ _heading: 'Nome', _field: 'name' }, ...],
33
+ perPage: 10
34
+ });
35
+
36
+ // Alternativa: dati inline (array di oggetti) al posto di json
37
+ el.init({
38
+ data: [{ name: 'Mario', city: 'Roma' }, ...],
39
+ cols: [{ _heading: 'Nome', _field: 'name' }, ...],
40
+ });
41
+ // Se presenti entrambi, `data` ha la precedenza su `json`.
42
+
43
+ // Se appena creato, appendere dopo init():
44
+ document.body.appendChild(el);
45
+ ```
46
+
47
+ ## Utilizzo misto – attributi HTML + script
48
+
49
+ I parametri non inclusi in init() vengono letti dall'attributo HTML corrispondente. Si possono quindi separare configurazione statica (`cols`, `perPage`) nel markup e dati dinamici (json) nello script:
50
+
51
+ ```html
52
+ <s-datatable
53
+ id="table-utenti"
54
+ cols='[{"_heading":"Nome","_field":"name"},{"_heading":"Città","_field":"city"}]'
55
+ perPage="20"
56
+ ></s-datatable>
57
+ ```
58
+
59
+ ```javascript
60
+ el.init({ json: '/api/utenti.json' }); // → json da script, cols e perPage da attributo
61
+ ```
62
+
63
+ Ordine di precedenza: config (script) > attributo HTML > default interno.
64
+
65
+ Per ignorare esplicitamente un attributo e ricadere sul default, passare `null` nel config:
66
+
67
+ ```javascript
68
+ el.init({ json: '/api/data.json', cols: null }); // → cols = null, l'attributo viene saltato
69
+ ```
70
+
71
+ ## Reazione ad eventi esterni
72
+
73
+ Il componente ascolta tre CustomEvent che altri componenti possono emettere sul `document` (o su qualsiasi antenato del componente nel DOM):
74
+
75
+ ### `datatable:search`
76
+
77
+ Applica una ricerca su una o più colonne.
78
+
79
+ `detail: { term: string, columns?: number[] }`
80
+
81
+ Esempio – ricerca globale:
82
+
83
+ ```javascript
84
+ document.dispatchEvent(new CustomEvent('datatable:search', {
85
+ detail: { term: 'mario' }
86
+ }));
87
+ ```
88
+
89
+ Esempio – ricerca su colonna 0 e 2:
90
+
91
+ ```javascript
92
+ document.dispatchEvent(new CustomEvent('datatable:search', {
93
+ detail: { term: 'mario', columns: [0, 2] }
94
+ }));
95
+ ```
96
+
97
+ ### `datatable:multisearch`
98
+
99
+ Applica più filtri contemporaneamente (uno per colonna).
100
+
101
+ `detail: { queries: Array<{ terms: string[], columns?: number[] }> }`
102
+
103
+ Esempio – filtra colonna 1 per "mario" E colonna 3 per "roma":
104
+
105
+ ```javascript
106
+ document.dispatchEvent(new CustomEvent('datatable:multisearch', {
107
+ detail: {
108
+ queries: [
109
+ { terms: ['gino'], columns: [1] },
110
+ { terms: ['roma'], columns: [3] }
111
+ ]
112
+ }
113
+ }));
114
+ ```
115
+
116
+ ### `datatable:reload`
117
+
118
+ Ricarica i dati da un nuovo URL JSON (o dallo stesso URL originale se json è omesso), opzionalmente con nuove colonne o dati inline.
119
+
120
+ `detail: { json?: string, data?: Array, cols?: Array }`
121
+
122
+ Esempio – reload stesso URL:
123
+
124
+ ```javascript
125
+ document.dispatchEvent(new CustomEvent('datatable:reload', {
126
+ detail: {}
127
+ }));
128
+ ```
129
+
130
+ Esempio – nuovo endpoint + nuove colonne:
131
+
132
+
133
+ ```javascript
134
+ document.dispatchEvent(new CustomEvent('datatable:reload', {
135
+ detail: {
136
+ json: '/api/data-filtered.json',
137
+ cols: [{ _heading: 'Nome', _field: 'name' }]
138
+ }
139
+ }));
140
+ ```
141
+
142
+ Esempio – dati inline (senza fetch):
143
+
144
+ ```javascript
145
+ document.dispatchEvent(new CustomEvent('datatable:reload', {
146
+ detail: {
147
+ data: [{ name: 'Mario', city: 'Roma' }],
148
+ cols: [{ _heading: 'Nome', _field: 'name' }]
149
+ }
150
+ }));
151
+ ```
152
+
153
+
154
+ ## Targeting – se nella pagina ci sono più istanze di s-datatable
155
+
156
+
157
+ Aggiungere l'attributo `id` al componente e includere `target` nel detail:
158
+
159
+ `<s-datatable id="table-utenti" ...></s-datatable>`
160
+
161
+ ```javascript
162
+ document.dispatchEvent(new CustomEvent('datatable:search', {
163
+ detail: { term: 'mario', target: 'table-utenti' }
164
+ }));
165
+ ```
166
+
167
+ Se `target` è assente, l'evento viene raccolto da TUTTE le istanze.
168
+
169
+ ## Esempio parametro `cols`
170
+
171
+ ```javascript
172
+ {
173
+
174
+ cols: [
175
+ {
176
+ _field // chiave campo json (anche composta es: `key1.key2`),
177
+ _heading // testo th
178
+ _title // testo `title` del `th`
179
+ _renderMode // preset rendering predefiniti
180
+ _renderTpl // stringa mustache-like per la composizione della cella (chiavi indicate forma `[[chiave]]`)
181
+ type // parametri simple-datatable (senza `_`)
182
+ searchable // ...
183
+ sortable // ...
184
+ // ...
185
+ }
186
+ ]
187
+ }