Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add sonner 1.7.0 features #102

Merged
merged 3 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"license": "MIT",
"devDependencies": {
"@iconify/json": "^2.2.172",
"@microsoft/api-extractor": "^7.47.10",
"@microsoft/api-extractor": "^7.47.11",
"@nuxt/devtools": "^1.6.0",
"@nuxt/kit": "^3.13.2",
"@nuxt/module-builder": "^0.8.4",
Expand All @@ -62,7 +62,7 @@
"@types/node": "^18.19.8",
"@unocss/reset": "^0.63.4",
"@vitejs/plugin-vue": "^5.1.4",
"@vue/tsconfig": "^0.5.1",
"@vue/tsconfig": "^0.6.0",
"@vueuse/core": "^11.1.0",
"@vueuse/head": "^2.0.0",
"clean-css": "^5.3.3",
Expand All @@ -72,9 +72,9 @@
"unocss": "^0.63.4",
"unplugin-icons": "^0.19.3",
"unplugin-vue-components": "^0.27.4",
"vite": "^5.4.9",
"vite": "^5.4.11",
"vue": "^3.5.12",
"vue-sonner": "^1.2.4",
"vue-tsc": "^2.1.6"
"vue-tsc": "^2.1.10"
}
}
221 changes: 116 additions & 105 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

59 changes: 31 additions & 28 deletions src/packages/Toast.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@

<template v-else>
<template v-if="toastType !== 'default' || toast.icon || toast.promise">
<div data-icon="">
<div data-icon="" :class="cn(classes?.icon, toast?.classes?.icon)">
<template
v-if="(toast.promise || toastType === 'loading') && !toast.icon"
>
Expand Down Expand Up @@ -153,6 +153,7 @@
if (!isAction(toast.action!)) return;
if (event.defaultPrevented) return;
toast.action.onClick?.(event);
if (event.defaultPrevented) return;
deleteToast();
}
"
Expand All @@ -167,7 +168,7 @@
<script lang="ts" setup>
import './styles.css'

import { computed, onMounted, onUnmounted, ref, watch, watchEffect } from 'vue'
import { computed, onBeforeUnmount, onMounted, onUnmounted, ref, watch, watchEffect } from 'vue'
import { type HeightT, type ToastProps, type ToastT, isAction } from './types'
import { useIsDocumentHidden } from './hooks'

Expand All @@ -189,8 +190,10 @@ const mounted = ref(false)
const removed = ref(false)
const swiping = ref(false)
const swipeOut = ref(false)
const swiped = ref(false)
const offsetBeforeRemove = ref(0)
const initialHeight = ref(0)
const remainingTime = ref(props.toast.duration || props.duration || TOAST_LIFETIME)
const dragStartTime = ref<Date | null>(null)
const toastRef = ref<HTMLLIElement | null>(null)
const isFront = computed(() => props.index === 0)
Expand All @@ -213,7 +216,6 @@ const duration = computed(
)

const closeTimerStartTimeRef = ref(0)
const offset = ref(0)
const lastCloseTimerStartTimeRef = ref(0)
const pointerStartRef = ref<{ x: number; y: number } | null>(null)
const coords = computed(() => props.position.split('-'))
Expand All @@ -238,6 +240,10 @@ const isDocumentHidden = useIsDocumentHidden()
const invert = computed(() => props.toast.invert || props.invert)
const disabled = computed(() => toastType.value === 'loading')

const offset = computed(() => {
return heightIndex.value * props.gap! + toastsHeightBefore.value || 0
})

