@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.
- package/README.md +8 -8
- package/dist/animations/gsapConfig.d.ts +2 -1
- package/dist/animations/gsapConfig.d.ts.map +1 -1
- package/dist/composables/{useToast.d.ts → useSoftToast.d.ts} +6 -6
- package/dist/composables/useSoftToast.d.ts.map +1 -0
- package/dist/composables/useSoftToast.test.d.ts +2 -0
- package/dist/composables/useSoftToast.test.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +794 -756
- package/dist/stores/toastStore.d.ts +1 -1
- package/dist/stores/toastStore.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/dist/types/index.d.ts +5 -5
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +15 -2
- package/src/animations/gsapConfig.ts +45 -14
- package/src/components/ToastItem.vue +41 -10
- package/src/components/ToastRegion.vue +6 -2
- package/src/composables/useFlash.ts +1 -1
- package/src/composables/{useToast.test.ts → useSoftToast.test.ts} +54 -54
- package/src/composables/{useToast.ts → useSoftToast.ts} +5 -5
- package/src/exports.test.ts +39 -22
- package/src/index.ts +1 -2
- package/src/stores/toastStore.ts +29 -9
- package/src/styles/toast.css +18 -9
- package/src/types/index.ts +5 -5
- package/dist/composables/useToast.d.ts.map +0 -1
- package/dist/composables/useToast.test.d.ts +0 -2
- package/dist/composables/useToast.test.d.ts.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'bun:test'
|
|
2
|
-
import {
|
|
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
|
-
// ───
|
|
10
|
+
// ─── useSoftToast() composable ────────────────────────────────────────────────────
|
|
11
11
|
|
|
12
|
-
describe('
|
|
12
|
+
describe('useSoftToast()', () => {
|
|
13
13
|
it('returns an object with all API methods', () => {
|
|
14
|
-
const t =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
// ───
|
|
113
|
+
// ─── softToast (static object) ────────────────────────────────────────────────
|
|
114
114
|
|
|
115
|
-
describe('
|
|
116
|
-
it('
|
|
117
|
-
|
|
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('
|
|
123
|
-
|
|
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('
|
|
128
|
-
|
|
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('
|
|
133
|
-
|
|
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('
|
|
138
|
-
|
|
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('
|
|
143
|
-
|
|
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('
|
|
149
|
-
const id =
|
|
150
|
-
|
|
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('
|
|
155
|
-
const id =
|
|
156
|
-
|
|
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
|
-
|
|
158
|
+
softToast.resume(id)
|
|
159
159
|
expect(toastStore.toasts.value[0].isPaused).toBe(false)
|
|
160
160
|
})
|
|
161
161
|
|
|
162
|
-
it('
|
|
163
|
-
const id =
|
|
164
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
// ───
|
|
177
|
+
// ─── softToast.promise() ─────────────────────────────────────────────────────────
|
|
178
178
|
|
|
179
|
-
describe('
|
|
179
|
+
describe('softToast.promise()', () => {
|
|
180
180
|
it('resolves: updates toast to success', async () => {
|
|
181
|
-
const id = await
|
|
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
|
|
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('
|
|
209
|
+
describe('useSoftToast() — flash API methods', () => {
|
|
210
210
|
it('returns flash, showFlashes, hasFlashes methods', () => {
|
|
211
|
-
const t =
|
|
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('
|
|
219
|
-
it('
|
|
220
|
-
expect(typeof
|
|
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('
|
|
224
|
-
expect(typeof
|
|
223
|
+
it('softToast.showFlashes is a function', () => {
|
|
224
|
+
expect(typeof softToast.showFlashes).toBe('function')
|
|
225
225
|
})
|
|
226
226
|
|
|
227
|
-
it('
|
|
228
|
-
expect(typeof
|
|
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
|
|
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 } =
|
|
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
|
|
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),
|
package/src/exports.test.ts
CHANGED
|
@@ -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
|
|
24
|
-
const {
|
|
25
|
-
expect(typeof
|
|
26
|
-
const api =
|
|
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
|
|
32
|
-
const {
|
|
33
|
-
expect(
|
|
34
|
-
expect(typeof
|
|
35
|
-
expect(typeof
|
|
36
|
-
expect(typeof
|
|
37
|
-
expect(typeof
|
|
38
|
-
expect(typeof
|
|
39
|
-
expect(typeof
|
|
40
|
-
expect(typeof
|
|
41
|
-
expect(typeof
|
|
42
|
-
expect(typeof
|
|
43
|
-
expect(typeof
|
|
44
|
-
expect(typeof
|
|
45
|
-
expect(typeof
|
|
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
|
|
48
|
-
expect(typeof
|
|
49
|
-
expect(typeof
|
|
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 {
|
|
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
|
-
|
package/src/stores/toastStore.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
}
|
package/src/styles/toast.css
CHANGED
|
@@ -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:
|
|
195
|
-
.soft-toast-
|
|
196
|
-
.soft-toast-description::-webkit-scrollbar
|
|
197
|
-
|
|
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.
|
|
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:
|
|
382
|
+
touch-action: none;
|
|
374
383
|
}
|
|
375
384
|
}
|
|
376
385
|
|
package/src/types/index.ts
CHANGED
|
@@ -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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useToast.test.d.ts","sourceRoot":"","sources":["../../src/composables/useToast.test.ts"],"names":[],"mappings":""}
|