emilsoftware-utilities 2.0.0-dev.63 → 2.0.0-dev.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.MD +9 -0
- package/dist/angular-components/aree-disponibili/aree-disponibili.js +898 -0
- package/dist/angular-components/aree-disponibili/demo.html +230 -0
- package/dist/emilsoftware-utilities-2.0.0-dev.64.tgz +0 -0
- package/package.json +3 -3
- package/dist/emilsoftware-utilities-2.0.0-dev.63.tgz +0 -0
package/README.MD
CHANGED
|
@@ -62,6 +62,15 @@ git push origin main
|
|
|
62
62
|
|
|
63
63
|
La documentazione operativa del modulo accessi si trova in [src/accessi-module/README.md](src/accessi-module/README.md).
|
|
64
64
|
|
|
65
|
+
## Componenti Angular / Browser
|
|
66
|
+
|
|
67
|
+
Componenti UI frameworkless (Web Components) pubblicati in
|
|
68
|
+
`dist/angular-components/`. Non richiedono Angular, Ionic o React; supportano
|
|
69
|
+
temi light/dark e i token Bootstrap 5.3.
|
|
70
|
+
|
|
71
|
+
- [Catalogo componenti](src/angular-components/README.md)
|
|
72
|
+
- [`aree-disponibili`](src/angular-components/aree-disponibili/README.md)
|
|
73
|
+
|
|
65
74
|
## Test e qualita
|
|
66
75
|
|
|
67
76
|
| Comando | Descrizione |
|
|
@@ -0,0 +1,898 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Emil Software - <es-aree-disponibili>
|
|
3
|
+
*
|
|
4
|
+
* Web Component frameworkless per mostrare le aree abilitate di un utente.
|
|
5
|
+
* Non dipende da Angular, React, Ionic o Bootstrap: basta includere questo file
|
|
6
|
+
* e usare il tag <es-aree-disponibili>.
|
|
7
|
+
*
|
|
8
|
+
* API pubblica:
|
|
9
|
+
* element.groups = [{ name, order, areas: [...] }]
|
|
10
|
+
* element.heading / subtitle / note / eyebrow / emptyText
|
|
11
|
+
* element.hrefMode = 'hash' | 'path' | 'none'
|
|
12
|
+
* element.theme = 'auto' | 'light' | 'dark'
|
|
13
|
+
* eventi: 'es-area-select' (area disponibile), 'es-area-blocked'
|
|
14
|
+
*
|
|
15
|
+
* I colori sono token CSS (--es-*) e, se presenti, ereditano i token
|
|
16
|
+
* Bootstrap 5.3 (--bs-*). Vedi README.md.
|
|
17
|
+
*/
|
|
18
|
+
(function () {
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
if (typeof window === 'undefined' || typeof customElements === 'undefined') {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const TAG = 'es-aree-disponibili';
|
|
26
|
+
const DEFAULT_UNAVAILABLE =
|
|
27
|
+
"La pagina {page} non esiste nell'app al momento.";
|
|
28
|
+
|
|
29
|
+
const ICONS = {
|
|
30
|
+
'home-outline':
|
|
31
|
+
'<path d="M3 10.5 12 3l9 7.5"/><path d="M5 9.5V21h14V9.5"/><path d="M9 21v-6h6v6"/>',
|
|
32
|
+
'people-outline':
|
|
33
|
+
'<circle cx="9" cy="8" r="3"/><path d="M3 20c0-3.3 2.7-6 6-6s6 2.7 6 6"/><path d="M16 5.5a3 3 0 0 1 0 5.8"/><path d="M17.5 14.4c2.1.8 3.5 2.8 3.5 5.6"/>',
|
|
34
|
+
'albums-outline':
|
|
35
|
+
'<rect x="3" y="5" width="18" height="14" rx="2"/><path d="M7 9h10M7 13h10M7 17h6"/>',
|
|
36
|
+
'bulb-outline':
|
|
37
|
+
'<path d="M9 18h6"/><path d="M10 21h4"/><path d="M12 3a6 6 0 0 0-4 10.5c.7.7 1 1.5 1 2.5h6c0-1 .3-1.8 1-2.5A6 6 0 0 0 12 3Z"/>',
|
|
38
|
+
'checkmark-circle-outline':
|
|
39
|
+
'<circle cx="12" cy="12" r="9"/><path d="m8.5 12 2.5 2.5 4.5-5"/>',
|
|
40
|
+
'checkmark-done-outline':
|
|
41
|
+
'<path d="m2 13 4 4 8-9"/><path d="m10 15 2 2 10-11"/>',
|
|
42
|
+
'document-outline':
|
|
43
|
+
'<path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9Z"/><path d="M14 3v6h6"/><path d="M8 13h8M8 17h5"/>',
|
|
44
|
+
'eye-outline':
|
|
45
|
+
'<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z"/><circle cx="12" cy="12" r="3"/>',
|
|
46
|
+
'information-circle-outline':
|
|
47
|
+
'<circle cx="12" cy="12" r="9"/><path d="M12 11v5"/><path d="M12 8h.01"/>',
|
|
48
|
+
'lock-closed-outline':
|
|
49
|
+
'<rect x="4" y="10" width="16" height="11" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/>',
|
|
50
|
+
'play-outline': '<path d="M8 5v14l11-7Z"/>',
|
|
51
|
+
'book-outline':
|
|
52
|
+
'<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2Z"/>',
|
|
53
|
+
'camera-outline':
|
|
54
|
+
'<path d="M4 7h3l2-3h6l2 3h3a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2Z"/><circle cx="12" cy="13" r="4"/>',
|
|
55
|
+
'analytics-outline': '<path d="M4 20V10M10 20V4M16 20v-7M22 20H2"/>',
|
|
56
|
+
'cash-outline':
|
|
57
|
+
'<rect x="2" y="6" width="20" height="12" rx="2"/><circle cx="12" cy="12" r="3"/><path d="M6 10v4M18 10v4"/>',
|
|
58
|
+
'chatbubbles-outline':
|
|
59
|
+
'<path d="M21 12a8 8 0 0 1-8 8H7l-4 3v-6.5A8 8 0 1 1 21 12Z"/>',
|
|
60
|
+
'folder-outline':
|
|
61
|
+
'<path d="M3 7a2 2 0 0 1 2-2h4l2 3h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"/>',
|
|
62
|
+
'megaphone-outline':
|
|
63
|
+
'<path d="M3 11v2a1 1 0 0 0 1 1h2l5 3V7L6 10H4a1 1 0 0 0-1 1Z"/><path d="M14 8a5 5 0 0 1 0 8"/>',
|
|
64
|
+
'shield-checkmark-outline':
|
|
65
|
+
'<path d="M12 3l7 3v5c0 5-3.5 8.5-7 10-3.5-1.5-7-5-7-10V6Z"/><path d="m9 12 2 2 4-4"/>',
|
|
66
|
+
'arrow-forward-outline': '<path d="M5 12h14M13 6l6 6-6 6"/>',
|
|
67
|
+
'compass-outline':
|
|
68
|
+
'<circle cx="12" cy="12" r="9"/><path d="m15.5 8.5-2 5-5 2 2-5 5-2Z"/>',
|
|
69
|
+
'grid-outline':
|
|
70
|
+
'<rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/>',
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const ESCAPES = {
|
|
74
|
+
'&': '&',
|
|
75
|
+
'<': '<',
|
|
76
|
+
'>': '>',
|
|
77
|
+
'"': '"',
|
|
78
|
+
"'": ''',
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
function escapeHtml(value) {
|
|
82
|
+
return String(value == null ? '' : value).replace(
|
|
83
|
+
/[&<>"']/g,
|
|
84
|
+
(character) => ESCAPES[character],
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function icon(name, className) {
|
|
89
|
+
const markup = ICONS[String(name || '').trim()] || ICONS['grid-outline'];
|
|
90
|
+
return (
|
|
91
|
+
'<svg class="' +
|
|
92
|
+
escapeHtml(className || 'icon') +
|
|
93
|
+
'" viewBox="0 0 24 24" fill="none" stroke="currentColor" ' +
|
|
94
|
+
'stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" ' +
|
|
95
|
+
'aria-hidden="true" focusable="false">' +
|
|
96
|
+
markup +
|
|
97
|
+
'</svg>'
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function normalizePage(value) {
|
|
102
|
+
const page = String(value == null ? '' : value).trim();
|
|
103
|
+
if (!page) return '';
|
|
104
|
+
return page.startsWith('/') ? page : '/' + page;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function normalizeArea(area) {
|
|
108
|
+
const route = String(area && area.route ? area.route : '').trim();
|
|
109
|
+
const page = normalizePage(area && area.page);
|
|
110
|
+
const available =
|
|
111
|
+
typeof (area && area.available) === 'boolean'
|
|
112
|
+
? area.available
|
|
113
|
+
: Boolean(route);
|
|
114
|
+
return {
|
|
115
|
+
code: String((area && area.code) || page || route || ''),
|
|
116
|
+
label: String(
|
|
117
|
+
(area && (area.label || area.name || area.description)) ||
|
|
118
|
+
(area && area.code) ||
|
|
119
|
+
page ||
|
|
120
|
+
route ||
|
|
121
|
+
'',
|
|
122
|
+
),
|
|
123
|
+
group: String((area && area.group) || ''),
|
|
124
|
+
icon: String((area && (area.icon || area.icona)) || 'grid-outline'),
|
|
125
|
+
route: available ? route : '',
|
|
126
|
+
page,
|
|
127
|
+
url: String((area && area.url) || ''),
|
|
128
|
+
available,
|
|
129
|
+
reason: String((area && area.reason) || ''),
|
|
130
|
+
order: Number((area && area.order) || 0),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Accetta sia una lista di gruppi gia pronta sia una lista piatta di aree.
|
|
136
|
+
* In quest'ultimo caso raggruppa per `group`.
|
|
137
|
+
*/
|
|
138
|
+
function normalizeGroups(input) {
|
|
139
|
+
if (!Array.isArray(input)) return [];
|
|
140
|
+
const groups = [];
|
|
141
|
+
const hasExplicitGroups = input.some(
|
|
142
|
+
(item) => item && Array.isArray(item.areas),
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
if (hasExplicitGroups) {
|
|
146
|
+
input.forEach((group, index) => {
|
|
147
|
+
if (!group || !Array.isArray(group.areas)) return;
|
|
148
|
+
const areas = group.areas.map(normalizeArea);
|
|
149
|
+
if (areas.length === 0) return;
|
|
150
|
+
groups.push({
|
|
151
|
+
name: String(group.name || group.label || 'Altre aree'),
|
|
152
|
+
order: Number(group.order == null ? index : group.order),
|
|
153
|
+
areas,
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
} else {
|
|
157
|
+
const byName = new Map();
|
|
158
|
+
input.map(normalizeArea).forEach((area, index) => {
|
|
159
|
+
const name = area.group || 'Altre aree';
|
|
160
|
+
let group = byName.get(name);
|
|
161
|
+
if (!group) {
|
|
162
|
+
group = { name, order: 999, areas: [] };
|
|
163
|
+
byName.set(name, group);
|
|
164
|
+
}
|
|
165
|
+
area.order = area.order || index;
|
|
166
|
+
group.areas.push(area);
|
|
167
|
+
});
|
|
168
|
+
byName.forEach((group) => groups.push(group));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
groups.sort((a, b) => a.order - b.order || a.name.localeCompare(b.name));
|
|
172
|
+
groups.forEach((group) =>
|
|
173
|
+
group.areas.sort(
|
|
174
|
+
(a, b) => a.order - b.order || a.label.localeCompare(b.label),
|
|
175
|
+
),
|
|
176
|
+
);
|
|
177
|
+
return groups;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const STYLES = `
|
|
181
|
+
:host {
|
|
182
|
+
--es-font: var(--bs-body-font-family, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);
|
|
183
|
+
--es-radius: 14px;
|
|
184
|
+
--es-radius-lg: 20px;
|
|
185
|
+
--es-bg: var(--bs-body-bg, #f4f6fb);
|
|
186
|
+
--es-surface: var(--bs-card-bg, #ffffff);
|
|
187
|
+
--es-surface-soft: var(--bs-tertiary-bg, #f8fafc);
|
|
188
|
+
--es-text: var(--bs-body-color, #0f172a);
|
|
189
|
+
--es-muted: var(--bs-secondary-color, #64748b);
|
|
190
|
+
--es-primary: var(--bs-primary, #2563eb);
|
|
191
|
+
--es-on-primary: var(--bs-primary-text-emphasis, #1d4ed8);
|
|
192
|
+
--es-primary-soft: var(--bs-primary-bg-subtle, rgba(37, 99, 235, 0.12));
|
|
193
|
+
--es-border: var(--bs-border-color, rgba(15, 23, 42, 0.08));
|
|
194
|
+
--es-disabled-bg: #eef2f7;
|
|
195
|
+
--es-disabled-text: #8a94a6;
|
|
196
|
+
--es-shadow: 0 10px 30px rgba(15, 23, 42, 0.08);
|
|
197
|
+
--es-shadow-hover: 0 16px 36px rgba(15, 23, 42, 0.14);
|
|
198
|
+
--es-success: #15803d;
|
|
199
|
+
|
|
200
|
+
display: block;
|
|
201
|
+
min-height: 100%;
|
|
202
|
+
color-scheme: light;
|
|
203
|
+
background: var(--es-bg);
|
|
204
|
+
color: var(--es-text);
|
|
205
|
+
font-family: var(--es-font);
|
|
206
|
+
font-size: 15px;
|
|
207
|
+
line-height: 1.5;
|
|
208
|
+
-webkit-font-smoothing: antialiased;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
:host([data-resolved-theme="dark"]) {
|
|
212
|
+
color-scheme: dark;
|
|
213
|
+
--es-bg: var(--bs-dark-bg-subtle, #0b1220);
|
|
214
|
+
--es-surface: #111c2e;
|
|
215
|
+
--es-surface-soft: #0e1728;
|
|
216
|
+
--es-text: #e2e8f0;
|
|
217
|
+
--es-muted: #94a3b8;
|
|
218
|
+
--es-primary: #60a5fa;
|
|
219
|
+
--es-on-primary: #bfdbfe;
|
|
220
|
+
--es-primary-soft: rgba(96, 165, 250, 0.16);
|
|
221
|
+
--es-border: rgba(148, 163, 184, 0.16);
|
|
222
|
+
--es-disabled-bg: #0e1829;
|
|
223
|
+
--es-disabled-text: #7b8aa0;
|
|
224
|
+
--es-shadow: 0 12px 34px rgba(0, 0, 0, 0.35);
|
|
225
|
+
--es-shadow-hover: 0 18px 42px rgba(0, 0, 0, 0.45);
|
|
226
|
+
--es-success: #4ade80;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
*,
|
|
230
|
+
*::before,
|
|
231
|
+
*::after {
|
|
232
|
+
box-sizing: border-box;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.shell {
|
|
236
|
+
width: min(960px, 100%);
|
|
237
|
+
margin: 0 auto;
|
|
238
|
+
padding: clamp(16px, 4vw, 40px) clamp(12px, 3vw, 28px) 48px;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.hero {
|
|
242
|
+
display: flex;
|
|
243
|
+
gap: 18px;
|
|
244
|
+
align-items: flex-start;
|
|
245
|
+
padding: clamp(18px, 3vw, 28px);
|
|
246
|
+
border: 1px solid var(--es-border);
|
|
247
|
+
border-radius: var(--es-radius-lg);
|
|
248
|
+
background: var(--es-surface);
|
|
249
|
+
box-shadow: var(--es-shadow);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.hero__icon {
|
|
253
|
+
display: grid;
|
|
254
|
+
place-items: center;
|
|
255
|
+
flex: 0 0 auto;
|
|
256
|
+
width: 52px;
|
|
257
|
+
height: 52px;
|
|
258
|
+
border-radius: 14px;
|
|
259
|
+
background: var(--es-primary-soft);
|
|
260
|
+
color: var(--es-primary);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.hero__icon svg {
|
|
264
|
+
width: 26px;
|
|
265
|
+
height: 26px;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.hero__copy {
|
|
269
|
+
min-width: 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.hero__eyebrow {
|
|
273
|
+
margin: 0 0 6px;
|
|
274
|
+
color: var(--es-muted);
|
|
275
|
+
font-size: 12px;
|
|
276
|
+
font-weight: 600;
|
|
277
|
+
letter-spacing: 0.08em;
|
|
278
|
+
text-transform: uppercase;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.hero h1 {
|
|
282
|
+
margin: 0 0 10px;
|
|
283
|
+
color: var(--es-text);
|
|
284
|
+
font-size: clamp(20px, 2.6vw, 26px);
|
|
285
|
+
font-weight: 700;
|
|
286
|
+
line-height: 1.2;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.hero p {
|
|
290
|
+
margin: 0 0 8px;
|
|
291
|
+
color: var(--es-text);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.hero p:last-child {
|
|
295
|
+
margin-bottom: 0;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.hero__note {
|
|
299
|
+
color: var(--es-muted) !important;
|
|
300
|
+
font-size: 13px;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.groups {
|
|
304
|
+
display: grid;
|
|
305
|
+
gap: 26px;
|
|
306
|
+
margin-top: 22px;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.group__title {
|
|
310
|
+
display: flex;
|
|
311
|
+
align-items: center;
|
|
312
|
+
gap: 8px;
|
|
313
|
+
margin: 0 4px 10px;
|
|
314
|
+
color: var(--es-muted);
|
|
315
|
+
font-size: 13px;
|
|
316
|
+
font-weight: 700;
|
|
317
|
+
letter-spacing: 0.06em;
|
|
318
|
+
text-transform: uppercase;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.group__count {
|
|
322
|
+
display: inline-grid;
|
|
323
|
+
place-items: center;
|
|
324
|
+
min-width: 22px;
|
|
325
|
+
height: 20px;
|
|
326
|
+
padding: 0 6px;
|
|
327
|
+
border-radius: 999px;
|
|
328
|
+
background: var(--es-primary-soft);
|
|
329
|
+
color: var(--es-primary);
|
|
330
|
+
font-size: 11px;
|
|
331
|
+
letter-spacing: 0;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.cards {
|
|
335
|
+
display: grid;
|
|
336
|
+
grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
|
|
337
|
+
gap: 12px;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.card {
|
|
341
|
+
display: flex;
|
|
342
|
+
align-items: center;
|
|
343
|
+
gap: 14px;
|
|
344
|
+
min-width: 0;
|
|
345
|
+
padding: 14px 16px;
|
|
346
|
+
border: 1px solid var(--es-border);
|
|
347
|
+
border-radius: var(--es-radius);
|
|
348
|
+
background: var(--es-surface);
|
|
349
|
+
box-shadow: 0 4px 14px rgba(15, 23, 42, 0.06);
|
|
350
|
+
color: inherit;
|
|
351
|
+
text-decoration: none;
|
|
352
|
+
transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.card:hover,
|
|
356
|
+
.card:focus-visible {
|
|
357
|
+
border-color: var(--es-primary);
|
|
358
|
+
box-shadow: var(--es-shadow-hover);
|
|
359
|
+
transform: translateY(-1px);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.card:focus-visible {
|
|
363
|
+
outline: 2px solid var(--es-primary);
|
|
364
|
+
outline-offset: 2px;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.card__icon {
|
|
368
|
+
display: grid;
|
|
369
|
+
place-items: center;
|
|
370
|
+
flex: 0 0 auto;
|
|
371
|
+
width: 44px;
|
|
372
|
+
height: 44px;
|
|
373
|
+
border-radius: 12px;
|
|
374
|
+
background: var(--es-primary-soft);
|
|
375
|
+
color: var(--es-primary);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.card__icon svg {
|
|
379
|
+
width: 22px;
|
|
380
|
+
height: 22px;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.card__body {
|
|
384
|
+
display: flex;
|
|
385
|
+
flex: 1 1 auto;
|
|
386
|
+
flex-direction: column;
|
|
387
|
+
gap: 2px;
|
|
388
|
+
min-width: 0;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.card__label {
|
|
392
|
+
color: var(--es-text);
|
|
393
|
+
font-size: 15px;
|
|
394
|
+
font-weight: 600;
|
|
395
|
+
overflow: hidden;
|
|
396
|
+
text-overflow: ellipsis;
|
|
397
|
+
white-space: nowrap;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.card__url {
|
|
401
|
+
overflow: hidden;
|
|
402
|
+
color: var(--es-muted);
|
|
403
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
404
|
+
font-size: 11px;
|
|
405
|
+
text-overflow: ellipsis;
|
|
406
|
+
white-space: nowrap;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.card__reason {
|
|
410
|
+
color: var(--es-muted);
|
|
411
|
+
font-size: 12px;
|
|
412
|
+
font-style: italic;
|
|
413
|
+
line-height: 1.35;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.card__action {
|
|
417
|
+
flex: 0 0 auto;
|
|
418
|
+
color: var(--es-muted);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.card__action svg {
|
|
422
|
+
width: 18px;
|
|
423
|
+
height: 18px;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.card--blocked {
|
|
427
|
+
border-style: dashed;
|
|
428
|
+
background: var(--es-disabled-bg);
|
|
429
|
+
box-shadow: none;
|
|
430
|
+
cursor: not-allowed;
|
|
431
|
+
opacity: 0.78;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.card--blocked:hover,
|
|
435
|
+
.card--blocked:focus-visible {
|
|
436
|
+
border-color: var(--es-border);
|
|
437
|
+
box-shadow: none;
|
|
438
|
+
transform: none;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.card--blocked .card__icon {
|
|
442
|
+
background: transparent;
|
|
443
|
+
color: var(--es-disabled-text);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.card--blocked .card__label {
|
|
447
|
+
color: var(--es-disabled-text);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.card--blocked .card__reason,
|
|
451
|
+
.card--blocked .card__action {
|
|
452
|
+
color: var(--es-disabled-text);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.empty {
|
|
456
|
+
display: grid;
|
|
457
|
+
place-items: center;
|
|
458
|
+
gap: 10px;
|
|
459
|
+
margin-top: 24px;
|
|
460
|
+
padding: 32px 20px;
|
|
461
|
+
border: 1px solid var(--es-border);
|
|
462
|
+
border-radius: 16px;
|
|
463
|
+
background: var(--es-surface);
|
|
464
|
+
color: var(--es-muted);
|
|
465
|
+
text-align: center;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.empty svg {
|
|
469
|
+
width: 32px;
|
|
470
|
+
height: 32px;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.legend {
|
|
474
|
+
display: flex;
|
|
475
|
+
align-items: center;
|
|
476
|
+
gap: 8px;
|
|
477
|
+
margin: 18px 4px 0;
|
|
478
|
+
color: var(--es-muted);
|
|
479
|
+
font-size: 13px;
|
|
480
|
+
line-height: 1.45;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.legend svg {
|
|
484
|
+
flex: 0 0 auto;
|
|
485
|
+
width: 18px;
|
|
486
|
+
height: 18px;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
@media (max-width: 560px) {
|
|
490
|
+
.hero {
|
|
491
|
+
flex-direction: column;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.cards {
|
|
495
|
+
grid-template-columns: 1fr;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
@media (prefers-reduced-motion: reduce) {
|
|
500
|
+
.card {
|
|
501
|
+
transition: none;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.card:hover,
|
|
505
|
+
.card:focus-visible {
|
|
506
|
+
transform: none;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
`;
|
|
510
|
+
|
|
511
|
+
class AreeDisponibiliElement extends HTMLElement {
|
|
512
|
+
static get observedAttributes() {
|
|
513
|
+
return [
|
|
514
|
+
'theme',
|
|
515
|
+
'heading',
|
|
516
|
+
'subtitle',
|
|
517
|
+
'note',
|
|
518
|
+
'eyebrow',
|
|
519
|
+
'empty-text',
|
|
520
|
+
'unavailable-template',
|
|
521
|
+
'href-mode',
|
|
522
|
+
];
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
#groups = [];
|
|
526
|
+
#root;
|
|
527
|
+
#media;
|
|
528
|
+
#observer;
|
|
529
|
+
#onThemeChange = () => this.#applyTheme();
|
|
530
|
+
#onClick = (event) => this.#handleClick(event);
|
|
531
|
+
|
|
532
|
+
constructor() {
|
|
533
|
+
super();
|
|
534
|
+
const shadow = this.attachShadow({ mode: 'open' });
|
|
535
|
+
const style = document.createElement('style');
|
|
536
|
+
style.textContent = STYLES;
|
|
537
|
+
shadow.appendChild(style);
|
|
538
|
+
this.#root = document.createElement('div');
|
|
539
|
+
shadow.appendChild(this.#root);
|
|
540
|
+
this.#root.addEventListener('click', this.#onClick);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
connectedCallback() {
|
|
544
|
+
this.#watchTheme();
|
|
545
|
+
this.#applyTheme();
|
|
546
|
+
this.render();
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
disconnectedCallback() {
|
|
550
|
+
if (this.#media) {
|
|
551
|
+
this.#media.removeEventListener('change', this.#onThemeChange);
|
|
552
|
+
this.#media = undefined;
|
|
553
|
+
}
|
|
554
|
+
if (this.#observer) {
|
|
555
|
+
this.#observer.disconnect();
|
|
556
|
+
this.#observer = undefined;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
attributeChangedCallback(name) {
|
|
561
|
+
if (name === 'theme') {
|
|
562
|
+
this.#applyTheme();
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
this.render();
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
get groups() {
|
|
569
|
+
return this.#groups;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
set groups(value) {
|
|
573
|
+
this.#groups = normalizeGroups(value);
|
|
574
|
+
this.render();
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
get heading() {
|
|
578
|
+
return this.getAttribute('heading') || 'Aree disponibili';
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
set heading(value) {
|
|
582
|
+
this.#setOptionalAttribute('heading', value);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
get subtitle() {
|
|
586
|
+
return this.getAttribute('subtitle') || '';
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
set subtitle(value) {
|
|
590
|
+
this.#setOptionalAttribute('subtitle', value);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
get note() {
|
|
594
|
+
return this.getAttribute('note') || '';
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
set note(value) {
|
|
598
|
+
this.#setOptionalAttribute('note', value);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
get eyebrow() {
|
|
602
|
+
return this.getAttribute('eyebrow') || 'Accesso personalizzato';
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
set eyebrow(value) {
|
|
606
|
+
this.#setOptionalAttribute('eyebrow', value);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
get emptyText() {
|
|
610
|
+
return (
|
|
611
|
+
this.getAttribute('empty-text') ||
|
|
612
|
+
"Nessuna area abilitata per il tuo utente. Contatta l'amministratore."
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
set emptyText(value) {
|
|
617
|
+
this.#setOptionalAttribute('empty-text', value);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
get unavailableTemplate() {
|
|
621
|
+
return this.getAttribute('unavailable-template') || DEFAULT_UNAVAILABLE;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
set unavailableTemplate(value) {
|
|
625
|
+
this.#setOptionalAttribute('unavailable-template', value);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
get hrefMode() {
|
|
629
|
+
const value = (this.getAttribute('href-mode') || 'hash').toLowerCase();
|
|
630
|
+
return value === 'path' || value === 'none' ? value : 'hash';
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
set hrefMode(value) {
|
|
634
|
+
this.#setOptionalAttribute('href-mode', value);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
get theme() {
|
|
638
|
+
return this.getAttribute('theme') || 'auto';
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
set theme(value) {
|
|
642
|
+
this.#setOptionalAttribute('theme', value);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
setGroups(value) {
|
|
646
|
+
this.groups = value;
|
|
647
|
+
return this;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
refresh() {
|
|
651
|
+
this.render();
|
|
652
|
+
return this;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
#setOptionalAttribute(name, value) {
|
|
656
|
+
if (value == null || value === '') {
|
|
657
|
+
this.removeAttribute(name);
|
|
658
|
+
} else {
|
|
659
|
+
this.setAttribute(name, String(value));
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
#watchTheme() {
|
|
664
|
+
if (this.#media) return;
|
|
665
|
+
this.#media = window.matchMedia('(prefers-color-scheme: dark)');
|
|
666
|
+
this.#media.addEventListener('change', this.#onThemeChange);
|
|
667
|
+
this.#observer = new MutationObserver(this.#onThemeChange);
|
|
668
|
+
this.#observer.observe(document.documentElement, {
|
|
669
|
+
attributes: true,
|
|
670
|
+
attributeFilter: ['class', 'data-bs-theme'],
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
#resolveTheme() {
|
|
675
|
+
const explicit = String(this.getAttribute('theme') || 'auto').toLowerCase();
|
|
676
|
+
if (explicit === 'light' || explicit === 'dark') return explicit;
|
|
677
|
+
|
|
678
|
+
const root = document.documentElement;
|
|
679
|
+
if (
|
|
680
|
+
root.getAttribute('data-bs-theme') === 'dark' ||
|
|
681
|
+
root.classList.contains('dark') ||
|
|
682
|
+
root.classList.contains('ion-palette-dark')
|
|
683
|
+
) {
|
|
684
|
+
return 'dark';
|
|
685
|
+
}
|
|
686
|
+
return this.#media && this.#media.matches ? 'dark' : 'light';
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
#applyTheme() {
|
|
690
|
+
const resolved = this.#resolveTheme();
|
|
691
|
+
if (this.getAttribute('data-resolved-theme') !== resolved) {
|
|
692
|
+
this.setAttribute('data-resolved-theme', resolved);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
#handleClick(event) {
|
|
697
|
+
const card = event.target && event.target.closest
|
|
698
|
+
? event.target.closest('[data-area]')
|
|
699
|
+
: null;
|
|
700
|
+
if (!card) return;
|
|
701
|
+
|
|
702
|
+
const groupIndex = Number(card.getAttribute('data-group'));
|
|
703
|
+
const areaIndex = Number(card.getAttribute('data-area'));
|
|
704
|
+
const group = this.#groups[groupIndex];
|
|
705
|
+
const area = group && group.areas[areaIndex];
|
|
706
|
+
if (!area) return;
|
|
707
|
+
|
|
708
|
+
const detail = {
|
|
709
|
+
area: { ...area },
|
|
710
|
+
group: group.name,
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
if (area.available) {
|
|
714
|
+
this.dispatchEvent(
|
|
715
|
+
new CustomEvent('es-area-select', {
|
|
716
|
+
detail,
|
|
717
|
+
bubbles: true,
|
|
718
|
+
composed: true,
|
|
719
|
+
}),
|
|
720
|
+
);
|
|
721
|
+
if (this.hrefMode === 'none') event.preventDefault();
|
|
722
|
+
} else {
|
|
723
|
+
event.preventDefault();
|
|
724
|
+
this.dispatchEvent(
|
|
725
|
+
new CustomEvent('es-area-blocked', {
|
|
726
|
+
detail: {
|
|
727
|
+
...detail,
|
|
728
|
+
reason: area.reason || this.#defaultReason(area),
|
|
729
|
+
},
|
|
730
|
+
bubbles: true,
|
|
731
|
+
composed: true,
|
|
732
|
+
}),
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
#defaultReason(area) {
|
|
738
|
+
return this.unavailableTemplate.replace('{page}', area.page || '');
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
#hrefFor(area) {
|
|
742
|
+
if (!area.available || !area.route) return '';
|
|
743
|
+
if (this.hrefMode === 'none') return '';
|
|
744
|
+
if (this.hrefMode === 'path') return area.route;
|
|
745
|
+
const route = area.route.startsWith('/') ? area.route : '/' + area.route;
|
|
746
|
+
return '#' + route;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
render() {
|
|
750
|
+
if (!this.isConnected) return;
|
|
751
|
+
|
|
752
|
+
const groups = this.#groups;
|
|
753
|
+
const totalAreas = groups.reduce(
|
|
754
|
+
(total, group) => total + group.areas.length,
|
|
755
|
+
0,
|
|
756
|
+
);
|
|
757
|
+
const unavailable = groups.reduce(
|
|
758
|
+
(total, group) =>
|
|
759
|
+
total + group.areas.filter((area) => !area.available).length,
|
|
760
|
+
0,
|
|
761
|
+
);
|
|
762
|
+
|
|
763
|
+
const header =
|
|
764
|
+
'<header class="hero">' +
|
|
765
|
+
'<span class="hero__icon">' +
|
|
766
|
+
icon('compass-outline') +
|
|
767
|
+
'</span>' +
|
|
768
|
+
'<div class="hero__copy">' +
|
|
769
|
+
(this.eyebrow
|
|
770
|
+
? '<p class="hero__eyebrow">' + escapeHtml(this.eyebrow) + '</p>'
|
|
771
|
+
: '') +
|
|
772
|
+
'<h1>' +
|
|
773
|
+
escapeHtml(this.heading) +
|
|
774
|
+
'</h1>' +
|
|
775
|
+
(this.subtitle ? '<p>' + escapeHtml(this.subtitle) + '</p>' : '') +
|
|
776
|
+
(this.note ? '<p class="hero__note">' + escapeHtml(this.note) + '</p>' : '') +
|
|
777
|
+
'</div>' +
|
|
778
|
+
'</header>';
|
|
779
|
+
|
|
780
|
+
const body =
|
|
781
|
+
totalAreas === 0
|
|
782
|
+
? '<section class="empty">' +
|
|
783
|
+
icon('lock-closed-outline') +
|
|
784
|
+
'<p>' +
|
|
785
|
+
escapeHtml(this.emptyText) +
|
|
786
|
+
'</p></section>'
|
|
787
|
+
: '<div class="groups">' +
|
|
788
|
+
groups.map((group, groupIndex) => this.#renderGroup(group, groupIndex)).join('') +
|
|
789
|
+
'</div>';
|
|
790
|
+
|
|
791
|
+
const legend =
|
|
792
|
+
unavailable > 0
|
|
793
|
+
? '<p class="legend">' +
|
|
794
|
+
icon('information-circle-outline') +
|
|
795
|
+
'<span>Le aree in grigio non sono ancora disponibili in questa ' +
|
|
796
|
+
'applicazione (' +
|
|
797
|
+
unavailable +
|
|
798
|
+
').</span></p>'
|
|
799
|
+
: '';
|
|
800
|
+
|
|
801
|
+
this.#root.innerHTML =
|
|
802
|
+
'<main class="shell">' + header + body + legend + '</main>';
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
#renderGroup(group, groupIndex) {
|
|
806
|
+
const cards = group.areas
|
|
807
|
+
.map((area, areaIndex) => this.#renderArea(area, groupIndex, areaIndex))
|
|
808
|
+
.join('');
|
|
809
|
+
return (
|
|
810
|
+
'<section class="group">' +
|
|
811
|
+
'<h2 class="group__title">' +
|
|
812
|
+
escapeHtml(group.name) +
|
|
813
|
+
'<span class="group__count">' +
|
|
814
|
+
group.areas.length +
|
|
815
|
+
'</span>' +
|
|
816
|
+
'</h2>' +
|
|
817
|
+
'<div class="cards">' +
|
|
818
|
+
cards +
|
|
819
|
+
'</div>' +
|
|
820
|
+
'</section>'
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
#renderArea(area, groupIndex, areaIndex) {
|
|
825
|
+
const href = this.#hrefFor(area);
|
|
826
|
+
const reason =
|
|
827
|
+
area.reason || this.#defaultReason(area);
|
|
828
|
+
const attributes =
|
|
829
|
+
'data-area data-group="' +
|
|
830
|
+
groupIndex +
|
|
831
|
+
'" data-area="' +
|
|
832
|
+
areaIndex +
|
|
833
|
+
'"';
|
|
834
|
+
const className = 'card' + (area.available ? '' : ' card--blocked');
|
|
835
|
+
const url = area.url || href;
|
|
836
|
+
const content =
|
|
837
|
+
'<span class="card__icon">' +
|
|
838
|
+
icon(area.icon) +
|
|
839
|
+
'</span>' +
|
|
840
|
+
'<span class="card__body">' +
|
|
841
|
+
'<span class="card__label" title="' +
|
|
842
|
+
escapeHtml(area.label) +
|
|
843
|
+
'">' +
|
|
844
|
+
escapeHtml(area.label) +
|
|
845
|
+
'</span>' +
|
|
846
|
+
(area.available
|
|
847
|
+
? url
|
|
848
|
+
? '<span class="card__url" title="' +
|
|
849
|
+
escapeHtml(url) +
|
|
850
|
+
'">' +
|
|
851
|
+
escapeHtml(url) +
|
|
852
|
+
'</span>'
|
|
853
|
+
: ''
|
|
854
|
+
: '<span class="card__reason">' +
|
|
855
|
+
escapeHtml(reason) +
|
|
856
|
+
'</span>') +
|
|
857
|
+
'</span>' +
|
|
858
|
+
'<span class="card__action">' +
|
|
859
|
+
icon(
|
|
860
|
+
area.available ? 'arrow-forward-outline' : 'lock-closed-outline',
|
|
861
|
+
) +
|
|
862
|
+
'</span>';
|
|
863
|
+
|
|
864
|
+
if (area.available) {
|
|
865
|
+
return (
|
|
866
|
+
'<a class="' +
|
|
867
|
+
className +
|
|
868
|
+
'" ' +
|
|
869
|
+
attributes +
|
|
870
|
+
(href ? ' href="' + escapeHtml(href) + '"' : '') +
|
|
871
|
+
'>' +
|
|
872
|
+
content +
|
|
873
|
+
'</a>'
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
return (
|
|
878
|
+
'<div class="' +
|
|
879
|
+
className +
|
|
880
|
+
'" ' +
|
|
881
|
+
attributes +
|
|
882
|
+
' role="link" aria-disabled="true" tabindex="0" title="' +
|
|
883
|
+
escapeHtml(reason) +
|
|
884
|
+
'">' +
|
|
885
|
+
content +
|
|
886
|
+
'</div>'
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
if (!customElements.get(TAG)) {
|
|
892
|
+
customElements.define(TAG, AreeDisponibiliElement);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// Esposto per integrazioni che preferiscono costruire l'elemento da codice.
|
|
896
|
+
window.EmilSoftware = window.EmilSoftware || {};
|
|
897
|
+
window.EmilSoftware.AreeDisponibiliElement = AreeDisponibiliElement;
|
|
898
|
+
})();
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="it" data-theme="light">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>es-aree-disponibili · demo</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
color-scheme: light;
|
|
10
|
+
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
body {
|
|
14
|
+
margin: 0;
|
|
15
|
+
min-height: 100vh;
|
|
16
|
+
background: #eef2f7;
|
|
17
|
+
color: #0f172a;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.toolbar {
|
|
21
|
+
position: sticky;
|
|
22
|
+
top: 0;
|
|
23
|
+
z-index: 10;
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-wrap: wrap;
|
|
26
|
+
gap: 10px;
|
|
27
|
+
align-items: center;
|
|
28
|
+
padding: 12px 20px;
|
|
29
|
+
border-bottom: 1px solid rgba(15, 23, 42, 0.08);
|
|
30
|
+
background: rgba(255, 255, 255, 0.92);
|
|
31
|
+
backdrop-filter: blur(8px);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.toolbar strong {
|
|
35
|
+
margin-right: auto;
|
|
36
|
+
font-size: 14px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.toolbar button {
|
|
40
|
+
padding: 7px 12px;
|
|
41
|
+
border: 1px solid rgba(15, 23, 42, 0.16);
|
|
42
|
+
border-radius: 999px;
|
|
43
|
+
background: #fff;
|
|
44
|
+
color: inherit;
|
|
45
|
+
font: inherit;
|
|
46
|
+
font-size: 13px;
|
|
47
|
+
cursor: pointer;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.toolbar button[aria-pressed="true"] {
|
|
51
|
+
border-color: #2563eb;
|
|
52
|
+
background: #2563eb;
|
|
53
|
+
color: #fff;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.event-log {
|
|
57
|
+
max-width: 960px;
|
|
58
|
+
margin: 0 auto;
|
|
59
|
+
padding: 0 28px 32px;
|
|
60
|
+
color: #64748b;
|
|
61
|
+
font-size: 12px;
|
|
62
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.event-log p {
|
|
66
|
+
margin: 4px 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
html[data-theme="dark"] {
|
|
70
|
+
color-scheme: dark;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
html[data-theme="dark"] body {
|
|
74
|
+
background: #0b1220;
|
|
75
|
+
color: #e2e8f0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
html[data-theme="dark"] .toolbar {
|
|
79
|
+
border-color: rgba(148, 163, 184, 0.16);
|
|
80
|
+
background: rgba(17, 28, 46, 0.92);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
html[data-theme="dark"] .toolbar button {
|
|
84
|
+
border-color: rgba(148, 163, 184, 0.28);
|
|
85
|
+
background: #111c2e;
|
|
86
|
+
color: #e2e8f0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
html[data-theme="dark"] .toolbar button[aria-pressed="true"] {
|
|
90
|
+
border-color: #60a5fa;
|
|
91
|
+
background: #2563eb;
|
|
92
|
+
color: #fff;
|
|
93
|
+
}
|
|
94
|
+
</style>
|
|
95
|
+
</head>
|
|
96
|
+
<body>
|
|
97
|
+
<div class="toolbar">
|
|
98
|
+
<strong>es-aree-disponibili</strong>
|
|
99
|
+
<button type="button" data-theme-button="light" aria-pressed="true">Light</button>
|
|
100
|
+
<button type="button" data-theme-button="dark" aria-pressed="false">Dark</button>
|
|
101
|
+
<button type="button" data-theme-button="auto" aria-pressed="false">Auto</button>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<es-aree-disponibili id="aree"></es-aree-disponibili>
|
|
105
|
+
|
|
106
|
+
<div class="event-log" id="events" aria-live="polite"></div>
|
|
107
|
+
|
|
108
|
+
<script src="./aree-disponibili.js"></script>
|
|
109
|
+
<script>
|
|
110
|
+
const element = document.getElementById('aree');
|
|
111
|
+
const events = document.getElementById('events');
|
|
112
|
+
|
|
113
|
+
element.groups = [
|
|
114
|
+
{
|
|
115
|
+
name: 'Produzione',
|
|
116
|
+
order: 4,
|
|
117
|
+
areas: [
|
|
118
|
+
{
|
|
119
|
+
code: 'MNUMAIN',
|
|
120
|
+
label: 'Home',
|
|
121
|
+
icon: 'home-outline',
|
|
122
|
+
route: '/main',
|
|
123
|
+
url: 'https://example.local/#/main',
|
|
124
|
+
available: true,
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
code: 'MNUAVANZA',
|
|
128
|
+
label: 'Avanzamento Produzione',
|
|
129
|
+
icon: 'play-outline',
|
|
130
|
+
page: '/avanzamento',
|
|
131
|
+
available: false,
|
|
132
|
+
reason: "La pagina /avanzamento non esiste nell'app al momento.",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
code: 'MNUJOURNAL',
|
|
136
|
+
label: 'Giornale Attivita',
|
|
137
|
+
icon: 'book-outline',
|
|
138
|
+
page: '/journal',
|
|
139
|
+
available: false,
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'Amministrazione',
|
|
145
|
+
order: 7,
|
|
146
|
+
areas: [
|
|
147
|
+
{
|
|
148
|
+
code: 'MNUUTENTI',
|
|
149
|
+
label: 'Gestione Utenti',
|
|
150
|
+
icon: 'people-outline',
|
|
151
|
+
route: '/lista-profili',
|
|
152
|
+
url: 'https://example.local/#/lista-profili',
|
|
153
|
+
available: true,
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: 'Qualita',
|
|
159
|
+
order: 16,
|
|
160
|
+
areas: [
|
|
161
|
+
{
|
|
162
|
+
code: 'STEX_ARRIVI_CQ',
|
|
163
|
+
label: 'SteX - Arrivi CQ',
|
|
164
|
+
icon: 'checkmark-done-outline',
|
|
165
|
+
route: '/cq-arrivi',
|
|
166
|
+
url: 'https://example.local/#/cq-arrivi',
|
|
167
|
+
available: true,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
code: 'STEX_PARTITE_CQ',
|
|
171
|
+
label: 'SteX - Partite CQ',
|
|
172
|
+
icon: 'albums-outline',
|
|
173
|
+
route: '/cq-partite',
|
|
174
|
+
url: 'https://example.local/#/cq-partite',
|
|
175
|
+
available: true,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
code: 'MNUVASCATE',
|
|
179
|
+
label: 'Controllo Vascate',
|
|
180
|
+
icon: 'analytics-outline',
|
|
181
|
+
page: '/controllo-vascate',
|
|
182
|
+
available: false,
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
},
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
function log(message) {
|
|
189
|
+
const line = document.createElement('p');
|
|
190
|
+
line.textContent = new Date().toLocaleTimeString() + ' · ' + message;
|
|
191
|
+
events.prepend(line);
|
|
192
|
+
while (events.children.length > 6) {
|
|
193
|
+
events.lastElementChild.remove();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
element.addEventListener('es-area-select', (event) => {
|
|
198
|
+
log('select: ' + event.detail.area.label + ' → ' + event.detail.area.route);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
element.addEventListener('es-area-blocked', (event) => {
|
|
202
|
+
log('blocked: ' + event.detail.area.label + ' · ' + event.detail.reason);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
function applyTheme(theme) {
|
|
206
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
207
|
+
element.theme = theme;
|
|
208
|
+
for (const button of document.querySelectorAll('[data-theme-button]')) {
|
|
209
|
+
button.setAttribute(
|
|
210
|
+
'aria-pressed',
|
|
211
|
+
String(button.getAttribute('data-theme-button') === theme),
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
for (const button of document.querySelectorAll('[data-theme-button]')) {
|
|
217
|
+
button.addEventListener('click', () => {
|
|
218
|
+
const theme = button.getAttribute('data-theme-button');
|
|
219
|
+
applyTheme(theme);
|
|
220
|
+
const url = new URL(window.location.href);
|
|
221
|
+
url.searchParams.set('theme', theme);
|
|
222
|
+
history.replaceState(null, '', url);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Permette di aprire la demo direttamente in un tema: ?theme=dark
|
|
227
|
+
applyTheme(new URLSearchParams(window.location.search).get('theme') || 'light');
|
|
228
|
+
</script>
|
|
229
|
+
</body>
|
|
230
|
+
</html>
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "emilsoftware-utilities",
|
|
3
|
-
"version": "2.0.0-dev.
|
|
3
|
+
"version": "2.0.0-dev.64",
|
|
4
4
|
"description": "Utilities for EmilSoftware",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"typecheck": "tsc --noEmit",
|
|
22
22
|
"lint": "node scripts/lint.cjs",
|
|
23
23
|
"test": "npm run typecheck && npm run lint && npm run test:unit",
|
|
24
|
-
"test:unit": "node --test test/menus-catalog.mjs test/menu-scopes.cjs test/accessi-regressions.cjs test/password-reset-regressions.cjs test/two-factor.cjs test/accessi-schema.cjs test/utilities-regressions.cjs test/service-token-regressions.cjs test/accessi-email-config.cjs test/accessi-catalog-scripts.cjs",
|
|
25
|
-
"test:coverage": "node --experimental-test-coverage --test-coverage-lines=50 --test-coverage-functions=60 --test-coverage-branches=60 --test test/menus-catalog.mjs test/menu-scopes.cjs test/accessi-regressions.cjs test/password-reset-regressions.cjs test/two-factor.cjs test/accessi-schema.cjs test/utilities-regressions.cjs test/service-token-regressions.cjs test/accessi-email-config.cjs test/accessi-catalog-scripts.cjs",
|
|
24
|
+
"test:unit": "node --test test/angular-components.cjs test/menus-catalog.mjs test/menu-scopes.cjs test/accessi-regressions.cjs test/password-reset-regressions.cjs test/two-factor.cjs test/accessi-schema.cjs test/utilities-regressions.cjs test/service-token-regressions.cjs test/accessi-email-config.cjs test/accessi-catalog-scripts.cjs",
|
|
25
|
+
"test:coverage": "node --experimental-test-coverage --test-coverage-lines=50 --test-coverage-functions=60 --test-coverage-branches=60 --test test/angular-components.cjs test/menus-catalog.mjs test/menu-scopes.cjs test/accessi-regressions.cjs test/password-reset-regressions.cjs test/two-factor.cjs test/accessi-schema.cjs test/utilities-regressions.cjs test/service-token-regressions.cjs test/accessi-email-config.cjs test/accessi-catalog-scripts.cjs",
|
|
26
26
|
"test:accessi": "npm run test:unit",
|
|
27
27
|
"test:all": "npm test && npm run test:integration",
|
|
28
28
|
"test:integration": "node test/integration/run-firebird.cjs 5",
|
|
Binary file
|