jsswift 2.0.0 → 2.1.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 (38) hide show
  1. package/AI.md +38 -73
  2. package/README.md +2 -0
  3. package/dist/build/build-configured-runtime.d.mts +29 -0
  4. package/dist/build/build-configured-runtime.mjs +92 -0
  5. package/dist/build/runtime-layout.mjs +13 -0
  6. package/dist/build/select-ui.mjs +167 -0
  7. package/dist/build/vite-jss.d.mts +2 -0
  8. package/dist/build/vite-jss.mjs +73 -0
  9. package/dist/cms.js +3522 -0
  10. package/dist/documentions/AI.md +38 -73
  11. package/dist/documentions/README.md +4 -2
  12. package/dist/documentions/components/table.md +13 -0
  13. package/dist/documentions/configured-runtime.md +82 -0
  14. package/dist/documentions/examples/crud.md +18 -7
  15. package/dist/documentions/getting-started.md +2 -0
  16. package/dist/documentions/knowledge-map.json +20 -1
  17. package/dist/documentions/ui/data-feedback-overlays.md +2 -0
  18. package/dist/documentions/ui/forms.md +2 -0
  19. package/dist/ui-src/00-bootstrap.js +330 -0
  20. package/dist/ui-src/01-foundation-helpers.js +809 -0
  21. package/dist/ui-src/10-primitives-layout.js +5091 -0
  22. package/dist/ui-src/20-display-content.js +780 -0
  23. package/dist/ui-src/21-list-content.js +246 -0
  24. package/dist/ui-src/22-banner-content.js +376 -0
  25. package/dist/ui-src/23-tooltip.js +276 -0
  26. package/dist/ui-src/31-form-advanced.js +2784 -0
  27. package/dist/ui-src/40-navigation.js +1262 -0
  28. package/dist/ui-src/41-tab-panel.js +896 -0
  29. package/dist/ui-src/50-feedback.js +999 -0
  30. package/dist/ui-src/51-feedback-service.js +166 -0
  31. package/dist/ui-src/60-shell-app.js +1223 -0
  32. package/dist/ui-src/61-form-service.js +368 -0
  33. package/dist/ui-src/70-dialog.js +333 -0
  34. package/dist/ui-src/71-menu-overlays.js +1217 -0
  35. package/dist/ui-src/80-data.js +437 -0
  36. package/dist/ui-src/modules.json +212 -0
  37. package/dist/ui.js +12565 -0
  38. package/package.json +26 -2
