1
1
<template >
2
2
<li
3
+ ref =" toastRef"
3
4
:aria-live =" toast.important ? 'assertive' : 'polite'"
4
5
aria-atomic =" true"
5
6
role =" status"
6
7
tabindex =" 0"
7
- ref =" toastRef"
8
- data-sonner-toast =" "
8
+ data-sonner-toast =" true"
9
9
:class =" toastClass"
10
+ :data-rich-colors =" toast.richColors ?? defaultRichColors"
10
11
:data-styled =" !Boolean(toast.component || toast?.unstyled || unstyled)"
11
12
:data-mounted =" mounted"
12
13
:data-promise =" Boolean(toast.promise)"
39
40
<button
40
41
:aria-label =" closeButtonAriaLabel || 'Close toast'"
41
42
:data-disabled =" disabled"
42
- data-close-button
43
+ data-close-button = " true "
43
44
:class =" cn(classes?.closeButton, toast?.classes?.closeButton)"
44
45
@click =" handleCloseToast"
45
46
>
46
- <CloseIcon />
47
+ <template v-if =" icons ?.close " >
48
+ <component :is =" icons?.close" />
49
+ </template >
50
+ <template v-else >
51
+ <slot name =" close-icon" />
52
+ </template >
47
53
</button >
48
54
</template >
49
55
67
73
<component :is =" toast.icon" v-if =" toast.icon" />
68
74
69
75
<template v-else >
70
- <slot name = " success-icon " v-if =" toastType === 'success'" />
71
- <slot name = " error-icon " v-else-if =" toastType === 'error'" />
72
- <slot name = " warning-icon " v-else-if =" toastType === 'warning'" />
73
- <slot name = " info-icon " v-else-if =" toastType === 'info'" />
76
+ <slot v-if =" toastType === 'success'" name = " success-icon " />
77
+ <slot v-else-if =" toastType === 'error'" name = " error-icon " />
78
+ <slot v-else-if =" toastType === 'warning'" name = " warning-icon " />
79
+ <slot v-else-if =" toastType === 'info'" name = " info-icon " />
74
80
</template >
75
81
</div >
76
82
</template >
91
97
:class ="
92
98
cn(
93
99
descriptionClass,
94
- toast.descriptionClass ,
100
+ toastDescriptionClass ,
95
101
classes?.description,
96
102
toast.classes?.description
97
103
)
111
117
</div >
112
118
<template v-if =" toast .cancel " >
113
119
<button
120
+ :style =" toast.cancelButtonStyle || cancelButtonStyle"
114
121
:class =" cn(classes?.cancelButton, toast.classes?.cancelButton)"
115
122
data-button
116
123
data-cancel
117
124
@click ="
118
- () => {
119
- deleteToast()
120
- if (toast.cancel?.onClick) {
121
- toast.cancel.onClick()
122
- }
125
+ (event ) => {
126
+ if (!isAction(toast.cancel!)) return;
127
+ if (!dismissible) return;
128
+ toast.cancel.onClick?.(event);
129
+ deleteToast();
123
130
}
124
131
"
125
132
>
126
- {{ toast.cancel. label }}
133
+ {{ isAction( toast.cancel) ? toast.cancel?. label : toast.cancel }}
127
134
</button >
128
135
</template >
129
136
<template v-if =" toast .action " >
130
137
<button
138
+ :style =" toast.actionButtonStyle || actionButtonStyle"
131
139
:class =" cn(classes?.actionButton, toast.classes?.actionButton)"
132
140
data-button
141
+ data-action
133
142
@click ="
134
143
(event) => {
135
- toast.action?.onClick(event)
136
- if (event.defaultPrevented) return
137
- deleteToast()
144
+ if (!isAction(toast.action!)) return;
145
+ if (event.defaultPrevented) return;
146
+ toast.action.onClick?.(event);
147
+ deleteToast();
138
148
}
139
149
"
140
150
>
141
- {{ toast.action. label }}
151
+ {{ isAction( toast.action) ? toast.action?. label : toast.action }}
142
152
</button >
143
153
</template >
144
154
</template >
149
159
import ' ./styles.css'
150
160
151
161
import { computed , onMounted , onUnmounted , ref , watchEffect } from ' vue'
152
- import type { ToastProps , HeightT , ToastT } from ' ./types'
153
- import CloseIcon from ' ./assets/CloseIcon.vue'
162
+ import { type HeightT , type ToastProps , type ToastT , isAction } from ' ./types'
154
163
import { useIsDocumentHidden } from ' ./hooks'
155
164
156
- // Default lifetime of a toasts (in ms)
157
- const TOAST_LIFETIME = 4000
158
-
159
- // Default gap between toasts
160
- const GAP = 14
161
-
162
- const SWIPE_THRESHOLD = 20
163
-
164
- const TIME_BEFORE_UNMOUNT = 200
165
+ const props = defineProps <ToastProps >()
165
166
166
167
const emit = defineEmits <{
167
168
(e : ' update:heights' , heights : HeightT []): void
168
169
(e : ' removeToast' , toast : ToastT ): void
169
170
}>()
170
171
171
- const props = defineProps <ToastProps >()
172
+ // Default lifetime of a toasts (in ms)
173
+ const TOAST_LIFETIME = 4000
174
+
175
+ const SWIPE_THRESHOLD = 20
176
+
177
+ const TIME_BEFORE_UNMOUNT = 200
172
178
173
179
const mounted = ref (false )
174
180
const removed = ref (false )
175
181
const swiping = ref (false )
176
182
const swipeOut = ref (false )
177
183
const offsetBeforeRemove = ref (0 )
178
184
const initialHeight = ref (0 )
179
- let dragStartTime: number = 0
185
+ const dragStartTime = ref < Date | null >( null )
180
186
const toastRef = ref <HTMLLIElement | null >(null )
181
187
const isFront = computed (() => props .index === 0 )
182
188
const isVisible = computed (() => props .index + 1 <= props .visibleToasts )
183
189
const toastType = computed (() => props .toast .type )
184
190
const dismissible = computed (() => props .toast .dismissible !== false )
185
- const toastClass = computed (() => {
186
- return props .cn (
187
- props .classes ?.toast ,
188
- props .toast ?.classes ?.toast ,
189
- props .classes ?.default ,
190
- props .classes ?.[props .toast .type || ' default' ],
191
- props .toast ?.classes ?.[props .toast .type || ' default' ]
192
- )
193
- })
191
+ const toastClass = computed (() => props .toast .class || ' ' )
192
+ const toastDescriptionClass = computed (() => props .descriptionClass || ' ' )
194
193
195
194
const toastStyle = props .toast .style || {}
196
195
@@ -206,14 +205,15 @@ const duration = computed(
206
205
207
206
const closeTimerStartTimeRef = ref (0 )
208
207
const offset = ref (0 )
209
- const remainingTime = ref (duration .value )
210
208
const lastCloseTimerStartTimeRef = ref (0 )
211
209
const pointerStartRef = ref <{ x: number ; y: number } | null >(null )
212
210
const coords = computed (() => props .position .split (' -' ))
213
211
const y = computed (() => coords .value [0 ])
214
212
const x = computed (() => coords .value [1 ])
215
213
const isStringOfTitle = computed (() => typeof props .toast .title !== ' string' )
216
- const isStringOfDescription = computed (() => typeof props .toast .description !== ' string' )
214
+ const isStringOfDescription = computed (
215
+ () => typeof props .toast .description !== ' string'
216
+ )
217
217
218
218
const toastsHeightBefore = computed (() => {
219
219
return props .heights .reduce ((prev , curr , reducerIndex ) => {
@@ -265,17 +265,21 @@ onMounted(() => {
265
265
emit (' update:heights' , newHeightArr as HeightT [])
266
266
})
267
267
268
- const deleteToast = () => {
268
+ function deleteToast() {
269
269
// Save the offset for the exit swipe animation
270
270
removed .value = true
271
271
offsetBeforeRemove .value = offset .value
272
+ const height = props .heights .filter (
273
+ (height ) => height .toastId !== props .toast .id
274
+ )
275
+ emit (' update:heights' , height )
272
276
273
277
setTimeout (() => {
274
278
emit (' removeToast' , props .toast )
275
279
}, TIME_BEFORE_UNMOUNT )
276
280
}
277
281
278
- const handleCloseToast = () => {
282
+ function handleCloseToast() {
279
283
if (disabled .value || ! dismissible .value ) {
280
284
return
281
285
}
@@ -284,9 +288,9 @@ const handleCloseToast = () => {
284
288
props .toast .onDismiss ?.(props .toast )
285
289
}
286
290
287
- const onPointerDown = (event : PointerEvent ) => {
291
+ function onPointerDown(event : PointerEvent ) {
288
292
if (disabled .value || ! dismissible .value ) return
289
- dragStartTime = Date . now ()
293
+ dragStartTime . value = new Date ()
290
294
offsetBeforeRemove .value = offset .value
291
295
// Ensure we maintain correct pointer capture even when going outside of the toast (e.g. when swiping)
292
296
;(event .target as HTMLElement ).setPointerCapture (event .pointerId )
@@ -295,7 +299,7 @@ const onPointerDown = (event: PointerEvent) => {
295
299
pointerStartRef .value = { x: event .clientX , y: event .clientY }
296
300
}
297
301
298
- const onPointerUp = ( event : PointerEvent ) => {
302
+ function onPointerUp() {
299
303
if (swipeOut .value ) return
300
304
pointerStartRef .value = null
301
305
@@ -305,7 +309,7 @@ const onPointerUp = (event: PointerEvent) => {
305
309
.replace (' px' , ' ' ) || 0
306
310
)
307
311
308
- const timeTaken = ( Date . now () - dragStartTime ) || 50
312
+ const timeTaken = new Date (). getTime () - dragStartTime . value ?. getTime () !
309
313
const velocity = Math .abs (swipeAmount ) / timeTaken
310
314
311
315
// Remove only if treshold is met
@@ -321,8 +325,8 @@ const onPointerUp = (event: PointerEvent) => {
321
325
swiping .value = false
322
326
}
323
327
324
- const onPointerMove = (event : PointerEvent ) => {
325
- if (! pointerStartRef .value ) return
328
+ function onPointerMove(event : PointerEvent ) {
329
+ if (! pointerStartRef .value || ! dismissible . value ) return
326
330
327
331
const yPosition = event .clientY - pointerStartRef .value .y
328
332
const xPosition = event .clientX - pointerStartRef .value .x
@@ -342,37 +346,41 @@ const onPointerMove = (event: PointerEvent) => {
342
346
}
343
347
344
348
watchEffect (() => {
345
- offset .value = heightIndex .value * GAP + toastsHeightBefore .value
349
+ offset .value = heightIndex .value * props ?. gap ! + toastsHeightBefore .value
346
350
})
347
351
348
352
watchEffect ((onInvalidate ) => {
349
353
if (
350
354
(props .toast .promise && toastType .value === ' loading' ) ||
351
355
props .toast .duration === Infinity ||
352
356
props .toast .type === ' loading'
353
- )
357
+ ) {
354
358
return
359
+ }
355
360
let timeoutId: ReturnType <typeof setTimeout >
361
+ let remainingTime = duration .value
356
362
357
363
// Pause the timer on each hover
358
364
const pauseTimer = () => {
359
365
if (lastCloseTimerStartTimeRef .value < closeTimerStartTimeRef .value ) {
360
366
// Get the elapsed time since the timer started
361
- const elapsedTime = Date . now () - closeTimerStartTimeRef .value
367
+ const elapsedTime = new Date (). getTime () - closeTimerStartTimeRef .value
362
368
363
- remainingTime . value = remainingTime . value - elapsedTime
369
+ remainingTime = remainingTime - elapsedTime
364
370
}
365
371
366
- lastCloseTimerStartTimeRef .value = Date . now ()
372
+ lastCloseTimerStartTimeRef .value = new Date (). getTime ()
367
373
}
368
374
369
375
const startTimer = () => {
370
- closeTimerStartTimeRef .value = Date .now ()
376
+ if (remainingTime === Infinity ) return
377
+ closeTimerStartTimeRef .value = new Date ().getTime ()
378
+
371
379
// Let the toast know it has started
372
380
timeoutId = setTimeout (() => {
373
381
props .toast .onAutoClose ?.(props .toast )
374
382
deleteToast ()
375
- }, remainingTime . value )
383
+ }, remainingTime )
376
384
}
377
385
378
386
if (
@@ -390,11 +398,11 @@ watchEffect((onInvalidate) => {
390
398
})
391
399
})
392
400
393
- watchEffect (() => {
394
- if (props .toast .delete ) {
395
- deleteToast ()
396
- }
397
- })
401
+ // watchEffect(() => {
402
+ // if (props.toast.delete) {
403
+ // deleteToast()
404
+ // }
405
+ // })
398
406
399
407
onMounted (() => {
400
408
if (toastRef .value ) {
0 commit comments