@jsswift/ui 1.0.41

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 (55) hide show
  1. package/dist/css/animation.css +121 -0
  2. package/dist/css/base.css +621 -0
  3. package/dist/css/docs.css +216 -0
  4. package/dist/css/min-ui.css +1 -0
  5. package/dist/css/responsive.css +3259 -0
  6. package/dist/css/ui-components.css +8757 -0
  7. package/dist/css/ui.css +12765 -0
  8. package/dist/css/ui.min.css +1 -0
  9. package/dist/fonts/Nunito-Black.ttf +0 -0
  10. package/dist/fonts/Nunito-BlackItalic.ttf +0 -0
  11. package/dist/fonts/Nunito-Bold.ttf +0 -0
  12. package/dist/fonts/Nunito-BoldItalic.ttf +0 -0
  13. package/dist/fonts/Nunito-ExtraBold.ttf +0 -0
  14. package/dist/fonts/Nunito-ExtraBoldItalic.ttf +0 -0
  15. package/dist/fonts/Nunito-ExtraLight.ttf +0 -0
  16. package/dist/fonts/Nunito-ExtraLightItalic.ttf +0 -0
  17. package/dist/fonts/Nunito-Italic.ttf +0 -0
  18. package/dist/fonts/Nunito-Light.ttf +0 -0
  19. package/dist/fonts/Nunito-LightItalic.ttf +0 -0
  20. package/dist/fonts/Nunito-Medium.ttf +0 -0
  21. package/dist/fonts/Nunito-MediumItalic.ttf +0 -0
  22. package/dist/fonts/Nunito-Regular.ttf +0 -0
  23. package/dist/fonts/Nunito-SemiBold.ttf +0 -0
  24. package/dist/fonts/Nunito-SemiBoldItalic.ttf +0 -0
  25. package/dist/img/logo.svg +131 -0
  26. package/dist/img/svg/tabler-icons-sprite.svg +39534 -0
  27. package/dist/min-ui.js +34 -0
  28. package/dist/ui.js +21027 -0
  29. package/dist/ui.min.js +34 -0
  30. package/package.json +40 -0
  31. package/src/00-bootstrap.js +330 -0
  32. package/src/01-foundation-helpers.js +1208 -0
  33. package/src/10-primitives-layout.js +5972 -0
  34. package/src/20-display-content.js +1014 -0
  35. package/src/21-list-content.js +341 -0
  36. package/src/22-banner-content.js +489 -0
  37. package/src/23-tooltip.js +326 -0
  38. package/src/31-form-advanced.js +3149 -0
  39. package/src/40-navigation.js +1454 -0
  40. package/src/41-tab-panel.js +1090 -0
  41. package/src/50-feedback.js +1209 -0
  42. package/src/51-feedback-service.js +166 -0
  43. package/src/60-shell-app.js +1483 -0
  44. package/src/61-form-service.js +450 -0
  45. package/src/70-dialog.js +401 -0
  46. package/src/71-menu-overlays.js +1442 -0
  47. package/src/80-data.js +503 -0
  48. package/src/README.md +159 -0
  49. package/src/css/README.md +17 -0
  50. package/src/css/animation.css +121 -0
  51. package/src/css/base.css +621 -0
  52. package/src/css/docs.css +216 -0
  53. package/src/css/responsive.css +3259 -0
  54. package/src/css/ui-components.css +8757 -0
  55. package/src/modules.json +176 -0
