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