@jsswift/ui 1.0.54 → 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.
Files changed (39) hide show
  1. package/dist/css/animation.css +36 -36
  2. package/dist/css/base.css +374 -374
  3. package/dist/css/docs.css +54 -54
  4. package/dist/css/fonts-nunito.css +2 -2
  5. package/dist/css/min-ui.css +1 -1
  6. package/dist/css/responsive-base.css +549 -549
  7. package/dist/css/responsive.css +3225 -3225
  8. package/dist/css/ui-components.css +2902 -2930
  9. package/dist/css/ui-light.css +3853 -3881
  10. package/dist/css/ui-light.min.css +1 -1
  11. package/dist/css/ui.css +6530 -6558
  12. package/dist/css/ui.min.css +1 -1
  13. package/dist/min-ui.js +12 -12
  14. package/dist/ui.js +4354 -9379
  15. package/dist/ui.min.js +12 -12
  16. package/package.json +2 -2
  17. package/src/00-bootstrap.js +305 -305
  18. package/src/01-foundation-helpers.js +328 -325
  19. package/src/10-primitives-layout.js +242 -242
  20. package/src/20-display-content.js +76 -76
  21. package/src/21-list-content.js +21 -21
  22. package/src/22-banner-content.js +376 -376
  23. package/src/23-tooltip.js +9 -9
  24. package/src/31-form-advanced.js +141 -141
  25. package/src/40-navigation.js +81 -81
  26. package/src/41-tab-panel.js +37 -37
  27. package/src/50-feedback.js +52 -52
  28. package/src/51-feedback-service.js +166 -166
  29. package/src/60-shell-app.js +1082 -1082
  30. package/src/61-form-service.js +5 -5
  31. package/src/70-dialog.js +34 -34
  32. package/src/71-menu-overlays.js +73 -73
  33. package/src/80-data.js +38 -38
  34. package/src/css/animation.css +36 -36
  35. package/src/css/base.css +374 -374
  36. package/src/css/docs.css +54 -54
  37. package/src/css/fonts-nunito.css +2 -2
  38. package/src/css/responsive.css +3225 -3225
  39. package/src/css/ui-components.css +2902 -2930
@@ -30,17 +30,17 @@
30
30
  str.startsWith("hsla(") ||
31
31
  str.startsWith("var(")
32
32
  ) return str;
33
- if (isTokenCSS(str)) return `var(--cms-${str})`;
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 ? `cms-state-${state}` : "";
39
+ return state ? `jss-state-${state}` : "";
40
40
  });
41
41
 
42
42
  const cls = uiClass([
43
- "cms-spinner",
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(["cms-spinner-indicator", props.indicatorClass]),
82
+ class: uiClass(["jss-spinner-indicator", props.indicatorClass]),
83
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)"),
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: "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
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 ? `cms-state-${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(--cms-${state})`;
157
- if (typeof raw === "string" && isTokenCSS(raw)) return `var(--cms-${raw})`;
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
- "cms-progress-wrap",
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: "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" });
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: "cms-progress-body" });
271
- const startLabelHost = _.span({ class: "cms-progress-edge-label cms-progress-edge-label-left" });
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: "cms-progress",
273
+ class: "jss-progress",
274
274
  role: "progressbar"
275
275
  });
276
- const buffer = _.span({ class: "cms-progress-buffer" });
276
+ const buffer = _.span({ class: "jss-progress-buffer" });
277
277
  const fill = _.span({
278
278
  class: uiClass([
279
- "cms-progress-bar",
280
- "cms-singularity",
279
+ "jss-progress-bar",
280
+ "jss-singularity",
281
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"),
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 ? `cms-shadow-${shadow}` : "";
291
+ return shadow ? `jss-shadow-${shadow}` : "";
292
292
  }),
293
- uiWhen(props.gradient, "cms-gradient"),
294
- uiWhen(props.textGradient, "cms-text-gradient"),
295
- uiWhen(props.lightShadow, "cms-light-shadow"),
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: "cms-progress-bar-label" });
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: "cms-progress-edge-label cms-progress-edge-label-right" });
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("--cms-progress-height", getTrackHeight());
410
+ wrap.style.setProperty("--jss-progress-height", getTrackHeight());
411
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));
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("--cms-progress-radius");
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(--cms-on-primary, #fff)");
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(--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)");
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(["cms-loading-bar", props.class]);
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(["cms-loading-bar-progress", props.progressClass, progressProps.class]);
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: "cms-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
- "cms-toast",
85
- `cms-toast-${entry.type}`,
86
- `cms-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: "cms-toast-icon" }, ...iconNodes) : null,
94
- _.div(
95
- { class: "cms-toast-main" },
96
- _.div(
97
- { class: "cms-toast-copy" },
98
- titleNodes.length ? _.div({ class: "cms-toast-title" }, ...titleNodes) : null,
99
- messageNodes.length ? _.div({ class: "cms-toast-message" }, ...messageNodes) : null,
100
- descriptionNodes.length ? _.div({ class: "cms-toast-description" }, ...descriptionNodes) : null,
101
- bodyNodes.length ? _.div({ class: "cms-toast-body" }, ...bodyNodes) : null,
102
- metaNodes.length ? _.div({ class: "cms-toast-meta" }, ...metaNodes) : null
103
- ),
104
- actionsNodes.length ? _.div({ class: "cms-toast-actions" }, ...actionsNodes) : null
105
- ),
106
- dismissContent.length ? _.div({ class: "cms-toast-dismiss" }, ...dismissContent) : null
107
- );
108
-
109
- setPropertyProps(node, entry);
110
- return node;
111
- }
112
-
113
- function ensureToastRoot() {
114
- let root = document.querySelector(".cms-toast-layer");
115
- if (root) return root;
116
- root = document.createElement("div");
117
- root.className = "cms-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(["cms-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
-
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
+