2
2
import ' ../styles/styles.css'
3
3
import { computed , onMounted , onUnmounted , ref , watchEffect } from ' vue'
4
4
import { useIsDocumentHidden } from ' ../composables/useIsDocumentHidden'
5
- import type { HeightT , ToastProps , ToastT } from ' ../types'
6
- import CloseIcon from ' ./icons/CloseIcon.vue'
5
+ import { type HeightT , type ToastProps , type ToastT , isAction } from ' ./types'
7
6
8
7
const props = defineProps <ToastProps >()
9
8
@@ -14,12 +13,9 @@ const emit = defineEmits<{
14
13
15
14
// Default lifetime of a toasts (in ms)
16
15
const TOAST_LIFETIME = 4000
17
-
18
16
// Default gap between toasts
19
17
const GAP = 14
20
-
21
18
const SWIPE_THRESHOLD = 20
22
-
23
19
const TIME_BEFORE_UNMOUNT = 200
24
20
25
21
const mounted = ref (false )
@@ -34,15 +30,8 @@ const isFront = computed(() => props.index === 0)
34
30
const isVisible = computed (() => props .index + 1 <= props .visibleToasts )
35
31
const toastType = computed (() => props .toast .type )
36
32
const dismissible = computed (() => props .toast .dismissible !== false )
37
- const toastClass = computed (() => {
38
- return props .cn (
39
- props .classes ?.toast ,
40
- props .toast ?.classes ?.toast ,
41
- props .classes ?.default ,
42
- props .classes ?.[props .toast .type || ' default' ],
43
- props .toast ?.classes ?.[props .toast .type || ' default' ],
44
- )
45
- })
33
+ const toastClass = computed (() => props .toast .class || ' ' )
34
+ const toastDescriptionClass = computed (() => props .descriptionClass || ' ' )
46
35
47
36
const toastStyle = props .toast .style || {}
48
37
@@ -53,7 +42,6 @@ const duration = computed(() => props.toast.duration || props.duration || TOAST_
53
42
54
43
const closeTimerStartTimeRef = ref (0 )
55
44
const offset = ref (0 )
56
- const remainingTime = ref (duration .value )
57
45
const lastCloseTimerStartTimeRef = ref (0 )
58
46
const pointerStartRef = ref <{ x: number ; y: number } | null >(null )
59
47
const coords = computed (() => props .position .split (' -' ))
@@ -113,6 +101,9 @@ function deleteToast() {
113
101
removed .value = true
114
102
offsetBeforeRemove .value = offset .value
115
103
104
+ const height = props .heights .filter ((height ) => height .toastId !== props .toast .id )
105
+ emit (' update:heights' , height )
106
+
116
107
setTimeout (() => {
117
108
emit (' removeToast' , props .toast )
118
109
}, TIME_BEFORE_UNMOUNT )
@@ -136,7 +127,7 @@ function onPointerDown(event: PointerEvent) {
136
127
pointerStartRef .value = { x: event .clientX , y: event .clientY }
137
128
}
138
129
139
- function onPointerUp(event : PointerEvent ) {
130
+ function onPointerUp() {
140
131
if (swipeOut .value ) return
141
132
pointerStartRef .value = null
142
133
@@ -159,7 +150,7 @@ function onPointerUp(event: PointerEvent) {
159
150
}
160
151
161
152
function onPointerMove(event : PointerEvent ) {
162
- if (! pointerStartRef .value ) return
153
+ if (! pointerStartRef .value || ! dismissible . value ) return
163
154
164
155
const yPosition = event .clientY - pointerStartRef .value .y
165
156
const xPosition = event .clientX - pointerStartRef .value .x
@@ -179,37 +170,41 @@ function onPointerMove(event: PointerEvent) {
179
170
}
180
171
181
172
watchEffect (() => {
182
- offset .value = heightIndex .value * GAP + toastsHeightBefore .value
173
+ offset .value = heightIndex .value * props ?. gap + toastsHeightBefore .value
183
174
})
184
175
185
176
watchEffect ((onInvalidate ) => {
186
177
if (
187
178
(props .toast .promise && toastType .value === ' loading' ) ||
188
179
props .toast .duration === Number .POSITIVE_INFINITY ||
189
180
props .toast .type === ' loading'
190
- )
181
+ ) {
191
182
return
183
+ }
184
+
192
185
let timeoutId: ReturnType <typeof setTimeout >
186
+ let remainingTime = duration .value
193
187
194
188
// Pause the timer on each hover
195
189
const pauseTimer = () => {
196
190
if (lastCloseTimerStartTimeRef .value < closeTimerStartTimeRef .value ) {
197
191
// Get the elapsed time since the timer started
198
192
const elapsedTime = new Date ().getTime () - closeTimerStartTimeRef .value
199
193
200
- remainingTime . value = remainingTime . value - elapsedTime
194
+ remainingTime = remainingTime - elapsedTime
201
195
}
202
196
203
197
lastCloseTimerStartTimeRef .value = new Date ().getTime ()
204
198
}
205
199
206
200
const startTimer = () => {
201
+ if (remainingTime === Number .POSITIVE_INFINITY) return
207
202
closeTimerStartTimeRef .value = new Date ().getTime ()
208
203
// Let the toast know it has started
209
204
timeoutId = setTimeout (() => {
210
205
props .toast .onAutoClose ?.(props .toast )
211
206
deleteToast ()
212
- }, remainingTime . value )
207
+ }, remainingTime )
213
208
}
214
209
215
210
if (props .expanded || props .interacting || (props .pauseWhenPageIsHidden && isDocumentHidden )) pauseTimer ()
@@ -220,9 +215,9 @@ watchEffect((onInvalidate) => {
220
215
})
221
216
})
222
217
223
- watchEffect (() => {
224
- if (props .toast .delete ) deleteToast ()
225
- })
218
+ // watchEffect(() => {
219
+ // if (props.toast.delete) deleteToast()
220
+ // })
226
221
227
222
onMounted (() => {
228
223
if (toastRef .value ) {
@@ -253,8 +248,9 @@ onUnmounted(() => {
253
248
aria-atomic =" true"
254
249
role =" status"
255
250
tabindex =" 0"
256
- data-sonner-toast =" "
251
+ data-sonner-toast =" true "
257
252
:class =" toastClass"
253
+ :data-rich-colors =" toast.richColors ?? defaultRichColors"
258
254
:data-styled =" !Boolean(toast.component || toast?.unstyled || unstyled)"
259
255
:data-mounted =" mounted"
260
256
:data-promise =" Boolean(toast.promise)"
@@ -287,11 +283,16 @@ onUnmounted(() => {
287
283
<button
288
284
:aria-label =" closeButtonAriaLabel || 'Close toast'"
289
285
:data-disabled =" disabled"
290
- data-close-button
286
+ data-close-button = " true "
291
287
:class =" cn(classes?.closeButton, toast?.classes?.closeButton)"
292
288
@click =" handleCloseToast"
293
289
>
294
- <CloseIcon />
290
+ <template v-if =" icons ?.close " >
291
+ <component :is =" icons?.close" />
292
+ </template >
293
+ <template v-else >
294
+ <slot name =" close-icon" />
295
+ </template >
295
296
</button >
296
297
</template >
297
298
@@ -339,7 +340,7 @@ onUnmounted(() => {
339
340
:class ="
340
341
cn(
341
342
descriptionClass,
342
- toast.descriptionClass ,
343
+ toastDescriptionClass ,
343
344
classes?.description,
344
345
toast.classes?.description,
345
346
)
@@ -359,34 +360,38 @@ onUnmounted(() => {
359
360
</div >
360
361
<template v-if =" toast .cancel " >
361
362
<button
363
+ :style =" toast.cancelButtonStyle || cancelButtonStyle"
362
364
:class =" cn(classes?.cancelButton, toast.classes?.cancelButton)"
363
365
data-button
364
366
data-cancel
365
367
@click ="
366
- () => {
367
- deleteToast()
368
- if (toast.cancel?.onClick) {
369
- toast.cancel.onClick()
370
- }
368
+ (event ) => {
369
+ if (!isAction(toast.cancel!)) return;
370
+ if (!dismissible) return;
371
+ toast.cancel.onClick?.(event);
372
+ deleteToast();
371
373
}
372
374
"
373
375
>
374
- {{ toast.cancel. label }}
376
+ {{ isAction( toast.cancel) ? toast.cancel?. label : toast.cancel }}
375
377
</button >
376
378
</template >
377
379
<template v-if =" toast .action " >
378
380
<button
381
+ :style =" toast.actionButtonStyle || actionButtonStyle"
379
382
:class =" cn(classes?.actionButton, toast.classes?.actionButton)"
380
383
data-button
384
+ data-action
381
385
@click ="
382
386
(event) => {
383
- toast.action?.onClick(event)
384
- if (event.defaultPrevented) return
385
- deleteToast()
387
+ if (!isAction(toast.action!)) return;
388
+ if (event.defaultPrevented) return;
389
+ toast.action.onClick?.(event);
390
+ deleteToast();
386
391
}
387
392
"
388
393
>
389
- {{ toast.action. label }}
394
+ {{ isAction( toast.action) ? toast.action?. label : toast.action }}
390
395
</button >
391
396
</template >
392
397
</template >
0 commit comments