@jsswift/ui 1.0.44 → 1.0.46

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.
@@ -1,1209 +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(--cms-${str})`;
34
- return str;
35
- };
36
-
37
- const stateClass = uiComputed(props.state, () => {
38
- const state = normalizeState(uiUnwrap(props.state));
39
- return state ? `cms-state-${state}` : "";
40
- });
41
-
42
- const cls = uiClass([
43
- "cms-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(["cms-spinner-indicator", props.indicatorClass]),
83
- style: {
84
- "--cms-spinner-size": makeCssVarValue(props.size, toCssSize, "18px"),
85
- "--cms-spinner-thickness": makeCssVarValue(props.thickness, toCssSize, "2px"),
86
- "--cms-spinner-speed": makeCssVarValue(props.speed, (v) => typeof v === "number" ? `${v}ms` : String(v), "900ms"),
87
- "--cms-spinner-color": makeCssVarValue(props.color, resolveSpinnerColor, "var(--set-border-color, var(--cms-primary))"),
88
- "--cms-spinner-track": makeCssVarValue(props.trackColor, resolveSpinnerColor, "color-mix(in srgb, var(--cms-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: "cms-spinner-content" },
100
- labelNodes.length ? _.div({ class: "cms-spinner-label" }, ...labelNodes) : null,
101
- copyNodes.length ? _.div({ class: "cms-spinner-copy" }, ...copyNodes) : null,
102
- noteNodes.length ? _.div({ class: "cms-spinner-note" }, ...noteNodes) : null
103
- ));
104
- }
105
-
106
- const root = _.div(p, ...content);
107
- setPropertyProps(root, props);
108
- return root;
109
- };
110
- if (JSswift.isDev?.()) {
111
- UI.meta = UI.meta || {};
112
- UI.meta.Spinner = {
113
- signature: "UI.Spinner(...children) | UI.Spinner(props, ...children)",
114
- props: {
115
- size: "number|string",
116
- color: "string",
117
- thickness: "number|string",
118
- trackColor: "string",
119
- speed: "number|string",
120
- state: "primary|secondary|success|warning|danger|info|light|dark",
121
- label: "String|Node|Function|Array",
122
- note: "String|Node|Function|Array",
123
- vertical: "boolean",
124
- reverse: "boolean",
125
- center: "boolean",
126
- block: "boolean",
127
- pause: "boolean",
128
- paused: "boolean",
129
- ariaLabel: "string",
130
- indicatorClass: "string",
131
- indicatorStyle: "object",
132
- slots: "{ indicator?, label?, note?, default? }",
133
- class: "string",
134
- style: "object"
135
- },
136
- slots: {
137
- indicator: "Custom spinner indicator",
138
- label: "Primary label/content near the spinner",
139
- note: "Secondary supporting text",
140
- default: "Extra content rendered near the spinner"
141
- },
142
- returns: "HTMLDivElement",
143
- description: "Animated spinner with flexible layout, optional content, and controls for size, speed, and track."
144
- };
145
- }
146
- // Esempio: JSswift.ui.Spinner({ size: 24 })
147
-
148
- UI.Progress = (...args) => {
149
- const { props, children } = JSswift.uiNormalizeArgs(args);
150
- const slots = props.slots || {};
151
- const boundValue = props.model || ((uiIsSignal(props.value) || uiIsRod(props.value)) ? props.value : null);
152
- const model = resolveModel(boundValue, "UI.Progress:model");
153
- const stateClass = uiComputed([props.state, props.color], () => {
154
- const state = normalizeState(uiUnwrap(props.state) || uiUnwrap(props.color));
155
- return state ? `cms-state-${state}` : "";
156
- });
157
-
158
- const getNumber = (value, fallback) => {
159
- const next = Number(value);
160
- return Number.isFinite(next) ? next : fallback;
161
- };
162
- const getMin = () => getNumber(uiUnwrap(props.min), 0);
163
- const getMax = () => {
164
- const min = getMin();
165
- const max = getNumber(uiUnwrap(props.max), 100);
166
- return max < min ? min : max;
167
- };
168
- const getTrackHeight = () => {
169
- const raw = uiUnwrap(props.height ?? props.thickness ?? props.size);
170
- if (raw == null || raw === "") return "8px";
171
- if (typeof raw === "number") return `${raw}px`;
172
- if (typeof raw === "string") {
173
- const sizePreset = {
174
- xxs: "4px",
175
- xs: "6px",
176
- sm: "8px",
177
- md: "10px",
178
- lg: "12px",
179
- xl: "14px",
180
- xxl: "16px",
181
- xxxl: "18px"
182
- };
183
- return sizePreset[raw] || raw;
184
- }
185
- return "8px";
186
- };
187
- const resolveCssColor = (value, fallback = "") => {
188
- const raw = uiUnwrap(value);
189
- if (raw == null || raw === false || raw === "") return fallback;
190
- const state = normalizeState(raw);
191
- if (state) return `var(--cms-${state})`;
192
- if (typeof raw === "string" && isTokenCSS(raw)) return `var(--cms-${raw})`;
193
- return String(raw);
194
- };
195
- const normalizeValue = (value) => {
196
- const min = getMin();
197
- const max = getMax();
198
- const next = getNumber(value, min);
199
- return Math.min(max, Math.max(min, next));
200
- };
201
- const ratioFromValue = (value) => {
202
- const min = getMin();
203
- const max = getMax();
204
- const span = max - min;
205
- if (!span) return 0;
206
- const raw = (normalizeValue(value) - min) / span;
207
- const ratio = uiUnwrap(props.reverse) ? (1 - raw) : raw;
208
- return Math.max(0, Math.min(1, ratio));
209
- };
210
- const clampBuffer = (value) => {
211
- const normalized = normalizeValue(value);
212
- return normalized < getValue() ? getValue() : normalized;
213
- };
214
- const resolveDisplayValue = (value, percent, ctx) => {
215
- const formatter = props.formatValue;
216
- if (typeof formatter === "function") {
217
- const formatted = formatter(value, percent, ctx);
218
- if (formatted != null) return formatted;
219
- }
220
- return `${Math.round(percent)}%`;
221
- };
222
- const clearHost = (host) => {
223
- while (host.firstChild) host.removeChild(host.firstChild);
224
- };
225
- const renderInto = (host, nodes, display = "flex") => {
226
- clearHost(host);
227
- (nodes || []).forEach((node) => {
228
- if (node == null || node === false) return;
229
- if (node instanceof Node) {
230
- host.appendChild(node);
231
- return;
232
- }
233
- host.appendChild(document.createTextNode(String(node)));
234
- });
235
- host.style.display = host.childNodes.length ? display : "none";
236
- };
237
- const asArray = (value, ctx = {}) => slotToArray(uiUnwrap(value), ctx);
238
- const asIconArray = (value, as, ctx = {}) => {
239
- const raw = uiUnwrap(value);
240
- if (raw == null || raw === false || raw === "") return [];
241
- if (typeof raw === "string") return [UI.Icon({ name: raw, size: props.iconSize ?? 16 })];
242
- return asArray(raw, { ...ctx, as });
243
- };
244
- const resolveContentProp = (value) => {
245
- if (uiIsRod(value) || uiIsSignal(value)) return uiUnwrap(value);
246
- if (typeof value === "function" && value.length === 0) return value();
247
- return value;
248
- };
249
-
250
- const [getValue, setValue] = JSswift.reactive.signal(normalizeValue(
251
- model ? model.get() : (uiUnwrap(props.value) ?? uiUnwrap(props.min) ?? 0)
252
- ));
253
- const [getBuffer, setBuffer] = JSswift.reactive.signal(clampBuffer(
254
- uiUnwrap(props.buffer) ?? (model ? model.get() : (uiUnwrap(props.value) ?? uiUnwrap(props.min) ?? 0))
255
- ));
256
-
257
- const setProgressValue = (raw, opts = {}) => {
258
- const next = normalizeValue(raw);
259
- if (getValue() !== next) setValue(next);
260
- if (getBuffer() < next) setBuffer(next);
261
- if (model && opts.fromModel !== true) model.set(next);
262
- return next;
263
- };
264
- const setProgressBuffer = (raw) => {
265
- const next = clampBuffer(raw);
266
- if (getBuffer() !== next) setBuffer(next);
267
- return next;
268
- };
269
-
270
- const wrapProps = JSswift.omit(props, [
271
- "model", "value", "min", "max", "buffer", "class", "style", "slots",
272
- "label", "note", "showValue", "valueLabel", "insideLabel", "formatValue",
273
- "icon", "iconRight", "iconSize", "startLabel", "endLabel", "leftLabel", "rightLabel",
274
- "trackColor", "bufferColor", "height", "thickness", "size", "state", "color",
275
- "reverse", "striped", "animated", "indeterminate", "ariaLabel", "ariaValueText",
276
- "width", "dense", "outline", "flat", "border", "glossy", "glow", "glass",
277
- "shadow", "gradient", "textGradient", "lightShadow", "radius", "rounded", "textColor"
278
- ]);
279
- const rootStyle = {
280
- width: uiUnwrap(props.width) || "100%",
281
- ...(props.style || {})
282
- };
283
- const propColor = uiUnwrap(props.color);
284
- if ((propColor != null || uiIsReactive(props.color)) && !normalizeState(propColor || "")) {
285
- delete rootStyle.backgroundColor;
286
- }
287
- wrapProps.class = uiClass([
288
- "cms-progress-wrap",
289
- uiWhen(props.dense, "dense"),
290
- uiWhen(props.reverse, "is-reverse"),
291
- uiWhen(props.indeterminate, "is-indeterminate"),
292
- stateClass,
293
- props.class
294
- ]);
295
- wrapProps.style = rootStyle;
296
-
297
- const header = _.div({ class: "cms-progress-header" });
298
- const heading = _.div({ class: "cms-progress-heading" });
299
- const labelHost = _.div({ class: "cms-progress-label" });
300
- const noteHost = _.div({ class: "cms-progress-note" });
301
- const valueHost = _.div({ class: "cms-progress-value" });
302
- heading.append(labelHost, noteHost);
303
- header.append(heading, valueHost);
304
-
305
- const body = _.div({ class: "cms-progress-body" });
306
- const startLabelHost = _.span({ class: "cms-progress-edge-label cms-progress-edge-label-left" });
307
- const track = _.div({
308
- class: "cms-progress",
309
- role: "progressbar"
310
- });
311
- const buffer = _.span({ class: "cms-progress-buffer" });
312
- const fill = _.span({
313
- class: uiClass([
314
- "cms-progress-bar",
315
- "cms-singularity",
316
- stateClass,
317
- uiWhen(props.outline, "cms-outline"),
318
- uiWhen(props.flat, "cms-flat"),
319
- uiWhen(props.border, "cms-border"),
320
- uiWhen(props.glossy, "cms-glossy"),
321
- uiWhen(props.glow, "cms-glow"),
322
- uiWhen(props.glass, "cms-glass"),
323
- uiWhen(props.shadow, "cms-shadow"),
324
- uiComputed(props.shadow, () => {
325
- const shadow = normalizeShadow(uiUnwrap(props.shadow));
326
- return shadow ? `cms-shadow-${shadow}` : "";
327
- }),
328
- uiWhen(props.gradient, "cms-gradient"),
329
- uiWhen(props.textGradient, "cms-text-gradient"),
330
- uiWhen(props.lightShadow, "cms-light-shadow"),
331
- uiWhen(props.striped, "is-striped"),
332
- uiWhen(props.animated || props.indeterminate, "is-animated")
333
- ])
334
- });
335
- const insideHost = _.span({ class: "cms-progress-bar-label" });
336
- fill.appendChild(insideHost);
337
- track.append(buffer, fill);
338
- const endLabelHost = _.span({ class: "cms-progress-edge-label cms-progress-edge-label-right" });
339
- body.append(startLabelHost, track, endLabelHost);
340
-
341
- const wrap = _.div(wrapProps, header, body);
342
- setPropertyProps(fill, props);
343
-
344
- const renderHeader = () => {
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 iconNodes = renderSlotToArray(slots, "icon", ctx, null);
359
- const iconFallback = iconNodes.length ? [] : asIconArray(props.icon, "icon", ctx);
360
- const labelNodes = renderSlotToArray(slots, "label", ctx, resolveContentProp(props.label));
361
- const fallbackNodes = labelNodes.length ? labelNodes : renderSlotToArray(slots, "default", ctx, children);
362
- const rightIconNodes = renderSlotToArray(slots, "iconRight", ctx, null);
363
- const rightIconFallback = rightIconNodes.length ? [] : asIconArray(props.iconRight, "iconRight", ctx);
364
- const noteNodes = renderSlotToArray(slots, "note", ctx, resolveContentProp(props.note));
365
- const valueFallback = props.valueLabel != null
366
- ? resolveContentProp(props.valueLabel)
367
- : resolveDisplayValue(value, percent, ctx);
368
- const showValue = uiUnwrap(props.showValue);
369
- const outsideValueNodes = (showValue === true || props.valueLabel != null || JSswift.ui.getSlot(slots, "value") != null)
370
- ? renderSlotToArray(slots, "value", ctx, valueFallback)
371
- : [];
372
-
373
- renderInto(labelHost, [...iconFallback, ...iconNodes, ...fallbackNodes, ...rightIconFallback, ...rightIconNodes], "inline-flex");
374
- renderInto(noteHost, noteNodes, "block");
375
- renderInto(valueHost, showValue === "inside" ? [] : outsideValueNodes, "inline-flex");
376
- header.style.display = (labelHost.childNodes.length || noteHost.childNodes.length || valueHost.childNodes.length) ? "flex" : "none";
377
- };
378
-
379
- const renderEdgeLabels = () => {
380
- const value = getValue();
381
- const percent = ratioFromValue(value) * 100;
382
- const ctx = {
383
- value,
384
- percent,
385
- min: getMin(),
386
- max: getMax(),
387
- buffer: getBuffer(),
388
- progress: wrap,
389
- track,
390
- bar: fill,
391
- props
392
- };
393
- const startSource = resolveContentProp(props.startLabel ?? props.leftLabel);
394
- const endSource = resolveContentProp(props.endLabel ?? props.rightLabel);
395
- renderInto(startLabelHost, renderSlotToArray(slots, "startLabel", ctx, startSource), "inline-flex");
396
- renderInto(endLabelHost, renderSlotToArray(slots, "endLabel", ctx, endSource), "inline-flex");
397
- };
398
-
399
- const renderInsideValue = () => {
400
- const value = getValue();
401
- const percent = ratioFromValue(value) * 100;
402
- const ctx = {
403
- value,
404
- percent,
405
- min: getMin(),
406
- max: getMax(),
407
- buffer: getBuffer(),
408
- progress: wrap,
409
- track,
410
- bar: fill,
411
- props
412
- };
413
- const showValue = uiUnwrap(props.showValue);
414
- const insideFallback = props.insideLabel != null
415
- ? resolveContentProp(props.insideLabel)
416
- : resolveDisplayValue(value, percent, ctx);
417
- const insideNodes = (showValue === "inside" || props.insideLabel != null || JSswift.ui.getSlot(slots, "inside") != null)
418
- ? renderSlotToArray(slots, "inside", ctx, insideFallback)
419
- : [];
420
- renderInto(insideHost, insideNodes, "inline-flex");
421
- };
422
-
423
- const syncVisualState = () => {
424
- const value = getValue();
425
- const bufferValue = getBuffer();
426
- const min = getMin();
427
- const max = getMax();
428
- const percent = ratioFromValue(value) * 100;
429
- const bufferPercent = ratioFromValue(bufferValue) * 100;
430
- const tone = resolveCssColor(props.color, "");
431
- const trackTone = resolveCssColor(
432
- props.trackColor,
433
- tone ? `color-mix(in srgb, ${tone} 14%, transparent)` : "rgba(255,255,255,0.08)"
434
- );
435
- const bufferTone = resolveCssColor(
436
- props.bufferColor,
437
- tone ? `color-mix(in srgb, ${tone} 30%, transparent)` : "rgba(255,255,255,0.16)"
438
- );
439
- const ariaLabel = uiUnwrap(props.ariaLabel);
440
- const ariaValueText = uiUnwrap(props.ariaValueText);
441
- const isIndeterminate = !!uiUnwrap(props.indeterminate);
442
- const customRadius = uiUnwrap(props.radius);
443
-
444
- wrap.style.width = uiUnwrap(props.width) || "100%";
445
- wrap.style.setProperty("--cms-progress-height", getTrackHeight());
446
- if (customRadius != null) {
447
- if (typeof customRadius === "number") wrap.style.setProperty("--cms-progress-radius", `${customRadius}px`);
448
- else if (typeof customRadius === "string" && JSswift.uiSizes.includes(customRadius)) wrap.style.setProperty("--cms-progress-radius", `var(--cms-r-${customRadius})`);
449
- else wrap.style.setProperty("--cms-progress-radius", String(customRadius));
450
- } else {
451
- wrap.style.removeProperty("--cms-progress-radius");
452
- }
453
-
454
- track.style.background = trackTone;
455
- buffer.style.background = bufferTone;
456
- buffer.style.width = `${bufferPercent}%`;
457
-
458
- if (tone && !normalizeState(uiUnwrap(props.state) || uiUnwrap(props.color))) {
459
- fill.style.setProperty("--set-background-color", tone);
460
- fill.style.setProperty("--set-border-color", tone);
461
- fill.style.setProperty("--set-color", uiUnwrap(props.textColor) || "var(--cms-on-primary, #fff)");
462
- } else if (!normalizeState(uiUnwrap(props.state) || uiUnwrap(props.color))) {
463
- fill.style.setProperty("--set-background-color", "var(--cms-primary)");
464
- fill.style.setProperty("--set-border-color", "var(--cms-primary)");
465
- fill.style.setProperty("--set-color", uiUnwrap(props.textColor) || "var(--cms-on-primary, #fff)");
466
- } else {
467
- fill.style.removeProperty("--set-background-color");
468
- fill.style.removeProperty("--set-border-color");
469
- fill.style.removeProperty("--set-color");
470
- }
471
-
472
- if (isIndeterminate) {
473
- fill.style.width = "";
474
- track.removeAttribute("aria-valuenow");
475
- track.removeAttribute("aria-valuemin");
476
- track.removeAttribute("aria-valuemax");
477
- } else {
478
- fill.style.width = `${percent}%`;
479
- track.setAttribute("aria-valuemin", String(min));
480
- track.setAttribute("aria-valuemax", String(max));
481
- track.setAttribute("aria-valuenow", String(value));
482
- }
483
-
484
- if (ariaLabel != null) track.setAttribute("aria-label", String(ariaLabel));
485
- else if (typeof uiUnwrap(props.label) === "string") track.setAttribute("aria-label", String(uiUnwrap(props.label)));
486
- else track.removeAttribute("aria-label");
487
-
488
- if (ariaValueText != null) {
489
- track.setAttribute("aria-valuetext", String(ariaValueText));
490
- } else if (!isIndeterminate) {
491
- track.setAttribute("aria-valuetext", resolveDisplayValue(value, percent, { value, percent, min, max, buffer: bufferValue, progress: wrap, track, bar: fill, props }));
492
- } else {
493
- track.removeAttribute("aria-valuetext");
494
- }
495
-
496
- wrap.classList.toggle("has-start-label", startLabelHost.childNodes.length > 0);
497
- wrap.classList.toggle("has-end-label", endLabelHost.childNodes.length > 0);
498
- wrap.classList.toggle("has-inside-label", insideHost.childNodes.length > 0);
499
- };
500
-
501
- if (model) {
502
- setProgressValue(model.get(), { fromModel: true });
503
- model.watch((value) => { setProgressValue(value, { fromModel: true }); }, "UI.Progress:watch");
504
- } else if (uiIsReactive(props.value)) {
505
- JSswift.reactive.effect(() => {
506
- setProgressValue(uiUnwrap(props.value), { fromModel: true });
507
- }, "UI.Progress:value");
508
- } else {
509
- setProgressValue(props.value ?? props.min ?? 0, { fromModel: true });
510
- }
511
-
512
- if (uiIsReactive(props.buffer)) {
513
- JSswift.reactive.effect(() => {
514
- setProgressBuffer(uiUnwrap(props.buffer));
515
- }, "UI.Progress:buffer");
516
- } else {
517
- setProgressBuffer(props.buffer ?? props.value ?? props.min ?? 0);
518
- }
519
-
520
- JSswift.reactive.effect(() => {
521
- renderHeader();
522
- renderEdgeLabels();
523
- renderInsideValue();
524
- syncVisualState();
525
- }, "UI.Progress:render");
526
-
527
- wrap._track = track;
528
- wrap._bar = fill;
529
- wrap._buffer = buffer;
530
- wrap._getValue = getValue;
531
- wrap._getBuffer = getBuffer;
532
- wrap._setValue = (value) => setProgressValue(value);
533
- wrap._setBuffer = (value) => setProgressBuffer(value);
534
-
535
- return wrap;
536
- };
537
- if (JSswift.isDev?.()) {
538
- UI.meta = UI.meta || {};
539
- UI.meta.Progress = {
540
- signature: "UI.Progress(...children) | UI.Progress(props, ...children)",
541
- props: {
542
- value: "number|rod|[get,set] signal",
543
- model: "rod|[get,set] signal",
544
- min: "number",
545
- max: "number",
546
- buffer: "number|rod|[get,set] signal",
547
- label: "String|Node|Function|Array",
548
- note: "String|Node|Function|Array",
549
- showValue: "boolean|\"inside\"",
550
- valueLabel: "String|Node|Function|Array",
551
- insideLabel: "String|Node|Function|Array",
552
- formatValue: "function(value, percent, ctx)",
553
- icon: "String|Node|Function|Array",
554
- iconRight: "String|Node|Function|Array",
555
- startLabel: "String|Node|Function|Array",
556
- endLabel: "String|Node|Function|Array",
557
- leftLabel: "Alias of startLabel",
558
- rightLabel: "Alias of endLabel",
559
- width: "string|number",
560
- size: "string|number",
561
- height: "string|number",
562
- thickness: "Alias of height",
563
- color: "string",
564
- state: "primary|secondary|success|warning|danger|info|light|dark",
565
- trackColor: "string",
566
- bufferColor: "string",
567
- striped: "boolean",
568
- animated: "boolean",
569
- indeterminate: "boolean",
570
- reverse: "boolean",
571
- slots: "{ icon?, label?, note?, value?, inside?, startLabel?, endLabel?, default? }",
572
- class: "string",
573
- style: "object"
574
- },
575
- slots: {
576
- icon: "Icon before the label",
577
- label: "Main progress content",
578
- note: "Secondary content below the label",
579
- value: "External value on the right",
580
- inside: "Content inside the bar",
581
- startLabel: "Label on the left of the bar",
582
- endLabel: "Label on the right of the bar",
583
- default: "Fallback label content"
584
- },
585
- returns: "HTMLDivElement",
586
- description: "Standardized progress bar with optional header, buffer, semantic state, and reactive support."
587
- };
588
- }
589
- // Esempio: JSswift.ui.Progress({ value: 45 })
590
-
591
- UI.LoadingBar = function LoadingBar(...args) {
592
- const { props, children } = JSswift.uiNormalizeArgs(args);
593
- const getNumber = (value, fallback) => {
594
- const next = Number(value);
595
- return Number.isFinite(next) ? next : fallback;
596
- };
597
- const getMin = () => getNumber(uiUnwrap(props.min), 0);
598
- const getMax = () => {
599
- const min = getMin();
600
- const max = getNumber(uiUnwrap(props.max), 100);
601
- return max < min ? min : max;
602
- };
603
- const clampValue = (value) => {
604
- const min = getMin();
605
- const max = getMax();
606
- const next = getNumber(value, min);
607
- return Math.min(max, Math.max(min, next));
608
- };
609
- const toCssValue = (value, fallback = "") => {
610
- if (value == null || value === false || value === "") return fallback;
611
- if (typeof value === "number") return `${value}px`;
612
- return String(value);
613
- };
614
- const resolveTarget = (target) => {
615
- const raw = uiUnwrap(target);
616
- if (!raw) return document.body;
617
- if (raw === document) return document.body;
618
- if (typeof raw === "string") return document.querySelector(raw) || document.body;
619
- if (raw && raw.body instanceof HTMLElement) return raw.body;
620
- return raw instanceof HTMLElement ? raw : document.body;
621
- };
622
-
623
- const valueBinding = props.model || ((uiIsSignal(props.value) || uiIsRod(props.value)) ? props.value : null);
624
- const valueModel = resolveModel(valueBinding, "UI.LoadingBar:model");
625
- const bufferBinding = (uiIsSignal(props.buffer) || uiIsRod(props.buffer)) ? props.buffer : null;
626
- const bufferModel = resolveModel(bufferBinding, "UI.LoadingBar:buffer");
627
- const initialValue = valueModel ? valueModel.get() : (uiUnwrap(props.value) ?? getMin());
628
- const initialBuffer = bufferModel ? bufferModel.get() : (uiUnwrap(props.buffer) ?? initialValue);
629
- const [getValue, setValueSignal] = JSswift.reactive.signal(clampValue(initialValue));
630
- const [getBuffer, setBufferSignal] = JSswift.reactive.signal(Math.max(clampValue(initialBuffer), clampValue(initialValue)));
631
- const [getVisible, setVisibleSignal] = JSswift.reactive.signal(
632
- props.visible != null ? !!uiUnwrap(props.visible) : (clampValue(initialValue) > getMin() || !!uiUnwrap(props.indeterminate))
633
- );
634
-
635
- let trickleTimer = null;
636
- let doneTimer = null;
637
-
638
- const clearTrickle = () => {
639
- if (trickleTimer) {
640
- clearInterval(trickleTimer);
641
- trickleTimer = null;
642
- }
643
- };
644
- const clearDoneTimer = () => {
645
- if (doneTimer) {
646
- clearTimeout(doneTimer);
647
- doneTimer = null;
648
- }
649
- };
650
- const syncVisibility = (next, opts = {}) => {
651
- const visible = !!next;
652
- if (getVisible() !== visible) setVisibleSignal(visible);
653
- if (opts.fromExternal !== true && typeof props.onVisibleChange === "function") {
654
- props.onVisibleChange(visible);
655
- }
656
- return visible;
657
- };
658
- const syncValue = (raw, opts = {}) => {
659
- const next = clampValue(raw);
660
- if (getValue() !== next) setValueSignal(next);
661
- if (getBuffer() < next) {
662
- setBufferSignal(next);
663
- if (bufferModel && opts.fromExternal !== true) bufferModel.set(next);
664
- }
665
- if (valueModel && opts.fromExternal !== true) valueModel.set(next);
666
- return next;
667
- };
668
- const syncBuffer = (raw, opts = {}) => {
669
- const next = Math.max(syncValue(getValue(), { fromExternal: true }), clampValue(raw));
670
- if (getBuffer() !== next) setBufferSignal(next);
671
- if (bufferModel && opts.fromExternal !== true) bufferModel.set(next);
672
- return next;
673
- };
674
- const showIfNeeded = (nextValue = getValue()) => {
675
- if (props.visible != null) return;
676
- if (uiUnwrap(props.indeterminate) || nextValue > getMin()) syncVisibility(true);
677
- else if (uiUnwrap(props.hideOnZero) !== false) syncVisibility(false);
678
- };
679
- const startTrickle = () => {
680
- clearTrickle();
681
- if (uiUnwrap(props.trickle) === false || uiUnwrap(props.indeterminate)) return;
682
- const interval = getNumber(uiUnwrap(props.trickleInterval), 280);
683
- if (interval <= 0) return;
684
- trickleTimer = setInterval(() => {
685
- const current = getValue();
686
- const max = clampValue(uiUnwrap(props.trickleTo) ?? uiUnwrap(props.trickleMax) ?? 92);
687
- if (current >= max) return;
688
- const step = getNumber(uiUnwrap(props.trickleStep), current < 45 ? 12 : (current < 75 ? 7 : 3));
689
- syncValue(Math.min(max, current + step));
690
- }, interval);
691
- };
692
-
693
- const shellProps = JSswift.omit(props, [
694
- "model", "value", "min", "max", "buffer", "class", "style", "slots",
695
- "label", "note", "showValue", "valueLabel", "insideLabel", "formatValue",
696
- "icon", "iconRight", "iconSize", "startLabel", "endLabel", "leftLabel", "rightLabel",
697
- "trackColor", "bufferColor", "height", "thickness", "size", "state", "color",
698
- "reverse", "striped", "animated", "indeterminate", "ariaLabel", "ariaValueText",
699
- "width", "dense", "outline", "flat", "border", "glossy", "glow", "glass",
700
- "shadow", "gradient", "textGradient", "lightShadow", "radius", "rounded", "textColor",
701
- "target", "mount", "position", "top", "right", "bottom", "left", "inset", "zIndex",
702
- "visible", "autoStart", "hideOnZero", "startValue", "step", "trickle", "trickleStep",
703
- "trickleInterval", "trickleMax", "trickleTo", "doneValue", "doneDelay", "hideDelay",
704
- "resetValue", "progressClass", "progressStyle", "onVisibleChange"
705
- ]);
706
- shellProps.class = uiClass(["cms-loading-bar", props.class]);
707
- shellProps.style = { ...(props.style || {}) };
708
-
709
- const progressProps = JSswift.omit(props, [
710
- "target", "mount", "position", "top", "right", "bottom", "left", "inset", "zIndex",
711
- "visible", "autoStart", "hideOnZero", "startValue", "step", "trickle", "trickleStep",
712
- "trickleInterval", "trickleMax", "trickleTo", "doneValue", "doneDelay", "hideDelay",
713
- "resetValue", "progressClass", "progressStyle", "onVisibleChange"
714
- ]);
715
- progressProps.class = uiClass(["cms-loading-bar-progress", props.progressClass, progressProps.class]);
716
- progressProps.style = { ...(props.progressStyle || {}) };
717
- progressProps.value = [getValue, setValueSignal];
718
- progressProps.buffer = [getBuffer, setBufferSignal];
719
- if (progressProps.dense == null) progressProps.dense = true;
720
- if (progressProps.height == null && progressProps.thickness == null && progressProps.size == null) {
721
- progressProps.height = 3;
722
- }
723
- if (progressProps.width == null) progressProps.width = "100%";
724
-
725
- const root = _.div(shellProps);
726
- const progress = UI.Progress(progressProps, ...children);
727
- root.appendChild(progress);
728
-
729
- const set = (value) => {
730
- clearDoneTimer();
731
- const next = syncValue(value);
732
- showIfNeeded(next);
733
- return root;
734
- };
735
- const setBuffer = (value) => {
736
- clearDoneTimer();
737
- const next = syncBuffer(value);
738
- showIfNeeded(next);
739
- return root;
740
- };
741
- const inc = (step = uiUnwrap(props.step) ?? 8) => {
742
- clearDoneTimer();
743
- const current = getValue();
744
- const next = syncValue(current + getNumber(step, 0));
745
- showIfNeeded(next);
746
- return root;
747
- };
748
- const start = (value = uiUnwrap(props.startValue) ?? 12) => {
749
- clearDoneTimer();
750
- const next = Math.max(getValue(), clampValue(value));
751
- syncValue(next);
752
- syncBuffer(Math.max(getBuffer(), next));
753
- showIfNeeded(next);
754
- startTrickle();
755
- return root;
756
- };
757
- const reset = (value = uiUnwrap(props.resetValue) ?? getMin()) => {
758
- clearDoneTimer();
759
- clearTrickle();
760
- const next = clampValue(value);
761
- syncBuffer(next);
762
- syncValue(next);
763
- showIfNeeded(next);
764
- return root;
765
- };
766
- const done = (value = uiUnwrap(props.doneValue) ?? getMax()) => {
767
- clearDoneTimer();
768
- clearTrickle();
769
- const next = clampValue(value);
770
- syncBuffer(next);
771
- syncValue(next);
772
- showIfNeeded(next);
773
- const delay = Math.max(0, getNumber(uiUnwrap(props.hideDelay) ?? uiUnwrap(props.doneDelay), 220));
774
- doneTimer = setTimeout(() => { reset(); }, delay);
775
- return root;
776
- };
777
- const stop = (...innerArgs) => done(...innerArgs);
778
- const show = () => {
779
- syncVisibility(true);
780
- return root;
781
- };
782
- const hide = () => {
783
- clearDoneTimer();
784
- clearTrickle();
785
- syncVisibility(false);
786
- return root;
787
- };
788
- const destroy = () => {
789
- clearDoneTimer();
790
- clearTrickle();
791
- root.remove();
792
- return null;
793
- };
794
-
795
- root.el = root;
796
- root._progress = progress;
797
- root.get = () => getValue();
798
- root.getBuffer = () => getBuffer();
799
- root.set = set;
800
- root.setBuffer = setBuffer;
801
- root.inc = inc;
802
- root.start = start;
803
- root.done = done;
804
- root.complete = done;
805
- root.stop = stop;
806
- root.reset = reset;
807
- root.show = show;
808
- root.hide = hide;
809
- root.destroy = destroy;
810
- root._dispose = destroy;
811
-
812
- if (valueModel) {
813
- valueModel.watch((value) => { syncValue(value, { fromExternal: true }); showIfNeeded(clampValue(value)); }, "UI.LoadingBar:watch");
814
- } else if (uiIsReactive(props.value)) {
815
- JSswift.reactive.effect(() => {
816
- const next = clampValue(uiUnwrap(props.value));
817
- syncValue(next, { fromExternal: true });
818
- showIfNeeded(next);
819
- }, "UI.LoadingBar:value");
820
- }
821
-
822
- if (bufferModel) {
823
- bufferModel.watch((value) => { syncBuffer(value, { fromExternal: true }); }, "UI.LoadingBar:bufferWatch");
824
- } else if (uiIsReactive(props.buffer)) {
825
- JSswift.reactive.effect(() => {
826
- syncBuffer(uiUnwrap(props.buffer), { fromExternal: true });
827
- }, "UI.LoadingBar:buffer");
828
- }
829
-
830
- JSswift.reactive.effect(() => {
831
- const min = getMin();
832
- const max = getMax();
833
- const current = Math.min(max, Math.max(min, getValue()));
834
- const bufferCurrent = Math.max(current, Math.min(max, Math.max(min, getBuffer())));
835
- if (current !== getValue()) setValueSignal(current);
836
- if (bufferCurrent !== getBuffer()) setBufferSignal(bufferCurrent);
837
- }, "UI.LoadingBar:range");
838
-
839
- JSswift.reactive.effect(() => {
840
- if (props.visible == null) return;
841
- syncVisibility(uiUnwrap(props.visible), { fromExternal: true });
842
- }, "UI.LoadingBar:visible");
843
-
844
- JSswift.reactive.effect(() => {
845
- const position = uiUnwrap(props.position) || "fixed";
846
- const inset = uiUnwrap(props.inset);
847
- const top = uiUnwrap(props.top);
848
- const right = uiUnwrap(props.right);
849
- const bottom = uiUnwrap(props.bottom);
850
- const left = uiUnwrap(props.left);
851
- const width = uiUnwrap(props.width);
852
- const visible = props.visible != null ? !!uiUnwrap(props.visible) : getVisible();
853
- const inlineLike = position === "static" || position === "relative";
854
-
855
- root.classList.toggle("is-visible", visible);
856
- root.classList.toggle("is-inline", inlineLike);
857
- root.classList.toggle("has-track", !!uiUnwrap(props.trackColor));
858
-
859
- root.style.position = position;
860
- root.style.zIndex = String(uiUnwrap(props.zIndex) ?? 10002);
861
- root.style.width = toCssValue(width, inlineLike ? "100%" : "");
862
-
863
- if (inset != null && inset !== "") {
864
- root.style.inset = toCssValue(inset);
865
- root.style.removeProperty("top");
866
- root.style.removeProperty("right");
867
- root.style.removeProperty("bottom");
868
- root.style.removeProperty("left");
869
- } else {
870
- root.style.removeProperty("inset");
871
- if (!inlineLike && position !== "sticky") {
872
- root.style.top = toCssValue(top, "0px");
873
- root.style.right = toCssValue(right, width == null || width === "" ? "0px" : "");
874
- root.style.bottom = toCssValue(bottom, "");
875
- root.style.left = toCssValue(left, "0px");
876
- } else {
877
- root.style.top = toCssValue(top, "");
878
- root.style.right = toCssValue(right, "");
879
- root.style.bottom = toCssValue(bottom, "");
880
- root.style.left = toCssValue(left, "");
881
- }
882
- }
883
- }, "UI.LoadingBar:layout");
884
-
885
- setPropertyProps(root, props);
886
-
887
- if (uiUnwrap(props.mount) !== false) {
888
- resolveTarget(props.target).appendChild(root);
889
- }
890
- if (uiUnwrap(props.autoStart)) start();
891
- return root;
892
- };
893
- if (JSswift.isDev?.()) {
894
- UI.meta = UI.meta || {};
895
- UI.meta.LoadingBar = {
896
- signature: "UI.LoadingBar(...children) | UI.LoadingBar(props, ...children)",
897
- props: {
898
- value: "number|rod|[get,set] signal",
899
- model: "rod|[get,set] signal",
900
- buffer: "number|rod|[get,set] signal",
901
- min: "number",
902
- max: "number",
903
- height: "string|number",
904
- thickness: "Alias of height",
905
- size: "string|number",
906
- color: "string",
907
- state: "primary|secondary|success|warning|danger|info|light|dark",
908
- trackColor: "string",
909
- bufferColor: "string",
910
- striped: "boolean",
911
- animated: "boolean",
912
- indeterminate: "boolean",
913
- reverse: "boolean",
914
- width: "string|number",
915
- target: "HTMLElement|string",
916
- mount: "boolean",
917
- position: "fixed|absolute|relative|static|sticky",
918
- inset: "string|number",
919
- top: "string|number",
920
- right: "string|number",
921
- bottom: "string|number",
922
- left: "string|number",
923
- zIndex: "number",
924
- visible: "boolean",
925
- autoStart: "boolean",
926
- startValue: "number",
927
- step: "number",
928
- trickle: "boolean",
929
- trickleStep: "number",
930
- trickleInterval: "number",
931
- trickleMax: "number",
932
- trickleTo: "Alias of trickleMax",
933
- doneValue: "number",
934
- doneDelay: "Alias of hideDelay",
935
- hideDelay: "number",
936
- resetValue: "number",
937
- label: "String|Node|Function|Array",
938
- note: "String|Node|Function|Array",
939
- showValue: "boolean|\"inside\"",
940
- valueLabel: "String|Node|Function|Array",
941
- insideLabel: "String|Node|Function|Array",
942
- startLabel: "String|Node|Function|Array",
943
- endLabel: "String|Node|Function|Array",
944
- progressClass: "string",
945
- progressStyle: "object",
946
- slots: "{ icon?, label?, note?, value?, inside?, startLabel?, endLabel?, default? }",
947
- class: "string",
948
- style: "object"
949
- },
950
- slots: {
951
- icon: "Icon before the label",
952
- label: "Main content",
953
- note: "Secondary content",
954
- value: "External value on the right",
955
- inside: "Content inside the bar",
956
- startLabel: "Label on the left of the bar",
957
- endLabel: "Label on the right of the bar",
958
- default: "Fallback content"
959
- },
960
- returns: "HTMLDivElement with imperative API: .set(), .setBuffer(), .inc(), .start(), .done(), .stop(), .reset(), .show(), .hide(), .destroy()",
961
- description: "Loading bar based on UI.Progress, mountable on body or a custom container, controllable via model or imperative API."
962
- };
963
- }
964
- // Esempio: const lb = JSswift.ui.LoadingBar({ autoStart: true }); lb.done();
965
-
966
- const NOTIFY_POSITIONS = new Set([
967
- "top-left",
968
- "top-center",
969
- "top-right",
970
- "bottom-left",
971
- "bottom-center",
972
- "bottom-right"
973
- ]);
974
- const NOTIFY_VARIANTS = new Set(["soft", "solid", "outline"]);
975
- const NOTIFY_DEFAULT_TITLES = {
976
- success: "Success",
977
- warning: "Warning",
978
- danger: "Error",
979
- info: "Info",
980
- primary: "Notice",
981
- secondary: "Notice",
982
- light: "Notice",
983
- dark: "Notice"
984
- };
985
- const notifyIconMap = {
986
- success: "check_circle",
987
- warning: "warning",
988
- danger: "error",
989
- info: "info",
990
- primary: "bolt",
991
- secondary: "notifications",
992
- light: "notifications",
993
- dark: "shield"
994
- };
995
-
996
- const normalizeNotifyPosition = (value) => {
997
- const raw = String(uiUnwrap(value) || "").trim().toLowerCase();
998
- if (!raw) return "bottom-right";
999
- if (NOTIFY_POSITIONS.has(raw)) return raw;
1000
- if (raw === "top") return "top-right";
1001
- if (raw === "bottom") return "bottom-right";
1002
- if (raw === "left") return "bottom-left";
1003
- if (raw === "right") return "bottom-right";
1004
- if (raw === "center") return "bottom-center";
1005
- return "bottom-right";
1006
- };
1007
-
1008
- const normalizeNotifyVariant = (value) => {
1009
- const raw = String(uiUnwrap(value) || "").trim().toLowerCase();
1010
- return NOTIFY_VARIANTS.has(raw) ? raw : "soft";
1011
- };
1012
-
1013
- const normalizeNotifyTimeout = (value, tone) => {
1014
- const raw = uiUnwrap(value);
1015
- if (raw === false || raw === null) return 0;
1016
- if (raw === true) return tone === "danger" ? 3500 : 2500;
1017
- if (raw === "" || raw === undefined) return tone === "danger" ? 3500 : 2500;
1018
- const num = Number(raw);
1019
- return Number.isFinite(num) && num >= 0 ? num : (tone === "danger" ? 3500 : 2500);
1020
- };
1021
-
1022
- const normalizeNotifyPayload = (input = {}, forcedType = "") => {
1023
- const src = uiIsPlainObject(input) ? { ...input } : { message: input };
1024
- const tone = normalizeState(uiUnwrap(forcedType) || uiUnwrap(src.type) || uiUnwrap(src.state) || uiUnwrap(src.color) || "info") || "info";
1025
- const timeout = normalizeNotifyTimeout(src.timeout ?? src.duration ?? src.delay, tone);
1026
- const closable = src.closable ?? src.dismissible ?? (src.dismiss != null) ?? (timeout === 0);
1027
- const title = src.title === undefined
1028
- ? (src.label === undefined ? NOTIFY_DEFAULT_TITLES[tone] : src.label)
1029
- : src.title;
1030
-
1031
- return {
1032
- ...src,
1033
- type: tone,
1034
- state: tone,
1035
- title,
1036
- message: src.message ?? src.content ?? src.text ?? null,
1037
- description: src.description ?? src.subtitle ?? src.note ?? null,
1038
- meta: src.meta ?? src.caption ?? null,
1039
- body: src.body ?? null,
1040
- timeout,
1041
- closable: !!closable,
1042
- dismissLabel: src.dismissLabel || src.closeLabel || "Chiudi notifica",
1043
- position: normalizeNotifyPosition(src.position),
1044
- variant: normalizeNotifyVariant(src.variant),
1045
- slots: src.slots || {},
1046
- role: src.role || ((tone === "danger" || tone === "warning") ? "alert" : "status")
1047
- };
1048
- };
1049
-
1050
- const buildNotifyOptionsFromArgs = (args, forcedType = "", defaultTitle = "") => {
1051
- if (!args.length) {
1052
- return normalizeNotifyPayload(defaultTitle ? { type: forcedType, title: defaultTitle } : { type: forcedType });
1053
- }
1054
-
1055
- if (uiIsPlainObject(args[0])) {
1056
- const { props, children } = JSswift.uiNormalizeArgs(args);
1057
- const next = { ...props };
1058
- if (!next.type && !next.state && !next.color && forcedType) next.type = forcedType;
1059
- if (next.title == null && defaultTitle) next.title = defaultTitle;
1060
- if (children.length) {
1061
- if (next.message == null && next.description == null && next.body == null && children.length === 1) {
1062
- next.message = children[0];
1063
- } else if (next.body == null) {
1064
- next.body = children.length === 1 ? children[0] : children;
1065
- }
1066
- }
1067
- return normalizeNotifyPayload(next, forcedType);
1068
- }
1069
-
1070
- const [message, second, third] = args;
1071
- let next = {};
1072
- if (uiIsPlainObject(second)) {
1073
- next = { ...second, message };
1074
- } else if (uiIsPlainObject(third)) {
1075
- next = { ...third, message, title: second };
1076
- } else {
1077
- next = { message, title: second };
1078
- }
1079
- if (forcedType && !next.type && !next.state && !next.color) next.type = forcedType;
1080
- if (next.title == null && defaultTitle) next.title = defaultTitle;
1081
- return normalizeNotifyPayload(next, forcedType);
1082
- };
1083
-
1084
- const createNotifyShortcut = (type, defaultTitle) => (...args) =>
1085
- app.services.notify?.show?.(buildNotifyOptionsFromArgs(args, type, defaultTitle));
1086
-
1087
- const resolveNotifyPromiseOutcome = (value, fallbackMessage, fallbackTitle) => {
1088
- if (value === false) return false;
1089
- if (typeof value === "function") {
1090
- const next = value();
1091
- return resolveNotifyPromiseOutcome(next, fallbackMessage, fallbackTitle);
1092
- }
1093
- if (uiIsPlainObject(value)) return value;
1094
- if (value != null) return { message: value, title: fallbackTitle };
1095
- return { message: fallbackMessage, title: fallbackTitle };
1096
- };
1097
-
1098
- UI.Notify = (...args) => app.services.notify?.show?.(buildNotifyOptionsFromArgs(args));
1099
- UI.Notify.show = UI.Notify;
1100
- UI.Notify.success = createNotifyShortcut("success", "Success");
1101
- UI.Notify.error = createNotifyShortcut("danger", "Error");
1102
- UI.Notify.warning = createNotifyShortcut("warning", "Warning");
1103
- UI.Notify.info = createNotifyShortcut("info", "Info");
1104
- UI.Notify.primary = createNotifyShortcut("primary", "Notice");
1105
- UI.Notify.secondary = createNotifyShortcut("secondary", "Notice");
1106
- UI.Notify.remove = (id) => app.services.notify?.remove?.(id);
1107
- UI.Notify.clear = () => app.services.notify?.clear?.();
1108
- UI.Notify.update = (id, patch = {}) => app.services.notify?.update?.(id, patch);
1109
- UI.Notify.promise = async (task, opts = {}) => {
1110
- const loadingInput = opts.loading === false
1111
- ? false
1112
- : resolveNotifyPromiseOutcome(
1113
- typeof opts.loading === "function" ? () => opts.loading() : opts.loading,
1114
- "Operazione in corso...",
1115
- "In corso"
1116
- );
1117
- const loadingId = loadingInput === false
1118
- ? null
1119
- : app.services.notify?.show?.(normalizeNotifyPayload({
1120
- type: "info",
1121
- timeout: 0,
1122
- closable: false,
1123
- ...loadingInput
1124
- }, "info"));
1125
-
1126
- try {
1127
- const promise = typeof task === "function" ? task() : task;
1128
- const result = await promise;
1129
- const successInput = resolveNotifyPromiseOutcome(
1130
- typeof opts.success === "function" ? () => opts.success(result) : opts.success,
1131
- "Operazione completata con successo.",
1132
- "Completato"
1133
- );
1134
- if (successInput !== false) {
1135
- if (loadingId != null) app.services.notify?.update?.(loadingId, normalizeNotifyPayload(successInput, "success"));
1136
- else app.services.notify?.show?.(normalizeNotifyPayload(successInput, "success"));
1137
- } else if (loadingId != null) {
1138
- app.services.notify?.remove?.(loadingId);
1139
- }
1140
- return result;
1141
- } catch (error) {
1142
- const errorInput = resolveNotifyPromiseOutcome(
1143
- typeof opts.error === "function" ? () => opts.error(error) : opts.error,
1144
- error?.message || "Operazione fallita.",
1145
- "Errore"
1146
- );
1147
- if (errorInput !== false) {
1148
- if (loadingId != null) app.services.notify?.update?.(loadingId, normalizeNotifyPayload(errorInput, "danger"));
1149
- else app.services.notify?.show?.(normalizeNotifyPayload(errorInput, "danger"));
1150
- } else if (loadingId != null) {
1151
- app.services.notify?.remove?.(loadingId);
1152
- }
1153
- throw error;
1154
- }
1155
- };
1156
- if (JSswift.isDev?.()) {
1157
- UI.meta = UI.meta || {};
1158
- UI.meta.Notify = {
1159
- signature: "UI.Notify(message, title?, opts?) | UI.Notify(opts, ...children)",
1160
- props: {
1161
- id: "string",
1162
- type: "success|warning|danger|error|info|primary|secondary|light|dark",
1163
- state: "Alias of type",
1164
- color: "Alias of type",
1165
- title: "String|Node|Function|Array|false",
1166
- message: "String|Node|Function|Array",
1167
- description: "String|Node|Function|Array",
1168
- meta: "String|Node|Function|Array",
1169
- body: "Node|Function|Array",
1170
- icon: "String|Node|Function|Array|false",
1171
- actions: "Node|Function|Array",
1172
- dismiss: "Node|Function|Array",
1173
- timeout: "number|false",
1174
- duration: "Alias of timeout",
1175
- closable: "boolean",
1176
- dismissLabel: "string",
1177
- position: "top-left|top-center|top-right|bottom-left|bottom-center|bottom-right",
1178
- variant: "soft|solid|outline",
1179
- slots: "{ icon?, title?, message?, description?, meta?, actions?, dismiss?, default? }",
1180
- class: "string",
1181
- style: "object"
1182
- },
1183
- slots: {
1184
- icon: "Leading visual/icon content",
1185
- title: "Toast title content",
1186
- message: "Primary message content",
1187
- description: "Secondary/supporting text",
1188
- meta: "Meta info or badges",
1189
- actions: "Actions area content",
1190
- dismiss: "Custom dismiss control",
1191
- default: "Extra body content under the message"
1192
- },
1193
- methods: {
1194
- show: "Alias of UI.Notify(...)",
1195
- success: "UI.Notify.success(message|opts, title?)",
1196
- error: "UI.Notify.error(message|opts, title?)",
1197
- warning: "UI.Notify.warning(message|opts, title?)",
1198
- info: "UI.Notify.info(message|opts, title?)",
1199
- update: "UI.Notify.update(id, patch)",
1200
- remove: "UI.Notify.remove(id)",
1201
- clear: "UI.Notify.clear()",
1202
- promise: "UI.Notify.promise(promise|fn, { loading?, success?, error? })"
1203
- },
1204
- returns: "string|null (toast id)",
1205
- description: "Standardized notify service with structured payloads, semantic shortcuts, update/remove/clear, and promise support."
1206
- };
1207
- }
1208
-
1209
-
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(--cms-${str})`;
34
+ return str;
35
+ };
36
+
37
+ const stateClass = uiComputed(props.state, () => {
38
+ const state = normalizeState(uiUnwrap(props.state));
39
+ return state ? `cms-state-${state}` : "";
40
+ });
41
+
42
+ const cls = uiClass([
43
+ "cms-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(["cms-spinner-indicator", props.indicatorClass]),
83
+ style: {
84
+ "--cms-spinner-size": makeCssVarValue(props.size, toCssSize, "18px"),
85
+ "--cms-spinner-thickness": makeCssVarValue(props.thickness, toCssSize, "2px"),
86
+ "--cms-spinner-speed": makeCssVarValue(props.speed, (v) => typeof v === "number" ? `${v}ms` : String(v), "900ms"),
87
+ "--cms-spinner-color": makeCssVarValue(props.color, resolveSpinnerColor, "var(--set-border-color, var(--cms-primary))"),
88
+ "--cms-spinner-track": makeCssVarValue(props.trackColor, resolveSpinnerColor, "color-mix(in srgb, var(--cms-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: "cms-spinner-content" },
100
+ labelNodes.length ? _.div({ class: "cms-spinner-label" }, ...labelNodes) : null,
101
+ copyNodes.length ? _.div({ class: "cms-spinner-copy" }, ...copyNodes) : null,
102
+ noteNodes.length ? _.div({ class: "cms-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 ? `cms-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(--cms-${state})`;
157
+ if (typeof raw === "string" && isTokenCSS(raw)) return `var(--cms-${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
+ "cms-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: "cms-progress-header" });
263
+ const heading = _.div({ class: "cms-progress-heading" });
264
+ const labelHost = _.div({ class: "cms-progress-label" });
265
+ const noteHost = _.div({ class: "cms-progress-note" });
266
+ const valueHost = _.div({ class: "cms-progress-value" });
267
+ heading.append(labelHost, noteHost);
268
+ header.append(heading, valueHost);
269
+
270
+ const body = _.div({ class: "cms-progress-body" });
271
+ const startLabelHost = _.span({ class: "cms-progress-edge-label cms-progress-edge-label-left" });
272
+ const track = _.div({
273
+ class: "cms-progress",
274
+ role: "progressbar"
275
+ });
276
+ const buffer = _.span({ class: "cms-progress-buffer" });
277
+ const fill = _.span({
278
+ class: uiClass([
279
+ "cms-progress-bar",
280
+ "cms-singularity",
281
+ stateClass,
282
+ uiWhen(props.outline, "cms-outline"),
283
+ uiWhen(props.flat, "cms-flat"),
284
+ uiWhen(props.border, "cms-border"),
285
+ uiWhen(props.glossy, "cms-glossy"),
286
+ uiWhen(props.glow, "cms-glow"),
287
+ uiWhen(props.glass, "cms-glass"),
288
+ uiWhen(props.shadow, "cms-shadow"),
289
+ uiComputed(props.shadow, () => {
290
+ const shadow = normalizeShadow(uiUnwrap(props.shadow));
291
+ return shadow ? `cms-shadow-${shadow}` : "";
292
+ }),
293
+ uiWhen(props.gradient, "cms-gradient"),
294
+ uiWhen(props.textGradient, "cms-text-gradient"),
295
+ uiWhen(props.lightShadow, "cms-light-shadow"),
296
+ uiWhen(props.striped, "is-striped"),
297
+ uiWhen(props.animated || props.indeterminate, "is-animated")
298
+ ])
299
+ });
300
+ const insideHost = _.span({ class: "cms-progress-bar-label" });
301
+ fill.appendChild(insideHost);
302
+ track.append(buffer, fill);
303
+ const endLabelHost = _.span({ class: "cms-progress-edge-label cms-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("--cms-progress-height", getTrackHeight());
411
+ if (customRadius != null) {
412
+ if (typeof customRadius === "number") wrap.style.setProperty("--cms-progress-radius", `${customRadius}px`);
413
+ else if (typeof customRadius === "string" && JSswift.uiSizes.includes(customRadius)) wrap.style.setProperty("--cms-progress-radius", `var(--cms-r-${customRadius})`);
414
+ else wrap.style.setProperty("--cms-progress-radius", String(customRadius));
415
+ } else {
416
+ wrap.style.removeProperty("--cms-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(--cms-on-primary, #fff)");
427
+ } else if (!normalizeState(uiUnwrap(props.state) || uiUnwrap(props.color))) {
428
+ fill.style.setProperty("--set-background-color", "var(--cms-primary)");
429
+ fill.style.setProperty("--set-border-color", "var(--cms-primary)");
430
+ fill.style.setProperty("--set-color", uiUnwrap(props.textColor) || "var(--cms-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(["cms-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(["cms-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
+ };