@jsswift/ui 1.0.53 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/css/animation.css +36 -36
  2. package/dist/css/base.css +374 -374
  3. package/dist/css/docs.css +54 -54
  4. package/dist/css/fonts-nunito.css +2 -2
  5. package/dist/css/min-ui.css +1 -1
  6. package/dist/css/responsive-base.css +557 -0
  7. package/dist/css/responsive.css +3225 -3225
  8. package/dist/css/ui-components.css +2902 -2930
  9. package/dist/css/ui-light.css +9908 -0
  10. package/dist/css/ui-light.min.css +1 -0
  11. package/dist/css/ui.css +6530 -6558
  12. package/dist/css/ui.min.css +1 -1
  13. package/dist/min-ui.js +12 -12
  14. package/dist/ui.js +4354 -9379
  15. package/dist/ui.min.js +12 -12
  16. package/package.json +4 -2
  17. package/src/00-bootstrap.js +305 -305
  18. package/src/01-foundation-helpers.js +328 -325
  19. package/src/10-primitives-layout.js +242 -242
  20. package/src/20-display-content.js +76 -76
  21. package/src/21-list-content.js +21 -21
  22. package/src/22-banner-content.js +376 -376
  23. package/src/23-tooltip.js +9 -9
  24. package/src/31-form-advanced.js +141 -141
  25. package/src/40-navigation.js +81 -81
  26. package/src/41-tab-panel.js +37 -37
  27. package/src/50-feedback.js +52 -52
  28. package/src/51-feedback-service.js +166 -166
  29. package/src/60-shell-app.js +1082 -1082
  30. package/src/61-form-service.js +5 -5
  31. package/src/70-dialog.js +34 -34
  32. package/src/71-menu-overlays.js +73 -73
  33. package/src/80-data.js +38 -38
  34. package/src/css/README.md +2 -0
  35. package/src/css/animation.css +36 -36
  36. package/src/css/base.css +374 -374
  37. package/src/css/docs.css +54 -54
  38. package/src/css/fonts-nunito.css +2 -2
  39. package/src/css/responsive.css +3225 -3225
  40. package/src/css/ui-components.css +2902 -2930
