vue-toast-kit 1.0.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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1145 -0
  3. package/dist/composables/useToast.d.ts +28 -0
  4. package/dist/composables/useToastContext.d.ts +6 -0
  5. package/dist/composables/useToastState.d.ts +7 -0
  6. package/dist/core/GroupManager.d.ts +16 -0
  7. package/dist/core/ToastBuffer.d.ts +19 -0
  8. package/dist/core/ToastQueue.d.ts +55 -0
  9. package/dist/core/UndoTimer.d.ts +23 -0
  10. package/dist/core/types.d.ts +114 -0
  11. package/dist/index.d.ts +23 -0
  12. package/dist/module.d.ts +1 -0
  13. package/dist/nuxt/module.cjs +2 -0
  14. package/dist/nuxt/module.cjs.map +1 -0
  15. package/dist/nuxt/module.d.ts +1 -0
  16. package/dist/nuxt/module.js +34 -0
  17. package/dist/nuxt/module.js.map +1 -0
  18. package/dist/plugin.d.ts +6 -0
  19. package/dist/style.css +1 -0
  20. package/dist/testing.d.ts +14 -0
  21. package/dist/vue-toast-kit.cjs +2 -0
  22. package/dist/vue-toast-kit.cjs.map +1 -0
  23. package/dist/vue-toast-kit.d.ts +540 -0
  24. package/dist/vue-toast-kit.js +1000 -0
  25. package/dist/vue-toast-kit.js.map +1 -0
  26. package/package.json +89 -0
  27. package/src/components/Toast.vue +222 -0
  28. package/src/components/ToastActions.vue +34 -0
  29. package/src/components/ToastContainer.vue +257 -0
  30. package/src/components/ToastIcon.vue +53 -0
  31. package/src/components/ToastProgressBar.vue +18 -0
  32. package/src/composables/useToast.ts +152 -0
  33. package/src/composables/useToastContext.ts +63 -0
  34. package/src/composables/useToastState.ts +18 -0
  35. package/src/core/GroupManager.ts +105 -0
  36. package/src/core/ToastBuffer.ts +45 -0
  37. package/src/core/ToastQueue.ts +377 -0
  38. package/src/core/UndoTimer.ts +90 -0
  39. package/src/core/types.ts +142 -0
  40. package/src/env.d.ts +7 -0
  41. package/src/index.ts +51 -0
  42. package/src/nuxt/composables.ts +13 -0
  43. package/src/nuxt/module.ts +52 -0
  44. package/src/nuxt/plugin.ts +8 -0
  45. package/src/plugin.ts +18 -0
  46. package/src/styles/animations.css +106 -0
  47. package/src/styles/base.css +201 -0
  48. package/src/styles/themes/dark.css +30 -0
  49. package/src/styles/themes/light.css +30 -0
  50. package/src/styles/themes/system.css +32 -0
  51. package/src/styles/tokens.css +74 -0
  52. package/src/testing.ts +81 -0
