jsswift 2.0.1 → 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.
@@ -0,0 +1,246 @@
1
+ UI.List = (...args) => {
2
+ const { props, children } = JSswift.uiNormalizeArgs(args);
3
+ const slots = props.slots || {};
4
+ const ordered = !!uiUnwrap(props.number ?? props.ordered);
5
+ const marker = uiUnwrap(props.marker);
6
+ const itemSource = uiUnwrap(props.items);
7
+ const items = Array.isArray(itemSource) ? itemSource : (itemSource == null ? [] : [itemSource]);
8
+
9
+ const buildItemProps = (entryProps = {}) => {
10
+ const baseProps = props.itemProps || {};
11
+ const merged = { ...baseProps, ...entryProps };
12
+ if (props.divider != null && merged.divider == null) merged.divider = props.divider;
13
+ merged.class = uiClass([baseProps.class, props.itemClass, entryProps.class]);
14
+ merged.style = {
15
+ ...(baseProps.style || {}),
16
+ ...(props.itemStyle || {}),
17
+ ...(entryProps.style || {})
18
+ };
19
+ return merged;
20
+ };
21
+
22
+ const normalizeResolvedNode = (value) => {
23
+ if (value == null || value === false) return [];
24
+ if (isListItemNode(value)) return [value];
25
+ if (isUIPlainObject(value)) {
26
+ const itemChildren = value.children != null
27
+ ? asNodeArray(value.children)
28
+ : (value.content != null
29
+ ? asNodeArray(value.content)
30
+ : asNodeArray(value.node));
31
+ return [UI.Item(buildItemProps(value), ...itemChildren)];
32
+ }
33
+ if (Array.isArray(value)) {
34
+ return [UI.Item(buildItemProps(), ...value)];
35
+ }
36
+ if (value?.nodeType) {
37
+ return [UI.Item(buildItemProps(), value)];
38
+ }
39
+ return [UI.Item(buildItemProps(), value)];
40
+ };
41
+
42
+ const normalizeItemNode = (value, index, total, useItemSlot = true) => {
43
+ if (value == null || value === false) return [];
44
+ if (useItemSlot) {
45
+ const ctx = {
46
+ item: value,
47
+ index,
48
+ count: total,
49
+ first: index === 0,
50
+ last: index === total - 1
51
+ };
52
+ const slotted = renderSlotToArray(slots, "item", ctx, null);
53
+ if (slotted.length) {
54
+ return slotted.flatMap((node) => normalizeResolvedNode(node));
55
+ }
56
+ }
57
+ return normalizeResolvedNode(value);
58
+ };
59
+
60
+ const content = [];
61
+ items.forEach((item, index) => {
62
+ content.push(...normalizeItemNode(item, index, items.length));
63
+ });
64
+
65
+ const defaultNodes = renderSlotToArray(slots, "default", { items, count: items.length }, children);
66
+ defaultNodes.forEach((node, index) => {
67
+ content.push(...normalizeItemNode(node, items.length + index, items.length + defaultNodes.length, false));
68
+ });
69
+
70
+ if (!content.length) {
71
+ const emptyNodes = renderSlotToArray(slots, "empty", { items, count: 0 }, props.empty);
72
+ if (emptyNodes.length) {
73
+ if (emptyNodes.length === 1 && isListItemNode(emptyNodes[0])) {
74
+ content.push(emptyNodes[0]);
75
+ } else {
76
+ content.push(UI.Item({ class: "jss-item-empty", flat: true }, ...emptyNodes));
77
+ }
78
+ }
79
+ }
80
+
81
+ const cls = uiClass([
82
+ "jss-list",
83
+ uiWhen(props.dense, "dense"),
84
+ uiWhen(props.divider, "divider"),
85
+ uiWhen(marker === false || marker === "none", "jss-list-no-marker"),
86
+ props.class
87
+ ]);
88
+ const p = JSswift.omit(props, [
89
+ "dense", "divider", "slots", "number", "ordered", "items", "itemClass", "itemStyle", "itemProps",
90
+ "empty", "marker", "gap"
91
+ ]);
92
+ p.class = cls;
93
+ p.style = {
94
+ ...(props.style || {})
95
+ };
96
+ const gap = uiStyleValue(props.gap, toCssSize);
97
+ if (gap != null) p.style["--jss-list-gap"] = gap;
98
+ if (marker != null && marker !== false && marker !== true && marker !== "none") {
99
+ p.style.listStyleType = String(marker);
100
+ }
101
+
102
+ const list = _[ordered ? "ol" : "ul"](p, ...content);
103
+ return list;
104
+ };
105
+
106
+ // Esempio: JSswift.ui.List({}, JSswift.ui.Item({}, "Item"))
107
+
108
+ UI.Item = (...args) => {
109
+ const { props, children } = JSswift.uiNormalizeArgs(args);
110
+ if (props.state != null && props.color == null) props.color = props.state;
111
+ applyCommonProps(props);
112
+ const slots = props.slots || {};
113
+ const interactive = uiComputed([props.clickable, props.to], () => !!(uiUnwrap(props.clickable) || uiUnwrap(props.to)));
114
+ const active = uiComputed([props.active, props.selected], () => !!(uiUnwrap(props.active) || uiUnwrap(props.selected)));
115
+ const hasSurface = !!(
116
+ props.color != null || props.outline || props.border || props.glossy || props.glow || props.glass ||
117
+ props.shadow || props.gradient || props.textGradient || props.lightShadow || props.radius ||
118
+ props.rounded || props.flat
119
+ );
120
+
121
+ const resolveIcon = (value, size, as) => {
122
+ const raw = uiUnwrap(value);
123
+ if (raw == null || raw === false || raw === "") return null;
124
+ if (typeof raw === "string") return UI.Icon({ name: raw, size: size || "sm" });
125
+ return JSswift.ui.slot(raw, { as });
126
+ };
127
+
128
+ const iconNodes = renderSlotToArray(slots, "icon", {}, resolveIcon(props.icon, props.iconSize || props.size, "icon"));
129
+ const titleNodes = renderSlotToArray(slots, "title", {}, props.title ?? props.label);
130
+ const subtitleNodes = renderSlotToArray(slots, "subtitle", {}, props.subtitle ?? props.caption ?? props.description);
131
+ const metaNodes = renderSlotToArray(slots, "meta", {}, props.meta ?? props.eyebrow);
132
+ const bodyNodes = renderSlotToArray(slots, "body", {}, props.body ?? props.content);
133
+ const defaultNodes = renderSlotToArray(slots, "default", {}, children);
134
+ const actionsNodes = renderSlotToArray(slots, "actions", {}, props.actions ?? props.footer);
135
+ const asideFallback = props.aside ?? props.trailing ?? resolveIcon(props.iconRight, props.iconSize || props.size, "iconRight");
136
+ const asideNodes = renderSlotToArray(slots, "aside", {}, asideFallback);
137
+
138
+ const mergedBodyNodes = [...bodyNodes, ...defaultNodes];
139
+ const isInline = !(
140
+ hasSurface || props.clickable || props.to || iconNodes.length || titleNodes.length ||
141
+ subtitleNodes.length || metaNodes.length || actionsNodes.length || asideNodes.length ||
142
+ bodyNodes.length
143
+ );
144
+
145
+ const cls = uiClass([
146
+ "jss-item",
147
+ uiWhen(props.divider, "divider"),
148
+ uiWhen(hasSurface, "jss-clear-set"),
149
+ uiWhen(hasSurface, "jss-singularity"),
150
+ uiWhen(hasSurface, "jss-item-surface"),
151
+ uiWhen(isInline, "jss-item-inline"),
152
+ uiWhen(interactive, "jss-item-clickable"),
153
+ uiWhen(active, "is-active"),
154
+ uiWhen(props.disabled, "is-disabled"),
155
+ props.class
156
+ ]);
157
+ const p = JSswift.omit(props, [
158
+ "divider", "slots", "label", "title", "subtitle", "caption", "description", "meta", "eyebrow",
159
+ "body", "content", "children", "node", "icon", "iconRight", "iconSize", "aside", "trailing",
160
+ "actions", "footer", "clickable", "to", "active", "selected", "disabled", "state",
161
+ "color", "size", "outline", "flat", "border", "glossy", "glow", "glass", "gradient",
162
+ "textGradient", "lightShadow", "shadow", "rounded", "radius", "textColor", "dense"
163
+ ]);
164
+ p.class = cls;
165
+ p.style = {
166
+ ...(props.style || {})
167
+ };
168
+
169
+ const userOnClick = props.onClick;
170
+ const userOnKeydown = props.onKeydown;
171
+ const onClick = (e) => {
172
+ userOnClick?.(e);
173
+ if (e.defaultPrevented || uiUnwrap(props.disabled)) return;
174
+ const to = uiUnwrap(props.to);
175
+ if (to && JSswift.router?.navigate) {
176
+ e.preventDefault();
177
+ JSswift.router.navigate(to);
178
+ }
179
+ };
180
+ const onKeydown = (e) => {
181
+ userOnKeydown?.(e);
182
+ if (e.defaultPrevented || uiUnwrap(props.disabled)) return;
183
+ if (e.key === "Enter" || e.key === " ") {
184
+ e.preventDefault();
185
+ onClick(e);
186
+ }
187
+ };
188
+ if (uiUnwrap(props.disabled)) {
189
+ p["aria-disabled"] = "true";
190
+ }
191
+ if (uiUnwrap(props.clickable) || uiUnwrap(props.to)) {
192
+ p.onClick = onClick;
193
+ p.onKeydown = onKeydown;
194
+ if (p.tabIndex == null) p.tabIndex = uiUnwrap(props.disabled) ? -1 : 0;
195
+ if (p.role == null) p.role = "button";
196
+ }
197
+
198
+ const titleSection = titleNodes.length ? _.div({ class: "jss-item-title" }, ...titleNodes) : null;
199
+ const subtitleSection = subtitleNodes.length ? _.div({ class: "jss-item-subtitle" }, ...subtitleNodes) : null;
200
+ const metaSection = metaNodes.length ? _.div({ class: "jss-item-meta" }, ...metaNodes) : null;
201
+ const bodySection = mergedBodyNodes.length
202
+ ? _.div({ class: uiClass(["jss-item-body", uiWhen(!(titleNodes.length || subtitleNodes.length || metaNodes.length), "is-standalone")]) }, ...mergedBodyNodes)
203
+ : null;
204
+ const actionsSection = actionsNodes.length ? _.div({ class: "jss-item-actions" }, ...actionsNodes) : null;
205
+
206
+ const item = _.li(
207
+ p,
208
+ _.div(
209
+ { class: "jss-item-row" },
210
+ iconNodes.length ? _.div({ class: "jss-item-icon" }, ...iconNodes) : null,
211
+ _.div(
212
+ { class: "jss-item-main" },
213
+ metaSection,
214
+ titleSection,
215
+ subtitleSection,
216
+ bodySection,
217
+ actionsSection
218
+ ),
219
+ asideNodes.length ? _.div({ class: "jss-item-aside" }, ...asideNodes) : null
220
+ )
221
+ );
222
+ setPropertyProps(item, props);
223
+ return item;
224
+ };
225
+
226
+ // Esempio: JSswift.ui.Item({}, "Elemento")
227
+
228
+ UI.Separator = (...args) => {
229
+ const { props } = JSswift.uiNormalizeArgs(args);
230
+ const cls = uiClass(["jss-separator", uiWhen(props.vertical, "vertical"), props.class]);
231
+ const p = JSswift.omit(props, ["vertical", "size", "slots"]);
232
+ p.class = cls;
233
+ const style = { borderColor: "var(--jss-border)", ...(props.style || {}) };
234
+ const sizeValue = uiUnwrap(props.size);
235
+ const vertical = uiUnwrap(props.vertical);
236
+ if (sizeValue != null) {
237
+ const size = toCssSize(sizeValue);
238
+ if (vertical) style.width = size;
239
+ else style.height = size;
240
+ }
241
+ p.style = style;
242
+ return _.hr(p);
243
+ };
244
+
245
+ // Esempio: JSswift.ui.Separator()
246
+
@@ -0,0 +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) ? `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
+