jsswift 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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
+