@@ -0,0 +1,45 @@
1
+ import type { VNode } from 'vue'
2
+ import type { ToastOptions } from './types'
3
+
4
+ interface BufferedToast {
5
+ message: string | VNode
6
+ options: ToastOptions
7
+ }
8
+
9
+ export const isServer = typeof window === 'undefined'
10
+
11
+ export class ToastBuffer {
12
+ private buffer: BufferedToast[] = []
13
+ private flushed = false
14
+ private flushCallbacks: Array<(items: BufferedToast[]) => void> = []
15
+
16
+ push(message: string | VNode, options: ToastOptions): void {
17
+ if (this.flushed) return
18
+ this.buffer.push({ message, options })
19
+ }
20
+
21
+ onFlush(cb: (items: BufferedToast[]) => void): void {
22
+ this.flushCallbacks.push(cb)
23
+ }
24
+
25
+ flush(): void {
26
+ if (this.flushed) return
27
+ this.flushed = true
28
+ const items = [...this.buffer]
29
+ this.buffer = []
30
+ for (const cb of this.flushCallbacks) {
31
+ cb(items)
32
+ }
33
+ this.flushCallbacks = []
34
+ }
35
+
36
+ isFlushed(): boolean {
37
+ return this.flushed
38
+ }
39
+
40
+ get size(): number {
41
+ return this.buffer.length
42
+ }
43
+ }
44
+
45
+ export const globalBuffer = new ToastBuffer()
@@ -0,0 +1,377 @@
1
+ import { ref, reactive, shallowReactive, type VNode } from 'vue'
2
+ import { GroupManager } from './GroupManager'
3
+ import { UndoTimer } from './UndoTimer'
4
+ import { PRIORITY_ORDER, DEFAULT_OPTIONS, type ToastItem, type ToastOptions, type ToastPosition } from './types'
5
+
6
+ let idCounter = 0
7
+ function generateId(): string {
8
+ return `vtk-${Date.now()}-${++idCounter}`
9
+ }
10
+
11
+ const PERSIST_KEY = 'vtk-persist'
12
+
13
+ type EventListener<T extends unknown[]> = (...args: T) => void
14
+
15
+ export class ToastQueue {
16
+ active = shallowReactive<ToastItem[]>([])
17
+ pending = shallowReactive<ToastItem[]>([])
18
+
19
+ private maxVisible: number
20
+ private hiddenItems = reactive(new Set<string>())
21
+ private timers = new Map<string, UndoTimer>()
22
+ private groupManager: GroupManager
23
+
24
+ private rateLimit: number
25
+ private rateLimitWindowMs: number
26
+ private recentAddTimes: number[] = []
27
+
28
+ private persistStorage: boolean
29
+
30
+ private addListeners = new Set<EventListener<[ToastItem]>>()
31
+ private dismissListeners = new Set<EventListener<[string]>>()
32
+ private updateListeners = new Set<EventListener<[string, Partial<ToastOptions>]>>()
33
+
34
+ constructor(maxVisible = 5, options: { rateLimit?: number; rateLimitWindowMs?: number; persistStorage?: boolean } = {}) {
35
+ this.maxVisible = maxVisible
36
+ this.rateLimit = options.rateLimit ?? 0
37
+ this.rateLimitWindowMs = options.rateLimitWindowMs ?? 1000
38
+ this.persistStorage = options.persistStorage ?? false
39
+
40
+ this.groupManager = new GroupManager(
41
+ (ids) => [...this.active, ...this.pending].filter(t => ids.includes(t.id)),
42
+ (id) => this.hiddenItems.add(id),
43
+ (id) => this.hiddenItems.delete(id),
44
+ )
45
+
46
+ if (this.persistStorage) {
47
+ this.restoreFromStorage()
48
+ }
49
+ }
50
+
51
+ /** Subscribe to toast add events. Returns an unsubscribe function. */
52
+ onAdd(fn: EventListener<[ToastItem]>): () => void {
53
+ this.addListeners.add(fn)
54
+ return () => this.addListeners.delete(fn)
55
+ }
56
+
57
+ /** Subscribe to toast dismiss events. Returns an unsubscribe function. */
58
+ onDismiss(fn: EventListener<[string]>): () => void {
59
+ this.dismissListeners.add(fn)
60
+ return () => this.dismissListeners.delete(fn)
61
+ }
62
+
63
+ /** Subscribe to toast update events. Returns an unsubscribe function. */
64
+ onUpdate(fn: EventListener<[string, Partial<ToastOptions>]>): () => void {
65
+ this.updateListeners.add(fn)
66
+ return () => this.updateListeners.delete(fn)
67
+ }
68
+
69
+ private emit<T extends unknown[]>(set: Set<EventListener<T>>, ...args: T): void {
70
+ set.forEach(fn => fn(...args))
71
+ }
72
+
73
+ private isRateLimited(): boolean {
74
+ if (!this.rateLimit) return false
75
+ const now = Date.now()
76
+ this.recentAddTimes = this.recentAddTimes.filter(t => now - t < this.rateLimitWindowMs)
77
+ if (this.recentAddTimes.length >= this.rateLimit) return true
78
+ this.recentAddTimes.push(now)
79
+ return false
80
+ }
81
+
82
+ private restoreFromStorage(): void {
83
+ if (typeof localStorage === 'undefined') return
84
+ try {
85
+ const raw = localStorage.getItem(PERSIST_KEY)
86
+ if (!raw) return
87
+ const items: Array<{ id: string; message: string; options: ToastOptions }> = JSON.parse(raw)
88
+ for (const item of items) {
89
+ this.add(item.message, { ...item.options, id: item.id })
90
+ }
91
+ } catch {
92
+ localStorage.removeItem(PERSIST_KEY)
93
+ }
94
+ }
95
+
96
+ private saveToStorage(item: ToastItem): void {
97
+ if (typeof localStorage === 'undefined') return
98
+ try {
99
+ const raw = localStorage.getItem(PERSIST_KEY)
100
+ const items: Array<{ id: string; message: string; options: ToastOptions }> = raw ? JSON.parse(raw) : []
101
+ if (!items.find(i => i.id === item.id)) {
102
+ items.push({ id: item.id, message: item.message as string, options: item.options })
103
+ localStorage.setItem(PERSIST_KEY, JSON.stringify(items))
104
+ }
105
+ } catch { /* noop */ }
106
+ }
107
+
108
+ private removeFromStorage(id: string): void {
109
+ if (typeof localStorage === 'undefined') return
110
+ try {
111
+ const raw = localStorage.getItem(PERSIST_KEY)
112
+ if (!raw) return
113
+ const items: Array<{ id: string }> = JSON.parse(raw)
114
+ const filtered = items.filter(i => i.id !== id)
115
+ if (filtered.length) {
116
+ localStorage.setItem(PERSIST_KEY, JSON.stringify(filtered))
117
+ } else {
118
+ localStorage.removeItem(PERSIST_KEY)
119
+ }
120
+ } catch { /* noop */ }
121
+ }
122
+
123
+ get visibleActive(): ToastItem[] {
124
+ return this.active.filter(t => !this.hiddenItems.has(t.id))
125
+ }
126
+
127
+ isHidden(id: string): boolean {
128
+ return this.hiddenItems.has(id)
129
+ }
130
+
131
+ add(message: string | VNode, options: ToastOptions = {}): string {
132
+ if (this.isRateLimited()) return ''
133
+
134
+ const id = options.id ?? generateId()
135
+
136
+ const existing = this.active.find(t => t.id === id)
137
+ if (existing) {
138
+ this.mergeOptions(existing, options)
139
+ return id
140
+ }
141
+
142
+ const item = this.createItem(id, message, options)
143
+
144
+ if (this.visibleActive.length < this.maxVisible) {
145
+ this.active.push(item)
146
+ this.startTimer(item)
147
+ if (options.groupKey) this.groupManager.add(id, options.groupKey)
148
+ if (this.persistStorage && item.options.persist) this.saveToStorage(item)
149
+ this.emit(this.addListeners, item)
150
+ return id
151
+ }
152
+
153
+ const priority = options.priority ?? 'normal'
154
+ if (PRIORITY_ORDER[priority] > PRIORITY_ORDER['normal']) {
155
+ const lowestIdx = this.findLowestPriorityIndex()
156
+ if (lowestIdx !== -1) {
157
+ const evicted = this.active[lowestIdx]
158
+ if (PRIORITY_ORDER[priority] > PRIORITY_ORDER[evicted.options.priority]) {
159
+ this.stopTimer(evicted.id)
160
+ this.active.splice(lowestIdx, 1)
161
+ this.pending.unshift(evicted)
162
+ this.active.push(item)
163
+ this.startTimer(item)
164
+ if (options.groupKey) this.groupManager.add(id, options.groupKey)
165
+ if (this.persistStorage && item.options.persist) this.saveToStorage(item)
166
+ this.emit(this.addListeners, item)
167
+ return id
168
+ }
169
+ }
170
+ }
171
+
172
+ this.pending.push(item)
173
+ this.sortPending()
174
+ if (options.groupKey) this.groupManager.add(id, options.groupKey)
175
+ if (this.persistStorage && item.options.persist) this.saveToStorage(item)
176
+ this.emit(this.addListeners, item)
177
+ return id
178
+ }
179
+
180
+ remove(id: string): void {
181
+ this.stopTimer(id)
182
+
183
+ const activeIdx = this.active.findIndex(t => t.id === id)
184
+ if (activeIdx !== -1) {
185
+ const item = this.active[activeIdx]
186
+ this.active.splice(activeIdx, 1)
187
+ this.hiddenItems.delete(id)
188
+
189
+ if (item.options.groupKey) this.groupManager.remove(id, item.options.groupKey)
190
+ if (this.persistStorage) this.removeFromStorage(id)
191
+
192
+ if (this.pending.length > 0) {
193
+ const next = this.pending.shift()!
194
+ this.active.push(next)
195
+ this.startTimer(next)
196
+ }
197
+ return
198
+ }
199
+
200
+ const pendingIdx = this.pending.findIndex(t => t.id === id)
201
+ if (pendingIdx !== -1) {
202
+ const item = this.pending[pendingIdx]
203
+ this.pending.splice(pendingIdx, 1)
204
+ this.hiddenItems.delete(id)
205
+ if (item.options.groupKey) this.groupManager.remove(id, item.options.groupKey)
206
+ if (this.persistStorage) this.removeFromStorage(id)
207
+ }
208
+ }
209
+
210
+ update(id: string, partial: Partial<ToastOptions>): void {
211
+ const item = [...this.active, ...this.pending].find(t => t.id === id)
212
+ if (!item) return
213
+ this.mergeOptions(item, partial)
214
+ this.emit(this.updateListeners, id, partial)
215
+ }
216
+
217
+ dismiss(id?: string): void {
218
+ if (id === undefined) {
219
+ const ids = [...this.active, ...this.pending].map(t => t.id)
220
+ ids.forEach(i => this.remove(i))
221
+ return
222
+ }
223
+ const item = [...this.active, ...this.pending].find(t => t.id === id)
224
+ if (item) {
225
+ item.options.onClose?.()
226
+ this.remove(id)
227
+ this.emit(this.dismissListeners, id)
228
+ }
229
+ }
230
+
231
+ dismissAll(position?: ToastPosition): void {
232
+ const targets = [...this.active, ...this.pending].filter(
233
+ t => !position || t.options.position === position,
234
+ )
235
+ targets.forEach(t => {
236
+ t.options.onClose?.()
237
+ this.remove(t.id)
238
+ this.emit(this.dismissListeners, t.id)
239
+ })
240
+ }
241
+
242
+ isActive(id: string): boolean {
243
+ return this.active.some(t => t.id === id)
244
+ }
245
+
246
+ pauseAll(): void {
247
+ this.active.forEach(t => t.pause())
248
+ }
249
+
250
+ resumeAll(): void {
251
+ this.active.forEach(t => t.resume())
252
+ }
253
+
254
+ setMaxVisible(n: number): void {
255
+ this.maxVisible = n
256
+ while (this.visibleActive.length < n && this.pending.length > 0) {
257
+ const next = this.pending.shift()!
258
+ this.active.push(next)
259
+ this.startTimer(next)
260
+ }
261
+ }
262
+
263
+ toggleGroupExpand(groupKey: string): void {
264
+ this.groupManager.toggleExpand(groupKey)
265
+ }
266
+
267
+ isGroupExpanded(groupKey: string): boolean {
268
+ return this.groupManager.isExpanded(groupKey)
269
+ }
270
+
271
+ destroy(): void {
272
+ this.timers.forEach(t => t.destroy())
273
+ this.timers.clear()
274
+ this.groupManager.clear()
275
+ this.active.splice(0)
276
+ this.pending.splice(0)
277
+ this.hiddenItems.clear()
278
+ this.addListeners.clear()
279
+ this.dismissListeners.clear()
280
+ this.updateListeners.clear()
281
+ }
282
+
283
+ private createItem(id: string, message: string | VNode, options: ToastOptions): ToastItem {
284
+ const remaining = ref(1)
285
+ const isPaused = ref(false)
286
+ const groupCount = ref(1)
287
+ const isGrouped = ref(false)
288
+
289
+ const mergedOptions = {
290
+ ...DEFAULT_OPTIONS,
291
+ ...options,
292
+ type: options.type ?? 'info',
293
+ priority: options.priority ?? 'normal',
294
+ duration: options.duration ?? 4000,
295
+ closable: options.closable ?? true,
296
+ pauseOnHover: options.pauseOnHover ?? true,
297
+ pauseOnFocusLoss: options.pauseOnFocusLoss ?? true,
298
+ swipeToDismiss: options.swipeToDismiss ?? true,
299
+ persist: options.persist ?? false,
300
+ }
301
+
302
+ const item: ToastItem = {
303
+ id,
304
+ message,
305
+ options: mergedOptions as ToastItem['options'],
306
+ createdAt: Date.now(),
307
+ remaining,
308
+ isPaused,
309
+ groupCount,
310
+ isGrouped,
311
+ pause: () => {
312
+ isPaused.value = true
313
+ this.timers.get(id)?.pause()
314
+ },
315
+ resume: () => {
316
+ isPaused.value = false
317
+ this.timers.get(id)?.resume()
318
+ },
319
+ dismiss: () => this.dismiss(id),
320
+ update: (opts) => this.update(id, opts),
321
+ }
322
+
323
+ return item
324
+ }
325
+
326
+ private startTimer(item: ToastItem): void {
327
+ const duration = item.options.undo?.duration ?? item.options.duration
328
+ if (!duration) return
329
+
330
+ const timer = new UndoTimer(
331
+ duration,
332
+ () => {
333
+ item.options.onAutoClose?.()
334
+ this.remove(item.id)
335
+ },
336
+ (r) => {
337
+ item.remaining.value = r
338
+ },
339
+ )
340
+ this.timers.set(item.id, timer)
341
+ timer.start()
342
+ }
343
+
344
+ private stopTimer(id: string): void {
345
+ this.timers.get(id)?.destroy()
346
+ this.timers.delete(id)
347
+ }
348
+
349
+ private mergeOptions(item: ToastItem, partial: Partial<ToastOptions>): void {
350
+ Object.assign(item.options, partial)
351
+ if ('message' in partial) {
352
+ item.message = (partial as Record<string, unknown>).message as string | VNode
353
+ }
354
+ }
355
+
356
+ private findLowestPriorityIndex(): number {
357
+ let lowestIdx = -1
358
+ let lowestPriority = 999
359
+ this.active.forEach((t, i) => {
360
+ const p = PRIORITY_ORDER[t.options.priority]
361
+ if (p < lowestPriority) {
362
+ lowestPriority = p
363
+ lowestIdx = i
364
+ }
365
+ })
366
+ return lowestIdx
367
+ }
368
+
369
+ private sortPending(): void {
370
+ this.pending.sort((a, b) => {
371
+ const pa = PRIORITY_ORDER[a.options.priority]
372
+ const pb = PRIORITY_ORDER[b.options.priority]
373
+ if (pb !== pa) return pb - pa
374
+ return a.createdAt - b.createdAt
375
+ })
376
+ }
377
+ }
@@ -0,0 +1,90 @@
1
+ export class UndoTimer {
2
+ private timerId: ReturnType<typeof setTimeout> | null = null
3
+ private tickId: ReturnType<typeof setInterval> | null = null
4
+ private elapsed = 0
5
+ private startTime = 0
6
+ private _remaining = 1
7
+ private _isPaused = false
8
+
9
+ private static readonly TICK_INTERVAL = 50
10
+
11
+ readonly duration: number
12
+ readonly onExpire: () => void
13
+ readonly onTick?: (remaining: number) => void
14
+
15
+ constructor(duration: number, onExpire: () => void, onTick?: (remaining: number) => void) {
16
+ this.duration = duration
17
+ this.onExpire = onExpire
18
+ this.onTick = onTick
19
+ }
20
+
21
+ get remaining(): number {
22
+ return this._remaining
23
+ }
24
+
25
+ get isPaused(): boolean {
26
+ return this._isPaused
27
+ }
28
+
29
+ start(): void {
30
+ this.elapsed = 0
31
+ this._remaining = 1
32
+ this.startTime = Date.now()
33
+ this.scheduleExpiry(this.duration)
34
+ if (this.onTick) this.startTick()
35
+ }
36
+
37
+ pause(): void {
38
+ if (this._isPaused) return
39
+ this._isPaused = true
40
+ this.elapsed += Date.now() - this.startTime
41
+ this.clearExpiry()
42
+ this.clearTick()
43
+ }
44
+
45
+ resume(): void {
46
+ if (!this._isPaused) return
47
+ this._isPaused = false
48
+ const remainingMs = this.duration - this.elapsed
49
+ this.startTime = Date.now()
50
+ this.scheduleExpiry(remainingMs)
51
+ if (this.onTick) this.startTick()
52
+ }
53
+
54
+ destroy(): void {
55
+ this.clearExpiry()
56
+ this.clearTick()
57
+ }
58
+
59
+ private scheduleExpiry(delay: number): void {
60
+ this.timerId = setTimeout(() => {
61
+ this.clearTick()
62
+ this._remaining = 0
63
+ this.onTick?.(0)
64
+ this.timerId = null
65
+ this.onExpire()
66
+ }, delay)
67
+ }
68
+
69
+ private clearExpiry(): void {
70
+ if (this.timerId !== null) {
71
+ clearTimeout(this.timerId)
72
+ this.timerId = null
73
+ }
74
+ }
75
+
76
+ private startTick(): void {
77
+ this.tickId = setInterval(() => {
78
+ const totalElapsed = this.elapsed + (Date.now() - this.startTime)
79
+ this._remaining = Math.max(0, 1 - totalElapsed / this.duration)
80
+ this.onTick?.(this._remaining)
81
+ }, UndoTimer.TICK_INTERVAL)
82
+ }
83
+
84
+ private clearTick(): void {
85
+ if (this.tickId !== null) {
86
+ clearInterval(this.tickId)
87
+ this.tickId = null
88
+ }
89
+ }
90
+ }
@@ -0,0 +1,142 @@
1
+ import type { Component, VNode, Ref } from 'vue'
2
+
3
+ export type ToastType = 'info' | 'success' | 'warning' | 'error' | 'loading' | 'custom'
4
+ export type ToastPriority = 'critical' | 'high' | 'normal' | 'low'
5
+ export type ToastPosition =
6
+ | 'top-left' | 'top-center' | 'top-right'
7
+ | 'bottom-left' | 'bottom-center' | 'bottom-right'
8
+
9
+ export interface ToastAction {
10
+ label: string
11
+ onClick: () => void
12
+ }
13
+
14
+ export interface ToastUndo {
15
+ label?: string
16
+ onUndo: () => void | Promise<void>
17
+ duration?: number
18
+ }
19
+
20
+ export interface ToastDesignTokens {
21
+ colorBg?: string
22
+ colorText?: string
23
+ colorBorder?: string
24
+ colorSuccess?: string
25
+ colorError?: string
26
+ colorWarning?: string
27
+ colorInfo?: string
28
+ colorLoading?: string
29
+ fontFamily?: string
30
+ fontSize?: string
31
+ fontWeight?: string
32
+ lineHeight?: string
33
+ borderRadius?: string
34
+ borderWidth?: string
35
+ shadow?: string
36
+ paddingX?: string
37
+ paddingY?: string
38
+ iconSize?: string
39
+ progressHeight?: string
40
+ maxWidth?: string
41
+ minWidth?: string
42
+ transitionDuration?: string
43
+ transitionEasing?: string
44
+ zIndex?: string
45
+ }
46
+
47
+ export interface ToastOptions {
48
+ id?: string
49
+ type?: ToastType
50
+ priority?: ToastPriority
51
+ duration?: number
52
+ position?: ToastPosition
53
+ closable?: boolean
54
+ groupKey?: string
55
+ icon?: Component | string | false
56
+ action?: ToastAction
57
+ undo?: ToastUndo
58
+ onClose?: () => void
59
+ onAutoClose?: () => void
60
+ pauseOnHover?: boolean
61
+ pauseOnFocusLoss?: boolean
62
+ swipeToDismiss?: boolean
63
+ persist?: boolean
64
+ component?: Component
65
+ componentProps?: Record<string, unknown>
66
+ ariaLive?: 'assertive' | 'polite'
67
+ theme?: 'light' | 'dark' | 'system' | ToastDesignTokens
68
+ }
69
+
70
+ export interface ToastItem {
71
+ id: string
72
+ message: string | VNode
73
+ options: Required<Omit<ToastOptions, 'component' | 'componentProps' | 'icon' | 'action' | 'undo' | 'theme' | 'position'>> & {
74
+ position?: ToastPosition
75
+ component?: Component
76
+ componentProps?: Record<string, unknown>
77
+ icon?: Component | string | false
78
+ action?: ToastAction
79
+ undo?: ToastUndo
80
+ theme?: 'light' | 'dark' | 'system' | ToastDesignTokens
81
+ }
82
+ createdAt: number
83
+ remaining: Ref<number>
84
+ isPaused: Ref<boolean>
85
+ groupCount: Ref<number>
86
+ isGrouped: Ref<boolean>
87
+ pause(): void
88
+ resume(): void
89
+ dismiss(): void
90
+ update(opts: Partial<ToastOptions>): void
91
+ }
92
+
93
+ export interface PromiseToastMessages<T = unknown> {
94
+ loading: string
95
+ success: string | ((data: T) => string)
96
+ error: string | ((err: unknown) => string)
97
+ }
98
+
99
+ export interface ToastContext {
100
+ queue: import('./ToastQueue').ToastQueue
101
+ addToast(message: string | VNode, options?: ToastOptions): string
102
+ dismiss(id?: string): void
103
+ update(id: string, options: Partial<ToastOptions>): void
104
+ isActive(id: string): boolean
105
+ }
106
+
107
+ export interface GlobalToastOptions {
108
+ position?: ToastPosition
109
+ maxVisible?: number
110
+ duration?: number
111
+ theme?: 'light' | 'dark' | 'system'
112
+ ignoreSSR?: boolean
113
+ pauseOnHover?: boolean
114
+ pauseOnFocusLoss?: boolean
115
+ closable?: boolean
116
+ /** Max toasts added within rateLimitWindowMs before extras are dropped. */
117
+ rateLimit?: number
118
+ /** Window in ms for rateLimit (default: 1000). */
119
+ rateLimitWindowMs?: number
120
+ /** Enable automatic localStorage persist/restore for toasts with persist:true. */
121
+ persistStorage?: boolean
122
+ }
123
+
124
+ export const PRIORITY_ORDER: Record<ToastPriority, number> = {
125
+ critical: 3,
126
+ high: 2,
127
+ normal: 1,
128
+ low: 0,
129
+ }
130
+
131
+ export const DEFAULT_OPTIONS: Required<Omit<ToastOptions, 'id' | 'component' | 'componentProps' | 'icon' | 'action' | 'undo' | 'groupKey' | 'theme' | 'onClose' | 'onAutoClose' | 'ariaLive' | 'position'>> = {
132
+ type: 'info',
133
+ priority: 'normal',
134
+ duration: 4000,
135
+ closable: true,
136
+ pauseOnHover: true,
137
+ pauseOnFocusLoss: true,
138
+ swipeToDismiss: true,
139
+ persist: false,
140
+ }
141
+
142
+ export const TOAST_CONTEXT_KEY = Symbol('vue-toast-kit-context')
package/src/env.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ declare module '*.vue' {
4
+ import type { DefineComponent } from 'vue'
5
+ const component: DefineComponent
6
+ export default component
7
+ }
package/src/index.ts ADDED
@@ -0,0 +1,51 @@
1
+ // Styles
2
+ import './styles/tokens.css'
3
+ import './styles/base.css'
4
+ import './styles/animations.css'
5
+ import './styles/themes/light.css'
6
+ import './styles/themes/dark.css'
7
+ import './styles/themes/system.css'
8
+
9
+ // Core types
10
+ export type {
11
+ ToastType,
12
+ ToastPriority,
13
+ ToastPosition,
14
+ ToastOptions,
15
+ ToastItem,
16
+ ToastAction,
17
+ ToastUndo,
18
+ PromiseToastMessages,
19
+ ToastContext,
20
+ GlobalToastOptions,
21
+ ToastDesignTokens,
22
+ } from './core/types'
23
+
24
+ export { TOAST_CONTEXT_KEY, PRIORITY_ORDER, DEFAULT_OPTIONS } from './core/types'
25
+
26
+ // Core classes (for advanced use)
27
+ export { ToastQueue } from './core/ToastQueue'
28
+ export { UndoTimer } from './core/UndoTimer'
29
+ export { GroupManager } from './core/GroupManager'
30
+ export { globalBuffer, isServer } from './core/ToastBuffer'
31
+
32
+ // Composables
33
+ export { useToast, toast } from './composables/useToast'
34
+ export type { ToastApi } from './composables/useToast'
35
+ export { useToastState } from './composables/useToastState'
36
+ export {
37
+ useToastContext,
38
+ createToastContext,
39
+ getOrCreateGlobalContext,
40
+ } from './composables/useToastContext'
41
+
42
+ // Components
43
+ export { default as ToastContainer } from './components/ToastContainer.vue'
44
+ export { default as Toast } from './components/Toast.vue'
45
+ export { default as ToastIcon } from './components/ToastIcon.vue'
46
+ export { default as ToastProgressBar } from './components/ToastProgressBar.vue'
47
+ export { default as ToastActions } from './components/ToastActions.vue'
48
+
49
+ // Plugin
50
+ export { VueToastPlugin } from './plugin'
51
+ export type { VueToastPluginOptions } from './plugin'