@@ -0,0 +1,489 @@
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
+ if (JSswift.isDev?.()) {
159
+ UI.meta = UI.meta || {};
160
+ UI.meta.Banner = {
161
+ signature: "UI.Banner(...children) | UI.Banner(props, ...children)",
162
+ props: {
163
+ title: "String|Node|Function|Array",
164
+ message: "String|Node|Function|Array",
165
+ description: "String|Node|Function|Array",
166
+ meta: "String|Node|Function|Array",
167
+ icon: "String|Node|Function|Array|false",
168
+ actions: "Node|Function|Array",
169
+ aside: "Node|Function|Array",
170
+ body: "Node|Function|Array",
171
+ dismissible: "boolean",
172
+ dismiss: "Node|Function|Array",
173
+ onDismiss: "function",
174
+ closeLabel: "string",
175
+ type: "success|warning|danger|error|info|primary|secondary|light|dark",
176
+ state: "success|warning|danger|error|info|primary|secondary|light|dark",
177
+ accent: "string",
178
+ variant: "soft|solid|outline|ghost",
179
+ actionsPlacement: "end|bottom",
180
+ dense: "boolean",
181
+ stack: "boolean",
182
+ slots: "{ icon?, title?, message?, description?, meta?, actions?, aside?, dismiss?, default? }",
183
+ class: "string",
184
+ style: "object"
185
+ },
186
+ slots: {
187
+ icon: "Leading visual/icon content",
188
+ title: "Banner title content",
189
+ message: "Primary message content",
190
+ description: "Secondary/supporting text",
191
+ meta: "Meta information content",
192
+ actions: "Actions area content",
193
+ aside: "Right side support content",
194
+ dismiss: "Custom dismiss control",
195
+ default: "Extra banner body content"
196
+ },
197
+ returns: "HTMLDivElement",
198
+ description: "Structured banner with tone, actions, dismiss, and composable slots."
199
+ };
200
+ }
201
+ // Esempio: JSswift.ui.Banner({ type: "warning", title: "Pagamento in sospeso", message: "Aggiorna il batch entro le 18:00" })
202
+
203
+ UI.Alert = (...args) => {
204
+ const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
205
+ const slots = rawProps.slots || {};
206
+
207
+ const resolveStateValue = () => normalizeState(uiUnwrap(rawProps.type) || uiUnwrap(rawProps.state) || uiUnwrap(rawProps.color) || "warning");
208
+ const stateClass = uiComputed([rawProps.type, rawProps.state, rawProps.color], () => {
209
+ const state = resolveStateValue();
210
+ return state ? `cms-state-${state}` : "";
211
+ });
212
+
213
+ const props = { ...rawProps };
214
+ const p = JSswift.omit(props, [
215
+ "actions", "aside", "closeLabel", "description", "dismiss", "dismissible",
216
+ "icon", "iconSize", "message", "meta", "onDismiss", "slots", "state",
217
+ "subtitle", "title", "type", "variant"
218
+ ]);
219
+ p.class = uiClass([
220
+ "cms-clear-set",
221
+ "cms-alert",
222
+ "cms-singularity",
223
+ stateClass,
224
+ uiWhen(rawProps.dismissible, "is-dismissible"),
225
+ props.class
226
+ ]);
227
+ p.style = { ...(props.style || {}) };
228
+
229
+ const autoIconMap = {
230
+ success: "check_circle",
231
+ warning: "warning",
232
+ danger: "error",
233
+ info: "info",
234
+ primary: "bolt",
235
+ secondary: "campaign",
236
+ dark: "shield",
237
+ light: "notifications"
238
+ };
239
+
240
+ const iconFallback = (() => {
241
+ if (rawProps.icon === false || rawProps.icon === null) return null;
242
+ if (rawProps.icon != null) {
243
+ return typeof rawProps.icon === "string"
244
+ ? UI.Icon({ name: rawProps.icon, size: rawProps.iconSize || "sm" })
245
+ : JSswift.ui.slot(rawProps.icon, { as: "icon" });
246
+ }
247
+ const iconName = autoIconMap[resolveStateValue()] || autoIconMap.warning;
248
+ return UI.Icon({ name: iconName, size: rawProps.iconSize || "sm" });
249
+ })();
250
+
251
+ const titleNodes = renderSlotToArray(slots, "title", {}, rawProps.title);
252
+ const messageNodes = renderSlotToArray(slots, "message", {}, rawProps.message != null ? rawProps.message : children);
253
+ const descriptionNodes = renderSlotToArray(slots, "description", {}, rawProps.description ?? rawProps.subtitle);
254
+ const metaNodes = renderSlotToArray(slots, "meta", {}, rawProps.meta);
255
+ const actionsNodes = renderSlotToArray(slots, "actions", {}, rawProps.actions);
256
+ const asideNodes = renderSlotToArray(slots, "aside", {}, rawProps.aside);
257
+ const iconNodes = renderSlotToArray(slots, "icon", {}, iconFallback);
258
+ const dismissNodes = renderSlotToArray(slots, "dismiss", {}, rawProps.dismiss);
259
+
260
+ let alertEl = null;
261
+ const defaultDismissNode = rawProps.dismissible || rawProps.onDismiss
262
+ ? UI.Btn({
263
+ class: "cms-alert-close",
264
+ outline: true,
265
+ size: "sm",
266
+ "aria-label": rawProps.closeLabel || "Chiudi alert",
267
+ onClick: (e) => {
268
+ rawProps.onDismiss?.(e);
269
+ if (e.defaultPrevented) return;
270
+ alertEl?.remove();
271
+ }
272
+ }, UI.Icon({ name: "close", size: "sm" }))
273
+ : null;
274
+
275
+ alertEl = _.div(
276
+ p,
277
+ iconNodes.length ? _.div({ class: "cms-alert-icon" }, ...iconNodes) : null,
278
+ _.div(
279
+ { class: "cms-alert-body" },
280
+ titleNodes.length ? _.div({ class: "cms-alert-title" }, ...titleNodes) : null,
281
+ messageNodes.length ? _.div({ class: "cms-alert-message" }, ...messageNodes) : null,
282
+ descriptionNodes.length ? _.div({ class: "cms-alert-description" }, ...descriptionNodes) : null,
283
+ metaNodes.length ? _.div({ class: "cms-alert-meta" }, ...metaNodes) : null,
284
+ actionsNodes.length ? _.div({ class: "cms-alert-actions" }, ...actionsNodes) : null
285
+ ),
286
+ (asideNodes.length || dismissNodes.length || defaultDismissNode)
287
+ ? _.div(
288
+ { class: "cms-alert-side" },
289
+ ...asideNodes,
290
+ ...(dismissNodes.length ? dismissNodes : (defaultDismissNode ? [defaultDismissNode] : []))
291
+ )
292
+ : null
293
+ );
294
+
295
+ alertEl.setAttribute("role", resolveStateValue() === "danger" || resolveStateValue() === "warning" ? "alert" : "status");
296
+ setPropertyProps(alertEl, rawProps);
297
+ return alertEl;
298
+ };
299
+ if (JSswift.isDev?.()) {
300
+ UI.meta = UI.meta || {};
301
+ UI.meta.Alert = {
302
+ signature: "UI.Alert(...children) | UI.Alert(props, ...children)",
303
+ props: {
304
+ title: "String|Node|Function|Array",
305
+ message: "String|Node|Function|Array",
306
+ description: "String|Node|Function|Array",
307
+ meta: "Node|Function|Array",
308
+ icon: "String|Node|Function|Array|false",
309
+ actions: "Node|Function|Array",
310
+ aside: "Node|Function|Array",
311
+ dismissible: "boolean",
312
+ dismiss: "Node|Function|Array",
313
+ onDismiss: "function",
314
+ closeLabel: "string",
315
+ type: "success|warning|danger|error|info|primary|secondary|light|dark",
316
+ state: "success|warning|danger|error|info|primary|secondary|light|dark",
317
+ slots: "{ icon?, title?, message?, description?, meta?, actions?, aside?, dismiss?, default? }",
318
+ class: "string",
319
+ style: "object"
320
+ },
321
+ slots: {
322
+ icon: "Leading alert icon content",
323
+ title: "Alert title content",
324
+ message: "Primary alert copy",
325
+ description: "Secondary/supporting text",
326
+ meta: "Inline meta content",
327
+ actions: "Alert actions cluster",
328
+ aside: "Right side support content",
329
+ dismiss: "Custom dismiss control",
330
+ default: "Fallback alert message content"
331
+ },
332
+ returns: "HTMLDivElement",
333
+ description: "Alert compatto per warning inline, policy note e feedback persistente dentro page o card."
334
+ };
335
+ }
336
+ // Esempio: JSswift.ui.Alert({ type: "warning", title: "Review richiesta", message: "Controlla il batch prima del go-live" })
337
+
338
+ UI.EmptyState = (...args) => {
339
+ const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
340
+ const slots = rawProps.slots || {};
341
+ const resolveStateValue = () => normalizeState(uiUnwrap(rawProps.state) || uiUnwrap(rawProps.color) || "");
342
+ const stateClass = uiComputed([rawProps.state, rawProps.color], () => {
343
+ const state = resolveStateValue();
344
+ return state ? `cms-state-${state}` : "";
345
+ });
346
+
347
+ const props = { ...rawProps };
348
+ const p = JSswift.omit(props, [
349
+ "actions", "description", "eyebrow", "icon", "iconSize", "illustration", "media",
350
+ "message", "meta", "slots", "state", "title"
351
+ ]);
352
+ p.class = uiClass([
353
+ "cms-clear-set",
354
+ "cms-empty-state",
355
+ "cms-singularity",
356
+ stateClass,
357
+ uiWhen(rawProps.compact, "compact"),
358
+ uiWhen(rawProps.inline, "inline"),
359
+ props.class
360
+ ]);
361
+ p.style = { ...(props.style || {}) };
362
+
363
+ const iconFallback = rawProps.icon === false
364
+ ? null
365
+ : (rawProps.icon != null
366
+ ? (typeof rawProps.icon === "string"
367
+ ? UI.Icon({ name: rawProps.icon, size: rawProps.iconSize || "xl" })
368
+ : JSswift.ui.slot(rawProps.icon, { as: "icon" }))
369
+ : UI.Icon({ name: "inbox", size: rawProps.iconSize || "xl" }));
370
+
371
+ const illustrationNodes = renderSlotToArray(slots, "illustration", {}, rawProps.illustration ?? rawProps.media);
372
+ const iconNodes = renderSlotToArray(slots, "icon", {}, iconFallback);
373
+ const eyebrowNodes = renderSlotToArray(slots, "eyebrow", {}, rawProps.eyebrow);
374
+ const titleNodes = renderSlotToArray(slots, "title", {}, rawProps.title);
375
+ const messageNodes = renderSlotToArray(slots, "message", {}, rawProps.message != null ? rawProps.message : children);
376
+ const descriptionNodes = renderSlotToArray(slots, "description", {}, rawProps.description);
377
+ const metaNodes = renderSlotToArray(slots, "meta", {}, rawProps.meta);
378
+ const actionsNodes = renderSlotToArray(slots, "actions", {}, rawProps.actions);
379
+ const bodyNodes = renderSlotToArray(slots, "default", {}, rawProps.message != null ? children : null);
380
+
381
+ const emptyState = _.section(
382
+ p,
383
+ illustrationNodes.length
384
+ ? _.div({ class: "cms-empty-state-illustration" }, ...illustrationNodes)
385
+ : null,
386
+ iconNodes.length ? _.div({ class: "cms-empty-state-icon" }, ...iconNodes) : null,
387
+ eyebrowNodes.length ? _.div({ class: "cms-empty-state-eyebrow" }, ...eyebrowNodes) : null,
388
+ titleNodes.length ? _.div({ class: "cms-empty-state-title" }, ...titleNodes) : null,
389
+ messageNodes.length ? _.div({ class: "cms-empty-state-message" }, ...messageNodes) : null,
390
+ descriptionNodes.length ? _.div({ class: "cms-empty-state-description" }, ...descriptionNodes) : null,
391
+ metaNodes.length ? _.div({ class: "cms-empty-state-meta" }, ...metaNodes) : null,
392
+ bodyNodes.length ? _.div({ class: "cms-empty-state-extra" }, ...bodyNodes) : null,
393
+ actionsNodes.length ? _.div({ class: "cms-empty-state-actions" }, ...actionsNodes) : null
394
+ );
395
+
396
+ setPropertyProps(emptyState, rawProps);
397
+ return emptyState;
398
+ };
399
+ if (JSswift.isDev?.()) {
400
+ UI.meta = UI.meta || {};
401
+ UI.meta.EmptyState = {
402
+ signature: "UI.EmptyState(...children) | UI.EmptyState(props, ...children)",
403
+ props: {
404
+ eyebrow: "String|Node|Function|Array",
405
+ title: "String|Node|Function|Array",
406
+ message: "String|Node|Function|Array",
407
+ description: "String|Node|Function|Array",
408
+ meta: "Node|Function|Array",
409
+ icon: "String|Node|Function|Array|false",
410
+ illustration: "Node|Function|Array",
411
+ media: "Alias of illustration",
412
+ actions: "Node|Function|Array",
413
+ compact: "boolean",
414
+ inline: "boolean",
415
+ state: "primary|secondary|success|warning|danger|info|light|dark",
416
+ slots: "{ illustration?, icon?, eyebrow?, title?, message?, description?, meta?, actions?, default? }",
417
+ class: "string",
418
+ style: "object"
419
+ },
420
+ slots: {
421
+ illustration: "Top visual / illustration content",
422
+ icon: "Leading empty-state icon",
423
+ eyebrow: "Small kicker content",
424
+ title: "Primary empty-state heading",
425
+ message: "Primary message content",
426
+ description: "Secondary supporting text",
427
+ meta: "Meta badges or summary content",
428
+ actions: "Actions cluster",
429
+ default: "Extra empty-state body content"
430
+ },
431
+ returns: "HTMLElement",
432
+ description: "Surface per zero-results, onboarding vuoti e pannelli senza dati con CTA di recupero."
433
+ };
434
+ }
435
+ // Esempio: JSswift.ui.EmptyState({ title: "No results", message: "Prova a cambiare i filtri" })
436
+
437
+ // -------------------------------
438
+ // 3) APP SHELL
439
+ // -------------------------------
440
+ let drawerStateKey = "jsswift:drawer-open";
441
+ const drawerToggleIcons = new Set();
442
+ const drawerElsByKey = new Map();
443
+ const readDrawerOpen = (key = drawerStateKey) => {
444
+ const store = JSswift?.store;
445
+ if (store?.get) {
446
+ const stored = store.get(key, undefined);
447
+ if (typeof stored === "boolean") return stored;
448
+ }
449
+ try {
450
+ return localStorage.getItem(key) === "1";
451
+ } catch {
452
+ return false;
453
+ }
454
+ };
455
+ const writeDrawerOpen = (open, key = drawerStateKey) => {
456
+ const store = JSswift?.store;
457
+ if (store?.set) {
458
+ store.set(key, !!open);
459
+ return;
460
+ }
461
+ try {
462
+ localStorage.setItem(key, open ? "1" : "0");
463
+ } catch {
464
+ // ignore storage errors
465
+ }
466
+ };
467
+ let drawerOpen = readDrawerOpen();
468
+ const setDrawerOpen = (open, key = drawerStateKey) => {
469
+ drawerOpen = !!open;
470
+ const drawerEls = drawerElsByKey.get(key);
471
+ if (drawerEls && drawerEls.size) {
472
+ drawerEls.forEach((el) => el?.classList.toggle("open", drawerOpen));
473
+ } else {
474
+ const drawerEl = document.querySelector(".cms-drawer");
475
+ if (drawerEl) drawerEl.classList.toggle("open", drawerOpen);
476
+ }
477
+ drawerToggleIcons.forEach((entry) => {
478
+ if (!entry) return;
479
+ if (typeof entry.update === "function") {
480
+ entry.update(drawerOpen);
481
+ return;
482
+ }
483
+ const { el, openIcon, closeIcon } = entry;
484
+ if (el) el.textContent = drawerOpen ? openIcon : closeIcon;
485
+ });
486
+ writeDrawerOpen(drawerOpen, key);
487
+ return drawerOpen;
488
+ };
489
+