onMounted(() => {
if (!mounted.value) return

Expand All @@ -250,6 +256,7 @@ onMounted(() => {
initialHeight.value = newHeight

let newHeightArr

const alreadyExists = props.heights.find(
(height) => height.toastId === props.toast.id
)
Expand Down Expand Up @@ -290,7 +297,7 @@ function deleteToast() {

function handleCloseToast() {
if (disabled.value || !dismissible.value) {
return
return {}
}

deleteToast()
Expand All @@ -309,7 +316,7 @@ function onPointerDown(event: PointerEvent) {
}

function onPointerUp() {
if (swipeOut.value) return
if (swipeOut.value || !dismissible) return;
pointerStartRef.value = null

const swipeAmount = Number(
Expand All @@ -327,6 +334,7 @@ function onPointerUp() {
props.toast.onDismiss?.(props.toast)
deleteToast()
swipeOut.value = true
swiped.value = false
return
}

Expand All @@ -338,25 +346,20 @@ function onPointerMove(event: PointerEvent) {
if (!pointerStartRef.value || !dismissible.value) return

const yPosition = event.clientY - pointerStartRef.value.y
const xPosition = event.clientX - pointerStartRef.value.x

const clamp = coords.value[0] === 'top' ? Math.min : Math.max
const clampedY = clamp(0, yPosition)
const swipeStartThreshold = event.pointerType === 'touch' ? 10 : 2
const isAllowedToSwipe = Math.abs(clampedY) > swipeStartThreshold

if (isAllowedToSwipe) {
toastRef.value?.style.setProperty('--swipe-amount', `${yPosition}px`)
} else if (Math.abs(xPosition) > swipeStartThreshold) {
// User is swiping in wrong direction so we disable swipe gesture
// for the current pointer down interaction
pointerStartRef.value = null

// @ts-expect-error
const isHighlighted = window.getSelection()?.toString().length > 0;

const swipeAmount = y.value === 'top' ? Math.min(0, yPosition) : Math.max(0, yPosition);

if (Math.abs(swipeAmount) > 0) {
swiped.value = true;
}
}

watchEffect(() => {
offset.value = heightIndex.value * props?.gap! + toastsHeightBefore.value
})
if (isHighlighted) return;

toastRef.value?.style.setProperty('--swipe-amount', `${swipeAmount}px`);
}

watchEffect((onInvalidate) => {
if (
Expand All @@ -367,29 +370,28 @@ watchEffect((onInvalidate) => {
return
}
let timeoutId: ReturnType<typeof setTimeout>
let remainingTime = duration.value

// Pause the timer on each hover
const pauseTimer = () => {
if (lastCloseTimerStartTimeRef.value < closeTimerStartTimeRef.value) {
// Get the elapsed time since the timer started
const elapsedTime = new Date().getTime() - closeTimerStartTimeRef.value

remainingTime = remainingTime - elapsedTime
remainingTime.value = remainingTime.value - elapsedTime
}

lastCloseTimerStartTimeRef.value = new Date().getTime()
}

const startTimer = () => {
if (remainingTime === Infinity) return
if (remainingTime.value === Infinity) return
closeTimerStartTimeRef.value = new Date().getTime()

// Let the toast know it has started
timeoutId = setTimeout(() => {
props.toast.onAutoClose?.(props.toast)
deleteToast()
}, remainingTime)
}, remainingTime.value)
}

if (
Expand Down Expand Up @@ -417,6 +419,8 @@ watch(
)

onMounted(() => {
mounted.value = true

if (toastRef.value) {
const height = toastRef.value.getBoundingClientRect().height
// Add toast height tot heights array after the toast is mounted
Expand All @@ -428,10 +432,9 @@ onMounted(() => {
]
emit('update:heights', newHeights)
}
mounted.value = true
})

onUnmounted(() => {
onBeforeUnmount(() => {
xiaoluoboding marked this conversation as resolved.
Show resolved Hide resolved
if (toastRef.value) {
const newHeights = props.heights.filter(
(height) => height.toastId !== props.toast.id
Expand Down
26 changes: 21 additions & 5 deletions src/packages/Toaster.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<!-- Remove item from normal navigation flow, only available via hotkey -->
<section :aria-label="`${containerAriaLabel} ${hotkeyLabel}`" :tabIndex="-1">
<section :aria-label="`${containerAriaLabel} ${hotkeyLabel}`" :tabIndex="-1" aria-live="polite" aria-relevant="additions text" aria-atomic="false">
<template v-for="(pos, index) in possiblePositions" :key="pos">
<ol
ref="listRef"
Expand All @@ -12,6 +12,7 @@
:data-rich-colors="richColors"
:data-y-position="pos.split('-')[0]"
:data-x-position="pos.split('-')[1]"
:data-lifted="expanded && toasts.length > 1 && !expand"
:style="
{
'--front-toast-height': `${heights[0]?.height}px`,
Expand Down Expand Up @@ -272,7 +273,7 @@ function onPointerDown(event: PointerEvent) {

if (isNotDismissible) return
}
interacting.value = false
interacting.value = true
}

watchEffect((onInvalidate) => {
Expand Down Expand Up @@ -331,15 +332,30 @@ watch(

if (typeof window === 'undefined') return

window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', ({ matches }) => {
const darkMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');

try {
// Chrome & Firefox
darkMediaQuery.addEventListener('change', ({ matches }) => {
if (matches) {
actualTheme.value = 'dark'
} else {
actualTheme.value = 'light'
}
})
} catch(error) {
darkMediaQuery.addListener(({ matches }) => {
try {
if (matches) {
actualTheme.value = 'dark'
} else {
actualTheme.value = 'light'
}
} catch (e) {
console.error(e);
}
});
}
}
)

Expand Down
18 changes: 10 additions & 8 deletions src/packages/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import type {

let toastsCounter = 0

type titleT = (() => string | Component) | string | Component;

class Observer {
subscribers: Array<(toast: ExternalToast | ToastToDismiss) => void>
toasts: Array<ToastT | ToastToDismiss>
Expand Down Expand Up @@ -40,7 +42,7 @@ class Observer {

create = (
data: ExternalToast & {
message?: string | Component
message?: titleT
type?: ToastTypes
promise?: PromiseT
}
Expand Down Expand Up @@ -90,27 +92,27 @@ class Observer {
return id
}

message = (message: string | Component, data?: ExternalToast) => {
message = (message: titleT, data?: ExternalToast) => {
return this.create({ ...data, message, type: 'default' })
}

error = (message: string | Component, data?: ExternalToast) => {
error = (message: titleT, data?: ExternalToast) => {
return this.create({ ...data, type: 'error', message })
}

success = (message: string | Component, data?: ExternalToast) => {
success = (message: titleT, data?: ExternalToast) => {
return this.create({ ...data, type: 'success', message })
}

info = (message: string | Component, data?: ExternalToast) => {
info = (message: titleT, data?: ExternalToast) => {
return this.create({ ...data, type: 'info', message })
}

warning = (message: string | Component, data?: ExternalToast) => {
warning = (message: titleT, data?: ExternalToast) => {
return this.create({ ...data, type: 'warning', message })
}

loading = (message: string | Component, data?: ExternalToast) => {
loading = (message: titleT, data?: ExternalToast) => {
return this.create({ ...data, type: 'loading', message })
}

Expand Down Expand Up @@ -229,7 +231,7 @@ class Observer {
export const ToastState = new Observer()

// bind this to the toast function
function toastFunction(message: string | Component, data?: ExternalToast) {
function toastFunction(message: titleT, data?: ExternalToast) {
const id = data?.id || toastsCounter++

ToastState.create({
Expand Down
Loading