@@ -1,376 +1,376 @@
1
- UI.Banner = (...args) => {
2
- const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
3
- const slots = rawProps.slots || {};
4
-
5
- const resolveStateValue = () => normalizeState(uiUnwrap(rawProps.type) || uiUnwrap(rawProps.state) || "");
6
- const ctx = {
7
- state: resolveStateValue,
8
- dismissible: () => !!uiUnwrap(rawProps.dismissible)
9
- };
10
-
11
- const typeClass = uiComputed([rawProps.type, rawProps.state], () => resolveStateValue());
12
- const variantClass = uiComputed(rawProps.variant, () => {
13
- const variant = String(uiUnwrap(rawProps.variant) || "").toLowerCase();
14
- return ["solid", "outline", "ghost"].includes(variant) ? `cms-banner-${variant}` : "";
15
- });
16
- const actionsPlacementClass = uiComputed([rawProps.actionsPlacement, rawProps.stack], () => {
17
- const placement = String(uiUnwrap(rawProps.actionsPlacement) || "").toLowerCase();
18
- return placement === "bottom" || !!uiUnwrap(rawProps.stack) ? "cms-banner-actions-bottom" : "";
19
- });
20
- const stackClass = uiComputed(rawProps.stack, () => uiUnwrap(rawProps.stack) ? "cms-banner-stack" : "");
21
-
22
- const props = { ...rawProps };
23
- const p = JSswift.omit(props, [
24
- "actions", "actionsPlacement", "accent", "aside", "body", "closeLabel", "description",
25
- "dismiss", "dismissible", "icon", "iconSize", "message", "meta", "onDismiss", "slots",
26
- "stack", "state", "subtitle", "title", "type", "variant"
27
- ]);
28
- p.class = uiClass([
29
- "cms-clear-set",
30
- "cms-banner",
31
- "cms-singularity",
32
- typeClass,
33
- variantClass,
34
- actionsPlacementClass,
35
- stackClass,
36
- props.class
37
- ]);
38
- p.style = { ...(props.style || {}) };
39
-
40
- const toneToCss = (value) => {
41
- if (value == null || value === "") return "";
42
- const v = uiUnwrap(value);
43
- if (v == null || v === "") return "";
44
- const s = String(v);
45
- if (isTokenCSS(s)) return `var(--cms-${s})`;
46
- return s;
47
- };
48
-
49
- if (rawProps.accent != null || uiIsReactive(rawProps.accent)) {
50
- p.style["--cms-banner-accent"] = () => toneToCss(rawProps.accent);
51
- p.style["--cms-banner-border"] = () => {
52
- const tone = toneToCss(rawProps.accent);
53
- return tone ? `color-mix(in srgb, ${tone} 38%, var(--cms-border-color))` : "";
54
- };
55
- p.style["--cms-banner-bg"] = () => {
56
- const tone = toneToCss(rawProps.accent);
57
- return tone ? `color-mix(in srgb, ${tone} 14%, var(--cms-panel))` : "";
58
- };
59
- p.style["--cms-banner-color"] = () => {
60
- const tone = toneToCss(rawProps.accent);
61
- return tone ? `color-mix(in srgb, ${tone} 78%, white)` : "";
62
- };
63
- }
64
-
65
- const autoIconMap = {
66
- success: "check_circle",
67
- warning: "warning",
68
- danger: "error",
69
- info: "info",
70
- primary: "bolt",
71
- secondary: "campaign",
72
- dark: "shield",
73
- light: "notifications"
74
- };
75
-
76
- const iconFallback = (() => {
77
- if (rawProps.icon === false || rawProps.icon === null) return null;
78
- if (rawProps.icon != null) {
79
- return typeof rawProps.icon === "string"
80
- ? UI.Icon({ name: rawProps.icon, size: rawProps.iconSize || rawProps.size || "md" })
81
- : JSswift.ui.slot(rawProps.icon, { as: "icon" });
82
- }
83
- const state = resolveStateValue();
84
- const iconName = state ? autoIconMap[state] : null;
85
- return iconName ? UI.Icon({ name: iconName, size: rawProps.iconSize || rawProps.size || "md" }) : null;
86
- })();
87
-
88
- const titleNodes = renderSlotToArray(slots, "title", ctx, rawProps.title);
89
- const messageNodes = renderSlotToArray(slots, "message", ctx, rawProps.message != null ? rawProps.message : children);
90
- const descriptionNodes = renderSlotToArray(slots, "description", ctx, rawProps.description ?? rawProps.subtitle);
91
- const metaNodes = renderSlotToArray(slots, "meta", ctx, rawProps.meta);
92
- const bodyNodes = renderSlotToArray(
93
- slots,
94
- "default",
95
- ctx,
96
- rawProps.body != null ? [rawProps.body, ...(children || [])] : (rawProps.message != null ? children : null)
97
- );
98
- const iconNodes = renderSlotToArray(slots, "icon", ctx, iconFallback);
99
- const asideNodes = renderSlotToArray(slots, "aside", ctx, rawProps.aside);
100
- const actionsNodes = renderSlotToArray(slots, "actions", ctx, rawProps.actions);
101
- const dismissNodes = renderSlotToArray(slots, "dismiss", ctx, rawProps.dismiss);
102
-
103
- const hasBottomActions = (() => {
104
- const placement = String(uiUnwrap(rawProps.actionsPlacement) || "").toLowerCase();
105
- return placement === "bottom" || !!uiUnwrap(rawProps.stack);
106
- })();
107
- const isDismissible = !!uiUnwrap(rawProps.dismissible) || !!rawProps.onDismiss;
108
-
109
- let bannerEl = null;
110
- const defaultDismissNode = isDismissible
111
- ? UI.Btn({
112
- class: "cms-banner-close",
113
- outline: true,
114
- size: rawProps.size || "sm",
115
- "aria-label": rawProps.closeLabel || "Chiudi banner",
116
- onClick: (e) => {
117
- rawProps.onDismiss?.(e);
118
- if (e.defaultPrevented) return;
119
- bannerEl?.remove();
120
- }
121
- }, UI.Icon({ name: "close", size: rawProps.size || "sm" }))
122
- : null;
123
-
124
- const sideNodes = [
125
- ...asideNodes,
126
- ...(!hasBottomActions ? actionsNodes : []),
127
- ...(dismissNodes.length ? dismissNodes : (defaultDismissNode ? [defaultDismissNode] : []))
128
- ];
129
-
130
- bannerEl = _.div(
131
- p,
132
- iconNodes.length ? _.div({ class: "cms-banner-icon" }, ...iconNodes) : null,
133
- _.div(
134
- { class: "cms-banner-body" },
135
- _.div(
136
- { class: "cms-banner-copy" },
137
- titleNodes.length ? _.div({ class: "cms-banner-title" }, ...titleNodes) : null,
138
- messageNodes.length ? _.div({ class: "cms-banner-message" }, ...messageNodes) : null,
139
- descriptionNodes.length ? _.div({ class: "cms-banner-description" }, ...descriptionNodes) : null,
140
- bodyNodes.length ? _.div({ class: "cms-banner-extra" }, ...bodyNodes) : null,
141
- metaNodes.length ? _.div({ class: "cms-banner-meta" }, ...metaNodes) : null
142
- ),
143
- hasBottomActions && actionsNodes.length
144
- ? _.div({ class: "cms-banner-actions" }, ...actionsNodes)
145
- : null
146
- ),
147
- sideNodes.length ? _.div({ class: "cms-banner-side" }, ...sideNodes) : null
148
- );
149
-
150
- const role = p.role || (() => {
151
- const state = resolveStateValue();
152
- return state === "danger" || state === "warning" ? "alert" : "status";
153
- })();
154
- bannerEl.setAttribute("role", role);
155
- setPropertyProps(bannerEl, rawProps);
156
- return bannerEl;
157
- };
158
-
159
- // Esempio: JSswift.ui.Banner({ type: "warning", title: "Pagamento in sospeso", message: "Aggiorna il batch entro le 18:00" })
160
-
161
- UI.Alert = (...args) => {
162
- const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
163
- const slots = rawProps.slots || {};
164
-
165
- const resolveStateValue = () => normalizeState(uiUnwrap(rawProps.type) || uiUnwrap(rawProps.state) || uiUnwrap(rawProps.color) || "warning");
166
- const stateClass = uiComputed([rawProps.type, rawProps.state, rawProps.color], () => {
167
- const state = resolveStateValue();
168
- return state ? `cms-state-${state}` : "";
169
- });
170
-
171
- const props = { ...rawProps };
172
- const p = JSswift.omit(props, [
173
- "actions", "aside", "closeLabel", "description", "dismiss", "dismissible",
174
- "icon", "iconSize", "message", "meta", "onDismiss", "slots", "state",
175
- "subtitle", "title", "type", "variant"
176
- ]);
177
- p.class = uiClass([
178
- "cms-clear-set",
179
- "cms-alert",
180
- "cms-singularity",
181
- stateClass,
182
- uiWhen(rawProps.dismissible, "is-dismissible"),
183
- props.class
184
- ]);
185
- p.style = { ...(props.style || {}) };
186
-
187
- const autoIconMap = {
188
- success: "check_circle",
189
- warning: "warning",
190
- danger: "error",
191
- info: "info",
192
- primary: "bolt",
193
- secondary: "campaign",
194
- dark: "shield",
195
- light: "notifications"
196
- };
197
-
198
- const iconFallback = (() => {
199
- if (rawProps.icon === false || rawProps.icon === null) return null;
200
- if (rawProps.icon != null) {
201
- return typeof rawProps.icon === "string"
202
- ? UI.Icon({ name: rawProps.icon, size: rawProps.iconSize || "sm" })
203
- : JSswift.ui.slot(rawProps.icon, { as: "icon" });
204
- }
205
- const iconName = autoIconMap[resolveStateValue()] || autoIconMap.warning;
206
- return UI.Icon({ name: iconName, size: rawProps.iconSize || "sm" });
207
- })();
208
-
209
- const titleNodes = renderSlotToArray(slots, "title", {}, rawProps.title);
210
- const messageNodes = renderSlotToArray(slots, "message", {}, rawProps.message != null ? rawProps.message : children);
211
- const descriptionNodes = renderSlotToArray(slots, "description", {}, rawProps.description ?? rawProps.subtitle);
212
- const metaNodes = renderSlotToArray(slots, "meta", {}, rawProps.meta);
213
- const actionsNodes = renderSlotToArray(slots, "actions", {}, rawProps.actions);
214
- const asideNodes = renderSlotToArray(slots, "aside", {}, rawProps.aside);
215
- const iconNodes = renderSlotToArray(slots, "icon", {}, iconFallback);
216
- const dismissNodes = renderSlotToArray(slots, "dismiss", {}, rawProps.dismiss);
217
-
218
- let alertEl = null;
219
- const defaultDismissNode = rawProps.dismissible || rawProps.onDismiss
220
- ? UI.Btn({
221
- class: "cms-alert-close",
222
- outline: true,
223
- size: "sm",
224
- "aria-label": rawProps.closeLabel || "Chiudi alert",
225
- onClick: (e) => {
226
- rawProps.onDismiss?.(e);
227
- if (e.defaultPrevented) return;
228
- alertEl?.remove();
229
- }
230
- }, UI.Icon({ name: "close", size: "sm" }))
231
- : null;
232
-
233
- alertEl = _.div(
234
- p,
235
- iconNodes.length ? _.div({ class: "cms-alert-icon" }, ...iconNodes) : null,
236
- _.div(
237
- { class: "cms-alert-body" },
238
- titleNodes.length ? _.div({ class: "cms-alert-title" }, ...titleNodes) : null,
239
- messageNodes.length ? _.div({ class: "cms-alert-message" }, ...messageNodes) : null,
240
- descriptionNodes.length ? _.div({ class: "cms-alert-description" }, ...descriptionNodes) : null,
241
- metaNodes.length ? _.div({ class: "cms-alert-meta" }, ...metaNodes) : null,
242
- actionsNodes.length ? _.div({ class: "cms-alert-actions" }, ...actionsNodes) : null
243
- ),
244
- (asideNodes.length || dismissNodes.length || defaultDismissNode)
245
- ? _.div(
246
- { class: "cms-alert-side" },
247
- ...asideNodes,
248
- ...(dismissNodes.length ? dismissNodes : (defaultDismissNode ? [defaultDismissNode] : []))
249
- )
250
- : null
251
- );
252
-
253
- alertEl.setAttribute("role", resolveStateValue() === "danger" || resolveStateValue() === "warning" ? "alert" : "status");
254
- setPropertyProps(alertEl, rawProps);
255
- return alertEl;
256
- };
257
-
258
- // Esempio: JSswift.ui.Alert({ type: "warning", title: "Review richiesta", message: "Controlla il batch prima del go-live" })
259
-
260
- UI.EmptyState = (...args) => {
261
- const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
262
- const slots = rawProps.slots || {};
263
- const resolveStateValue = () => normalizeState(uiUnwrap(rawProps.state) || uiUnwrap(rawProps.color) || "");
264
- const stateClass = uiComputed([rawProps.state, rawProps.color], () => {
265
- const state = resolveStateValue();
266
- return state ? `cms-state-${state}` : "";
267
- });
268
-
269
- const props = { ...rawProps };
270
- const p = JSswift.omit(props, [
271
- "actions", "description", "eyebrow", "icon", "iconSize", "illustration", "media",
272
- "message", "meta", "slots", "state", "title"
273
- ]);
274
- p.class = uiClass([
275
- "cms-clear-set",
276
- "cms-empty-state",
277
- "cms-singularity",
278
- stateClass,
279
- uiWhen(rawProps.compact, "compact"),
280
- uiWhen(rawProps.inline, "inline"),
281
- props.class
282
- ]);
283
- p.style = { ...(props.style || {}) };
284
-
285
- const iconFallback = rawProps.icon === false
286
- ? null
287
- : (rawProps.icon != null
288
- ? (typeof rawProps.icon === "string"
289
- ? UI.Icon({ name: rawProps.icon, size: rawProps.iconSize || "xl" })
290
- : JSswift.ui.slot(rawProps.icon, { as: "icon" }))
291
- : UI.Icon({ name: "inbox", size: rawProps.iconSize || "xl" }));
292
-
293
- const illustrationNodes = renderSlotToArray(slots, "illustration", {}, rawProps.illustration ?? rawProps.media);
294
- const iconNodes = renderSlotToArray(slots, "icon", {}, iconFallback);
295
- const eyebrowNodes = renderSlotToArray(slots, "eyebrow", {}, rawProps.eyebrow);
296
- const titleNodes = renderSlotToArray(slots, "title", {}, rawProps.title);
297
- const messageNodes = renderSlotToArray(slots, "message", {}, rawProps.message != null ? rawProps.message : children);
298
- const descriptionNodes = renderSlotToArray(slots, "description", {}, rawProps.description);
299
- const metaNodes = renderSlotToArray(slots, "meta", {}, rawProps.meta);
300
- const actionsNodes = renderSlotToArray(slots, "actions", {}, rawProps.actions);
301
- const bodyNodes = renderSlotToArray(slots, "default", {}, rawProps.message != null ? children : null);
302
-
303
- const emptyState = _.section(
304
- p,
305
- illustrationNodes.length
306
- ? _.div({ class: "cms-empty-state-illustration" }, ...illustrationNodes)
307
- : null,
308
- iconNodes.length ? _.div({ class: "cms-empty-state-icon" }, ...iconNodes) : null,
309
- eyebrowNodes.length ? _.div({ class: "cms-empty-state-eyebrow" }, ...eyebrowNodes) : null,
310
- titleNodes.length ? _.div({ class: "cms-empty-state-title" }, ...titleNodes) : null,
311
- messageNodes.length ? _.div({ class: "cms-empty-state-message" }, ...messageNodes) : null,
312
- descriptionNodes.length ? _.div({ class: "cms-empty-state-description" }, ...descriptionNodes) : null,
313
- metaNodes.length ? _.div({ class: "cms-empty-state-meta" }, ...metaNodes) : null,
314
- bodyNodes.length ? _.div({ class: "cms-empty-state-extra" }, ...bodyNodes) : null,
315
- actionsNodes.length ? _.div({ class: "cms-empty-state-actions" }, ...actionsNodes) : null
316
- );
317
-
318
- setPropertyProps(emptyState, rawProps);
319
- return emptyState;
320
- };
321
-
322
- // Esempio: JSswift.ui.EmptyState({ title: "No results", message: "Prova a cambiare i filtri" })
323
-
324
- // -------------------------------
325
- // 3) APP SHELL
326
- // -------------------------------
327
- let drawerStateKey = "jsswift:drawer-open";
328
- const drawerToggleIcons = new Set();
329
- const drawerElsByKey = new Map();
330
- const readDrawerOpen = (key = drawerStateKey) => {
331
- const store = JSswift?.store;
332
- if (store?.get) {
333
- const stored = store.get(key, undefined);
334
- if (typeof stored === "boolean") return stored;
335
- }
336
- try {
337
- return localStorage.getItem(key) === "1";
338
- } catch {
339
- return false;
340
- }
341
- };
342
- const writeDrawerOpen = (open, key = drawerStateKey) => {
343
- const store = JSswift?.store;
344
- if (store?.set) {
345
- store.set(key, !!open);
346
- return;
347
- }
348
- try {
349
- localStorage.setItem(key, open ? "1" : "0");
350
- } catch {
351
- // ignore storage errors
352
- }
353
- };
354
- let drawerOpen = readDrawerOpen();
355
- const setDrawerOpen = (open, key = drawerStateKey) => {
356
- drawerOpen = !!open;
357
- const drawerEls = drawerElsByKey.get(key);
358
- if (drawerEls && drawerEls.size) {
359
- drawerEls.forEach((el) => el?.classList.toggle("open", drawerOpen));
360
- } else {
361
- const drawerEl = document.querySelector(".cms-drawer");
362
- if (drawerEl) drawerEl.classList.toggle("open", drawerOpen);
363
- }
364
- drawerToggleIcons.forEach((entry) => {
365
- if (!entry) return;
366
- if (typeof entry.update === "function") {
367
- entry.update(drawerOpen);
368
- return;
369
- }
370
- const { el, openIcon, closeIcon } = entry;
371
- if (el) el.textContent = drawerOpen ? openIcon : closeIcon;
372
- });
373
- writeDrawerOpen(drawerOpen, key);
374
- return drawerOpen;
375
- };
376
-
1
+ UI.Banner = (...args) => {
2
+ const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
3
+ const slots = rawProps.slots || {};
4
+
5
+ const resolveStateValue = () => normalizeState(uiUnwrap(rawProps.type) || uiUnwrap(rawProps.state) || "");
6
+ const ctx = {
7
+ state: resolveStateValue,
8
+ dismissible: () => !!uiUnwrap(rawProps.dismissible)
9
+ };
10
+
11
+ const typeClass = uiComputed([rawProps.type, rawProps.state], () => resolveStateValue());
12
+ const variantClass = uiComputed(rawProps.variant, () => {
13
+ const variant = String(uiUnwrap(rawProps.variant) || "").toLowerCase();
14
+ return ["solid", "outline", "ghost"].includes(variant) ? `jss-banner-${variant}` : "";
15
+ });
16
+ const actionsPlacementClass = uiComputed([rawProps.actionsPlacement, rawProps.stack], () => {
17
+ const placement = String(uiUnwrap(rawProps.actionsPlacement) || "").toLowerCase();
18
+ return placement === "bottom" || !!uiUnwrap(rawProps.stack) ? "jss-banner-actions-bottom" : "";
19
+ });
20
+ const stackClass = uiComputed(rawProps.stack, () => uiUnwrap(rawProps.stack) ? "jss-banner-stack" : "");
21
+
22
+ const props = { ...rawProps };
23
+ const p = JSswift.omit(props, [
24
+ "actions", "actionsPlacement", "accent", "aside", "body", "closeLabel", "description",
25
+ "dismiss", "dismissible", "icon", "iconSize", "message", "meta", "onDismiss", "slots",
26
+ "stack", "state", "subtitle", "title", "type", "variant"
27
+ ]);
28
+ p.class = uiClass([
29
+ "jss-clear-set",
30
+ "jss-banner",
31
+ "jss-singularity",
32
+ typeClass,
33
+ variantClass,
34
+ actionsPlacementClass,
35
+ stackClass,
36
+ props.class
37
+ ]);
38
+ p.style = { ...(props.style || {}) };
39
+
40
+ const toneToCss = (value) => {
41
+ if (value == null || value === "") return "";
42
+ const v = uiUnwrap(value);
43
+ if (v == null || v === "") return "";
44
+ const s = String(v);
45
+ if (isTokenCSS(s)) return `var(--jss-${s})`;
46
+ return s;
47
+ };
48
+
49
+ if (rawProps.accent != null || uiIsReactive(rawProps.accent)) {
50
+ p.style["--jss-banner-accent"] = () => toneToCss(rawProps.accent);
51
+ p.style["--jss-banner-border"] = () => {
52
+ const tone = toneToCss(rawProps.accent);
53
+ return tone ? `color-mix(in srgb, ${tone} 38%, var(--jss-border-color))` : "";
54
+ };
55
+ p.style["--jss-banner-bg"] = () => {
56
+ const tone = toneToCss(rawProps.accent);
57
+ return tone ? `color-mix(in srgb, ${tone} 14%, var(--jss-panel))` : "";
58
+ };
59
+ p.style["--jss-banner-color"] = () => {
60
+ const tone = toneToCss(rawProps.accent);
61
+ return tone ? `color-mix(in srgb, ${tone} 78%, white)` : "";
62
+ };
63
+ }
64
+
65
+ const autoIconMap = {
66
+ success: "check_circle",
67
+ warning: "warning",
68
+ danger: "error",
69
+ info: "info",
70
+ primary: "bolt",
71
+ secondary: "campaign",
72
+ dark: "shield",
73
+ light: "notifications"
74
+ };
75
+
76
+ const iconFallback = (() => {
77
+ if (rawProps.icon === false || rawProps.icon === null) return null;
78
+ if (rawProps.icon != null) {
79
+ return typeof rawProps.icon === "string"
80
+ ? UI.Icon({ name: rawProps.icon, size: rawProps.iconSize || rawProps.size || "md" })
81
+ : JSswift.ui.slot(rawProps.icon, { as: "icon" });
82
+ }
83
+ const state = resolveStateValue();
84
+ const iconName = state ? autoIconMap[state] : null;
85
+ return iconName ? UI.Icon({ name: iconName, size: rawProps.iconSize || rawProps.size || "md" }) : null;
86
+ })();
87
+
88
+ const titleNodes = renderSlotToArray(slots, "title", ctx, rawProps.title);
89
+ const messageNodes = renderSlotToArray(slots, "message", ctx, rawProps.message != null ? rawProps.message : children);
90
+ const descriptionNodes = renderSlotToArray(slots, "description", ctx, rawProps.description ?? rawProps.subtitle);
91
+ const metaNodes = renderSlotToArray(slots, "meta", ctx, rawProps.meta);
92
+ const bodyNodes = renderSlotToArray(
93
+ slots,
94
+ "default",
95
+ ctx,
96
+ rawProps.body != null ? [rawProps.body, ...(children || [])] : (rawProps.message != null ? children : null)
97
+ );
98
+ const iconNodes = renderSlotToArray(slots, "icon", ctx, iconFallback);
99
+ const asideNodes = renderSlotToArray(slots, "aside", ctx, rawProps.aside);
100
+ const actionsNodes = renderSlotToArray(slots, "actions", ctx, rawProps.actions);
101
+ const dismissNodes = renderSlotToArray(slots, "dismiss", ctx, rawProps.dismiss);
102
+
103
+ const hasBottomActions = (() => {
104
+ const placement = String(uiUnwrap(rawProps.actionsPlacement) || "").toLowerCase();
105
+ return placement === "bottom" || !!uiUnwrap(rawProps.stack);
106
+ })();
107
+ const isDismissible = !!uiUnwrap(rawProps.dismissible) || !!rawProps.onDismiss;
108
+
109
+ let bannerEl = null;
110
+ const defaultDismissNode = isDismissible
111
+ ? UI.Btn({
112
+ class: "jss-banner-close",
113
+ outline: true,
114
+ size: rawProps.size || "sm",
115
+ "aria-label": rawProps.closeLabel || "Chiudi banner",
116
+ onClick: (e) => {
117
+ rawProps.onDismiss?.(e);
118
+ if (e.defaultPrevented) return;
119
+ bannerEl?.remove();
120
+ }
121
+ }, UI.Icon({ name: "close", size: rawProps.size || "sm" }))
122
+ : null;
123
+
124
+ const sideNodes = [
125
+ ...asideNodes,
126
+ ...(!hasBottomActions ? actionsNodes : []),
127
+ ...(dismissNodes.length ? dismissNodes : (defaultDismissNode ? [defaultDismissNode] : []))
128
+ ];
129
+
130
+ bannerEl = _.div(
131
+ p,
132
+ iconNodes.length ? _.div({ class: "jss-banner-icon" }, ...iconNodes) : null,
133
+ _.div(
134
+ { class: "jss-banner-body" },
135
+ _.div(
136
+ { class: "jss-banner-copy" },
137
+ titleNodes.length ? _.div({ class: "jss-banner-title" }, ...titleNodes) : null,
138
+ messageNodes.length ? _.div({ class: "jss-banner-message" }, ...messageNodes) : null,
139
+ descriptionNodes.length ? _.div({ class: "jss-banner-description" }, ...descriptionNodes) : null,
140
+ bodyNodes.length ? _.div({ class: "jss-banner-extra" }, ...bodyNodes) : null,
141
+ metaNodes.length ? _.div({ class: "jss-banner-meta" }, ...metaNodes) : null
142
+ ),
143
+ hasBottomActions && actionsNodes.length
144
+ ? _.div({ class: "jss-banner-actions" }, ...actionsNodes)
145
+ : null
146
+ ),
147
+ sideNodes.length ? _.div({ class: "jss-banner-side" }, ...sideNodes) : null
148
+ );
149
+
150
+ const role = p.role || (() => {
151
+ const state = resolveStateValue();
152
+ return state === "danger" || state === "warning" ? "alert" : "status";
153
+ })();
154
+ bannerEl.setAttribute("role", role);
155
+ setPropertyProps(bannerEl, rawProps);
156
+ return bannerEl;
157
+ };
158
+
159
+ // Esempio: JSswift.ui.Banner({ type: "warning", title: "Pagamento in sospeso", message: "Aggiorna il batch entro le 18:00" })
160
+
161
+ UI.Alert = (...args) => {
162
+ const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
163
+ const slots = rawProps.slots || {};
164
+
165
+ const resolveStateValue = () => normalizeState(uiUnwrap(rawProps.type) || uiUnwrap(rawProps.state) || uiUnwrap(rawProps.color) || "warning");
166
+ const stateClass = uiComputed([rawProps.type, rawProps.state, rawProps.color], () => {
167
+ const state = resolveStateValue();
168
+ return state ? `jss-state-${state}` : "";
169
+ });
170
+
171
+ const props = { ...rawProps };
172
+ const p = JSswift.omit(props, [
173
+ "actions", "aside", "closeLabel", "description", "dismiss", "dismissible",
174
+ "icon", "iconSize", "message", "meta", "onDismiss", "slots", "state",
175
+ "subtitle", "title", "type", "variant"
176
+ ]);
177
+ p.class = uiClass([
178
+ "jss-clear-set",
179
+ "jss-alert",
180
+ "jss-singularity",
181
+ stateClass,
182
+ uiWhen(rawProps.dismissible, "is-dismissible"),
183
+ props.class
184
+ ]);
185
+ p.style = { ...(props.style || {}) };
186
+
187
+ const autoIconMap = {
188
+ success: "check_circle",
189
+ warning: "warning",
190
+ danger: "error",
191
+ info: "info",
192
+ primary: "bolt",
193
+ secondary: "campaign",
194
+ dark: "shield",
195
+ light: "notifications"
196
+ };
197
+
198
+ const iconFallback = (() => {
199
+ if (rawProps.icon === false || rawProps.icon === null) return null;
200
+ if (rawProps.icon != null) {
201
+ return typeof rawProps.icon === "string"
202
+ ? UI.Icon({ name: rawProps.icon, size: rawProps.iconSize || "sm" })
203
+ : JSswift.ui.slot(rawProps.icon, { as: "icon" });
204
+ }
205
+ const iconName = autoIconMap[resolveStateValue()] || autoIconMap.warning;
206
+ return UI.Icon({ name: iconName, size: rawProps.iconSize || "sm" });
207
+ })();
208
+
209
+ const titleNodes = renderSlotToArray(slots, "title", {}, rawProps.title);
210
+ const messageNodes = renderSlotToArray(slots, "message", {}, rawProps.message != null ? rawProps.message : children);
211
+ const descriptionNodes = renderSlotToArray(slots, "description", {}, rawProps.description ?? rawProps.subtitle);
212
+ const metaNodes = renderSlotToArray(slots, "meta", {}, rawProps.meta);
213
+ const actionsNodes = renderSlotToArray(slots, "actions", {}, rawProps.actions);
214
+ const asideNodes = renderSlotToArray(slots, "aside", {}, rawProps.aside);
215
+ const iconNodes = renderSlotToArray(slots, "icon", {}, iconFallback);
216
+ const dismissNodes = renderSlotToArray(slots, "dismiss", {}, rawProps.dismiss);
217
+
218
+ let alertEl = null;
219
+ const defaultDismissNode = rawProps.dismissible || rawProps.onDismiss
220
+ ? UI.Btn({
221
+ class: "jss-alert-close",
222
+ outline: true,
223
+ size: "sm",
224
+ "aria-label": rawProps.closeLabel || "Chiudi alert",
225
+ onClick: (e) => {
226
+ rawProps.onDismiss?.(e);
227
+ if (e.defaultPrevented) return;
228
+ alertEl?.remove();
229
+ }
230
+ }, UI.Icon({ name: "close", size: "sm" }))
231
+ : null;
232
+
233
+ alertEl = _.div(
234
+ p,
235
+ iconNodes.length ? _.div({ class: "jss-alert-icon" }, ...iconNodes) : null,
236
+ _.div(
237
+ { class: "jss-alert-body" },
238
+ titleNodes.length ? _.div({ class: "jss-alert-title" }, ...titleNodes) : null,
239
+ messageNodes.length ? _.div({ class: "jss-alert-message" }, ...messageNodes) : null,
240
+ descriptionNodes.length ? _.div({ class: "jss-alert-description" }, ...descriptionNodes) : null,
241
+ metaNodes.length ? _.div({ class: "jss-alert-meta" }, ...metaNodes) : null,
242
+ actionsNodes.length ? _.div({ class: "jss-alert-actions" }, ...actionsNodes) : null
243
+ ),
244
+ (asideNodes.length || dismissNodes.length || defaultDismissNode)
245
+ ? _.div(
246
+ { class: "jss-alert-side" },
247
+ ...asideNodes,
248
+ ...(dismissNodes.length ? dismissNodes : (defaultDismissNode ? [defaultDismissNode] : []))
249
+ )
250
+ : null
251
+ );
252
+
253
+ alertEl.setAttribute("role", resolveStateValue() === "danger" || resolveStateValue() === "warning" ? "alert" : "status");
254
+ setPropertyProps(alertEl, rawProps);
255
+ return alertEl;
256
+ };
257
+
258
+ // Esempio: JSswift.ui.Alert({ type: "warning", title: "Review richiesta", message: "Controlla il batch prima del go-live" })
259
+
260
+ UI.EmptyState = (...args) => {
261
+ const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
262
+ const slots = rawProps.slots || {};
263
+ const resolveStateValue = () => normalizeState(uiUnwrap(rawProps.state) || uiUnwrap(rawProps.color) || "");
264
+ const stateClass = uiComputed([rawProps.state, rawProps.color], () => {
265
+ const state = resolveStateValue();
266
+ return state ? `jss-state-${state}` : "";
267
+ });
268
+
269
+ const props = { ...rawProps };
270
+ const p = JSswift.omit(props, [
271
+ "actions", "description", "eyebrow", "icon", "iconSize", "illustration", "media",
272
+ "message", "meta", "slots", "state", "title"
273
+ ]);
274
+ p.class = uiClass([
275
+ "jss-clear-set",
276
+ "jss-empty-state",
277
+ "jss-singularity",
278
+ stateClass,
279
+ uiWhen(rawProps.compact, "compact"),
280
+ uiWhen(rawProps.inline, "inline"),
281
+ props.class
282
+ ]);
283
+ p.style = { ...(props.style || {}) };
284
+
285
+ const iconFallback = rawProps.icon === false
286
+ ? null
287
+ : (rawProps.icon != null
288
+ ? (typeof rawProps.icon === "string"
289
+ ? UI.Icon({ name: rawProps.icon, size: rawProps.iconSize || "xl" })
290
+ : JSswift.ui.slot(rawProps.icon, { as: "icon" }))
291
+ : UI.Icon({ name: "inbox", size: rawProps.iconSize || "xl" }));
292
+
293
+ const illustrationNodes = renderSlotToArray(slots, "illustration", {}, rawProps.illustration ?? rawProps.media);
294
+ const iconNodes = renderSlotToArray(slots, "icon", {}, iconFallback);
295
+ const eyebrowNodes = renderSlotToArray(slots, "eyebrow", {}, rawProps.eyebrow);
296
+ const titleNodes = renderSlotToArray(slots, "title", {}, rawProps.title);
297
+ const messageNodes = renderSlotToArray(slots, "message", {}, rawProps.message != null ? rawProps.message : children);
298
+ const descriptionNodes = renderSlotToArray(slots, "description", {}, rawProps.description);
299
+ const metaNodes = renderSlotToArray(slots, "meta", {}, rawProps.meta);
300
+ const actionsNodes = renderSlotToArray(slots, "actions", {}, rawProps.actions);
301
+ const bodyNodes = renderSlotToArray(slots, "default", {}, rawProps.message != null ? children : null);
302
+
303
+ const emptyState = _.section(
304
+ p,
305
+ illustrationNodes.length
306
+ ? _.div({ class: "jss-empty-state-illustration" }, ...illustrationNodes)
307
+ : null,
308
+ iconNodes.length ? _.div({ class: "jss-empty-state-icon" }, ...iconNodes) : null,
309
+ eyebrowNodes.length ? _.div({ class: "jss-empty-state-eyebrow" }, ...eyebrowNodes) : null,
310
+ titleNodes.length ? _.div({ class: "jss-empty-state-title" }, ...titleNodes) : null,
311
+ messageNodes.length ? _.div({ class: "jss-empty-state-message" }, ...messageNodes) : null,
312
+ descriptionNodes.length ? _.div({ class: "jss-empty-state-description" }, ...descriptionNodes) : null,
313
+ metaNodes.length ? _.div({ class: "jss-empty-state-meta" }, ...metaNodes) : null,
314
+ bodyNodes.length ? _.div({ class: "jss-empty-state-extra" }, ...bodyNodes) : null,
315
+ actionsNodes.length ? _.div({ class: "jss-empty-state-actions" }, ...actionsNodes) : null
316
+ );
317
+
318
+ setPropertyProps(emptyState, rawProps);
319
+ return emptyState;
320
+ };
321
+
322
+ // Esempio: JSswift.ui.EmptyState({ title: "No results", message: "Prova a cambiare i filtri" })
323
+
324
+ // -------------------------------
325
+ // 3) APP SHELL
326
+ // -------------------------------
327
+ let drawerStateKey = "jsswift:drawer-open";
328
+ const drawerToggleIcons = new Set();
329
+ const drawerElsByKey = new Map();
330
+ const readDrawerOpen = (key = drawerStateKey) => {
331
+ const store = JSswift?.store;
332
+ if (store?.get) {
333
+ const stored = store.get(key, undefined);
334
+ if (typeof stored === "boolean") return stored;
335
+ }
336
+ try {
337
+ return localStorage.getItem(key) === "1";
338
+ } catch {
339
+ return false;
340
+ }
341
+ };
342
+ const writeDrawerOpen = (open, key = drawerStateKey) => {
343
+ const store = JSswift?.store;
344
+ if (store?.set) {
345
+ store.set(key, !!open);
346
+ return;
347
+ }
348
+ try {
349
+ localStorage.setItem(key, open ? "1" : "0");
350
+ } catch {
351
+ // ignore storage errors
352
+ }
353
+ };
354
+ let drawerOpen = readDrawerOpen();
355
+ const setDrawerOpen = (open, key = drawerStateKey) => {
356
+ drawerOpen = !!open;
357
+ const drawerEls = drawerElsByKey.get(key);
358
+ if (drawerEls && drawerEls.size) {
359
+ drawerEls.forEach((el) => el?.classList.toggle("open", drawerOpen));
360
+ } else {
361
+ const drawerEl = document.querySelector(".jss-drawer");
362
+ if (drawerEl) drawerEl.classList.toggle("open", drawerOpen);
363
+ }
364
+ drawerToggleIcons.forEach((entry) => {
365
+ if (!entry) return;
366
+ if (typeof entry.update === "function") {
367
+ entry.update(drawerOpen);
368
+ return;
369
+ }
370
+ const { el, openIcon, closeIcon } = entry;
371
+ if (el) el.textContent = drawerOpen ? openIcon : closeIcon;
372
+ });
373
+ writeDrawerOpen(drawerOpen, key);
374
+ return drawerOpen;
375
+ };
376
+