@@ -0,0 +1,780 @@
1
+ UI.Icon = (...args) => {
2
+ let props = {};
3
+ let children = [];
4
+ let name = "home";
5
+ let hasName = false;
6
+ const a = args[0];
7
+ const b = args[1];
8
+ const isPlainObject = (v) => v && typeof v === "object" && !v.nodeType && !Array.isArray(v) && !(v instanceof Function);
9
+
10
+ if (typeof a === "string") {
11
+ name = a;
12
+ hasName = true;
13
+ if (isPlainObject(b)) {
14
+ props = b;
15
+ if (args.length > 2) children = args.slice(2);
16
+ } else if (args.length > 1) {
17
+ children = args.slice(1);
18
+ }
19
+ } else if (isPlainObject(a)) {
20
+ props = a;
21
+ if (a.name != null) {
22
+ name = a.name;
23
+ hasName = true;
24
+ }
25
+ if (args.length > 1) children = args.slice(1);
26
+ } else if (args.length) {
27
+ children = args;
28
+ }
29
+
30
+ if (!hasName && children.length) {
31
+ name = children.length === 1 ? children[0] : children;
32
+ }
33
+ applyCommonProps(props);
34
+
35
+ const slots = props.slots || {};
36
+
37
+ const size = uiUnwrap(props.size);
38
+ const color = uiUnwrap(props.color);
39
+ const isFill = String(name).endsWith("-fill");
40
+ const style = { ...(props.style || {}) };
41
+ const sizeClass = uiComputed(props.size, () => {
42
+ const v = uiUnwrap(props.size);
43
+ return (typeof v === "string" && sizeMap[v]) ? v : "";
44
+ });
45
+
46
+ const hasVisualSurface = !!(props.color || props.clickable || props.border || props.glossy || props.glow || props.glass || props.shadow || props.outline || props.gradient || props.textGradient || props.lightShadow || props.radius);
47
+ const builtInSpriteIcons = {
48
+ "chevron-down": {
49
+ viewBox: "0 0 24 24",
50
+ paths: [{ d: "M6 9l6 6l6 -6", fill: "none", stroke: "currentColor", "stroke-width": 2, "stroke-linecap": "round", "stroke-linejoin": "round" }]
51
+ }
52
+ };
53
+ const createBuiltInSpriteIcon = (id) => {
54
+ const def = builtInSpriteIcons[id];
55
+ if (!def) return null;
56
+ return _.svg(
57
+ { width: "100%", height: "100%", viewBox: def.viewBox, fill: "none", "aria-hidden": "true", focusable: "false" },
58
+ ...def.paths.map((path) => _.path(path))
59
+ );
60
+ };
61
+ const wrapIcon = (icon) => {
62
+ const tooltip = uiUnwrap(props.tooltip);
63
+ if (tooltip == null || tooltip === false || !UI.Tooltip) return icon;
64
+ const tooltipProps = isPlainObject(tooltip)
65
+ ? { ...tooltip }
66
+ : { text: tooltip };
67
+ if (isPlainObject(props.tooltipProps)) Object.assign(tooltipProps, props.tooltipProps);
68
+ return UI.Tooltip({ ...tooltipProps, target: icon });
69
+ };
70
+
71
+ const cls = uiClass([
72
+ "jss-icon",
73
+ "material-icons",
74
+ ...(hasVisualSurface ? ["jss-clear-set", "jss-singularity"] : []),
75
+ sizeClass,
76
+ props.class
77
+ ]);
78
+ const p = JSswift.omit(props, ["name", "size", "class", "style", "label", "border", "color", "icon", "iconRight", "removable", "onRemove", "dense", "flat", "glossy", "outline", "slots", "spriteUrl", "tooltip", "tooltipProps"]);
79
+ p.class = cls;
80
+ if (Object.keys(style).length) p.style = style;
81
+
82
+ if (typeof name === "function" || (name && typeof name === "object")) {
83
+ const customNode = JSswift.ui.renderSlot(slots, "default", {}, name);
84
+ const content = renderSlotToArray(null, "default", {}, customNode);
85
+ const icon = _.span({ ...p, "data-icon": "custom" }, ...content);
86
+ setPropertyProps(icon, props);
87
+ return wrapIcon(icon);
88
+ }
89
+
90
+ const nameStr = String(name);
91
+ const useHref = nameStr.includes("#") ? nameStr : "";
92
+ let icon = null;
93
+ if (useHref) {
94
+ const spriteUrl =
95
+ props.spriteUrl ||
96
+ JSswift.config?.iconSpriteUrl ||
97
+ (typeof globalThis !== "undefined" ? globalThis.JSswift_setting?.iconSpriteUrl : null);
98
+ const symbolId = useHref.slice(useHref.indexOf("#") + 1);
99
+ const svg = spriteUrl
100
+ ? _.svg(
101
+ { width: "100%", height: "100%" },
102
+ _.use({ href: spriteUrl + useHref })
103
+ )
104
+ : createBuiltInSpriteIcon(symbolId) || _.svg(
105
+ { width: "100%", height: "100%" },
106
+ _.use({ href: useHref })
107
+ );
108
+ if (isFill) svg.style.fill = "currentColor";
109
+ icon = _.span({ ...p, "data-icon": nameStr }, svg, ...children);
110
+ } else {
111
+ icon = _.span({ ...p, "data-icon": nameStr }, nameStr, ...children);
112
+ }
113
+
114
+ if (size != null) {
115
+ let v = size;
116
+ if (JSswift.uiSizes.includes(size)) {
117
+ v = `var(--jss-icon-size-${size})`;
118
+ }
119
+ v = typeof v === "number" ? v + "px" : String(v);
120
+ icon.style.setProperty("--set-icon-size", v);
121
+ }
122
+
123
+ setPropertyProps(icon, props);
124
+ return wrapIcon(icon);
125
+ };
126
+
127
+ // Esempio: JSswift.ui.Icon({ name: "home", size: 18 })
128
+
129
+ UI.Badge = (...args) => {
130
+ const { props, children } = JSswift.uiNormalizeArgs(args);
131
+ const slots = props.slots || {};
132
+ const sizeClass = uiComputed(props.size, () => {
133
+ const v = uiUnwrap(props.size);
134
+ return (typeof v === "string" && sizeMap[v]) ? `jss-size-${v}` : "";
135
+ });
136
+ const cls = uiClass([
137
+ "jss-clear-set",
138
+ "jss-badge",
139
+ "jss-singularity",
140
+ sizeClass,
141
+ uiWhen(props.outline, "outline"),
142
+ props.class
143
+ ]);
144
+ const p = JSswift.omit(props, [
145
+ "label",
146
+ "color",
147
+ "outline",
148
+ "size",
149
+ "slots",
150
+ "notification",
151
+ "iconSize",
152
+ "topLeft",
153
+ "topRight",
154
+ "bottomLeft",
155
+ "bottomRight",
156
+ "left",
157
+ "right"
158
+ ]);
159
+ p.class = cls;
160
+
161
+ const style = {
162
+ ...(props.style || {})
163
+ };
164
+ if (props.size) {
165
+ const size = uiUnwrap(props.size);
166
+ if (!(typeof size === "string" && sizeMap[size])) {
167
+ style.fontSize = toCssSize(props.size);
168
+ }
169
+ }
170
+ p.style = style;
171
+
172
+ const renderNamedSlotToArray = (names, ctx, fallback) => {
173
+ const list = Array.isArray(names) ? names : [names];
174
+ for (const name of list) {
175
+ if (JSswift.ui.getSlot(slots, name) != null) {
176
+ return renderSlotToArray(slots, name, ctx, fallback);
177
+ }
178
+ }
179
+ return fallback == null || fallback === false || fallback === ""
180
+ ? []
181
+ : renderSlotToArray(null, "default", ctx, fallback);
182
+ };
183
+
184
+ const resolveIconSize = () => {
185
+ const raw = uiUnwrap(props.iconSize ?? props.size);
186
+ if (typeof raw === "number") return Math.max(10, Math.round(raw * 0.85));
187
+ if (typeof raw === "string") {
188
+ const map = {
189
+ xxs: 10,
190
+ xs: 11,
191
+ sm: 12,
192
+ md: 13,
193
+ lg: 14,
194
+ xl: 16,
195
+ xxl: 18,
196
+ xxxl: 20
197
+ };
198
+ return map[raw] || 14;
199
+ }
200
+ return 14;
201
+ };
202
+
203
+ const resolveIconFallback = (source, as) => {
204
+ const raw = uiUnwrap(source);
205
+ if (raw == null || raw === false || raw === "") return null;
206
+ if (typeof raw === "string") return UI.Icon({ name: raw, size: resolveIconSize() });
207
+ return JSswift.ui.slot(raw, { as });
208
+ };
209
+
210
+ const renderIconAnchor = (slotNames, propKey, position) => _.dynamic(() => {
211
+ const iconFallback = resolveIconFallback(props[propKey], position);
212
+ const nodes = renderNamedSlotToArray(slotNames, { position }, iconFallback);
213
+ if (!nodes.length) return null;
214
+ return _.span({
215
+ class: `jss-badge-anchor jss-badge-anchor-${position}`,
216
+ "data-badge-slot": position
217
+ }, ...nodes);
218
+ });
219
+
220
+ const content = _.dynamic(() => {
221
+ const labelFallback = uiUnwrap(props.label);
222
+ let labelNodes = renderNamedSlotToArray(["label"], {}, labelFallback);
223
+ if (!labelNodes.length) labelNodes = renderNamedSlotToArray(["default"], {}, children);
224
+ return _.span(
225
+ { class: "jss-badge-content" },
226
+ _.span({ class: "jss-badge-label" }, ...(labelNodes.length ? labelNodes : [""]))
227
+ );
228
+ });
229
+
230
+ const notification = _.dynamic(() => {
231
+ const rawNotification = uiUnwrap(props.notification);
232
+ const fallback = rawNotification == null || rawNotification === false || rawNotification === ""
233
+ ? null
234
+ : rawNotification;
235
+ const nodes = renderNamedSlotToArray(["notification"], { notification: rawNotification }, fallback);
236
+ if (!nodes.length) return null;
237
+ return _.span({
238
+ class: "jss-badge-notification",
239
+ "data-badge-slot": "notification",
240
+ "aria-label": typeof rawNotification === "number" ? `${rawNotification} notifications` : null
241
+ }, ...nodes);
242
+ });
243
+
244
+ const wrap = _.span(
245
+ renderIconAnchor(["left"], "left", "left"),
246
+ renderIconAnchor(["centerLeft", "center-left"], "centerLeft", "center-left"),
247
+ p,
248
+ content,
249
+ renderIconAnchor(["topLeft", "top-left"], "topLeft", "top-left"),
250
+ renderIconAnchor(["topRight", "top-right"], "topRight", "top-right"),
251
+ renderIconAnchor(["bottomLeft", "bottom-left"], "bottomLeft", "bottom-left"),
252
+ renderIconAnchor(["bottomRight", "bottom-right"], "bottomRight", "bottom-right"),
253
+ renderIconAnchor(["centerRight", "center-right"], "centerRight", "center-right"),
254
+ renderIconAnchor(["right"], "right", "right"),
255
+ notification
256
+ );
257
+ // se esiste un props con left o right
258
+ if (props.topLeft || props.centerLeft || props.bottomLeft) {
259
+ wrap.style.setProperty("padding-left", "15px");
260
+ }
261
+ if (props.topRight || props.centerRight || props.bottomRight) {
262
+ wrap.style.setProperty("padding-right", "15px");
263
+ }
264
+
265
+ setPropertyProps(wrap, props);
266
+ return wrap;
267
+ };
268
+
269
+ // Esempio: JSswift.ui.Badge({ label: "New" })
270
+
271
+ const avatarGetInitials = (value) => {
272
+ if (value == null || value === false) return "";
273
+ const text = String(value)
274
+ .replace(/[._-]+/g, " ")
275
+ .replace(/\s+/g, " ")
276
+ .trim();
277
+ if (!text) return "";
278
+ const words = text.split(" ").filter(Boolean);
279
+ if (!words.length) return "";
280
+ if (words.length === 1) {
281
+ const token = words[0].replace(/[^a-z0-9]/gi, "");
282
+ return token.slice(0, 2).toUpperCase();
283
+ }
284
+ return words.slice(0, 2).map((part) => part.charAt(0).toUpperCase()).join("");
285
+ };
286
+
287
+ const avatarStatusState = (value, fallback) => {
288
+ const raw = value == null || value === true ? fallback : value;
289
+ if (raw == null || raw === false) return normalizeState(fallback);
290
+ const token = String(raw).toLowerCase();
291
+ if (token === "online" || token === "available" || token === "active") return "success";
292
+ if (token === "away" || token === "pending") return "warning";
293
+ if (token === "busy" || token === "dnd" || token === "blocked") return "danger";
294
+ if (token === "offline" || token === "idle") return "secondary";
295
+ return normalizeState(token) || normalizeState(fallback);
296
+ };
297
+
298
+ const avatarPrimitiveBadge = (value, className = "jss-avatar-badge") => {
299
+ if (value == null || value === false) return null;
300
+ if (value instanceof Node || Array.isArray(value)) return value;
301
+ if (typeof value === "function") return value;
302
+ if (value === true) return _.span({ class: className });
303
+ return _.span({ class: className }, String(value));
304
+ };
305
+
306
+ const avatarPrimitiveStatus = (value, color) => {
307
+ if (value == null || value === false) {
308
+ if (!color) return null;
309
+ const fallbackState = avatarStatusState(null, color);
310
+ return _.span({ class: uiClass(["jss-avatar-status", fallbackState ? `jss-state-${fallbackState}` : ""]) });
311
+ }
312
+ if (value instanceof Node || Array.isArray(value)) return value;
313
+ if (typeof value === "function") return value;
314
+ const state = avatarStatusState(value, color);
315
+ if (value === true || state) {
316
+ return _.span({ class: uiClass(["jss-avatar-status", state ? `jss-state-${state}` : ""]) });
317
+ }
318
+ return avatarPrimitiveBadge(value, "jss-avatar-badge");
319
+ };
320
+
321
+ const avatarRenderAnchor = (className, nodes) => {
322
+ if (!nodes || !nodes.length) return null;
323
+ return _.span({ class: uiClass(["jss-avatar-anchor", className]) }, ...nodes);
324
+ };
325
+
326
+ UI.Avatar = (...args) => {
327
+ const { props, children } = JSswift.uiNormalizeArgs(args);
328
+ const slots = props.slots || {};
329
+ const sizeClass = uiComputed(props.size, () => {
330
+ const v = uiUnwrap(props.size);
331
+ return (typeof v === "string" && JSswift.uiSizes?.includes(v)) ? `jss-size-${v}` : "";
332
+ });
333
+ const stateClass = uiComputed([props.color, props.state], () => {
334
+ const v = uiUnwrap(props.color) || uiUnwrap(props.state) || "";
335
+ const state = normalizeState(v);
336
+ return state ? `jss-state-${state}` : "";
337
+ });
338
+ const cls = uiClass([
339
+ "jss-clear-set",
340
+ "jss-avatar",
341
+ "jss-singularity",
342
+ sizeClass,
343
+ stateClass,
344
+ uiWhen(props.square, "jss-avatar-square"),
345
+ uiWhen(props.elevated, "elevated"),
346
+ props.class
347
+ ]);
348
+
349
+ const p = JSswift.omit(props, [
350
+ "src", "srcset", "srcSet", "sizes", "alt", "label", "name", "initials", "text",
351
+ "size", "fontSize", "textSize", "radius", "rounded", "square", "elevated",
352
+ "icon", "iconSize", "media", "fit", "badge", "notification", "status",
353
+ "statusColor", "presence", "topLeft", "topRight", "bottomLeft", "bottomRight",
354
+ "slots"
355
+ ]);
356
+ p.class = cls;
357
+
358
+ const style = { ...(props.style || {}) };
359
+ const sizeStyle = uiStyleValue(props.size, (value) => {
360
+ if (typeof value === "string" && JSswift.uiSizes?.includes(value)) return "";
361
+ return toCssSize(value);
362
+ }, "");
363
+ if (sizeStyle != null) {
364
+ style.width = sizeStyle;
365
+ style.height = sizeStyle;
366
+ }
367
+
368
+ const fontSizeValue = props.fontSize ?? props.textSize;
369
+ const fontSizeStyle = fontSizeValue != null
370
+ ? uiStyleValue(fontSizeValue, toCssSize)
371
+ : uiStyleValue(props.size, (value) => {
372
+ if (typeof value === "string" && JSswift.uiSizes?.includes(value)) return "";
373
+ return `clamp(11px, calc(${toCssSize(value)} * 0.34), 28px)`;
374
+ }, "");
375
+ if (fontSizeStyle != null) style.fontSize = fontSizeStyle;
376
+
377
+ const radiusStyle = uiStyleValue(props.radius, (value) => {
378
+ const normalized = normalizeRadius(value);
379
+ if (normalized === "xxxl") return "999px";
380
+ if (normalized) return `var(--jss-r-${normalized})`;
381
+ if (typeof value === "number") return `${value}px`;
382
+ return String(value);
383
+ });
384
+ if (radiusStyle != null) style["--set-border-radius"] = radiusStyle;
385
+ p.style = style;
386
+
387
+ const ctx = { props };
388
+ const altText = uiComputed([props.alt, props.label, props.name, props.text], () => {
389
+ const raw = uiUnwrap(props.alt) || uiUnwrap(props.label) || uiUnwrap(props.name) || uiUnwrap(props.text) || "avatar";
390
+ return String(raw);
391
+ });
392
+ const iconFallback = props.icon != null
393
+ ? (typeof props.icon === "string"
394
+ ? UI.Icon({ name: props.icon, size: props.iconSize || props.size || "sm" })
395
+ : JSswift.ui.slot(props.icon, { as: "icon" }))
396
+ : null;
397
+ const labelFallback = uiComputed([props.initials, props.text, props.label, props.name], () => {
398
+ const explicit = uiUnwrap(props.initials) ?? uiUnwrap(props.text);
399
+ if (explicit != null) return explicit;
400
+ const rawLabel = uiUnwrap(props.label) ?? uiUnwrap(props.name);
401
+ if (typeof rawLabel === "string") return avatarGetInitials(rawLabel);
402
+ return rawLabel;
403
+ });
404
+ const badgeFallback = avatarPrimitiveBadge(uiUnwrap(props.badge ?? props.notification));
405
+ const statusFallback = avatarPrimitiveStatus(
406
+ uiUnwrap(props.status ?? props.presence),
407
+ uiUnwrap(props.statusColor)
408
+ );
409
+
410
+ let mediaNodes = renderSlotToArray(slots, "media", ctx, props.media);
411
+ if (!mediaNodes.length && props.src) {
412
+ const imgProps = {
413
+ class: "jss-avatar-img",
414
+ src: props.src,
415
+ alt: altText,
416
+ sizes: props.sizes,
417
+ style: {}
418
+ };
419
+ if (props.srcset != null) imgProps.srcset = props.srcset;
420
+ if (props.srcSet != null) imgProps.srcset = props.srcSet;
421
+ const fitStyle = uiStyleValue(props.fit);
422
+ if (fitStyle != null) imgProps.style.objectFit = fitStyle;
423
+ mediaNodes = [_.img(imgProps)];
424
+ }
425
+
426
+ const defaultNodes = renderSlotToArray(slots, "default", ctx, children);
427
+ const fallbackNodes = renderSlotToArray(slots, "fallback", ctx, null);
428
+ const labelNodes = renderSlotToArray(slots, "label", ctx, labelFallback);
429
+ const iconNodes = renderSlotToArray(slots, "icon", ctx, iconFallback);
430
+ const mainNodes = mediaNodes.length
431
+ ? mediaNodes
432
+ : (defaultNodes.length
433
+ ? defaultNodes
434
+ : (fallbackNodes.length
435
+ ? fallbackNodes
436
+ : (labelNodes.length
437
+ ? labelNodes
438
+ : (iconNodes.length ? iconNodes : ["?"]))));
439
+
440
+ const bodyClass = mediaNodes.length ? "jss-avatar-media" : "jss-avatar-fallback";
441
+ const body = _.span({ class: "jss-avatar-body" },
442
+ _.span({ class: bodyClass }, ...mainNodes)
443
+ );
444
+
445
+ let topLeftNodes = renderSlotToArray(slots, "topLeft", ctx, props.topLeft);
446
+ let topRightNodes = renderSlotToArray(slots, "topRight", ctx, props.topRight);
447
+ let bottomLeftNodes = renderSlotToArray(slots, "bottomLeft", ctx, props.bottomLeft);
448
+ let bottomRightNodes = renderSlotToArray(slots, "bottomRight", ctx, props.bottomRight);
449
+ const badgeNodes = renderSlotToArray(slots, "badge", ctx, badgeFallback);
450
+ const statusNodes = renderSlotToArray(slots, "status", ctx, statusFallback);
451
+
452
+ if (!topRightNodes.length && badgeNodes.length) topRightNodes = badgeNodes;
453
+ if (!bottomRightNodes.length && statusNodes.length) bottomRightNodes = statusNodes;
454
+
455
+ const wrap = _.div(
456
+ p,
457
+ body,
458
+ avatarRenderAnchor("jss-avatar-anchor-top-left", topLeftNodes),
459
+ avatarRenderAnchor("jss-avatar-anchor-top-right", topRightNodes),
460
+ avatarRenderAnchor("jss-avatar-anchor-bottom-left", bottomLeftNodes),
461
+ avatarRenderAnchor("jss-avatar-anchor-bottom-right", bottomRightNodes)
462
+ );
463
+ setPropertyProps(wrap, props);
464
+ return wrap;
465
+ };
466
+
467
+ // Esempio: JSswift.ui.Avatar({ label: "CM" })
468
+
469
+ UI.Chip = (...args) => {
470
+ const { props, children } = JSswift.uiNormalizeArgs(args);
471
+ const slots = props.slots || {};
472
+ const sizeClass = uiComputed(props.size, () => {
473
+ const v = uiUnwrap(props.size);
474
+ return (typeof v === "string" && sizeMap[v]) ? v : "";
475
+ });
476
+ const cls = uiClass([
477
+ "jss-clear-set",
478
+ "jss-chip",
479
+ "jss-singularity",
480
+ sizeClass,
481
+ props.class
482
+ ]);
483
+ const p = JSswift.omit(props, ["label", "border", "color", "icon", "iconRight", "removable", "onRemove", "dense", "flat", "glossy", "outline", "slots"]);
484
+ p.class = cls;
485
+ p.style = {
486
+ ...(props.style || {})
487
+ };
488
+
489
+ const iconFallback = props.icon
490
+ ? (typeof props.icon === "string" ? UI.Icon({ name: props.icon, size: props?.size ?? null }) : JSswift.ui.slot(props.icon, { as: "icon" }))
491
+ : null;
492
+ const iconRightFallback = props.iconRight
493
+ ? (typeof props.iconRight === "string" ? UI.Icon({ name: props.iconRight, size: props?.size ?? null }) : JSswift.ui.slot(props.iconRight, { as: "iconRight" }))
494
+ : null;
495
+ const iconNode = JSswift.ui.renderSlot(slots, "icon", {}, iconFallback);
496
+ const iconRightNode = JSswift.ui.renderSlot(slots, "iconRight", {}, iconRightFallback);
497
+ const labelNodes = renderSlotToArray(slots, "label", {}, props.label);
498
+ const labelNode = labelNodes.length ? labelNodes : renderSlotToArray(slots, "default", {}, children);
499
+
500
+ const iconNodes = renderSlotToArray(null, "default", {}, iconNode);
501
+ const iconRightNodes = renderSlotToArray(null, "default", {}, iconRightNode);
502
+ const wrap = _.span(p, ...iconNodes, ...(labelNode.length ? labelNode : [""]), ...iconRightNodes);
503
+ if (props.removable) {
504
+ const btn = UI.Btn({ class: "jss-chip-remove", onClick: props.onRemove, size: props?.size ?? null }, UI.Icon({ size: props?.size ?? null, name: "close" }));
505
+ wrap.appendChild(btn);
506
+ btn.onclick = () => {
507
+ if (props.onRemove) {
508
+ props.onRemove();
509
+ }
510
+ wrap.remove();
511
+ }
512
+ }
513
+ setPropertyProps(wrap, props);
514
+ return wrap;
515
+ };
516
+
517
+ // Esempio: JSswift.ui.Chip({ label: "Tag", removable: true })
518
+
519
+ UI.Stat = (...args) => {
520
+ const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
521
+ const slots = rawProps.slots || {};
522
+ const resolveStateValue = () => normalizeState(uiUnwrap(rawProps.state) || uiUnwrap(rawProps.color) || "");
523
+ const stateClass = uiComputed([rawProps.state, rawProps.color], () => {
524
+ const state = resolveStateValue();
525
+ return state ? `jss-state-${state}` : "";
526
+ });
527
+ const trendClass = uiComputed(rawProps.trend, () => {
528
+ const raw = String(uiUnwrap(rawProps.trend) || "").toLowerCase();
529
+ if (["up", "positive", "success", "gain", "increase"].includes(raw)) return "is-positive";
530
+ if (["down", "negative", "danger", "loss", "decrease"].includes(raw)) return "is-negative";
531
+ if (["flat", "neutral", "steady", "stable"].includes(raw)) return "is-neutral";
532
+ return "";
533
+ });
534
+
535
+ const props = { ...rawProps };
536
+ const p = JSswift.omit(props, [
537
+ "actions",
538
+ "aside",
539
+ "delta",
540
+ "eyebrow",
541
+ "footer",
542
+ "icon",
543
+ "iconSize",
544
+ "label",
545
+ "meta",
546
+ "note",
547
+ "slots",
548
+ "state",
549
+ "subtitle",
550
+ "title",
551
+ "trend",
552
+ "value"
553
+ ]);
554
+ p.class = uiClass([
555
+ "jss-clear-set",
556
+ "jss-stat",
557
+ "jss-singularity",
558
+ stateClass,
559
+ trendClass,
560
+ props.class
561
+ ]);
562
+ p.style = { ...(props.style || {}) };
563
+
564
+ const renderNamed = (slotName, fallback, ctx = {}) => renderSlotToArray(
565
+ slots,
566
+ slotName,
567
+ ctx,
568
+ fallback
569
+ );
570
+
571
+ const iconFallback = rawProps.icon == null
572
+ ? null
573
+ : (typeof rawProps.icon === "string"
574
+ ? UI.Icon({ name: rawProps.icon, size: rawProps.iconSize || "md" })
575
+ : JSswift.ui.slot(rawProps.icon, { as: "icon" }));
576
+
577
+ const eyebrowNodes = renderNamed("eyebrow", rawProps.eyebrow);
578
+ const labelNodes = renderNamed("label", rawProps.label ?? rawProps.title);
579
+ const valueNodes = renderNamed("value", rawProps.value);
580
+ const noteNodes = renderNamed("note", rawProps.note ?? rawProps.subtitle);
581
+ const metaNodes = renderNamed("meta", rawProps.meta);
582
+ const deltaNodes = renderNamed("delta", rawProps.delta, { trend: rawProps.trend });
583
+ const iconNodes = renderNamed("icon", iconFallback);
584
+ const asideNodes = renderNamed("aside", rawProps.aside);
585
+ const footerNodes = renderNamed("footer", rawProps.footer);
586
+ const actionsNodes = renderNamed("actions", rawProps.actions);
587
+ const bodyNodes = renderNamed("default", children);
588
+
589
+ const stat = _.article(
590
+ p,
591
+ _.div(
592
+ { class: "jss-stat-head" },
593
+ _.div(
594
+ { class: "jss-stat-lead" },
595
+ iconNodes.length ? _.div({ class: "jss-stat-icon" }, ...iconNodes) : null,
596
+ _.div(
597
+ { class: "jss-stat-copy" },
598
+ eyebrowNodes.length ? _.div({ class: "jss-stat-eyebrow" }, ...eyebrowNodes) : null,
599
+ labelNodes.length ? _.div({ class: "jss-stat-label" }, ...labelNodes) : null
600
+ )
601
+ ),
602
+ asideNodes.length ? _.div({ class: "jss-stat-aside" }, ...asideNodes) : null
603
+ ),
604
+ _.div(
605
+ { class: "jss-stat-body" },
606
+ valueNodes.length ? _.div({ class: "jss-stat-value" }, ...valueNodes) : null,
607
+ deltaNodes.length
608
+ ? _.div({ class: "jss-stat-delta" }, ...deltaNodes)
609
+ : null,
610
+ noteNodes.length ? _.div({ class: "jss-stat-note" }, ...noteNodes) : null,
611
+ metaNodes.length ? _.div({ class: "jss-stat-meta" }, ...metaNodes) : null,
612
+ bodyNodes.length ? _.div({ class: "jss-stat-extra" }, ...bodyNodes) : null
613
+ ),
614
+ (footerNodes.length || actionsNodes.length)
615
+ ? _.div(
616
+ { class: "jss-stat-footer" },
617
+ footerNodes.length ? _.div({ class: "jss-stat-footer-copy" }, ...footerNodes) : null,
618
+ actionsNodes.length ? _.div({ class: "jss-stat-actions" }, ...actionsNodes) : null
619
+ )
620
+ : null
621
+ );
622
+
623
+ setPropertyProps(stat, rawProps);
624
+ return stat;
625
+ };
626
+
627
+ // Esempio: JSswift.ui.Stat({ label: "Revenue", value: "€ 128k", delta: "+18%" })
628
+
629
+ UI.Kpi = (...args) => {
630
+ const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
631
+ const slots = rawProps.slots || {};
632
+ const resolveStateValue = () => normalizeState(uiUnwrap(rawProps.state) || uiUnwrap(rawProps.color) || "");
633
+ const stateClass = uiComputed([rawProps.state, rawProps.color], () => {
634
+ const state = resolveStateValue();
635
+ return state ? `jss-state-${state}` : "";
636
+ });
637
+
638
+ const props = { ...rawProps };
639
+ const p = JSswift.omit(props, [
640
+ "actions",
641
+ "aside",
642
+ "delta",
643
+ "eyebrow",
644
+ "footer",
645
+ "icon",
646
+ "iconSize",
647
+ "label",
648
+ "media",
649
+ "meta",
650
+ "note",
651
+ "slots",
652
+ "state",
653
+ "subtitle",
654
+ "title",
655
+ "trend",
656
+ "value"
657
+ ]);
658
+ p.class = uiClass([
659
+ "jss-clear-set",
660
+ "jss-kpi",
661
+ "jss-singularity",
662
+ stateClass,
663
+ props.class
664
+ ]);
665
+ p.style = { ...(props.style || {}) };
666
+
667
+ const renderNamed = (slotName, fallback, ctx = {}) => renderSlotToArray(
668
+ slots,
669
+ slotName,
670
+ ctx,
671
+ fallback
672
+ );
673
+
674
+ const iconFallback = rawProps.icon == null
675
+ ? null
676
+ : (typeof rawProps.icon === "string"
677
+ ? UI.Icon({ name: rawProps.icon, size: rawProps.iconSize || "lg" })
678
+ : JSswift.ui.slot(rawProps.icon, { as: "icon" }));
679
+
680
+ const eyebrowNodes = renderNamed("eyebrow", rawProps.eyebrow);
681
+ const titleNodes = renderNamed("title", rawProps.title ?? rawProps.label);
682
+ const valueNodes = renderNamed("value", rawProps.value);
683
+ const deltaNodes = renderNamed("delta", rawProps.delta, { trend: rawProps.trend });
684
+ const noteNodes = renderNamed("note", rawProps.note ?? rawProps.subtitle);
685
+ const metaNodes = renderNamed("meta", rawProps.meta);
686
+ const iconNodes = renderNamed("icon", iconFallback);
687
+ const asideNodes = renderNamed("aside", rawProps.aside);
688
+ const mediaNodes = renderNamed("media", rawProps.media);
689
+ const footerNodes = renderNamed("footer", rawProps.footer);
690
+ const actionsNodes = renderNamed("actions", rawProps.actions);
691
+ const bodyNodes = renderNamed("default", children);
692
+
693
+ const kpi = _.section(
694
+ p,
695
+ _.div(
696
+ { class: "jss-kpi-head" },
697
+ _.div(
698
+ { class: "jss-kpi-title-wrap" },
699
+ iconNodes.length ? _.div({ class: "jss-kpi-icon" }, ...iconNodes) : null,
700
+ _.div(
701
+ { class: "jss-kpi-copy" },
702
+ eyebrowNodes.length ? _.div({ class: "jss-kpi-eyebrow" }, ...eyebrowNodes) : null,
703
+ titleNodes.length ? _.div({ class: "jss-kpi-title" }, ...titleNodes) : null
704
+ )
705
+ ),
706
+ asideNodes.length ? _.div({ class: "jss-kpi-aside" }, ...asideNodes) : null
707
+ ),
708
+ _.div(
709
+ { class: "jss-kpi-core" },
710
+ _.div(
711
+ { class: "jss-kpi-value-wrap" },
712
+ valueNodes.length ? _.div({ class: "jss-kpi-value" }, ...valueNodes) : null,
713
+ deltaNodes.length ? _.div({ class: "jss-kpi-delta" }, ...deltaNodes) : null
714
+ ),
715
+ noteNodes.length ? _.div({ class: "jss-kpi-note" }, ...noteNodes) : null,
716
+ metaNodes.length ? _.div({ class: "jss-kpi-meta" }, ...metaNodes) : null,
717
+ mediaNodes.length ? _.div({ class: "jss-kpi-media" }, ...mediaNodes) : null,
718
+ bodyNodes.length ? _.div({ class: "jss-kpi-extra" }, ...bodyNodes) : null
719
+ ),
720
+ (footerNodes.length || actionsNodes.length)
721
+ ? _.div(
722
+ { class: "jss-kpi-footer" },
723
+ footerNodes.length ? _.div({ class: "jss-kpi-footer-copy" }, ...footerNodes) : null,
724
+ actionsNodes.length ? _.div({ class: "jss-kpi-actions" }, ...actionsNodes) : null
725
+ )
726
+ : null
727
+ );
728
+
729
+ setPropertyProps(kpi, rawProps);
730
+ return kpi;
731
+ };
732
+
733
+ // Esempio: JSswift.ui.Kpi({ title: "Orders", value: "342", delta: "+12%" })
734
+
735
+ const overlayAnimDuration = (el, fallback = 180) => {
736
+ const d = getComputedStyle(el).transitionDuration;
737
+ if (!d) return fallback;
738
+ const first = d.split(",")[0].trim();
739
+ if (!first) return fallback;
740
+ const ms = first.includes("ms") ? parseFloat(first) : parseFloat(first) * 1000;
741
+ return Number.isFinite(ms) ? ms : fallback;
742
+ };
743
+
744
+ const overlayEnter = (entry) => {
745
+ if (!entry?.overlay || !entry?.panel) return;
746
+ entry.overlay.classList.add("enter");
747
+ entry.panel.classList.add("enter");
748
+ requestAnimationFrame(() => {
749
+ if (!entry?.overlay || !entry?.panel) return;
750
+ entry.overlay.classList.add("entered");
751
+ entry.panel.classList.add("entered");
752
+ entry.overlay.classList.remove("enter");
753
+ entry.panel.classList.remove("enter");
754
+ });
755
+ };
756
+
757
+ const overlayLeave = (entry, done) => {
758
+ if (!entry?.overlay || !entry?.panel) {
759
+ done?.();
760
+ return;
761
+ }
762
+ if (entry._closing) return;
763
+ entry._closing = true;
764
+ entry.overlay.classList.add("leave");
765
+ entry.panel.classList.add("leave");
766
+ const finish = () => {
767
+ if (entry._closed) return;
768
+ entry._closed = true;
769
+ done?.();
770
+ };
771
+ const onEnd = (e) => {
772
+ if (e.target !== entry.panel) return;
773
+ entry.panel.removeEventListener("transitionend", onEnd);
774
+ finish();
775
+ };
776
+ entry.panel.addEventListener("transitionend", onEnd);
777
+ const ms = overlayAnimDuration(entry.panel, 180);
778
+ setTimeout(finish, ms + 40);
779
+ };
780
+