@soft-toast/vue 1.0.2 → 1.1.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.
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, beforeEach } from 'bun:test'
2
- import { useToast, toast } from './useToast'
2
+ import { useSoftToast, softToast } from './useSoftToast'
3
3
  import { toastStore } from '../stores/toastStore'
4
4
 
5
5
  // Reset store state before each test
@@ -7,11 +7,11 @@ beforeEach(() => {
7
7
  toastStore.clearAll()
8
8
  })
9
9
 
10
- // ─── useToast() composable ────────────────────────────────────────────────────
10
+ // ─── useSoftToast() composable ────────────────────────────────────────────────────
11
11
 
12
- describe('useToast()', () => {
12
+ describe('useSoftToast()', () => {
13
13
  it('returns an object with all API methods', () => {
14
- const t = useToast()
14
+ const t = useSoftToast()
15
15
  expect(typeof t.default).toBe('function')
16
16
  expect(typeof t.success).toBe('function')
17
17
  expect(typeof t.error).toBe('function')
@@ -27,7 +27,7 @@ describe('useToast()', () => {
27
27
  })
28
28
 
29
29
  it('default() adds a toast with type "default"', () => {
30
- const t = useToast()
30
+ const t = useSoftToast()
31
31
  const id = t.default('Hello')
32
32
  const toasts = toastStore.toasts.value
33
33
  expect(toasts.length).toBe(1)
@@ -37,32 +37,32 @@ describe('useToast()', () => {
37
37
  })
38
38
 
39
39
  it('success() adds a toast with type "success"', () => {
40
- const t = useToast()
40
+ const t = useSoftToast()
41
41
  t.success('Done!')
42
42
  expect(toastStore.toasts.value[0].type).toBe('success')
43
43
  expect(toastStore.toasts.value[0].title).toBe('Done!')
44
44
  })
45
45
 
46
46
  it('error() adds a toast with type "error"', () => {
47
- const t = useToast()
47
+ const t = useSoftToast()
48
48
  t.error('Oops!')
49
49
  expect(toastStore.toasts.value[0].type).toBe('error')
50
50
  })
51
51
 
52
52
  it('warning() adds a toast with type "warning"', () => {
53
- const t = useToast()
53
+ const t = useSoftToast()
54
54
  t.warning('Watch out!')
55
55
  expect(toastStore.toasts.value[0].type).toBe('warning')
56
56
  })
57
57
 
58
58
  it('info() adds a toast with type "info"', () => {
59
- const t = useToast()
59
+ const t = useSoftToast()
60
60
  t.info('FYI')
61
61
  expect(toastStore.toasts.value[0].type).toBe('info')
62
62
  })
63
63
 
64
64
  it('custom() passes options through directly', () => {
65
- const t = useToast()
65
+ const t = useSoftToast()
66
66
  t.custom({ type: 'success', title: 'Custom', description: 'My desc', duration: 9999 })
67
67
  const added = toastStore.toasts.value[0]
68
68
  expect(added.title).toBe('Custom')
@@ -71,7 +71,7 @@ describe('useToast()', () => {
71
71
  })
72
72
 
73
73
  it('update() changes an existing toast', () => {
74
- const t = useToast()
74
+ const t = useSoftToast()
75
75
  const id = t.default('Before')
76
76
  t.update(id, { title: 'After', type: 'success' })
77
77
  const updated = toastStore.toasts.value.find(x => x.id === id)
@@ -80,7 +80,7 @@ describe('useToast()', () => {
80
80
  })
81
81
 
82
82
  it('dismiss() marks toast as isLeaving', () => {
83
- const t = useToast()
83
+ const t = useSoftToast()
84
84
  const id = t.default('Bye')
85
85
  t.dismiss(id)
86
86
  const toast = toastStore.toasts.value.find(x => x.id === id)
@@ -88,7 +88,7 @@ describe('useToast()', () => {
88
88
  })
89
89
 
90
90
  it('pause() and resume() toggle isPaused', () => {
91
- const t = useToast()
91
+ const t = useSoftToast()
92
92
  const id = t.default('Pausing')
93
93
  t.pause(id)
94
94
  expect(toastStore.toasts.value[0].isPaused).toBe(true)
@@ -97,7 +97,7 @@ describe('useToast()', () => {
97
97
  })
98
98
 
99
99
  it('forward options like duration, description, position', () => {
100
- const t = useToast()
100
+ const t = useSoftToast()
101
101
  t.success('With opts', {
102
102
  description: 'More info',
103
103
  duration: 1234,
@@ -110,75 +110,75 @@ describe('useToast()', () => {
110
110
  })
111
111
  })
112
112
 
113
- // ─── toast (static object) ────────────────────────────────────────────────────
113
+ // ─── softToast (static object) ────────────────────────────────────────────────
114
114
 
115
- describe('toast (static API)', () => {
116
- it('toast.success() adds a success toast', () => {
117
- toast.success('Static success')
115
+ describe('softToast (static API)', () => {
116
+ it('softToast.success() adds a success toast', () => {
117
+ softToast.success('Static success')
118
118
  expect(toastStore.toasts.value[0].type).toBe('success')
119
119
  expect(toastStore.toasts.value[0].title).toBe('Static success')
120
120
  })
121
121
 
122
- it('toast.error() adds an error toast', () => {
123
- toast.error('Static error')
122
+ it('softToast.error() adds an error toast', () => {
123
+ softToast.error('Static error')
124
124
  expect(toastStore.toasts.value[0].type).toBe('error')
125
125
  })
126
126
 
127
- it('toast.warning() adds a warning toast', () => {
128
- toast.warning('Careful')
127
+ it('softToast.warning() adds a warning toast', () => {
128
+ softToast.warning('Careful')
129
129
  expect(toastStore.toasts.value[0].type).toBe('warning')
130
130
  })
131
131
 
132
- it('toast.info() adds an info toast', () => {
133
- toast.info('Just so you know')
132
+ it('softToast.info() adds an info toast', () => {
133
+ softToast.info('Just so you know')
134
134
  expect(toastStore.toasts.value[0].type).toBe('info')
135
135
  })
136
136
 
137
- it('toast.default() adds a default toast', () => {
138
- toast.default('Hello world')
137
+ it('softToast.default() adds a default toast', () => {
138
+ softToast.default('Hello world')
139
139
  expect(toastStore.toasts.value[0].type).toBe('default')
140
140
  })
141
141
 
142
- it('toast.custom() adds toast with arbitrary options', () => {
143
- toast.custom({ title: 'Custom static', type: 'info', id: 'static-1' })
142
+ it('softToast.custom() adds toast with arbitrary options', () => {
143
+ softToast.custom({ title: 'Custom static', type: 'info', id: 'static-1' })
144
144
  expect(toastStore.toasts.value[0].id).toBe('static-1')
145
145
  expect(toastStore.toasts.value[0].title).toBe('Custom static')
146
146
  })
147
147
 
148
- it('toast.update() modifies a toast', () => {
149
- const id = toast.default('Initial')
150
- toast.update(id, { title: 'Updated' })
148
+ it('softToast.update() modifies a toast', () => {
149
+ const id = softToast.default('Initial')
150
+ softToast.update(id, { title: 'Updated' })
151
151
  expect(toastStore.toasts.value.find(x => x.id === id)?.title).toBe('Updated')
152
152
  })
153
153
 
154
- it('toast.pause() and toast.resume() work', () => {
155
- const id = toast.info('Pausable')
156
- toast.pause(id)
154
+ it('softToast.pause() and softToast.resume() work', () => {
155
+ const id = softToast.info('Pausable')
156
+ softToast.pause(id)
157
157
  expect(toastStore.toasts.value[0].isPaused).toBe(true)
158
- toast.resume(id)
158
+ softToast.resume(id)
159
159
  expect(toastStore.toasts.value[0].isPaused).toBe(false)
160
160
  })
161
161
 
162
- it('toast.dismiss() marks toast as leaving', () => {
163
- const id = toast.success('Going away')
164
- toast.dismiss(id)
162
+ it('softToast.dismiss() marks toast as leaving', () => {
163
+ const id = softToast.success('Going away')
164
+ softToast.dismiss(id)
165
165
  expect(toastStore.toasts.value.find(x => x.id === id)?.isLeaving).toBe(true)
166
166
  })
167
167
 
168
168
  it('multiple toasts are ordered newest-first', () => {
169
- toast.default('First')
170
- toast.success('Second')
171
- toast.error('Third')
169
+ softToast.default('First')
170
+ softToast.success('Second')
171
+ softToast.error('Third')
172
172
  const titles = toastStore.toasts.value.map(t => t.title)
173
173
  expect(titles).toEqual(['Third', 'Second', 'First'])
174
174
  })
175
175
  })
176
176
 
177
- // ─── toast.promise() ─────────────────────────────────────────────────────────
177
+ // ─── softToast.promise() ─────────────────────────────────────────────────────────
178
178
 
179
- describe('toast.promise()', () => {
179
+ describe('softToast.promise()', () => {
180
180
  it('resolves: updates toast to success', async () => {
181
- const id = await toast.promise(
181
+ const id = await softToast.promise(
182
182
  Promise.resolve('ok'),
183
183
  { loading: 'Loading…', success: 'Done!', error: 'Failed' }
184
184
  )
@@ -191,7 +191,7 @@ describe('toast.promise()', () => {
191
191
  it('rejects: updates toast to error and re-throws', async () => {
192
192
  let caught: unknown
193
193
  try {
194
- await toast.promise(
194
+ await softToast.promise(
195
195
  Promise.reject(new Error('boom')),
196
196
  { loading: 'Loading…', success: 'Done!', error: 'Something failed' }
197
197
  )
@@ -206,25 +206,25 @@ describe('toast.promise()', () => {
206
206
 
207
207
  // ─── Flash & sound API surface ────────────────────────────────────────────────
208
208
 
209
- describe('useToast() — flash API methods', () => {
209
+ describe('useSoftToast() — flash API methods', () => {
210
210
  it('returns flash, showFlashes, hasFlashes methods', () => {
211
- const t = useToast()
211
+ const t = useSoftToast()
212
212
  expect(typeof t.flash).toBe('function')
213
213
  expect(typeof t.showFlashes).toBe('function')
214
214
  expect(typeof t.hasFlashes).toBe('function')
215
215
  })
216
216
  })
217
217
 
218
- describe('toast (static) — flash API methods', () => {
219
- it('toast.flash is a function', () => {
220
- expect(typeof toast.flash).toBe('function')
218
+ describe('softToast (static) — flash API methods', () => {
219
+ it('softToast.flash is a function', () => {
220
+ expect(typeof softToast.flash).toBe('function')
221
221
  })
222
222
 
223
- it('toast.showFlashes is a function', () => {
224
- expect(typeof toast.showFlashes).toBe('function')
223
+ it('softToast.showFlashes is a function', () => {
224
+ expect(typeof softToast.showFlashes).toBe('function')
225
225
  })
226
226
 
227
- it('toast.hasFlashes is a function', () => {
228
- expect(typeof toast.hasFlashes).toBe('function')
227
+ it('softToast.hasFlashes is a function', () => {
228
+ expect(typeof softToast.hasFlashes).toBe('function')
229
229
  })
230
230
  })
@@ -4,7 +4,7 @@ import { queueFlash, consumeFlashes, hasPendingFlashes } from './useFlash'
4
4
 
5
5
  // ─── Composable API ───────────────────────────────────────────────────────────
6
6
 
7
- export const useToast = () => ({
7
+ export const useSoftToast = () => ({
8
8
  default: (title: string, options?: Omit<ToastOptions, 'type' | 'title'>) =>
9
9
  toastStore.add({ ...options, type: 'default', title }),
10
10
 
@@ -25,7 +25,7 @@ export const useToast = () => ({
25
25
 
26
26
  promise: <T>(
27
27
  promiseFn: Promise<T>,
28
- messages: ToastPromiseMessages,
28
+ messages: ToastPromiseMessages<T>,
29
29
  options?: Omit<ToastOptions, 'type' | 'promise' | 'promiseMessages'>
30
30
  ): Promise<T> => toastStore.promise(promiseFn, messages, options),
31
31
 
@@ -44,7 +44,7 @@ export const useToast = () => ({
44
44
  * Perfect for the "submit → redirect → show success" pattern.
45
45
  *
46
46
  * @example
47
- * const { flash } = useToast()
47
+ * const { flash } = useSoftToast()
48
48
  * await api.save()
49
49
  * flash('Saved!', { type: 'success' })
50
50
  * router.push('/dashboard')
@@ -64,7 +64,7 @@ export const useToast = () => ({
64
64
 
65
65
  // ─── Singleton API (usable outside components) ────────────────────────────────
66
66
 
67
- export const toast = {
67
+ export const softToast = {
68
68
  default: (title: string, options?: Omit<ToastOptions, 'type' | 'title'>) =>
69
69
  toastStore.add({ ...options, type: 'default', title }),
70
70
  success: (title: string, options?: Omit<ToastOptions, 'type' | 'title'>) =>
@@ -79,7 +79,7 @@ export const toast = {
79
79
  toastStore.loading(title, options),
80
80
  promise: <T>(
81
81
  promiseFn: Promise<T>,
82
- messages: ToastPromiseMessages,
82
+ messages: ToastPromiseMessages<T>,
83
83
  options?: Omit<ToastOptions, 'type' | 'promise' | 'promiseMessages'>
84
84
  ): Promise<T> => toastStore.promise(promiseFn, messages, options),
85
85
  custom: (options: ToastOptions) => toastStore.add(options),
@@ -4,6 +4,23 @@ import { describe, it, expect } from 'bun:test'
4
4
  // This acts as a "contract test" — if something is accidentally removed, CI will catch it.
5
5
 
6
6
  describe('Package exports', () => {
7
+ it('exports the expected public API', async () => {
8
+ const api = await import('./index')
9
+ expect(Object.keys(api).sort()).toEqual([
10
+ 'SoftToastPlugin',
11
+ 'ToastContainer',
12
+ 'ToastItem',
13
+ 'consumeFlashes',
14
+ 'getToastOptions',
15
+ 'hasPendingFlashes',
16
+ 'queueFlash',
17
+ 'softToast',
18
+ 'toastStore',
19
+ 'useFlash',
20
+ 'useSoftToast',
21
+ ])
22
+ })
23
+
7
24
  it('exports SoftToastPlugin with install()', async () => {
8
25
  const { SoftToastPlugin } = await import('./index')
9
26
  expect(SoftToastPlugin).toBeDefined()
@@ -20,33 +37,33 @@ describe('Package exports', () => {
20
37
  expect(opts).toHaveProperty('theme')
21
38
  })
22
39
 
23
- it('exports useToast()', async () => {
24
- const { useToast } = await import('./index')
25
- expect(typeof useToast).toBe('function')
26
- const api = useToast()
40
+ it('exports useSoftToast()', async () => {
41
+ const { useSoftToast } = await import('./index')
42
+ expect(typeof useSoftToast).toBe('function')
43
+ const api = useSoftToast()
27
44
  expect(typeof api.success).toBe('function')
28
45
  expect(typeof api.error).toBe('function')
29
46
  })
30
47
 
31
- it('exports toast object', async () => {
32
- const { toast } = await import('./index')
33
- expect(toast).toBeDefined()
34
- expect(typeof toast.success).toBe('function')
35
- expect(typeof toast.error).toBe('function')
36
- expect(typeof toast.warning).toBe('function')
37
- expect(typeof toast.info).toBe('function')
38
- expect(typeof toast.default).toBe('function')
39
- expect(typeof toast.promise).toBe('function')
40
- expect(typeof toast.custom).toBe('function')
41
- expect(typeof toast.update).toBe('function')
42
- expect(typeof toast.dismiss).toBe('function')
43
- expect(typeof toast.dismissAll).toBe('function')
44
- expect(typeof toast.pause).toBe('function')
45
- expect(typeof toast.resume).toBe('function')
48
+ it('exports softToast object', async () => {
49
+ const { softToast } = await import('./index')
50
+ expect(softToast).toBeDefined()
51
+ expect(typeof softToast.success).toBe('function')
52
+ expect(typeof softToast.error).toBe('function')
53
+ expect(typeof softToast.warning).toBe('function')
54
+ expect(typeof softToast.info).toBe('function')
55
+ expect(typeof softToast.default).toBe('function')
56
+ expect(typeof softToast.promise).toBe('function')
57
+ expect(typeof softToast.custom).toBe('function')
58
+ expect(typeof softToast.update).toBe('function')
59
+ expect(typeof softToast.dismiss).toBe('function')
60
+ expect(typeof softToast.dismissAll).toBe('function')
61
+ expect(typeof softToast.pause).toBe('function')
62
+ expect(typeof softToast.resume).toBe('function')
46
63
  // Flash & sound API
47
- expect(typeof toast.flash).toBe('function')
48
- expect(typeof toast.showFlashes).toBe('function')
49
- expect(typeof toast.hasFlashes).toBe('function')
64
+ expect(typeof softToast.flash).toBe('function')
65
+ expect(typeof softToast.showFlashes).toBe('function')
66
+ expect(typeof softToast.hasFlashes).toBe('function')
50
67
  })
51
68
 
52
69
  it('exports toastStore with all methods', async () => {
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // Main exports
2
2
  export { SoftToastPlugin, getToastOptions } from './plugin'
3
- export { useToast, toast } from './composables/useToast'
3
+ export { useSoftToast, softToast } from './composables/useSoftToast'
4
4
  export { useFlash, queueFlash, consumeFlashes, hasPendingFlashes } from './composables/useFlash'
5
5
  export { toastStore } from './stores/toastStore'
6
6
 
@@ -22,4 +22,3 @@ export type {
22
22
  AnimationPreset,
23
23
  QueueOverflow
24
24
  } from './types'
25
-
@@ -29,6 +29,14 @@ const toasts = ref<Toast[]>([])
29
29
  // Non-reactive timer state — lives outside Vue reactivity to avoid per-frame re-renders
30
30
  const timerMap = new Map<string, { remainingTime: number; isPaused: boolean }>()
31
31
 
32
+ const resetTimer = (id: string, duration: number, isPaused = false) => {
33
+ if (duration === Infinity) {
34
+ timerMap.delete(id)
35
+ return
36
+ }
37
+ timerMap.set(id, { remainingTime: duration, isPaused })
38
+ }
39
+
32
40
  // ─── Computed ─────────────────────────────────────────────────────────────────
33
41
 
34
42
  const getToastsByPosition = (position: ToastPosition) =>
@@ -40,7 +48,7 @@ const allToasts = computed(() => toasts.value)
40
48
 
41
49
  /**
42
50
  * Add a toast — or UPDATE an existing one if the same `id` is already visible.
43
- * This automatic deduplication means calling toast.error('Oops', { id: 'network-error' })
51
+ * This automatic deduplication means calling softToast.error('Oops', { id: 'network-error' })
44
52
  * five times in a row results in ONE toast that refreshes its content each time.
45
53
  */
46
54
  const add = (options: ToastOptions): string => {
@@ -60,6 +68,7 @@ const add = (options: ToastOptions): string => {
60
68
  isPaused: existing.isPaused,
61
69
  isLeaving: false,
62
70
  }
71
+ resetTimer(id, options.duration ?? existing.duration, existing.isPaused)
63
72
  // Play sound again if configured (content changed)
64
73
  const sound = options.sound
65
74
  const vol = options.soundVolume ?? 0.5
@@ -86,7 +95,7 @@ const add = (options: ToastOptions): string => {
86
95
  }
87
96
 
88
97
  // Register in non-reactive timer map
89
- timerMap.set(id, { remainingTime: duration, isPaused: false })
98
+ resetTimer(id, duration, false)
90
99
 
91
100
  toasts.value.unshift(toast)
92
101
  startTickLoop()
@@ -102,7 +111,18 @@ const add = (options: ToastOptions): string => {
102
111
  const update = (id: string, options: Partial<ToastOptions>) => {
103
112
  const index = toasts.value.findIndex((t) => t.id === id)
104
113
  if (index !== -1) {
105
- toasts.value[index] = { ...toasts.value[index], ...options }
114
+ const existing = toasts.value[index]
115
+ const nextDuration = options.duration ?? existing.duration
116
+ const nextRemainingTime =
117
+ options.duration === undefined ? existing.remainingTime : nextDuration
118
+ toasts.value[index] = {
119
+ ...existing,
120
+ ...options,
121
+ remainingTime: nextRemainingTime,
122
+ }
123
+ if (options.duration !== undefined) {
124
+ resetTimer(id, nextDuration, existing.isPaused)
125
+ }
106
126
  }
107
127
  }
108
128
 
@@ -232,7 +252,7 @@ const loading = (title: string, options?: Omit<ToastOptions, 'type' | 'title'>)
232
252
 
233
253
  const promise = async <T>(
234
254
  promiseFn: Promise<T>,
235
- messages: ToastPromiseMessages,
255
+ messages: ToastPromiseMessages<T>,
236
256
  options?: Omit<ToastOptions, 'type' | 'promise' | 'promiseMessages'>
237
257
  ): Promise<T> => {
238
258
  const id = add({
@@ -246,16 +266,16 @@ const promise = async <T>(
246
266
  const result = await promiseFn
247
267
  update(id, {
248
268
  type: 'success',
249
- title: messages.success,
250
- description: messages.description?.success,
269
+ title: typeof messages.success === 'function' ? messages.success(result) : messages.success,
270
+ description: typeof messages.description?.success === 'function' ? messages.description.success(result) : messages.description?.success,
251
271
  duration: 4000,
252
272
  })
253
273
  return result
254
274
  } catch (err) {
255
275
  update(id, {
256
276
  type: 'error',
257
- title: messages.error,
258
- description: messages.description?.error,
277
+ title: typeof messages.error === 'function' ? messages.error(err) : messages.error,
278
+ description: typeof messages.description?.error === 'function' ? messages.description.error(err) : messages.description?.error,
259
279
  action: messages.action?.error,
260
280
  duration: 6000,
261
281
  })
@@ -292,7 +312,7 @@ export interface ToastStore {
292
312
  warning: (title: string, options?: Omit<ToastOptions, 'type' | 'title'>) => string
293
313
  info: (title: string, options?: Omit<ToastOptions, 'type' | 'title'>) => string
294
314
  loading: (title: string, options?: Omit<ToastOptions, 'type' | 'title'>) => string
295
- promise: <T>(promiseFn: Promise<T>, messages: ToastPromiseMessages, options?: Omit<ToastOptions, 'type' | 'promise' | 'promiseMessages'>) => Promise<T>
315
+ promise: <T>(promiseFn: Promise<T>, messages: ToastPromiseMessages<T>, options?: Omit<ToastOptions, 'type' | 'promise' | 'promiseMessages'>) => Promise<T>
296
316
  clearAll: () => void
297
317
  remove: (id: string) => void
298
318
  }
@@ -59,9 +59,15 @@
59
59
  touch-action: none;
60
60
  }
61
61
 
62
+ .soft-toast-item[data-leaving="true"] {
63
+ pointer-events: none;
64
+ touch-action: none;
65
+ }
66
+
62
67
  /* Show grab cursor on swipeable toasts to hint at the gesture */
63
68
  .soft-toast-item--swipeable {
64
69
  cursor: grab;
70
+ touch-action: none;
65
71
  }
66
72
  .soft-toast-item--swipeable:active {
67
73
  cursor: grabbing;
@@ -188,17 +194,20 @@
188
194
  max-height: 120px;
189
195
  overflow-y: auto;
190
196
  padding-right: 4px;
197
+ scrollbar-width: none;
191
198
  }
192
199
 
193
- /* Custom Scrollbar for long descriptions */
194
- .soft-toast-description::-webkit-scrollbar { width: 4px; }
195
- .soft-toast-description::-webkit-scrollbar-track { background: transparent; }
196
- .soft-toast-description::-webkit-scrollbar-thumb {
197
- background: rgba(0, 0, 0, 0.1);
200
+ /* Custom Scrollbar for long descriptions — hidden by default, shown on hover */
201
+ .soft-toast-description::-webkit-scrollbar { width: 0; }
202
+ .soft-toast-item:hover .soft-toast-description { scrollbar-width: thin; scrollbar-color: rgba(0,0,0,0.15) transparent; }
203
+ .soft-toast-item:hover .soft-toast-description::-webkit-scrollbar { width: 4px; }
204
+ .soft-toast-item:hover .soft-toast-description::-webkit-scrollbar-track { background: transparent; }
205
+ .soft-toast-item:hover .soft-toast-description::-webkit-scrollbar-thumb {
206
+ background: rgba(0, 0, 0, 0.15);
198
207
  border-radius: 4px;
199
208
  }
200
- .soft-toast-description::-webkit-scrollbar-thumb:hover {
201
- background: rgba(0, 0, 0, 0.2);
209
+ .soft-toast-item:hover .soft-toast-description::-webkit-scrollbar-thumb:hover {
210
+ background: rgba(0, 0, 0, 0.25);
202
211
  }
203
212
 
204
213
  /* Timestamp — lives BELOW all content, never overlaps close button */
@@ -368,9 +377,9 @@
368
377
  .soft-toast-container:not([data-expanded="true"]) .soft-toast-item[data-st-index="0"] {
369
378
  touch-action: manipulation;
370
379
  }
371
- /* Expanded: all toasts can swipe */
380
+ /* Expanded: all toasts can swipe — JS handles both axes */
372
381
  .soft-toast-container[data-expanded="true"] .soft-toast-item[data-interactive="true"] {
373
- touch-action: pan-y;
382
+ touch-action: none;
374
383
  }
375
384
  }
376
385
 
@@ -13,13 +13,13 @@ export interface ToastAction {
13
13
  class?: string
14
14
  }
15
15
 
16
- export interface ToastPromiseMessages {
16
+ export interface ToastPromiseMessages<T = unknown> {
17
17
  loading: string
18
- success: string
19
- error: string
18
+ success: string | ((data: T) => string)
19
+ error: string | ((err: unknown) => string)
20
20
  description?: {
21
- success?: string
22
- error?: string
21
+ success?: string | ((data: T) => string)
22
+ error?: string | ((err: unknown) => string)
23
23
  }
24
24
  action?: {
25
25
  error?: ToastAction
@@ -1 +0,0 @@
1
- {"version":3,"file":"useToast.d.ts","sourceRoot":"","sources":["../../src/composables/useToast.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAMlE,eAAO,MAAM,QAAQ;qBACF,MAAM,YAAY,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;qBAGtD,MAAM,YAAY,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;mBAGxD,MAAM,YAAY,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;qBAGpD,MAAM,YAAY,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;kBAGzD,MAAM,YAAY,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;qBAGnD,MAAM,YAAY,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;cAG7D,CAAC,aACE,OAAO,CAAC,CAAC,CAAC,YACX,oBAAoB,YACpB,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,iBAAiB,CAAC,KACnE,OAAO,CAAC,CAAC,CAAC;sBAEK,YAAY;iBAEjB,MAAM,WAAW,OAAO,CAAC,YAAY,CAAC;mBAEpC,MAAM;;gBAGT,MAAM;iBACL,MAAM;IAEnB;;;;;;;;;OASG;mBACY,MAAM,YAAW,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IAGjE;;;OAGG;;IAGH,wEAAwE;;CAExE,CAAA;AAIF,eAAO,MAAM,KAAK;qBACC,MAAM,YAAY,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;qBAEtD,MAAM,YAAY,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;mBAExD,MAAM,YAAY,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;qBAEpD,MAAM,YAAY,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;kBAEzD,MAAM,YAAY,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;qBAEnD,MAAM,YAAY,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;cAE7D,CAAC,aACE,OAAO,CAAC,CAAC,CAAC,YACX,oBAAoB,YACpB,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,iBAAiB,CAAC,KACnE,OAAO,CAAC,CAAC,CAAC;sBACK,YAAY;iBACjB,MAAM,WAAW,OAAO,CAAC,YAAY,CAAC;mBACpC,MAAM;;gBAET,MAAM;iBACL,MAAM;mBACJ,MAAM,YAAW,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;;;CAIlE,CAAA"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=useToast.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useToast.test.d.ts","sourceRoot":"","sources":["../../src/composables/useToast.test.ts"],"names":[],"mappings":""}