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,999 @@
1
+ UI.Spinner = (...args) => {
2
+ const { props, children } = JSswift.uiNormalizeArgs(args);
3
+ const slots = props.slots || {};
4
+
5
+ const makeCssVarValue = (value, mapper, fallback) => {
6
+ if (uiIsReactive(value)) {
7
+ return () => {
8
+ const raw = uiUnwrap(value);
9
+ if (raw == null || raw === false || raw === "") return fallback;
10
+ const next = mapper ? mapper(raw) : raw;
11
+ return next == null || next === "" ? fallback : next;
12
+ };
13
+ }
14
+ if (value == null || value === false || value === "") return fallback;
15
+ const next = mapper ? mapper(value) : value;
16
+ return next == null || next === "" ? fallback : next;
17
+ };
18
+
19
+ const resolveSpinnerColor = (value) => {
20
+ if (value == null || value === false || value === "") return "";
21
+ const state = normalizeState(value);
22
+ if (state) return "";
23
+ const str = String(value).trim();
24
+ if (!str) return "";
25
+ if (
26
+ str.startsWith("#") ||
27
+ str.startsWith("rgb(") ||
28
+ str.startsWith("rgba(") ||
29
+ str.startsWith("hsl(") ||
30
+ str.startsWith("hsla(") ||
31
+ str.startsWith("var(")
32
+ ) return str;
33
+ if (isTokenCSS(str)) return `var(--jss-${str})`;
34
+ return str;
35
+ };
36
+
37
+ const stateClass = uiComputed(props.state, () => {
38
+ const state = normalizeState(uiUnwrap(props.state));
39
+ return state ? `jss-state-${state}` : "";
40
+ });
41
+
42
+ const cls = uiClass([
43
+ "jss-spinner",
44
+ stateClass,
45
+ uiWhen(props.vertical, "vertical"),
46
+ uiWhen(props.reverse, "reverse"),
47
+ uiWhen(props.center, "center"),
48
+ uiWhen(props.block, "block"),
49
+ uiWhen(props.pause || props.paused, "paused"),
50
+ props.class
51
+ ]);
52
+
53
+ const p = JSswift.omit(props, [
54
+ "ariaLabel", "block", "center", "color", "indicatorClass", "indicatorStyle",
55
+ "label", "note", "pause", "paused", "reverse", "size", "slots", "speed",
56
+ "state", "thickness", "trackColor", "vertical"
57
+ ]);
58
+ p.class = cls;
59
+
60
+ const rootStyle = { ...(props.style || {}) };
61
+ if (props.color != null && Object.prototype.hasOwnProperty.call(rootStyle, "backgroundColor")) {
62
+ delete rootStyle.backgroundColor;
63
+ }
64
+ p.style = rootStyle;
65
+ if (p.role == null) p.role = "status";
66
+ if (p["aria-live"] == null) p["aria-live"] = "polite";
67
+ if (p["aria-busy"] == null) p["aria-busy"] = "true";
68
+
69
+ const ctx = { props };
70
+ const labelNodes = renderSlotToArray(slots, "label", ctx, props.label);
71
+ const copyNodes = renderSlotToArray(slots, "default", ctx, children);
72
+ const noteNodes = renderSlotToArray(slots, "note", ctx, props.note);
73
+ const hasText = labelNodes.length || copyNodes.length || noteNodes.length;
74
+
75
+ if (!hasText) {
76
+ const ariaLabel = uiUnwrap(props.ariaLabel ?? props["aria-label"]);
77
+ if (ariaLabel) p["aria-label"] = ariaLabel;
78
+ else if (p["aria-label"] == null) p["aria-label"] = "Loading";
79
+ }
80
+
81
+ const indicatorFallback = _.span({
82
+ class: uiClass(["jss-spinner-indicator", props.indicatorClass]),
83
+ style: {
84
+ "--jss-spinner-size": makeCssVarValue(props.size, toCssSize, "18px"),
85
+ "--jss-spinner-thickness": makeCssVarValue(props.thickness, toCssSize, "2px"),
86
+ "--jss-spinner-speed": makeCssVarValue(props.speed, (v) => typeof v === "number" ? `${v}ms` : String(v), "900ms"),
87
+ "--jss-spinner-color": makeCssVarValue(props.color, resolveSpinnerColor, "var(--set-border-color, var(--jss-primary))"),
88
+ "--jss-spinner-track": makeCssVarValue(props.trackColor, resolveSpinnerColor, "color-mix(in srgb, var(--jss-spinner-color) 18%, transparent)"),
89
+ ...(props.indicatorStyle || {})
90
+ },
91
+ "aria-hidden": "true"
92
+ });
93
+ const indicatorNodes = renderSlotToArray(slots, "indicator", ctx, indicatorFallback);
94
+ const content = [];
95
+
96
+ if (indicatorNodes.length) content.push(...indicatorNodes);
97
+ if (hasText) {
98
+ content.push(_.div(
99
+ { class: "jss-spinner-content" },
100
+ labelNodes.length ? _.div({ class: "jss-spinner-label" }, ...labelNodes) : null,
101
+ copyNodes.length ? _.div({ class: "jss-spinner-copy" }, ...copyNodes) : null,
102
+ noteNodes.length ? _.div({ class: "jss-spinner-note" }, ...noteNodes) : null
103
+ ));
104
+ }
105
+
106
+ const root = _.div(p, ...content);
107
+ setPropertyProps(root, props);
108
+ return root;
109
+ };
110
+
111
+ // Esempio: JSswift.ui.Spinner({ size: 24 })
112
+
113
+ UI.Progress = (...args) => {
114
+ const { props, children } = JSswift.uiNormalizeArgs(args);
115
+ const slots = props.slots || {};
116
+ const boundValue = props.model || ((uiIsSignal(props.value) || uiIsRod(props.value)) ? props.value : null);
117
+ const model = resolveModel(boundValue, "UI.Progress:model");
118
+ const stateClass = uiComputed([props.state, props.color], () => {
119
+ const state = normalizeState(uiUnwrap(props.state) || uiUnwrap(props.color));
120
+ return state ? `jss-state-${state}` : "";
121
+ });
122
+
123
+ const getNumber = (value, fallback) => {
124
+ const next = Number(value);
125
+ return Number.isFinite(next) ? next : fallback;
126
+ };
127
+ const getMin = () => getNumber(uiUnwrap(props.min), 0);
128
+ const getMax = () => {
129
+ const min = getMin();
130
+ const max = getNumber(uiUnwrap(props.max), 100);
131
+ return max < min ? min : max;
132
+ };
133
+ const getTrackHeight = () => {
134
+ const raw = uiUnwrap(props.height ?? props.thickness ?? props.size);
135
+ if (raw == null || raw === "") return "8px";
136
+ if (typeof raw === "number") return `${raw}px`;
137
+ if (typeof raw === "string") {
138
+ const sizePreset = {
139
+ xxs: "4px",
140
+ xs: "6px",
141
+ sm: "8px",
142
+ md: "10px",
143
+ lg: "12px",
144
+ xl: "14px",
145
+ xxl: "16px",
146
+ xxxl: "18px"
147
+ };
148
+ return sizePreset[raw] || raw;
149
+ }
150
+ return "8px";
151
+ };
152
+ const resolveCssColor = (value, fallback = "") => {
153
+ const raw = uiUnwrap(value);
154
+ if (raw == null || raw === false || raw === "") return fallback;
155
+ const state = normalizeState(raw);
156
+ if (state) return `var(--jss-${state})`;
157
+ if (typeof raw === "string" && isTokenCSS(raw)) return `var(--jss-${raw})`;
158
+ return String(raw);
159
+ };
160
+ const normalizeValue = (value) => {
161
+ const min = getMin();
162
+ const max = getMax();
163
+ const next = getNumber(value, min);
164
+ return Math.min(max, Math.max(min, next));
165
+ };
166
+ const ratioFromValue = (value) => {
167
+ const min = getMin();
168
+ const max = getMax();
169
+ const span = max - min;
170
+ if (!span) return 0;
171
+ const raw = (normalizeValue(value) - min) / span;
172
+ const ratio = uiUnwrap(props.reverse) ? (1 - raw) : raw;
173
+ return Math.max(0, Math.min(1, ratio));
174
+ };
175
+ const clampBuffer = (value) => {
176
+ const normalized = normalizeValue(value);
177
+ return normalized < getValue() ? getValue() : normalized;
178
+ };
179
+ const resolveDisplayValue = (value, percent, ctx) => {
180
+ const formatter = props.formatValue;
181
+ if (typeof formatter === "function") {
182
+ const formatted = formatter(value, percent, ctx);
183
+ if (formatted != null) return formatted;
184
+ }
185
+ return `${Math.round(percent)}%`;
186
+ };
187
+ const clearHost = (host) => {
188
+ while (host.firstChild) host.removeChild(host.firstChild);
189
+ };
190
+ const renderInto = (host, nodes, display = "flex") => {
191
+ clearHost(host);
192
+ (nodes || []).forEach((node) => {
193
+ if (node == null || node === false) return;
194
+ if (node instanceof Node) {
195
+ host.appendChild(node);
196
+ return;
197
+ }
198
+ host.appendChild(document.createTextNode(String(node)));
199
+ });
200
+ host.style.display = host.childNodes.length ? display : "none";
201
+ };
202
+ const asArray = (value, ctx = {}) => slotToArray(uiUnwrap(value), ctx);
203
+ const asIconArray = (value, as, ctx = {}) => {
204
+ const raw = uiUnwrap(value);
205
+ if (raw == null || raw === false || raw === "") return [];
206
+ if (typeof raw === "string") return [UI.Icon({ name: raw, size: props.iconSize ?? 16 })];
207
+ return asArray(raw, { ...ctx, as });
208
+ };
209
+ const resolveContentProp = (value) => {
210
+ if (uiIsRod(value) || uiIsSignal(value)) return uiUnwrap(value);
211
+ if (typeof value === "function" && value.length === 0) return value();
212
+ return value;
213
+ };
214
+
215
+ const [getValue, setValue] = JSswift.reactive.signal(normalizeValue(
216
+ model ? model.get() : (uiUnwrap(props.value) ?? uiUnwrap(props.min) ?? 0)
217
+ ));
218
+ const [getBuffer, setBuffer] = JSswift.reactive.signal(clampBuffer(
219
+ uiUnwrap(props.buffer) ?? (model ? model.get() : (uiUnwrap(props.value) ?? uiUnwrap(props.min) ?? 0))
220
+ ));
221
+
222
+ const setProgressValue = (raw, opts = {}) => {
223
+ const next = normalizeValue(raw);
224
+ if (getValue() !== next) setValue(next);
225
+ if (getBuffer() < next) setBuffer(next);
226
+ if (model && opts.fromModel !== true) model.set(next);
227
+ return next;
228
+ };
229
+ const setProgressBuffer = (raw) => {
230
+ const next = clampBuffer(raw);
231
+ if (getBuffer() !== next) setBuffer(next);
232
+ return next;
233
+ };
234
+
235
+ const wrapProps = JSswift.omit(props, [
236
+ "model", "value", "min", "max", "buffer", "class", "style", "slots",
237
+ "label", "note", "showValue", "valueLabel", "insideLabel", "formatValue",
238
+ "icon", "iconRight", "iconSize", "startLabel", "endLabel", "leftLabel", "rightLabel",
239
+ "trackColor", "bufferColor", "height", "thickness", "size", "state", "color",
240
+ "reverse", "striped", "animated", "indeterminate", "ariaLabel", "ariaValueText",
241
+ "width", "dense", "outline", "flat", "border", "glossy", "glow", "glass",
242
+ "shadow", "gradient", "textGradient", "lightShadow", "radius", "rounded", "textColor"
243
+ ]);
244
+ const rootStyle = {
245
+ width: uiUnwrap(props.width) || "100%",
246
+ ...(props.style || {})
247
+ };
248
+ const propColor = uiUnwrap(props.color);
249
+ if ((propColor != null || uiIsReactive(props.color)) && !normalizeState(propColor || "")) {
250
+ delete rootStyle.backgroundColor;
251
+ }
252
+ wrapProps.class = uiClass([
253
+ "jss-progress-wrap",
254
+ uiWhen(props.dense, "dense"),
255
+ uiWhen(props.reverse, "is-reverse"),
256
+ uiWhen(props.indeterminate, "is-indeterminate"),
257
+ stateClass,
258
+ props.class
259
+ ]);
260
+ wrapProps.style = rootStyle;
261
+
262
+ const header = _.div({ class: "jss-progress-header" });
263
+ const heading = _.div({ class: "jss-progress-heading" });
264
+ const labelHost = _.div({ class: "jss-progress-label" });
265
+ const noteHost = _.div({ class: "jss-progress-note" });
266
+ const valueHost = _.div({ class: "jss-progress-value" });
267
+ heading.append(labelHost, noteHost);
268
+ header.append(heading, valueHost);
269
+
270
+ const body = _.div({ class: "jss-progress-body" });
271
+ const startLabelHost = _.span({ class: "jss-progress-edge-label jss-progress-edge-label-left" });
272
+ const track = _.div({
273
+ class: "jss-progress",
274
+ role: "progressbar"
275
+ });
276
+ const buffer = _.span({ class: "jss-progress-buffer" });
277
+ const fill = _.span({
278
+ class: uiClass([
279
+ "jss-progress-bar",
280
+ "jss-singularity",
281
+ stateClass,
282
+ uiWhen(props.outline, "jss-outline"),
283
+ uiWhen(props.flat, "jss-flat"),
284
+ uiWhen(props.border, "jss-border"),
285
+ uiWhen(props.glossy, "jss-glossy"),
286
+ uiWhen(props.glow, "jss-glow"),
287
+ uiWhen(props.glass, "jss-glass"),
288
+ uiWhen(props.shadow, "jss-shadow"),
289
+ uiComputed(props.shadow, () => {
290
+ const shadow = normalizeShadow(uiUnwrap(props.shadow));
291
+ return shadow ? `jss-shadow-${shadow}` : "";
292
+ }),
293
+ uiWhen(props.gradient, "jss-gradient"),
294
+ uiWhen(props.textGradient, "jss-text-gradient"),
295
+ uiWhen(props.lightShadow, "jss-light-shadow"),
296
+ uiWhen(props.striped, "is-striped"),
297
+ uiWhen(props.animated || props.indeterminate, "is-animated")
298
+ ])
299
+ });
300
+ const insideHost = _.span({ class: "jss-progress-bar-label" });
301
+ fill.appendChild(insideHost);
302
+ track.append(buffer, fill);
303
+ const endLabelHost = _.span({ class: "jss-progress-edge-label jss-progress-edge-label-right" });
304
+ body.append(startLabelHost, track, endLabelHost);
305
+
306
+ const wrap = _.div(wrapProps, header, body);
307
+ setPropertyProps(fill, props);
308
+
309
+ const renderHeader = () => {
310
+ const value = getValue();
311
+ const percent = ratioFromValue(value) * 100;
312
+ const ctx = {
313
+ value,
314
+ percent,
315
+ min: getMin(),
316
+ max: getMax(),
317
+ buffer: getBuffer(),
318
+ progress: wrap,
319
+ track,
320
+ bar: fill,
321
+ props
322
+ };
323
+ const iconNodes = renderSlotToArray(slots, "icon", ctx, null);
324
+ const iconFallback = iconNodes.length ? [] : asIconArray(props.icon, "icon", ctx);
325
+ const labelNodes = renderSlotToArray(slots, "label", ctx, resolveContentProp(props.label));
326
+ const fallbackNodes = labelNodes.length ? labelNodes : renderSlotToArray(slots, "default", ctx, children);
327
+ const rightIconNodes = renderSlotToArray(slots, "iconRight", ctx, null);
328
+ const rightIconFallback = rightIconNodes.length ? [] : asIconArray(props.iconRight, "iconRight", ctx);
329
+ const noteNodes = renderSlotToArray(slots, "note", ctx, resolveContentProp(props.note));
330
+ const valueFallback = props.valueLabel != null
331
+ ? resolveContentProp(props.valueLabel)
332
+ : resolveDisplayValue(value, percent, ctx);
333
+ const showValue = uiUnwrap(props.showValue);
334
+ const outsideValueNodes = (showValue === true || props.valueLabel != null || JSswift.ui.getSlot(slots, "value") != null)
335
+ ? renderSlotToArray(slots, "value", ctx, valueFallback)
336
+ : [];
337
+
338
+ renderInto(labelHost, [...iconFallback, ...iconNodes, ...fallbackNodes, ...rightIconFallback, ...rightIconNodes], "inline-flex");
339
+ renderInto(noteHost, noteNodes, "block");
340
+ renderInto(valueHost, showValue === "inside" ? [] : outsideValueNodes, "inline-flex");
341
+ header.style.display = (labelHost.childNodes.length || noteHost.childNodes.length || valueHost.childNodes.length) ? "flex" : "none";
342
+ };
343
+
344
+ const renderEdgeLabels = () => {
345
+ const value = getValue();
346
+ const percent = ratioFromValue(value) * 100;
347
+ const ctx = {
348
+ value,
349
+ percent,
350
+ min: getMin(),
351
+ max: getMax(),
352
+ buffer: getBuffer(),
353
+ progress: wrap,
354
+ track,
355
+ bar: fill,
356
+ props
357
+ };
358
+ const startSource = resolveContentProp(props.startLabel ?? props.leftLabel);
359
+ const endSource = resolveContentProp(props.endLabel ?? props.rightLabel);
360
+ renderInto(startLabelHost, renderSlotToArray(slots, "startLabel", ctx, startSource), "inline-flex");
361
+ renderInto(endLabelHost, renderSlotToArray(slots, "endLabel", ctx, endSource), "inline-flex");
362
+ };
363
+
364
+ const renderInsideValue = () => {
365
+ const value = getValue();
366
+ const percent = ratioFromValue(value) * 100;
367
+ const ctx = {
368
+ value,
369
+ percent,
370
+ min: getMin(),
371
+ max: getMax(),
372
+ buffer: getBuffer(),
373
+ progress: wrap,
374
+ track,
375
+ bar: fill,
376
+ props
377
+ };
378
+ const showValue = uiUnwrap(props.showValue);
379
+ const insideFallback = props.insideLabel != null
380
+ ? resolveContentProp(props.insideLabel)
381
+ : resolveDisplayValue(value, percent, ctx);
382
+ const insideNodes = (showValue === "inside" || props.insideLabel != null || JSswift.ui.getSlot(slots, "inside") != null)
383
+ ? renderSlotToArray(slots, "inside", ctx, insideFallback)
384
+ : [];
385
+ renderInto(insideHost, insideNodes, "inline-flex");
386
+ };
387
+
388
+ const syncVisualState = () => {
389
+ const value = getValue();
390
+ const bufferValue = getBuffer();
391
+ const min = getMin();
392
+ const max = getMax();
393
+ const percent = ratioFromValue(value) * 100;
394
+ const bufferPercent = ratioFromValue(bufferValue) * 100;
395
+ const tone = resolveCssColor(props.color, "");
396
+ const trackTone = resolveCssColor(
397
+ props.trackColor,
398
+ tone ? `color-mix(in srgb, ${tone} 14%, transparent)` : "rgba(255,255,255,0.08)"
399
+ );
400
+ const bufferTone = resolveCssColor(
401
+ props.bufferColor,
402
+ tone ? `color-mix(in srgb, ${tone} 30%, transparent)` : "rgba(255,255,255,0.16)"
403
+ );
404
+ const ariaLabel = uiUnwrap(props.ariaLabel);
405
+ const ariaValueText = uiUnwrap(props.ariaValueText);
406
+ const isIndeterminate = !!uiUnwrap(props.indeterminate);
407
+ const customRadius = uiUnwrap(props.radius);
408
+
409
+ wrap.style.width = uiUnwrap(props.width) || "100%";
410
+ wrap.style.setProperty("--jss-progress-height", getTrackHeight());
411
+ if (customRadius != null) {
412
+ if (typeof customRadius === "number") wrap.style.setProperty("--jss-progress-radius", `${customRadius}px`);
413
+ else if (typeof customRadius === "string" && JSswift.uiSizes.includes(customRadius)) wrap.style.setProperty("--jss-progress-radius", `var(--jss-r-${customRadius})`);
414
+ else wrap.style.setProperty("--jss-progress-radius", String(customRadius));
415
+ } else {
416
+ wrap.style.removeProperty("--jss-progress-radius");
417
+ }
418
+
419
+ track.style.background = trackTone;
420
+ buffer.style.background = bufferTone;
421
+ buffer.style.width = `${bufferPercent}%`;
422
+
423
+ if (tone && !normalizeState(uiUnwrap(props.state) || uiUnwrap(props.color))) {
424
+ fill.style.setProperty("--set-background-color", tone);
425
+ fill.style.setProperty("--set-border-color", tone);
426
+ fill.style.setProperty("--set-color", uiUnwrap(props.textColor) || "var(--jss-on-primary, #fff)");
427
+ } else if (!normalizeState(uiUnwrap(props.state) || uiUnwrap(props.color))) {
428
+ fill.style.setProperty("--set-background-color", "var(--jss-primary)");
429
+ fill.style.setProperty("--set-border-color", "var(--jss-primary)");
430
+ fill.style.setProperty("--set-color", uiUnwrap(props.textColor) || "var(--jss-on-primary, #fff)");
431
+ } else {
432
+ fill.style.removeProperty("--set-background-color");
433
+ fill.style.removeProperty("--set-border-color");
434
+ fill.style.removeProperty("--set-color");
435
+ }
436
+
437
+ if (isIndeterminate) {
438
+ fill.style.width = "";
439
+ track.removeAttribute("aria-valuenow");
440
+ track.removeAttribute("aria-valuemin");
441
+ track.removeAttribute("aria-valuemax");
442
+ } else {
443
+ fill.style.width = `${percent}%`;
444
+ track.setAttribute("aria-valuemin", String(min));
445
+ track.setAttribute("aria-valuemax", String(max));
446
+ track.setAttribute("aria-valuenow", String(value));
447
+ }
448
+
449
+ if (ariaLabel != null) track.setAttribute("aria-label", String(ariaLabel));
450
+ else if (typeof uiUnwrap(props.label) === "string") track.setAttribute("aria-label", String(uiUnwrap(props.label)));
451
+ else track.removeAttribute("aria-label");
452
+
453
+ if (ariaValueText != null) {
454
+ track.setAttribute("aria-valuetext", String(ariaValueText));
455
+ } else if (!isIndeterminate) {
456
+ track.setAttribute("aria-valuetext", resolveDisplayValue(value, percent, { value, percent, min, max, buffer: bufferValue, progress: wrap, track, bar: fill, props }));
457
+ } else {
458
+ track.removeAttribute("aria-valuetext");
459
+ }
460
+
461
+ wrap.classList.toggle("has-start-label", startLabelHost.childNodes.length > 0);
462
+ wrap.classList.toggle("has-end-label", endLabelHost.childNodes.length > 0);
463
+ wrap.classList.toggle("has-inside-label", insideHost.childNodes.length > 0);
464
+ };
465
+
466
+ if (model) {
467
+ setProgressValue(model.get(), { fromModel: true });
468
+ model.watch((value) => { setProgressValue(value, { fromModel: true }); }, "UI.Progress:watch");
469
+ } else if (uiIsReactive(props.value)) {
470
+ JSswift.reactive.effect(() => {
471
+ setProgressValue(uiUnwrap(props.value), { fromModel: true });
472
+ }, "UI.Progress:value");
473
+ } else {
474
+ setProgressValue(props.value ?? props.min ?? 0, { fromModel: true });
475
+ }
476
+
477
+ if (uiIsReactive(props.buffer)) {
478
+ JSswift.reactive.effect(() => {
479
+ setProgressBuffer(uiUnwrap(props.buffer));
480
+ }, "UI.Progress:buffer");
481
+ } else {
482
+ setProgressBuffer(props.buffer ?? props.value ?? props.min ?? 0);
483
+ }
484
+
485
+ JSswift.reactive.effect(() => {
486
+ renderHeader();
487
+ renderEdgeLabels();
488
+ renderInsideValue();
489
+ syncVisualState();
490
+ }, "UI.Progress:render");
491
+
492
+ wrap._track = track;
493
+ wrap._bar = fill;
494
+ wrap._buffer = buffer;
495
+ wrap._getValue = getValue;
496
+ wrap._getBuffer = getBuffer;
497
+ wrap._setValue = (value) => setProgressValue(value);
498
+ wrap._setBuffer = (value) => setProgressBuffer(value);
499
+
500
+ return wrap;
501
+ };
502
+
503
+ // Esempio: JSswift.ui.Progress({ value: 45 })
504
+
505
+ UI.LoadingBar = function LoadingBar(...args) {
506
+ const { props, children } = JSswift.uiNormalizeArgs(args);
507
+ const getNumber = (value, fallback) => {
508
+ const next = Number(value);
509
+ return Number.isFinite(next) ? next : fallback;
510
+ };
511
+ const getMin = () => getNumber(uiUnwrap(props.min), 0);
512
+ const getMax = () => {
513
+ const min = getMin();
514
+ const max = getNumber(uiUnwrap(props.max), 100);
515
+ return max < min ? min : max;
516
+ };
517
+ const clampValue = (value) => {
518
+ const min = getMin();
519
+ const max = getMax();
520
+ const next = getNumber(value, min);
521
+ return Math.min(max, Math.max(min, next));
522
+ };
523
+ const toCssValue = (value, fallback = "") => {
524
+ if (value == null || value === false || value === "") return fallback;
525
+ if (typeof value === "number") return `${value}px`;
526
+ return String(value);
527
+ };
528
+ const resolveTarget = (target) => {
529
+ const raw = uiUnwrap(target);
530
+ if (!raw) return document.body;
531
+ if (raw === document) return document.body;
532
+ if (typeof raw === "string") return document.querySelector(raw) || document.body;
533
+ if (raw && raw.body instanceof HTMLElement) return raw.body;
534
+ return raw instanceof HTMLElement ? raw : document.body;
535
+ };
536
+
537
+ const valueBinding = props.model || ((uiIsSignal(props.value) || uiIsRod(props.value)) ? props.value : null);
538
+ const valueModel = resolveModel(valueBinding, "UI.LoadingBar:model");
539
+ const bufferBinding = (uiIsSignal(props.buffer) || uiIsRod(props.buffer)) ? props.buffer : null;
540
+ const bufferModel = resolveModel(bufferBinding, "UI.LoadingBar:buffer");
541
+ const initialValue = valueModel ? valueModel.get() : (uiUnwrap(props.value) ?? getMin());
542
+ const initialBuffer = bufferModel ? bufferModel.get() : (uiUnwrap(props.buffer) ?? initialValue);
543
+ const [getValue, setValueSignal] = JSswift.reactive.signal(clampValue(initialValue));
544
+ const [getBuffer, setBufferSignal] = JSswift.reactive.signal(Math.max(clampValue(initialBuffer), clampValue(initialValue)));
545
+ const [getVisible, setVisibleSignal] = JSswift.reactive.signal(
546
+ props.visible != null ? !!uiUnwrap(props.visible) : (clampValue(initialValue) > getMin() || !!uiUnwrap(props.indeterminate))
547
+ );
548
+
549
+ let trickleTimer = null;
550
+ let doneTimer = null;
551
+
552
+ const clearTrickle = () => {
553
+ if (trickleTimer) {
554
+ clearInterval(trickleTimer);
555
+ trickleTimer = null;
556
+ }
557
+ };
558
+ const clearDoneTimer = () => {
559
+ if (doneTimer) {
560
+ clearTimeout(doneTimer);
561
+ doneTimer = null;
562
+ }
563
+ };
564
+ const syncVisibility = (next, opts = {}) => {
565
+ const visible = !!next;
566
+ if (getVisible() !== visible) setVisibleSignal(visible);
567
+ if (opts.fromExternal !== true && typeof props.onVisibleChange === "function") {
568
+ props.onVisibleChange(visible);
569
+ }
570
+ return visible;
571
+ };
572
+ const syncValue = (raw, opts = {}) => {
573
+ const next = clampValue(raw);
574
+ if (getValue() !== next) setValueSignal(next);
575
+ if (getBuffer() < next) {
576
+ setBufferSignal(next);
577
+ if (bufferModel && opts.fromExternal !== true) bufferModel.set(next);
578
+ }
579
+ if (valueModel && opts.fromExternal !== true) valueModel.set(next);
580
+ return next;
581
+ };
582
+ const syncBuffer = (raw, opts = {}) => {
583
+ const next = Math.max(syncValue(getValue(), { fromExternal: true }), clampValue(raw));
584
+ if (getBuffer() !== next) setBufferSignal(next);
585
+ if (bufferModel && opts.fromExternal !== true) bufferModel.set(next);
586
+ return next;
587
+ };
588
+ const showIfNeeded = (nextValue = getValue()) => {
589
+ if (props.visible != null) return;
590
+ if (uiUnwrap(props.indeterminate) || nextValue > getMin()) syncVisibility(true);
591
+ else if (uiUnwrap(props.hideOnZero) !== false) syncVisibility(false);
592
+ };
593
+ const startTrickle = () => {
594
+ clearTrickle();
595
+ if (uiUnwrap(props.trickle) === false || uiUnwrap(props.indeterminate)) return;
596
+ const interval = getNumber(uiUnwrap(props.trickleInterval), 280);
597
+ if (interval <= 0) return;
598
+ trickleTimer = setInterval(() => {
599
+ const current = getValue();
600
+ const max = clampValue(uiUnwrap(props.trickleTo) ?? uiUnwrap(props.trickleMax) ?? 92);
601
+ if (current >= max) return;
602
+ const step = getNumber(uiUnwrap(props.trickleStep), current < 45 ? 12 : (current < 75 ? 7 : 3));
603
+ syncValue(Math.min(max, current + step));
604
+ }, interval);
605
+ };
606
+
607
+ const shellProps = JSswift.omit(props, [
608
+ "model", "value", "min", "max", "buffer", "class", "style", "slots",
609
+ "label", "note", "showValue", "valueLabel", "insideLabel", "formatValue",
610
+ "icon", "iconRight", "iconSize", "startLabel", "endLabel", "leftLabel", "rightLabel",
611
+ "trackColor", "bufferColor", "height", "thickness", "size", "state", "color",
612
+ "reverse", "striped", "animated", "indeterminate", "ariaLabel", "ariaValueText",
613
+ "width", "dense", "outline", "flat", "border", "glossy", "glow", "glass",
614
+ "shadow", "gradient", "textGradient", "lightShadow", "radius", "rounded", "textColor",
615
+ "target", "mount", "position", "top", "right", "bottom", "left", "inset", "zIndex",
616
+ "visible", "autoStart", "hideOnZero", "startValue", "step", "trickle", "trickleStep",
617
+ "trickleInterval", "trickleMax", "trickleTo", "doneValue", "doneDelay", "hideDelay",
618
+ "resetValue", "progressClass", "progressStyle", "onVisibleChange"
619
+ ]);
620
+ shellProps.class = uiClass(["jss-loading-bar", props.class]);
621
+ shellProps.style = { ...(props.style || {}) };
622
+
623
+ const progressProps = JSswift.omit(props, [
624
+ "target", "mount", "position", "top", "right", "bottom", "left", "inset", "zIndex",
625
+ "visible", "autoStart", "hideOnZero", "startValue", "step", "trickle", "trickleStep",
626
+ "trickleInterval", "trickleMax", "trickleTo", "doneValue", "doneDelay", "hideDelay",
627
+ "resetValue", "progressClass", "progressStyle", "onVisibleChange"
628
+ ]);
629
+ progressProps.class = uiClass(["jss-loading-bar-progress", props.progressClass, progressProps.class]);
630
+ progressProps.style = { ...(props.progressStyle || {}) };
631
+ progressProps.value = [getValue, setValueSignal];
632
+ progressProps.buffer = [getBuffer, setBufferSignal];
633
+ if (progressProps.dense == null) progressProps.dense = true;
634
+ if (progressProps.height == null && progressProps.thickness == null && progressProps.size == null) {
635
+ progressProps.height = 3;
636
+ }
637
+ if (progressProps.width == null) progressProps.width = "100%";
638
+
639
+ const root = _.div(shellProps);
640
+ const progress = UI.Progress(progressProps, ...children);
641
+ root.appendChild(progress);
642
+
643
+ const set = (value) => {
644
+ clearDoneTimer();
645
+ const next = syncValue(value);
646
+ showIfNeeded(next);
647
+ return root;
648
+ };
649
+ const setBuffer = (value) => {
650
+ clearDoneTimer();
651
+ const next = syncBuffer(value);
652
+ showIfNeeded(next);
653
+ return root;
654
+ };
655
+ const inc = (step = uiUnwrap(props.step) ?? 8) => {
656
+ clearDoneTimer();
657
+ const current = getValue();
658
+ const next = syncValue(current + getNumber(step, 0));
659
+ showIfNeeded(next);
660
+ return root;
661
+ };
662
+ const start = (value = uiUnwrap(props.startValue) ?? 12) => {
663
+ clearDoneTimer();
664
+ const next = Math.max(getValue(), clampValue(value));
665
+ syncValue(next);
666
+ syncBuffer(Math.max(getBuffer(), next));
667
+ showIfNeeded(next);
668
+ startTrickle();
669
+ return root;
670
+ };
671
+ const reset = (value = uiUnwrap(props.resetValue) ?? getMin()) => {
672
+ clearDoneTimer();
673
+ clearTrickle();
674
+ const next = clampValue(value);
675
+ syncBuffer(next);
676
+ syncValue(next);
677
+ showIfNeeded(next);
678
+ return root;
679
+ };
680
+ const done = (value = uiUnwrap(props.doneValue) ?? getMax()) => {
681
+ clearDoneTimer();
682
+ clearTrickle();
683
+ const next = clampValue(value);
684
+ syncBuffer(next);
685
+ syncValue(next);
686
+ showIfNeeded(next);
687
+ const delay = Math.max(0, getNumber(uiUnwrap(props.hideDelay) ?? uiUnwrap(props.doneDelay), 220));
688
+ doneTimer = setTimeout(() => { reset(); }, delay);
689
+ return root;
690
+ };
691
+ const stop = (...innerArgs) => done(...innerArgs);
692
+ const show = () => {
693
+ syncVisibility(true);
694
+ return root;
695
+ };
696
+ const hide = () => {
697
+ clearDoneTimer();
698
+ clearTrickle();
699
+ syncVisibility(false);
700
+ return root;
701
+ };
702
+ const destroy = () => {
703
+ clearDoneTimer();
704
+ clearTrickle();
705
+ root.remove();
706
+ return null;
707
+ };
708
+
709
+ root.el = root;
710
+ root._progress = progress;
711
+ root.get = () => getValue();
712
+ root.getBuffer = () => getBuffer();
713
+ root.set = set;
714
+ root.setBuffer = setBuffer;
715
+ root.inc = inc;
716
+ root.start = start;
717
+ root.done = done;
718
+ root.complete = done;
719
+ root.stop = stop;
720
+ root.reset = reset;
721
+ root.show = show;
722
+ root.hide = hide;
723
+ root.destroy = destroy;
724
+ root._dispose = destroy;
725
+
726
+ if (valueModel) {
727
+ valueModel.watch((value) => { syncValue(value, { fromExternal: true }); showIfNeeded(clampValue(value)); }, "UI.LoadingBar:watch");
728
+ } else if (uiIsReactive(props.value)) {
729
+ JSswift.reactive.effect(() => {
730
+ const next = clampValue(uiUnwrap(props.value));
731
+ syncValue(next, { fromExternal: true });
732
+ showIfNeeded(next);
733
+ }, "UI.LoadingBar:value");
734
+ }
735
+
736
+ if (bufferModel) {
737
+ bufferModel.watch((value) => { syncBuffer(value, { fromExternal: true }); }, "UI.LoadingBar:bufferWatch");
738
+ } else if (uiIsReactive(props.buffer)) {
739
+ JSswift.reactive.effect(() => {
740
+ syncBuffer(uiUnwrap(props.buffer), { fromExternal: true });
741
+ }, "UI.LoadingBar:buffer");
742
+ }
743
+
744
+ JSswift.reactive.effect(() => {
745
+ const min = getMin();
746
+ const max = getMax();
747
+ const current = Math.min(max, Math.max(min, getValue()));
748
+ const bufferCurrent = Math.max(current, Math.min(max, Math.max(min, getBuffer())));
749
+ if (current !== getValue()) setValueSignal(current);
750
+ if (bufferCurrent !== getBuffer()) setBufferSignal(bufferCurrent);
751
+ }, "UI.LoadingBar:range");
752
+
753
+ JSswift.reactive.effect(() => {
754
+ if (props.visible == null) return;
755
+ syncVisibility(uiUnwrap(props.visible), { fromExternal: true });
756
+ }, "UI.LoadingBar:visible");
757
+
758
+ JSswift.reactive.effect(() => {
759
+ const position = uiUnwrap(props.position) || "fixed";
760
+ const inset = uiUnwrap(props.inset);
761
+ const top = uiUnwrap(props.top);
762
+ const right = uiUnwrap(props.right);
763
+ const bottom = uiUnwrap(props.bottom);
764
+ const left = uiUnwrap(props.left);
765
+ const width = uiUnwrap(props.width);
766
+ const visible = props.visible != null ? !!uiUnwrap(props.visible) : getVisible();
767
+ const inlineLike = position === "static" || position === "relative";
768
+
769
+ root.classList.toggle("is-visible", visible);
770
+ root.classList.toggle("is-inline", inlineLike);
771
+ root.classList.toggle("has-track", !!uiUnwrap(props.trackColor));
772
+
773
+ root.style.position = position;
774
+ root.style.zIndex = String(uiUnwrap(props.zIndex) ?? 10002);
775
+ root.style.width = toCssValue(width, inlineLike ? "100%" : "");
776
+
777
+ if (inset != null && inset !== "") {
778
+ root.style.inset = toCssValue(inset);
779
+ root.style.removeProperty("top");
780
+ root.style.removeProperty("right");
781
+ root.style.removeProperty("bottom");
782
+ root.style.removeProperty("left");
783
+ } else {
784
+ root.style.removeProperty("inset");
785
+ if (!inlineLike && position !== "sticky") {
786
+ root.style.top = toCssValue(top, "0px");
787
+ root.style.right = toCssValue(right, width == null || width === "" ? "0px" : "");
788
+ root.style.bottom = toCssValue(bottom, "");
789
+ root.style.left = toCssValue(left, "0px");
790
+ } else {
791
+ root.style.top = toCssValue(top, "");
792
+ root.style.right = toCssValue(right, "");
793
+ root.style.bottom = toCssValue(bottom, "");
794
+ root.style.left = toCssValue(left, "");
795
+ }
796
+ }
797
+ }, "UI.LoadingBar:layout");
798
+
799
+ setPropertyProps(root, props);
800
+
801
+ if (uiUnwrap(props.mount) !== false) {
802
+ resolveTarget(props.target).appendChild(root);
803
+ }
804
+ if (uiUnwrap(props.autoStart)) start();
805
+ return root;
806
+ };
807
+
808
+ // Esempio: const lb = JSswift.ui.LoadingBar({ autoStart: true }); lb.done();
809
+
810
+ const NOTIFY_POSITIONS = new Set([
811
+ "top-left",
812
+ "top-center",
813
+ "top-right",
814
+ "bottom-left",
815
+ "bottom-center",
816
+ "bottom-right"
817
+ ]);
818
+ const NOTIFY_VARIANTS = new Set(["soft", "solid", "outline"]);
819
+ const NOTIFY_DEFAULT_TITLES = {
820
+ success: "Success",
821
+ warning: "Warning",
822
+ danger: "Error",
823
+ info: "Info",
824
+ primary: "Notice",
825
+ secondary: "Notice",
826
+ light: "Notice",
827
+ dark: "Notice"
828
+ };
829
+ const notifyIconMap = {
830
+ success: "check_circle",
831
+ warning: "warning",
832
+ danger: "error",
833
+ info: "info",
834
+ primary: "bolt",
835
+ secondary: "notifications",
836
+ light: "notifications",
837
+ dark: "shield"
838
+ };
839
+
840
+ const normalizeNotifyPosition = (value) => {
841
+ const raw = String(uiUnwrap(value) || "").trim().toLowerCase();
842
+ if (!raw) return "bottom-right";
843
+ if (NOTIFY_POSITIONS.has(raw)) return raw;
844
+ if (raw === "top") return "top-right";
845
+ if (raw === "bottom") return "bottom-right";
846
+ if (raw === "left") return "bottom-left";
847
+ if (raw === "right") return "bottom-right";
848
+ if (raw === "center") return "bottom-center";
849
+ return "bottom-right";
850
+ };
851
+
852
+ const normalizeNotifyVariant = (value) => {
853
+ const raw = String(uiUnwrap(value) || "").trim().toLowerCase();
854
+ return NOTIFY_VARIANTS.has(raw) ? raw : "soft";
855
+ };
856
+
857
+ const normalizeNotifyTimeout = (value, tone) => {
858
+ const raw = uiUnwrap(value);
859
+ if (raw === false || raw === null) return 0;
860
+ if (raw === true) return tone === "danger" ? 3500 : 2500;
861
+ if (raw === "" || raw === undefined) return tone === "danger" ? 3500 : 2500;
862
+ const num = Number(raw);
863
+ return Number.isFinite(num) && num >= 0 ? num : (tone === "danger" ? 3500 : 2500);
864
+ };
865
+
866
+ const normalizeNotifyPayload = (input = {}, forcedType = "") => {
867
+ const src = uiIsPlainObject(input) ? { ...input } : { message: input };
868
+ const tone = normalizeState(uiUnwrap(forcedType) || uiUnwrap(src.type) || uiUnwrap(src.state) || uiUnwrap(src.color) || "info") || "info";
869
+ const timeout = normalizeNotifyTimeout(src.timeout ?? src.duration ?? src.delay, tone);
870
+ const closable = src.closable ?? src.dismissible ?? (src.dismiss != null) ?? (timeout === 0);
871
+ const title = src.title === undefined
872
+ ? (src.label === undefined ? NOTIFY_DEFAULT_TITLES[tone] : src.label)
873
+ : src.title;
874
+
875
+ return {
876
+ ...src,
877
+ type: tone,
878
+ state: tone,
879
+ title,
880
+ message: src.message ?? src.content ?? src.text ?? null,
881
+ description: src.description ?? src.subtitle ?? src.note ?? null,
882
+ meta: src.meta ?? src.caption ?? null,
883
+ body: src.body ?? null,
884
+ timeout,
885
+ closable: !!closable,
886
+ dismissLabel: src.dismissLabel || src.closeLabel || "Chiudi notifica",
887
+ position: normalizeNotifyPosition(src.position),
888
+ variant: normalizeNotifyVariant(src.variant),
889
+ slots: src.slots || {},
890
+ role: src.role || ((tone === "danger" || tone === "warning") ? "alert" : "status")
891
+ };
892
+ };
893
+
894
+ const buildNotifyOptionsFromArgs = (args, forcedType = "", defaultTitle = "") => {
895
+ if (!args.length) {
896
+ return normalizeNotifyPayload(defaultTitle ? { type: forcedType, title: defaultTitle } : { type: forcedType });
897
+ }
898
+
899
+ if (uiIsPlainObject(args[0])) {
900
+ const { props, children } = JSswift.uiNormalizeArgs(args);
901
+ const next = { ...props };
902
+ if (!next.type && !next.state && !next.color && forcedType) next.type = forcedType;
903
+ if (next.title == null && defaultTitle) next.title = defaultTitle;
904
+ if (children.length) {
905
+ if (next.message == null && next.description == null && next.body == null && children.length === 1) {
906
+ next.message = children[0];
907
+ } else if (next.body == null) {
908
+ next.body = children.length === 1 ? children[0] : children;
909
+ }
910
+ }
911
+ return normalizeNotifyPayload(next, forcedType);
912
+ }
913
+
914
+ const [message, second, third] = args;
915
+ let next = {};
916
+ if (uiIsPlainObject(second)) {
917
+ next = { ...second, message };
918
+ } else if (uiIsPlainObject(third)) {
919
+ next = { ...third, message, title: second };
920
+ } else {
921
+ next = { message, title: second };
922
+ }
923
+ if (forcedType && !next.type && !next.state && !next.color) next.type = forcedType;
924
+ if (next.title == null && defaultTitle) next.title = defaultTitle;
925
+ return normalizeNotifyPayload(next, forcedType);
926
+ };
927
+
928
+ const createNotifyShortcut = (type, defaultTitle) => (...args) =>
929
+ app.services.notify?.show?.(buildNotifyOptionsFromArgs(args, type, defaultTitle));
930
+
931
+ const resolveNotifyPromiseOutcome = (value, fallbackMessage, fallbackTitle) => {
932
+ if (value === false) return false;
933
+ if (typeof value === "function") {
934
+ const next = value();
935
+ return resolveNotifyPromiseOutcome(next, fallbackMessage, fallbackTitle);
936
+ }
937
+ if (uiIsPlainObject(value)) return value;
938
+ if (value != null) return { message: value, title: fallbackTitle };
939
+ return { message: fallbackMessage, title: fallbackTitle };
940
+ };
941
+
942
+ UI.Notify = (...args) => app.services.notify?.show?.(buildNotifyOptionsFromArgs(args));
943
+ UI.Notify.show = UI.Notify;
944
+ UI.Notify.success = createNotifyShortcut("success", "Success");
945
+ UI.Notify.error = createNotifyShortcut("danger", "Error");
946
+ UI.Notify.warning = createNotifyShortcut("warning", "Warning");
947
+ UI.Notify.info = createNotifyShortcut("info", "Info");
948
+ UI.Notify.primary = createNotifyShortcut("primary", "Notice");
949
+ UI.Notify.secondary = createNotifyShortcut("secondary", "Notice");
950
+ UI.Notify.remove = (id) => app.services.notify?.remove?.(id);
951
+ UI.Notify.clear = () => app.services.notify?.clear?.();
952
+ UI.Notify.update = (id, patch = {}) => app.services.notify?.update?.(id, patch);
953
+ UI.Notify.promise = async (task, opts = {}) => {
954
+ const loadingInput = opts.loading === false
955
+ ? false
956
+ : resolveNotifyPromiseOutcome(
957
+ typeof opts.loading === "function" ? () => opts.loading() : opts.loading,
958
+ "Operazione in corso...",
959
+ "In corso"
960
+ );
961
+ const loadingId = loadingInput === false
962
+ ? null
963
+ : app.services.notify?.show?.(normalizeNotifyPayload({
964
+ type: "info",
965
+ timeout: 0,
966
+ closable: false,
967
+ ...loadingInput
968
+ }, "info"));
969
+
970
+ try {
971
+ const promise = typeof task === "function" ? task() : task;
972
+ const result = await promise;
973
+ const successInput = resolveNotifyPromiseOutcome(
974
+ typeof opts.success === "function" ? () => opts.success(result) : opts.success,
975
+ "Operazione completata con successo.",
976
+ "Completato"
977
+ );
978
+ if (successInput !== false) {
979
+ if (loadingId != null) app.services.notify?.update?.(loadingId, normalizeNotifyPayload(successInput, "success"));
980
+ else app.services.notify?.show?.(normalizeNotifyPayload(successInput, "success"));
981
+ } else if (loadingId != null) {
982
+ app.services.notify?.remove?.(loadingId);
983
+ }
984
+ return result;
985
+ } catch (error) {
986
+ const errorInput = resolveNotifyPromiseOutcome(
987
+ typeof opts.error === "function" ? () => opts.error(error) : opts.error,
988
+ error?.message || "Operazione fallita.",
989
+ "Errore"
990
+ );
991
+ if (errorInput !== false) {
992
+ if (loadingId != null) app.services.notify?.update?.(loadingId, normalizeNotifyPayload(errorInput, "danger"));
993
+ else app.services.notify?.show?.(normalizeNotifyPayload(errorInput, "danger"));
994
+ } else if (loadingId != null) {
995
+ app.services.notify?.remove?.(loadingId);
996
+ }
997
+ throw error;
998
+ }
999
+ };