@soft-toast/vue 1.0.1 → 1.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.
- package/README.md +49 -14
- package/dist/animations/gsapConfig.d.ts +2 -1
- package/dist/animations/gsapConfig.d.ts.map +1 -1
- package/dist/composables/{useToast.d.ts → useSoftToast.d.ts} +6 -6
- package/dist/composables/useSoftToast.d.ts.map +1 -0
- package/dist/composables/useSoftToast.test.d.ts +2 -0
- package/dist/composables/useSoftToast.test.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1139 -848
- package/dist/plugin.d.ts.map +1 -1
- package/dist/stores/toastStore.d.ts +1 -1
- package/dist/stores/toastStore.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/dist/types/index.d.ts +8 -6
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +15 -2
- package/src/animations/gsapConfig.ts +53 -19
- package/src/components/ToastContainer.vue +2 -1
- package/src/components/ToastItem.vue +291 -65
- package/src/components/ToastRegion.vue +364 -87
- package/src/composables/useFlash.ts +1 -1
- package/src/composables/{useToast.test.ts → useSoftToast.test.ts} +54 -54
- package/src/composables/{useToast.ts → useSoftToast.ts} +5 -5
- package/src/exports.test.ts +39 -22
- package/src/index.ts +1 -2
- package/src/plugin.ts +8 -6
- package/src/stores/toastStore.ts +71 -21
- package/src/styles/toast.css +52 -13
- package/src/types/index.ts +8 -6
- package/dist/composables/useToast.d.ts.map +0 -1
- package/dist/composables/useToast.test.d.ts +0 -2
- package/dist/composables/useToast.test.d.ts.map +0 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from "vue";
|
|
3
3
|
import type { Toast } from "../types";
|
|
4
4
|
import { toastStore } from "../stores/toastStore";
|
|
5
|
-
import { Icon } from
|
|
5
|
+
import { Icon } from "@iconify/vue";
|
|
6
6
|
import ToastIcon from "./ToastIcon.vue";
|
|
7
7
|
import ToastProgress from "./ToastProgress.vue";
|
|
8
8
|
import { registerToastIcons } from "../icons";
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
positionAnimation,
|
|
13
13
|
killAnimations,
|
|
14
14
|
swipeExitAnimation,
|
|
15
|
-
swipeSnapBack,
|
|
16
15
|
} from "../animations/gsapConfig";
|
|
17
16
|
import { gsap } from "gsap";
|
|
18
17
|
|
|
@@ -28,6 +27,7 @@ interface Props {
|
|
|
28
27
|
expandedOffset?: number;
|
|
29
28
|
stackDirection?: "up" | "down";
|
|
30
29
|
reposition?: boolean;
|
|
30
|
+
interactive?: boolean;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -39,6 +39,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
39
39
|
expandedOffset: 0,
|
|
40
40
|
stackDirection: "up",
|
|
41
41
|
reposition: false,
|
|
42
|
+
interactive: true,
|
|
42
43
|
});
|
|
43
44
|
|
|
44
45
|
const toastRef = ref<HTMLElement | null>(null);
|
|
@@ -51,14 +52,25 @@ const formattedTime = computed(() => {
|
|
|
51
52
|
return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
52
53
|
});
|
|
53
54
|
|
|
55
|
+
const isSwipeToDismissEnabled = computed(() => props.swipeToDismiss !== false);
|
|
56
|
+
|
|
54
57
|
// ─── Dismiss ────────────────────────────────────────────────────────────────
|
|
55
58
|
|
|
56
59
|
const dismiss = () => {
|
|
57
60
|
if (isDismissing || !toastRef.value) return;
|
|
61
|
+
const toastId = props.toast.id;
|
|
62
|
+
const wasLeaving = props.toast.isLeaving;
|
|
58
63
|
isDismissing = true;
|
|
59
64
|
props.toast.isLeaving = true;
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
if (!wasLeaving) props.toast.onDismiss?.(toastId);
|
|
66
|
+
if (removeFallbackId !== null) {
|
|
67
|
+
window.clearTimeout(removeFallbackId);
|
|
68
|
+
}
|
|
69
|
+
removeFallbackId = window.setTimeout(() => {
|
|
70
|
+
toastStore.remove(toastId);
|
|
71
|
+
removeFallbackId = null;
|
|
72
|
+
}, 320);
|
|
73
|
+
exitAnimation(toastRef.value);
|
|
62
74
|
};
|
|
63
75
|
|
|
64
76
|
// ─── Action buttons ──────────────────────────────────────────────────────────
|
|
@@ -79,13 +91,19 @@ const handleAction = async (act: any) => {
|
|
|
79
91
|
|
|
80
92
|
const normalizedActions = computed(() => {
|
|
81
93
|
if (!props.toast.action) return [];
|
|
82
|
-
return Array.isArray(props.toast.action)
|
|
94
|
+
return Array.isArray(props.toast.action)
|
|
95
|
+
? props.toast.action
|
|
96
|
+
: [props.toast.action];
|
|
83
97
|
});
|
|
84
98
|
|
|
85
99
|
// ─── Stack position via GSAP ─────────────────────────────────────────────────
|
|
86
100
|
|
|
87
|
-
const applyStackPosition = (
|
|
88
|
-
|
|
101
|
+
const applyStackPosition = (
|
|
102
|
+
reposition = false,
|
|
103
|
+
overrideOffset?: number,
|
|
104
|
+
resetSwipe = false,
|
|
105
|
+
) => {
|
|
106
|
+
if (!toastRef.value || props.toast.isLeaving || isSwiping) return;
|
|
89
107
|
positionAnimation(toastRef.value, {
|
|
90
108
|
index: props.index,
|
|
91
109
|
expanded: props.expanded,
|
|
@@ -93,8 +111,9 @@ const applyStackPosition = (reposition = false) => {
|
|
|
93
111
|
bounce: props.toast.bounce,
|
|
94
112
|
spring: props.toast.spring,
|
|
95
113
|
direction: props.stackDirection,
|
|
96
|
-
expandedOffset: props.expandedOffset,
|
|
114
|
+
expandedOffset: overrideOffset ?? props.expandedOffset,
|
|
97
115
|
reposition,
|
|
116
|
+
resetSwipe,
|
|
98
117
|
});
|
|
99
118
|
};
|
|
100
119
|
|
|
@@ -107,12 +126,108 @@ const applyStackPosition = (reposition = false) => {
|
|
|
107
126
|
// - Miss threshold → elastic snap-back spring
|
|
108
127
|
|
|
109
128
|
let swipeStartX = 0;
|
|
129
|
+
let swipeStartY = 0;
|
|
110
130
|
let swipeStartTime = 0;
|
|
111
131
|
let isSwiping = false;
|
|
132
|
+
let directionLocked = false;
|
|
112
133
|
let swipeCurrentX = 0;
|
|
134
|
+
let activePointerId: number | null = null;
|
|
135
|
+
let activePointerTarget: HTMLElement | null = null;
|
|
136
|
+
let lostCaptureFallbackId: number | null = null;
|
|
137
|
+
let removeFallbackId: number | null = null;
|
|
138
|
+
|
|
139
|
+
const clearLostCaptureFallback = () => {
|
|
140
|
+
if (lostCaptureFallbackId === null) return;
|
|
141
|
+
window.clearTimeout(lostCaptureFallbackId);
|
|
142
|
+
lostCaptureFallbackId = null;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const releaseActivePointer = () => {
|
|
146
|
+
if (activePointerId === null || !activePointerTarget) return;
|
|
147
|
+
try {
|
|
148
|
+
if (activePointerTarget.hasPointerCapture(activePointerId)) {
|
|
149
|
+
activePointerTarget.releasePointerCapture(activePointerId);
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
// Safari can throw if capture was already lost during touch handoff.
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const removeSwipeFallbackListeners = () => {
|
|
157
|
+
window.removeEventListener("pointermove", handlePointerMove);
|
|
158
|
+
window.removeEventListener("pointerup", handlePointerUp);
|
|
159
|
+
window.removeEventListener("pointercancel", handlePointerCancel);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const resetSwipeTracking = () => {
|
|
163
|
+
isSwiping = false;
|
|
164
|
+
directionLocked = false;
|
|
165
|
+
clearLostCaptureFallback();
|
|
166
|
+
releaseActivePointer();
|
|
167
|
+
removeSwipeFallbackListeners();
|
|
168
|
+
activePointerId = null;
|
|
169
|
+
activePointerTarget = null;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const snapBackSwipe = () => {
|
|
173
|
+
if (!toastRef.value) return;
|
|
174
|
+
if (!props.expanded) toastStore.resume(props.toast.id);
|
|
175
|
+
applyStackPosition(true, undefined, true);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const completeSwipe = () => {
|
|
179
|
+
if (!toastRef.value) return;
|
|
180
|
+
|
|
181
|
+
const dx = swipeCurrentX;
|
|
182
|
+
const elapsed = Math.max(1, Date.now() - swipeStartTime);
|
|
183
|
+
const velocity = (Math.abs(dx) / elapsed) * 1000; // px/s
|
|
184
|
+
const width = toastRef.value.offsetWidth;
|
|
185
|
+
const threshold = width * 0.35;
|
|
186
|
+
resetSwipeTracking();
|
|
187
|
+
|
|
188
|
+
if (Math.abs(dx) >= threshold || velocity >= 500) {
|
|
189
|
+
// --- Dismiss: fly off in swipe direction ---
|
|
190
|
+
isDismissing = true;
|
|
191
|
+
const toastId = props.toast.id;
|
|
192
|
+
props.toast.isLeaving = true;
|
|
193
|
+
if (removeFallbackId !== null) {
|
|
194
|
+
window.clearTimeout(removeFallbackId);
|
|
195
|
+
}
|
|
196
|
+
removeFallbackId = window.setTimeout(() => {
|
|
197
|
+
toastStore.remove(toastId);
|
|
198
|
+
removeFallbackId = null;
|
|
199
|
+
}, 320);
|
|
200
|
+
const flyX = dx > 0 ? width * 1.6 : -width * 1.6;
|
|
201
|
+
swipeExitAnimation(toastRef.value, flyX);
|
|
202
|
+
} else {
|
|
203
|
+
// --- Snap back with spring ---
|
|
204
|
+
snapBackSwipe();
|
|
205
|
+
}
|
|
206
|
+
};
|
|
113
207
|
|
|
114
208
|
const handlePointerDown = (e: PointerEvent) => {
|
|
115
|
-
if
|
|
209
|
+
// Guard: isSwiping can be left true if lostpointercapture fired but its
|
|
210
|
+
// 300 ms fallback hasn't resolved yet. If there is no active captured
|
|
211
|
+
// pointer any more, reset the stale state so the next gesture isn't blocked.
|
|
212
|
+
if (isSwiping && activePointerId !== null) {
|
|
213
|
+
try {
|
|
214
|
+
const el = activePointerTarget ?? (e.currentTarget as HTMLElement);
|
|
215
|
+
if (!el.hasPointerCapture(activePointerId)) {
|
|
216
|
+
resetSwipeTracking();
|
|
217
|
+
snapBackSwipe();
|
|
218
|
+
}
|
|
219
|
+
} catch {
|
|
220
|
+
resetSwipeTracking();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (
|
|
224
|
+
!isSwipeToDismissEnabled.value ||
|
|
225
|
+
isDismissing ||
|
|
226
|
+
isSwiping ||
|
|
227
|
+
props.toast.isLeaving
|
|
228
|
+
) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
116
231
|
// Only primary button for mouse; all pointers for touch/stylus
|
|
117
232
|
if (e.pointerType === "mouse" && e.button !== 0) return;
|
|
118
233
|
// Ignore if target is a button/link (don't hijack action clicks)
|
|
@@ -120,12 +235,24 @@ const handlePointerDown = (e: PointerEvent) => {
|
|
|
120
235
|
if (target.closest("button, a")) return;
|
|
121
236
|
|
|
122
237
|
swipeStartX = e.clientX;
|
|
238
|
+
swipeStartY = e.clientY;
|
|
123
239
|
swipeStartTime = Date.now();
|
|
124
240
|
isSwiping = true;
|
|
241
|
+
directionLocked = false;
|
|
125
242
|
swipeCurrentX = 0;
|
|
243
|
+
activePointerId = e.pointerId;
|
|
244
|
+
activePointerTarget = e.currentTarget as HTMLElement;
|
|
245
|
+
gsap.killTweensOf(toastRef.value);
|
|
126
246
|
|
|
127
247
|
// Capture pointer so move/up fire even if cursor leaves the element
|
|
128
|
-
|
|
248
|
+
try {
|
|
249
|
+
activePointerTarget.setPointerCapture(e.pointerId);
|
|
250
|
+
} catch {
|
|
251
|
+
// Some WebKit touch paths may decline capture; window fallbacks still finish the gesture.
|
|
252
|
+
}
|
|
253
|
+
window.addEventListener("pointermove", handlePointerMove);
|
|
254
|
+
window.addEventListener("pointerup", handlePointerUp);
|
|
255
|
+
window.addEventListener("pointercancel", handlePointerCancel);
|
|
129
256
|
|
|
130
257
|
// Pause timer while swiping
|
|
131
258
|
toastStore.pause(props.toast.id);
|
|
@@ -133,8 +260,28 @@ const handlePointerDown = (e: PointerEvent) => {
|
|
|
133
260
|
|
|
134
261
|
const handlePointerMove = (e: PointerEvent) => {
|
|
135
262
|
if (!isSwiping || !toastRef.value) return;
|
|
263
|
+
if (activePointerId !== null && e.pointerId !== activePointerId) return;
|
|
264
|
+
clearLostCaptureFallback();
|
|
136
265
|
|
|
137
266
|
const dx = e.clientX - swipeStartX;
|
|
267
|
+
const dy = e.clientY - swipeStartY;
|
|
268
|
+
|
|
269
|
+
// Direction lock: first significant movement decides swipe vs scroll
|
|
270
|
+
if (!directionLocked) {
|
|
271
|
+
if (Math.abs(dy) > Math.abs(dx) + 4) {
|
|
272
|
+
// Vertical gesture — cancel swipe, let browser scroll
|
|
273
|
+
resetSwipeTracking();
|
|
274
|
+
snapBackSwipe();
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (Math.abs(dx) > Math.abs(dy) + 4) {
|
|
278
|
+
directionLocked = true;
|
|
279
|
+
} else {
|
|
280
|
+
return; // not enough movement yet
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
e.preventDefault();
|
|
138
285
|
swipeCurrentX = dx;
|
|
139
286
|
|
|
140
287
|
// Clamp opacity: full at 0, gone at 70% of width
|
|
@@ -148,41 +295,41 @@ const handlePointerMove = (e: PointerEvent) => {
|
|
|
148
295
|
|
|
149
296
|
const handlePointerUp = (e: PointerEvent) => {
|
|
150
297
|
if (!isSwiping || !toastRef.value) return;
|
|
151
|
-
|
|
298
|
+
if (activePointerId !== null && e.pointerId !== activePointerId) return;
|
|
152
299
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const velocity = (Math.abs(dx) / elapsed) * 1000; // px/s
|
|
156
|
-
const width = toastRef.value.offsetWidth;
|
|
157
|
-
const threshold = width * 0.35;
|
|
300
|
+
completeSwipe();
|
|
301
|
+
};
|
|
158
302
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
swipeExitAnimation(toastRef.value, flyX).then(() => {
|
|
165
|
-
toastStore.remove(props.toast.id);
|
|
166
|
-
});
|
|
167
|
-
} else {
|
|
168
|
-
// --- Snap back with spring ---
|
|
169
|
-
gsap.set(toastRef.value, { rotate: 0 }); // reset tilt first
|
|
170
|
-
swipeSnapBack(toastRef.value);
|
|
171
|
-
toastStore.resume(props.toast.id);
|
|
172
|
-
}
|
|
303
|
+
const handlePointerCancel = (e?: PointerEvent) => {
|
|
304
|
+
if (!isSwiping || !toastRef.value) return;
|
|
305
|
+
if (e && activePointerId !== null && e.pointerId !== activePointerId) return;
|
|
306
|
+
resetSwipeTracking();
|
|
307
|
+
snapBackSwipe();
|
|
173
308
|
};
|
|
174
309
|
|
|
175
|
-
const
|
|
310
|
+
const handleLostPointerCapture = (e: PointerEvent) => {
|
|
176
311
|
if (!isSwiping || !toastRef.value) return;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
312
|
+
if (activePointerId !== null && e.pointerId !== activePointerId) return;
|
|
313
|
+
activePointerTarget = null;
|
|
314
|
+
clearLostCaptureFallback();
|
|
315
|
+
lostCaptureFallbackId = window.setTimeout(() => {
|
|
316
|
+
if (!isSwiping) return;
|
|
317
|
+
resetSwipeTracking();
|
|
318
|
+
snapBackSwipe();
|
|
319
|
+
}, 150);
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const handleVisibilityChange = () => {
|
|
323
|
+
if (document.hidden && isSwiping) {
|
|
324
|
+
resetSwipeTracking();
|
|
325
|
+
snapBackSwipe();
|
|
326
|
+
}
|
|
181
327
|
};
|
|
182
328
|
|
|
183
329
|
// ─── Lifecycle ───────────────────────────────────────────────────────────────
|
|
184
330
|
|
|
185
331
|
onMounted(() => {
|
|
332
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
186
333
|
if (!toastRef.value) return;
|
|
187
334
|
const isBottom = props.toast.position.includes("bottom");
|
|
188
335
|
const tl = landingAnimation(toastRef.value, {
|
|
@@ -198,34 +345,65 @@ onMounted(() => {
|
|
|
198
345
|
|
|
199
346
|
// Split the watches so a dismiss only creates one restack animation.
|
|
200
347
|
// Collapsed stacks move by index; expanded stacks move by measured offsets.
|
|
201
|
-
watch(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
348
|
+
watch(
|
|
349
|
+
() => props.index,
|
|
350
|
+
(newIndex, oldIndex) => {
|
|
351
|
+
if (props.expanded) return;
|
|
352
|
+
applyStackPosition(newIndex < oldIndex || props.reposition);
|
|
353
|
+
},
|
|
354
|
+
);
|
|
205
355
|
|
|
206
|
-
watch(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
356
|
+
watch(
|
|
357
|
+
() => props.expandedOffset,
|
|
358
|
+
(newOffset, oldOffset) => {
|
|
359
|
+
if (!props.expanded) return;
|
|
360
|
+
applyStackPosition(newOffset !== oldOffset);
|
|
361
|
+
},
|
|
362
|
+
);
|
|
210
363
|
|
|
211
|
-
watch(
|
|
212
|
-
|
|
213
|
-
|
|
364
|
+
watch(
|
|
365
|
+
() => props.expanded,
|
|
366
|
+
(expanded) => {
|
|
367
|
+
if (!expanded) {
|
|
368
|
+
// Collapse: animate immediately back to stacked position
|
|
369
|
+
applyStackPosition(true);
|
|
370
|
+
}
|
|
371
|
+
// Expand: ToastRegion calls applyStackPosition directly after measuring
|
|
372
|
+
},
|
|
373
|
+
);
|
|
214
374
|
|
|
215
375
|
watch(
|
|
216
376
|
() => props.expanded,
|
|
217
377
|
(expanded) => {
|
|
218
378
|
if (expanded) toastStore.pause(props.toast.id);
|
|
219
|
-
else
|
|
379
|
+
else if (!isSwiping && !props.toast.isLeaving) {
|
|
380
|
+
toastStore.resume(props.toast.id);
|
|
381
|
+
}
|
|
220
382
|
},
|
|
221
383
|
);
|
|
222
384
|
|
|
223
385
|
watch(
|
|
224
386
|
() => props.toast.isLeaving,
|
|
225
|
-
(leaving) => {
|
|
387
|
+
(leaving) => {
|
|
388
|
+
if (leaving) {
|
|
389
|
+
if (isSwiping) resetSwipeTracking();
|
|
390
|
+
dismiss();
|
|
391
|
+
}
|
|
392
|
+
},
|
|
226
393
|
);
|
|
227
394
|
|
|
395
|
+
defineExpose({ applyStackPosition });
|
|
396
|
+
|
|
228
397
|
onUnmounted(() => {
|
|
398
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
399
|
+
if (isSwiping) {
|
|
400
|
+
resetSwipeTracking();
|
|
401
|
+
toastStore.resume(props.toast.id);
|
|
402
|
+
}
|
|
403
|
+
if (removeFallbackId !== null) {
|
|
404
|
+
window.clearTimeout(removeFallbackId);
|
|
405
|
+
removeFallbackId = null;
|
|
406
|
+
}
|
|
229
407
|
if (toastRef.value) killAnimations(toastRef.value);
|
|
230
408
|
});
|
|
231
409
|
</script>
|
|
@@ -234,17 +412,23 @@ onUnmounted(() => {
|
|
|
234
412
|
<div
|
|
235
413
|
ref="toastRef"
|
|
236
414
|
class="soft-toast-item"
|
|
237
|
-
:class="{ 'soft-toast-item--swipeable':
|
|
415
|
+
:class="{ 'soft-toast-item--swipeable': isSwipeToDismissEnabled }"
|
|
238
416
|
:data-type="toast.type"
|
|
239
417
|
:data-st-index="index"
|
|
418
|
+
:data-toast-id="toast.id"
|
|
240
419
|
:data-leaving="toast.isLeaving"
|
|
420
|
+
:data-interactive="interactive"
|
|
241
421
|
:style="{ zIndex: 1000 - index }"
|
|
242
422
|
@pointerdown="handlePointerDown"
|
|
243
|
-
@pointermove="handlePointerMove"
|
|
244
|
-
@pointerup="handlePointerUp"
|
|
245
423
|
@pointercancel="handlePointerCancel"
|
|
424
|
+
@lostpointercapture="handleLostPointerCapture"
|
|
246
425
|
>
|
|
247
|
-
<slot
|
|
426
|
+
<slot
|
|
427
|
+
name="close-button"
|
|
428
|
+
:toast="toast"
|
|
429
|
+
:dismiss="dismiss"
|
|
430
|
+
:close-button="closeButton"
|
|
431
|
+
>
|
|
248
432
|
<button
|
|
249
433
|
v-if="closeButton"
|
|
250
434
|
class="soft-toast-close"
|
|
@@ -254,15 +438,33 @@ onUnmounted(() => {
|
|
|
254
438
|
@click.stop="dismiss"
|
|
255
439
|
aria-label="Close"
|
|
256
440
|
>
|
|
257
|
-
<svg
|
|
258
|
-
|
|
259
|
-
|
|
441
|
+
<svg
|
|
442
|
+
width="10"
|
|
443
|
+
height="10"
|
|
444
|
+
viewBox="0 0 10 10"
|
|
445
|
+
fill="none"
|
|
446
|
+
class="st-close-icon"
|
|
447
|
+
>
|
|
448
|
+
<path
|
|
449
|
+
class="st-close-line-1"
|
|
450
|
+
d="M1 1L9 9"
|
|
451
|
+
stroke="currentColor"
|
|
452
|
+
stroke-width="1.6"
|
|
453
|
+
stroke-linecap="round"
|
|
454
|
+
/>
|
|
455
|
+
<path
|
|
456
|
+
class="st-close-line-2"
|
|
457
|
+
d="M9 1L1 9"
|
|
458
|
+
stroke="currentColor"
|
|
459
|
+
stroke-width="1.6"
|
|
460
|
+
stroke-linecap="round"
|
|
461
|
+
/>
|
|
260
462
|
</svg>
|
|
261
463
|
</button>
|
|
262
464
|
</slot>
|
|
263
465
|
|
|
264
466
|
<div class="soft-toast-content">
|
|
265
|
-
<slot name="icon" :toast="toast">
|
|
467
|
+
<slot name="icon" :toast="toast" :close-button="closeButton">
|
|
266
468
|
<div v-if="toast.type === 'promise'" class="soft-toast-icon">
|
|
267
469
|
<Icon
|
|
268
470
|
class="soft-toast-icon-svg"
|
|
@@ -271,7 +473,11 @@ onUnmounted(() => {
|
|
|
271
473
|
:height="18"
|
|
272
474
|
/>
|
|
273
475
|
</div>
|
|
274
|
-
<ToastIcon
|
|
476
|
+
<ToastIcon
|
|
477
|
+
v-else-if="toast.icon && typeof toast.icon === 'string'"
|
|
478
|
+
:type="toast.type"
|
|
479
|
+
:customIcon="toast.icon"
|
|
480
|
+
/>
|
|
275
481
|
<div v-else-if="toast.icon" class="soft-toast-icon">
|
|
276
482
|
<component :is="toast.icon" />
|
|
277
483
|
</div>
|
|
@@ -280,16 +486,25 @@ onUnmounted(() => {
|
|
|
280
486
|
|
|
281
487
|
<div class="soft-toast-body">
|
|
282
488
|
<div class="soft-toast-header-row">
|
|
283
|
-
<slot name="title" :toast="toast">
|
|
489
|
+
<slot name="title" :toast="toast" :close-button="closeButton">
|
|
284
490
|
<p
|
|
285
491
|
class="soft-toast-title"
|
|
286
|
-
:class="{
|
|
287
|
-
|
|
492
|
+
:class="{
|
|
493
|
+
'soft-toast-title--has-close':
|
|
494
|
+
closeButton === true || closeButton === 'top-right',
|
|
495
|
+
}"
|
|
496
|
+
>
|
|
497
|
+
{{ toast.title }}
|
|
498
|
+
</p>
|
|
288
499
|
</slot>
|
|
289
500
|
</div>
|
|
290
501
|
|
|
291
|
-
<div
|
|
292
|
-
|
|
502
|
+
<div
|
|
503
|
+
v-if="toast.description || toast.action"
|
|
504
|
+
class="soft-toast-extra"
|
|
505
|
+
style="overflow: hidden"
|
|
506
|
+
>
|
|
507
|
+
<slot name="description" :toast="toast" :close-button="closeButton">
|
|
293
508
|
<p v-if="toast.description" class="soft-toast-description">
|
|
294
509
|
<component
|
|
295
510
|
v-if="typeof toast.description === 'object'"
|
|
@@ -299,7 +514,13 @@ onUnmounted(() => {
|
|
|
299
514
|
</p>
|
|
300
515
|
</slot>
|
|
301
516
|
|
|
302
|
-
<slot
|
|
517
|
+
<slot
|
|
518
|
+
name="action"
|
|
519
|
+
:toast="toast"
|
|
520
|
+
:execute="handleAction"
|
|
521
|
+
:hasSucceeded="hasActionSucceeded"
|
|
522
|
+
:close-button="closeButton"
|
|
523
|
+
>
|
|
303
524
|
<div
|
|
304
525
|
v-if="normalizedActions.length > 0 && !hasActionSucceeded"
|
|
305
526
|
class="soft-toast-action"
|
|
@@ -308,7 +529,10 @@ onUnmounted(() => {
|
|
|
308
529
|
v-for="(act, idx) in normalizedActions"
|
|
309
530
|
:key="idx"
|
|
310
531
|
class="soft-toast-action-button"
|
|
311
|
-
:class="[
|
|
532
|
+
:class="[
|
|
533
|
+
act.class || '',
|
|
534
|
+
act.primary ? 'soft-toast-action-primary' : '',
|
|
535
|
+
]"
|
|
312
536
|
@click.stop="() => handleAction(act)"
|
|
313
537
|
>
|
|
314
538
|
{{ act.label }}
|
|
@@ -333,7 +557,9 @@ onUnmounted(() => {
|
|
|
333
557
|
</div>
|
|
334
558
|
|
|
335
559
|
<ToastProgress
|
|
336
|
-
v-if="
|
|
560
|
+
v-if="
|
|
561
|
+
toast.showProgress && toast.duration > 0 && toast.duration !== Infinity
|
|
562
|
+
"
|
|
337
563
|
:remaining-time="toast.remainingTime"
|
|
338
564
|
:total-duration="toast.duration"
|
|
339
565
|
:is-paused="toast.isPaused"
|