@jsswift/ui 1.0.53 → 2.0.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.
- package/dist/css/animation.css +36 -36
- package/dist/css/base.css +374 -374
- package/dist/css/docs.css +54 -54
- package/dist/css/fonts-nunito.css +2 -2
- package/dist/css/min-ui.css +1 -1
- package/dist/css/responsive-base.css +557 -0
- package/dist/css/responsive.css +3225 -3225
- package/dist/css/ui-components.css +2902 -2930
- package/dist/css/ui-light.css +9908 -0
- package/dist/css/ui-light.min.css +1 -0
- package/dist/css/ui.css +6530 -6558
- package/dist/css/ui.min.css +1 -1
- package/dist/min-ui.js +12 -12
- package/dist/ui.js +4354 -9379
- package/dist/ui.min.js +12 -12
- package/package.json +4 -2
- package/src/00-bootstrap.js +305 -305
- package/src/01-foundation-helpers.js +328 -325
- package/src/10-primitives-layout.js +242 -242
- package/src/20-display-content.js +76 -76
- package/src/21-list-content.js +21 -21
- package/src/22-banner-content.js +376 -376
- package/src/23-tooltip.js +9 -9
- package/src/31-form-advanced.js +141 -141
- package/src/40-navigation.js +81 -81
- package/src/41-tab-panel.js +37 -37
- package/src/50-feedback.js +52 -52
- package/src/51-feedback-service.js +166 -166
- package/src/60-shell-app.js +1082 -1082
- package/src/61-form-service.js +5 -5
- package/src/70-dialog.js +34 -34
- package/src/71-menu-overlays.js +73 -73
- package/src/80-data.js +38 -38
- package/src/css/README.md +2 -0
- package/src/css/animation.css +36 -36
- package/src/css/base.css +374 -374
- package/src/css/docs.css +54 -54
- package/src/css/fonts-nunito.css +2 -2
- package/src/css/responsive.css +3225 -3225
- package/src/css/ui-components.css +2902 -2930
package/src/50-feedback.js
CHANGED
|
@@ -30,17 +30,17 @@
|
|
|
30
30
|
str.startsWith("hsla(") ||
|
|
31
31
|
str.startsWith("var(")
|
|
32
32
|
) return str;
|
|
33
|
-
if (isTokenCSS(str)) return `var(--
|
|
33
|
+
if (isTokenCSS(str)) return `var(--jss-${str})`;
|
|
34
34
|
return str;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
const stateClass = uiComputed(props.state, () => {
|
|
38
38
|
const state = normalizeState(uiUnwrap(props.state));
|
|
39
|
-
return state ? `
|
|
39
|
+
return state ? `jss-state-${state}` : "";
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
const cls = uiClass([
|
|
43
|
-
"
|
|
43
|
+
"jss-spinner",
|
|
44
44
|
stateClass,
|
|
45
45
|
uiWhen(props.vertical, "vertical"),
|
|
46
46
|
uiWhen(props.reverse, "reverse"),
|
|
@@ -79,13 +79,13 @@
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
const indicatorFallback = _.span({
|
|
82
|
-
class: uiClass(["
|
|
82
|
+
class: uiClass(["jss-spinner-indicator", props.indicatorClass]),
|
|
83
83
|
style: {
|
|
84
|
-
"--
|
|
85
|
-
"--
|
|
86
|
-
"--
|
|
87
|
-
"--
|
|
88
|
-
"--
|
|
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
89
|
...(props.indicatorStyle || {})
|
|
90
90
|
},
|
|
91
91
|
"aria-hidden": "true"
|
|
@@ -96,10 +96,10 @@
|
|
|
96
96
|
if (indicatorNodes.length) content.push(...indicatorNodes);
|
|
97
97
|
if (hasText) {
|
|
98
98
|
content.push(_.div(
|
|
99
|
-
{ class: "
|
|
100
|
-
labelNodes.length ? _.div({ class: "
|
|
101
|
-
copyNodes.length ? _.div({ class: "
|
|
102
|
-
noteNodes.length ? _.div({ class: "
|
|
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
103
|
));
|
|
104
104
|
}
|
|
105
105
|
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
const model = resolveModel(boundValue, "UI.Progress:model");
|
|
118
118
|
const stateClass = uiComputed([props.state, props.color], () => {
|
|
119
119
|
const state = normalizeState(uiUnwrap(props.state) || uiUnwrap(props.color));
|
|
120
|
-
return state ? `
|
|
120
|
+
return state ? `jss-state-${state}` : "";
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
const getNumber = (value, fallback) => {
|
|
@@ -153,8 +153,8 @@
|
|
|
153
153
|
const raw = uiUnwrap(value);
|
|
154
154
|
if (raw == null || raw === false || raw === "") return fallback;
|
|
155
155
|
const state = normalizeState(raw);
|
|
156
|
-
if (state) return `var(--
|
|
157
|
-
if (typeof raw === "string" && isTokenCSS(raw)) return `var(--
|
|
156
|
+
if (state) return `var(--jss-${state})`;
|
|
157
|
+
if (typeof raw === "string" && isTokenCSS(raw)) return `var(--jss-${raw})`;
|
|
158
158
|
return String(raw);
|
|
159
159
|
};
|
|
160
160
|
const normalizeValue = (value) => {
|
|
@@ -250,7 +250,7 @@
|
|
|
250
250
|
delete rootStyle.backgroundColor;
|
|
251
251
|
}
|
|
252
252
|
wrapProps.class = uiClass([
|
|
253
|
-
"
|
|
253
|
+
"jss-progress-wrap",
|
|
254
254
|
uiWhen(props.dense, "dense"),
|
|
255
255
|
uiWhen(props.reverse, "is-reverse"),
|
|
256
256
|
uiWhen(props.indeterminate, "is-indeterminate"),
|
|
@@ -259,48 +259,48 @@
|
|
|
259
259
|
]);
|
|
260
260
|
wrapProps.style = rootStyle;
|
|
261
261
|
|
|
262
|
-
const header = _.div({ class: "
|
|
263
|
-
const heading = _.div({ class: "
|
|
264
|
-
const labelHost = _.div({ class: "
|
|
265
|
-
const noteHost = _.div({ class: "
|
|
266
|
-
const valueHost = _.div({ class: "
|
|
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
267
|
heading.append(labelHost, noteHost);
|
|
268
268
|
header.append(heading, valueHost);
|
|
269
269
|
|
|
270
|
-
const body = _.div({ class: "
|
|
271
|
-
const startLabelHost = _.span({ class: "
|
|
270
|
+
const body = _.div({ class: "jss-progress-body" });
|
|
271
|
+
const startLabelHost = _.span({ class: "jss-progress-edge-label jss-progress-edge-label-left" });
|
|
272
272
|
const track = _.div({
|
|
273
|
-
class: "
|
|
273
|
+
class: "jss-progress",
|
|
274
274
|
role: "progressbar"
|
|
275
275
|
});
|
|
276
|
-
const buffer = _.span({ class: "
|
|
276
|
+
const buffer = _.span({ class: "jss-progress-buffer" });
|
|
277
277
|
const fill = _.span({
|
|
278
278
|
class: uiClass([
|
|
279
|
-
"
|
|
280
|
-
"
|
|
279
|
+
"jss-progress-bar",
|
|
280
|
+
"jss-singularity",
|
|
281
281
|
stateClass,
|
|
282
|
-
uiWhen(props.outline, "
|
|
283
|
-
uiWhen(props.flat, "
|
|
284
|
-
uiWhen(props.border, "
|
|
285
|
-
uiWhen(props.glossy, "
|
|
286
|
-
uiWhen(props.glow, "
|
|
287
|
-
uiWhen(props.glass, "
|
|
288
|
-
uiWhen(props.shadow, "
|
|
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
289
|
uiComputed(props.shadow, () => {
|
|
290
290
|
const shadow = normalizeShadow(uiUnwrap(props.shadow));
|
|
291
|
-
return shadow ? `
|
|
291
|
+
return shadow ? `jss-shadow-${shadow}` : "";
|
|
292
292
|
}),
|
|
293
|
-
uiWhen(props.gradient, "
|
|
294
|
-
uiWhen(props.textGradient, "
|
|
295
|
-
uiWhen(props.lightShadow, "
|
|
293
|
+
uiWhen(props.gradient, "jss-gradient"),
|
|
294
|
+
uiWhen(props.textGradient, "jss-text-gradient"),
|
|
295
|
+
uiWhen(props.lightShadow, "jss-light-shadow"),
|
|
296
296
|
uiWhen(props.striped, "is-striped"),
|
|
297
297
|
uiWhen(props.animated || props.indeterminate, "is-animated")
|
|
298
298
|
])
|
|
299
299
|
});
|
|
300
|
-
const insideHost = _.span({ class: "
|
|
300
|
+
const insideHost = _.span({ class: "jss-progress-bar-label" });
|
|
301
301
|
fill.appendChild(insideHost);
|
|
302
302
|
track.append(buffer, fill);
|
|
303
|
-
const endLabelHost = _.span({ class: "
|
|
303
|
+
const endLabelHost = _.span({ class: "jss-progress-edge-label jss-progress-edge-label-right" });
|
|
304
304
|
body.append(startLabelHost, track, endLabelHost);
|
|
305
305
|
|
|
306
306
|
const wrap = _.div(wrapProps, header, body);
|
|
@@ -407,13 +407,13 @@
|
|
|
407
407
|
const customRadius = uiUnwrap(props.radius);
|
|
408
408
|
|
|
409
409
|
wrap.style.width = uiUnwrap(props.width) || "100%";
|
|
410
|
-
wrap.style.setProperty("--
|
|
410
|
+
wrap.style.setProperty("--jss-progress-height", getTrackHeight());
|
|
411
411
|
if (customRadius != null) {
|
|
412
|
-
if (typeof customRadius === "number") wrap.style.setProperty("--
|
|
413
|
-
else if (typeof customRadius === "string" && JSswift.uiSizes.includes(customRadius)) wrap.style.setProperty("--
|
|
414
|
-
else wrap.style.setProperty("--
|
|
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
415
|
} else {
|
|
416
|
-
wrap.style.removeProperty("--
|
|
416
|
+
wrap.style.removeProperty("--jss-progress-radius");
|
|
417
417
|
}
|
|
418
418
|
|
|
419
419
|
track.style.background = trackTone;
|
|
@@ -423,11 +423,11 @@
|
|
|
423
423
|
if (tone && !normalizeState(uiUnwrap(props.state) || uiUnwrap(props.color))) {
|
|
424
424
|
fill.style.setProperty("--set-background-color", tone);
|
|
425
425
|
fill.style.setProperty("--set-border-color", tone);
|
|
426
|
-
fill.style.setProperty("--set-color", uiUnwrap(props.textColor) || "var(--
|
|
426
|
+
fill.style.setProperty("--set-color", uiUnwrap(props.textColor) || "var(--jss-on-primary, #fff)");
|
|
427
427
|
} else if (!normalizeState(uiUnwrap(props.state) || uiUnwrap(props.color))) {
|
|
428
|
-
fill.style.setProperty("--set-background-color", "var(--
|
|
429
|
-
fill.style.setProperty("--set-border-color", "var(--
|
|
430
|
-
fill.style.setProperty("--set-color", uiUnwrap(props.textColor) || "var(--
|
|
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
431
|
} else {
|
|
432
432
|
fill.style.removeProperty("--set-background-color");
|
|
433
433
|
fill.style.removeProperty("--set-border-color");
|
|
@@ -617,7 +617,7 @@
|
|
|
617
617
|
"trickleInterval", "trickleMax", "trickleTo", "doneValue", "doneDelay", "hideDelay",
|
|
618
618
|
"resetValue", "progressClass", "progressStyle", "onVisibleChange"
|
|
619
619
|
]);
|
|
620
|
-
shellProps.class = uiClass(["
|
|
620
|
+
shellProps.class = uiClass(["jss-loading-bar", props.class]);
|
|
621
621
|
shellProps.style = { ...(props.style || {}) };
|
|
622
622
|
|
|
623
623
|
const progressProps = JSswift.omit(props, [
|
|
@@ -626,7 +626,7 @@
|
|
|
626
626
|
"trickleInterval", "trickleMax", "trickleTo", "doneValue", "doneDelay", "hideDelay",
|
|
627
627
|
"resetValue", "progressClass", "progressStyle", "onVisibleChange"
|
|
628
628
|
]);
|
|
629
|
-
progressProps.class = uiClass(["
|
|
629
|
+
progressProps.class = uiClass(["jss-loading-bar-progress", props.progressClass, progressProps.class]);
|
|
630
630
|
progressProps.style = { ...(props.progressStyle || {}) };
|
|
631
631
|
progressProps.value = [getValue, setValueSignal];
|
|
632
632
|
progressProps.buffer = [getBuffer, setBufferSignal];
|
|
@@ -1,166 +1,166 @@
|
|
|
1
|
-
// 4) NOTIFY SERVICE (toast)
|
|
2
|
-
// -------------------------------
|
|
3
|
-
const [toasts, setToasts] = app.reactive.signal([]);
|
|
4
|
-
const toastTimers = new Map();
|
|
5
|
-
|
|
6
|
-
function clearToastTimer(id) {
|
|
7
|
-
const timer = toastTimers.get(id);
|
|
8
|
-
if (timer) {
|
|
9
|
-
clearTimeout(timer);
|
|
10
|
-
toastTimers.delete(id);
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function removeToast(id) {
|
|
15
|
-
if (!id) return null;
|
|
16
|
-
clearToastTimer(id);
|
|
17
|
-
setToasts(toasts().filter((entry) => entry.id !== id));
|
|
18
|
-
return id;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function clearToasts() {
|
|
22
|
-
for (const id of toastTimers.keys()) clearToastTimer(id);
|
|
23
|
-
setToasts([]);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function scheduleToastTimer(entry) {
|
|
27
|
-
if (!entry?.id) return;
|
|
28
|
-
clearToastTimer(entry.id);
|
|
29
|
-
if (!(entry.timeout > 0)) return;
|
|
30
|
-
toastTimers.set(entry.id, setTimeout(() => {
|
|
31
|
-
removeToast(entry.id);
|
|
32
|
-
}, entry.timeout));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function updateToast(id, patch = {}) {
|
|
36
|
-
if (!id) return null;
|
|
37
|
-
let nextEntry = null;
|
|
38
|
-
setToasts(toasts().map((entry) => {
|
|
39
|
-
if (entry.id !== id) return entry;
|
|
40
|
-
nextEntry = normalizeNotifyPayload({ ...entry, ...patch, id: entry.id }, patch.type || patch.state || patch.color || entry.type);
|
|
41
|
-
return nextEntry;
|
|
42
|
-
}));
|
|
43
|
-
if (nextEntry) scheduleToastTimer(nextEntry);
|
|
44
|
-
return nextEntry?.id || null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function renderNotifyToast(entry) {
|
|
48
|
-
const close = () => removeToast(entry.id);
|
|
49
|
-
const update = (patch = {}) => updateToast(entry.id, patch);
|
|
50
|
-
const ctx = { toast: entry, id: entry.id, close, update };
|
|
51
|
-
const resolveNotifyRender = (value) => typeof value === "function" ? value(ctx) : value;
|
|
52
|
-
const iconFallback = (() => {
|
|
53
|
-
if (entry.icon === false || entry.icon === null) return null;
|
|
54
|
-
if (entry.icon != null) {
|
|
55
|
-
const resolvedIcon = resolveNotifyRender(entry.icon);
|
|
56
|
-
return typeof resolvedIcon === "string" ? UI.Icon({ name: resolvedIcon, size: "sm" }) : JSswift.ui.slot(resolvedIcon, { as: "icon" });
|
|
57
|
-
}
|
|
58
|
-
const iconName = notifyIconMap[entry.type];
|
|
59
|
-
return iconName ? UI.Icon({ name: iconName, size: "sm" }) : null;
|
|
60
|
-
})();
|
|
61
|
-
|
|
62
|
-
const iconNodes = renderSlotToArray(entry.slots, "icon", ctx, iconFallback);
|
|
63
|
-
const titleNodes = renderSlotToArray(entry.slots, "title", ctx, resolveNotifyRender(entry.title));
|
|
64
|
-
const messageNodes = renderSlotToArray(entry.slots, "message", ctx, resolveNotifyRender(entry.message));
|
|
65
|
-
const descriptionNodes = renderSlotToArray(entry.slots, "description", ctx, resolveNotifyRender(entry.description));
|
|
66
|
-
const metaNodes = renderSlotToArray(entry.slots, "meta", ctx, resolveNotifyRender(entry.meta));
|
|
67
|
-
const actionsNodes = renderSlotToArray(entry.slots, "actions", ctx, resolveNotifyRender(entry.actions));
|
|
68
|
-
const bodyNodes = renderSlotToArray(entry.slots, "default", ctx, resolveNotifyRender(entry.body));
|
|
69
|
-
const dismissNodes = renderSlotToArray(entry.slots, "dismiss", ctx, resolveNotifyRender(entry.dismiss));
|
|
70
|
-
const dismissContent = dismissNodes.length
|
|
71
|
-
? dismissNodes
|
|
72
|
-
: (entry.closable
|
|
73
|
-
? [UI.Btn({
|
|
74
|
-
class: "
|
|
75
|
-
outline: true,
|
|
76
|
-
size: "xs",
|
|
77
|
-
"aria-label": entry.dismissLabel,
|
|
78
|
-
onClick: close
|
|
79
|
-
}, UI.Icon({ name: "close", size: "xs" }))]
|
|
80
|
-
: []);
|
|
81
|
-
|
|
82
|
-
const node = _.div({
|
|
83
|
-
class: uiClass([
|
|
84
|
-
"
|
|
85
|
-
`
|
|
86
|
-
`
|
|
87
|
-
uiWhen(entry.closable, "is-closable"),
|
|
88
|
-
entry.class
|
|
89
|
-
]),
|
|
90
|
-
style: entry.style || {},
|
|
91
|
-
role: entry.role
|
|
92
|
-
},
|
|
93
|
-
iconNodes.length ? _.div({ class: "
|
|
94
|
-
_.div(
|
|
95
|
-
{ class: "
|
|
96
|
-
_.div(
|
|
97
|
-
{ class: "
|
|
98
|
-
titleNodes.length ? _.div({ class: "
|
|
99
|
-
messageNodes.length ? _.div({ class: "
|
|
100
|
-
descriptionNodes.length ? _.div({ class: "
|
|
101
|
-
bodyNodes.length ? _.div({ class: "
|
|
102
|
-
metaNodes.length ? _.div({ class: "
|
|
103
|
-
),
|
|
104
|
-
actionsNodes.length ? _.div({ class: "
|
|
105
|
-
),
|
|
106
|
-
dismissContent.length ? _.div({ class: "
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
setPropertyProps(node, entry);
|
|
110
|
-
return node;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function ensureToastRoot() {
|
|
114
|
-
let root = document.querySelector(".
|
|
115
|
-
if (root) return root;
|
|
116
|
-
root = document.createElement("div");
|
|
117
|
-
root.className = "
|
|
118
|
-
document.body.appendChild(root);
|
|
119
|
-
|
|
120
|
-
app.reactive.effect(() => {
|
|
121
|
-
const list = toasts();
|
|
122
|
-
root.innerHTML = "";
|
|
123
|
-
const groups = new Map();
|
|
124
|
-
for (const toast of list) {
|
|
125
|
-
const key = normalizeNotifyPosition(toast.position);
|
|
126
|
-
if (!groups.has(key)) groups.set(key, []);
|
|
127
|
-
groups.get(key).push(toast);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
for (const position of NOTIFY_POSITIONS) {
|
|
131
|
-
const entries = groups.get(position);
|
|
132
|
-
if (!entries?.length) continue;
|
|
133
|
-
const wrap = _.div({ class: uiClass(["
|
|
134
|
-
...entries.map((entry) => renderNotifyToast(entry))
|
|
135
|
-
);
|
|
136
|
-
root.appendChild(wrap);
|
|
137
|
-
}
|
|
138
|
-
}, "JSswiftUI:toasts");
|
|
139
|
-
|
|
140
|
-
return root;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function pushToast(input = {}) {
|
|
144
|
-
ensureToastRoot();
|
|
145
|
-
const entry = normalizeNotifyPayload(input);
|
|
146
|
-
entry.id = entry.id || Math.random().toString(36).slice(2);
|
|
147
|
-
setToasts([...toasts().filter((item) => item.id !== entry.id), entry]);
|
|
148
|
-
scheduleToastTimer(entry);
|
|
149
|
-
return entry.id;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
app.services.notify.show = pushToast;
|
|
153
|
-
app.services.notify.success = createNotifyShortcut("success", "Success");
|
|
154
|
-
app.services.notify.error = createNotifyShortcut("danger", "Error");
|
|
155
|
-
app.services.notify.warning = createNotifyShortcut("warning", "Warning");
|
|
156
|
-
app.services.notify.info = createNotifyShortcut("info", "Info");
|
|
157
|
-
app.services.notify.primary = createNotifyShortcut("primary", "Notice");
|
|
158
|
-
app.services.notify.secondary = createNotifyShortcut("secondary", "Notice");
|
|
159
|
-
app.services.notify.update = updateToast;
|
|
160
|
-
app.services.notify.remove = removeToast;
|
|
161
|
-
app.services.notify.clear = clearToasts;
|
|
162
|
-
app.services.notify.promise = UI.Notify.promise;
|
|
163
|
-
|
|
164
|
-
// Optional shortcut
|
|
165
|
-
app.notify = app.services.notify;
|
|
166
|
-
|
|
1
|
+
// 4) NOTIFY SERVICE (toast)
|
|
2
|
+
// -------------------------------
|
|
3
|
+
const [toasts, setToasts] = app.reactive.signal([]);
|
|
4
|
+
const toastTimers = new Map();
|
|
5
|
+
|
|
6
|
+
function clearToastTimer(id) {
|
|
7
|
+
const timer = toastTimers.get(id);
|
|
8
|
+
if (timer) {
|
|
9
|
+
clearTimeout(timer);
|
|
10
|
+
toastTimers.delete(id);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function removeToast(id) {
|
|
15
|
+
if (!id) return null;
|
|
16
|
+
clearToastTimer(id);
|
|
17
|
+
setToasts(toasts().filter((entry) => entry.id !== id));
|
|
18
|
+
return id;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function clearToasts() {
|
|
22
|
+
for (const id of toastTimers.keys()) clearToastTimer(id);
|
|
23
|
+
setToasts([]);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function scheduleToastTimer(entry) {
|
|
27
|
+
if (!entry?.id) return;
|
|
28
|
+
clearToastTimer(entry.id);
|
|
29
|
+
if (!(entry.timeout > 0)) return;
|
|
30
|
+
toastTimers.set(entry.id, setTimeout(() => {
|
|
31
|
+
removeToast(entry.id);
|
|
32
|
+
}, entry.timeout));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function updateToast(id, patch = {}) {
|
|
36
|
+
if (!id) return null;
|
|
37
|
+
let nextEntry = null;
|
|
38
|
+
setToasts(toasts().map((entry) => {
|
|
39
|
+
if (entry.id !== id) return entry;
|
|
40
|
+
nextEntry = normalizeNotifyPayload({ ...entry, ...patch, id: entry.id }, patch.type || patch.state || patch.color || entry.type);
|
|
41
|
+
return nextEntry;
|
|
42
|
+
}));
|
|
43
|
+
if (nextEntry) scheduleToastTimer(nextEntry);
|
|
44
|
+
return nextEntry?.id || null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function renderNotifyToast(entry) {
|
|
48
|
+
const close = () => removeToast(entry.id);
|
|
49
|
+
const update = (patch = {}) => updateToast(entry.id, patch);
|
|
50
|
+
const ctx = { toast: entry, id: entry.id, close, update };
|
|
51
|
+
const resolveNotifyRender = (value) => typeof value === "function" ? value(ctx) : value;
|
|
52
|
+
const iconFallback = (() => {
|
|
53
|
+
if (entry.icon === false || entry.icon === null) return null;
|
|
54
|
+
if (entry.icon != null) {
|
|
55
|
+
const resolvedIcon = resolveNotifyRender(entry.icon);
|
|
56
|
+
return typeof resolvedIcon === "string" ? UI.Icon({ name: resolvedIcon, size: "sm" }) : JSswift.ui.slot(resolvedIcon, { as: "icon" });
|
|
57
|
+
}
|
|
58
|
+
const iconName = notifyIconMap[entry.type];
|
|
59
|
+
return iconName ? UI.Icon({ name: iconName, size: "sm" }) : null;
|
|
60
|
+
})();
|
|
61
|
+
|
|
62
|
+
const iconNodes = renderSlotToArray(entry.slots, "icon", ctx, iconFallback);
|
|
63
|
+
const titleNodes = renderSlotToArray(entry.slots, "title", ctx, resolveNotifyRender(entry.title));
|
|
64
|
+
const messageNodes = renderSlotToArray(entry.slots, "message", ctx, resolveNotifyRender(entry.message));
|
|
65
|
+
const descriptionNodes = renderSlotToArray(entry.slots, "description", ctx, resolveNotifyRender(entry.description));
|
|
66
|
+
const metaNodes = renderSlotToArray(entry.slots, "meta", ctx, resolveNotifyRender(entry.meta));
|
|
67
|
+
const actionsNodes = renderSlotToArray(entry.slots, "actions", ctx, resolveNotifyRender(entry.actions));
|
|
68
|
+
const bodyNodes = renderSlotToArray(entry.slots, "default", ctx, resolveNotifyRender(entry.body));
|
|
69
|
+
const dismissNodes = renderSlotToArray(entry.slots, "dismiss", ctx, resolveNotifyRender(entry.dismiss));
|
|
70
|
+
const dismissContent = dismissNodes.length
|
|
71
|
+
? dismissNodes
|
|
72
|
+
: (entry.closable
|
|
73
|
+
? [UI.Btn({
|
|
74
|
+
class: "jss-toast-close",
|
|
75
|
+
outline: true,
|
|
76
|
+
size: "xs",
|
|
77
|
+
"aria-label": entry.dismissLabel,
|
|
78
|
+
onClick: close
|
|
79
|
+
}, UI.Icon({ name: "close", size: "xs" }))]
|
|
80
|
+
: []);
|
|
81
|
+
|
|
82
|
+
const node = _.div({
|
|
83
|
+
class: uiClass([
|
|
84
|
+
"jss-toast",
|
|
85
|
+
`jss-toast-${entry.type}`,
|
|
86
|
+
`jss-toast-${entry.variant}`,
|
|
87
|
+
uiWhen(entry.closable, "is-closable"),
|
|
88
|
+
entry.class
|
|
89
|
+
]),
|
|
90
|
+
style: entry.style || {},
|
|
91
|
+
role: entry.role
|
|
92
|
+
},
|
|
93
|
+
iconNodes.length ? _.div({ class: "jss-toast-icon" }, ...iconNodes) : null,
|
|
94
|
+
_.div(
|
|
95
|
+
{ class: "jss-toast-main" },
|
|
96
|
+
_.div(
|
|
97
|
+
{ class: "jss-toast-copy" },
|
|
98
|
+
titleNodes.length ? _.div({ class: "jss-toast-title" }, ...titleNodes) : null,
|
|
99
|
+
messageNodes.length ? _.div({ class: "jss-toast-message" }, ...messageNodes) : null,
|
|
100
|
+
descriptionNodes.length ? _.div({ class: "jss-toast-description" }, ...descriptionNodes) : null,
|
|
101
|
+
bodyNodes.length ? _.div({ class: "jss-toast-body" }, ...bodyNodes) : null,
|
|
102
|
+
metaNodes.length ? _.div({ class: "jss-toast-meta" }, ...metaNodes) : null
|
|
103
|
+
),
|
|
104
|
+
actionsNodes.length ? _.div({ class: "jss-toast-actions" }, ...actionsNodes) : null
|
|
105
|
+
),
|
|
106
|
+
dismissContent.length ? _.div({ class: "jss-toast-dismiss" }, ...dismissContent) : null
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
setPropertyProps(node, entry);
|
|
110
|
+
return node;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function ensureToastRoot() {
|
|
114
|
+
let root = document.querySelector(".jss-toast-layer");
|
|
115
|
+
if (root) return root;
|
|
116
|
+
root = document.createElement("div");
|
|
117
|
+
root.className = "jss-toast-layer";
|
|
118
|
+
document.body.appendChild(root);
|
|
119
|
+
|
|
120
|
+
app.reactive.effect(() => {
|
|
121
|
+
const list = toasts();
|
|
122
|
+
root.innerHTML = "";
|
|
123
|
+
const groups = new Map();
|
|
124
|
+
for (const toast of list) {
|
|
125
|
+
const key = normalizeNotifyPosition(toast.position);
|
|
126
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
127
|
+
groups.get(key).push(toast);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const position of NOTIFY_POSITIONS) {
|
|
131
|
+
const entries = groups.get(position);
|
|
132
|
+
if (!entries?.length) continue;
|
|
133
|
+
const wrap = _.div({ class: uiClass(["jss-toast-wrap", `is-${position}`]) },
|
|
134
|
+
...entries.map((entry) => renderNotifyToast(entry))
|
|
135
|
+
);
|
|
136
|
+
root.appendChild(wrap);
|
|
137
|
+
}
|
|
138
|
+
}, "JSswiftUI:toasts");
|
|
139
|
+
|
|
140
|
+
return root;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function pushToast(input = {}) {
|
|
144
|
+
ensureToastRoot();
|
|
145
|
+
const entry = normalizeNotifyPayload(input);
|
|
146
|
+
entry.id = entry.id || Math.random().toString(36).slice(2);
|
|
147
|
+
setToasts([...toasts().filter((item) => item.id !== entry.id), entry]);
|
|
148
|
+
scheduleToastTimer(entry);
|
|
149
|
+
return entry.id;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
app.services.notify.show = pushToast;
|
|
153
|
+
app.services.notify.success = createNotifyShortcut("success", "Success");
|
|
154
|
+
app.services.notify.error = createNotifyShortcut("danger", "Error");
|
|
155
|
+
app.services.notify.warning = createNotifyShortcut("warning", "Warning");
|
|
156
|
+
app.services.notify.info = createNotifyShortcut("info", "Info");
|
|
157
|
+
app.services.notify.primary = createNotifyShortcut("primary", "Notice");
|
|
158
|
+
app.services.notify.secondary = createNotifyShortcut("secondary", "Notice");
|
|
159
|
+
app.services.notify.update = updateToast;
|
|
160
|
+
app.services.notify.remove = removeToast;
|
|
161
|
+
app.services.notify.clear = clearToasts;
|
|
162
|
+
app.services.notify.promise = UI.Notify.promise;
|
|
163
|
+
|
|
164
|
+
// Optional shortcut
|
|
165
|
+
app.notify = app.services.notify;
|
|
166
|
+
|