@rkosafo/cai.components 0.0.82 → 0.0.83
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.
|
@@ -233,7 +233,7 @@
|
|
|
233
233
|
<div
|
|
234
234
|
bind:this={ref}
|
|
235
235
|
use:focusTrap
|
|
236
|
-
class="fixed inset-0 z-40
|
|
236
|
+
class="fixed inset-0 z-40 overflow-y-auto p-4"
|
|
237
237
|
onclick={_onclick}
|
|
238
238
|
onkeydown={(ev) => {
|
|
239
239
|
if (ev.key === 'Escape' && !permanent) {
|
|
@@ -253,22 +253,22 @@
|
|
|
253
253
|
class: clsx(
|
|
254
254
|
theme?.base,
|
|
255
255
|
className,
|
|
256
|
-
'
|
|
256
|
+
fullscreen ? 'z-10' : 'absolute z-10',
|
|
257
257
|
!modal && 'shadow-xl'
|
|
258
258
|
)
|
|
259
259
|
})}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
260
|
+
tabindex="-1"
|
|
261
|
+
onsubmit={_onsubmit}
|
|
262
|
+
transition:transition|global={paramsOptions as ParamsType}
|
|
263
|
+
{...restProps}
|
|
264
|
+
>
|
|
265
|
+
{#if form}
|
|
266
|
+
<form method="dialog" class={formCls({ class: clsx(theme?.form) })}>
|
|
267
|
+
{@render content()}
|
|
268
|
+
</form>
|
|
269
|
+
{:else}
|
|
267
270
|
{@render content()}
|
|
268
|
-
|
|
269
|
-
{:else}
|
|
270
|
-
{@render content()}
|
|
271
|
-
{/if}
|
|
271
|
+
{/if}
|
|
272
272
|
</div>
|
|
273
273
|
</div>
|
|
274
274
|
{/if}
|
|
@@ -34,6 +34,15 @@
|
|
|
34
34
|
export type CustomToastOptions = ToastOptions;
|
|
35
35
|
export type ToastTheme = Record<string, never>;
|
|
36
36
|
|
|
37
|
+
export type ToastSoundPreset =
|
|
38
|
+
| 'chime'
|
|
39
|
+
| 'ding'
|
|
40
|
+
| 'bell'
|
|
41
|
+
| 'soft'
|
|
42
|
+
| 'pop'
|
|
43
|
+
| 'pulse'
|
|
44
|
+
| 'airy';
|
|
45
|
+
|
|
37
46
|
export interface ToasterProps {
|
|
38
47
|
position?: ToastPosition;
|
|
39
48
|
duration?: number;
|
|
@@ -43,6 +52,8 @@
|
|
|
43
52
|
containerStyle?: string;
|
|
44
53
|
containerClassName?: string;
|
|
45
54
|
fireWithSound?: boolean;
|
|
55
|
+
/** Web Audio preset when `fireWithSound` is true. Default `chime`. */
|
|
56
|
+
soundPreset?: ToastSoundPreset;
|
|
46
57
|
// Kept for backward compatibility (no-op in wrapper mode)
|
|
47
58
|
className?: string;
|
|
48
59
|
theme?: ToastTheme;
|
|
@@ -58,58 +69,188 @@
|
|
|
58
69
|
|
|
59
70
|
let currentDuration = 4000;
|
|
60
71
|
let currentFireWithSound = false;
|
|
72
|
+
let currentSoundPreset: ToastSoundPreset = 'chime';
|
|
61
73
|
|
|
62
|
-
|
|
63
|
-
if (!currentFireWithSound || typeof window === 'undefined') return;
|
|
74
|
+
let sharedAudioContext: AudioContext | null = null;
|
|
64
75
|
|
|
76
|
+
function getSharedAudioContext(): AudioContext | null {
|
|
77
|
+
if (typeof window === 'undefined') return null;
|
|
65
78
|
try {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
gain.gain.exponentialRampToValueAtTime(0.06, context.currentTime + 0.01);
|
|
76
|
-
gain.gain.exponentialRampToValueAtTime(0.0001, context.currentTime + 0.18);
|
|
77
|
-
oscillator.connect(gain);
|
|
78
|
-
gain.connect(context.destination);
|
|
79
|
-
oscillator.start();
|
|
80
|
-
oscillator.stop(context.currentTime + 0.2);
|
|
79
|
+
if (sharedAudioContext && sharedAudioContext.state !== 'closed') {
|
|
80
|
+
return sharedAudioContext;
|
|
81
|
+
}
|
|
82
|
+
sharedAudioContext = null;
|
|
83
|
+
const AudioContextClass =
|
|
84
|
+
window.AudioContext || (window as Window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;
|
|
85
|
+
if (!AudioContextClass) return null;
|
|
86
|
+
sharedAudioContext = new AudioContextClass();
|
|
87
|
+
return sharedAudioContext;
|
|
81
88
|
} catch {
|
|
82
|
-
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function envelopeAttackRelease(
|
|
94
|
+
gain: GainNode,
|
|
95
|
+
ctx: AudioContext,
|
|
96
|
+
t: number,
|
|
97
|
+
peak: number,
|
|
98
|
+
attack: number,
|
|
99
|
+
release: number
|
|
100
|
+
) {
|
|
101
|
+
gain.gain.setValueAtTime(0.0001, t);
|
|
102
|
+
gain.gain.exponentialRampToValueAtTime(peak, t + attack);
|
|
103
|
+
gain.gain.exponentialRampToValueAtTime(0.0001, t + attack + release);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function playPresetSound(ctx: AudioContext, preset: ToastSoundPreset) {
|
|
107
|
+
const t0 = ctx.currentTime;
|
|
108
|
+
|
|
109
|
+
switch (preset) {
|
|
110
|
+
case 'chime': {
|
|
111
|
+
const freqs = [523.25, 659.25];
|
|
112
|
+
for (let i = 0; i < freqs.length; i++) {
|
|
113
|
+
const start = t0 + i * 0.065;
|
|
114
|
+
const osc = ctx.createOscillator();
|
|
115
|
+
const g = ctx.createGain();
|
|
116
|
+
osc.type = 'sine';
|
|
117
|
+
osc.frequency.setValueAtTime(freqs[i], start);
|
|
118
|
+
envelopeAttackRelease(g, ctx, start, 0.07, 0.012, 0.11);
|
|
119
|
+
osc.connect(g);
|
|
120
|
+
g.connect(ctx.destination);
|
|
121
|
+
osc.start(start);
|
|
122
|
+
osc.stop(start + 0.2);
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case 'ding': {
|
|
127
|
+
const osc = ctx.createOscillator();
|
|
128
|
+
const g = ctx.createGain();
|
|
129
|
+
osc.type = 'sine';
|
|
130
|
+
osc.frequency.setValueAtTime(1046.5, t0);
|
|
131
|
+
envelopeAttackRelease(g, ctx, t0, 0.08, 0.004, 0.14);
|
|
132
|
+
osc.connect(g);
|
|
133
|
+
g.connect(ctx.destination);
|
|
134
|
+
osc.start(t0);
|
|
135
|
+
osc.stop(t0 + 0.2);
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case 'bell': {
|
|
139
|
+
const osc = ctx.createOscillator();
|
|
140
|
+
const g = ctx.createGain();
|
|
141
|
+
osc.type = 'triangle';
|
|
142
|
+
osc.frequency.setValueAtTime(740, t0);
|
|
143
|
+
envelopeAttackRelease(g, ctx, t0, 0.06, 0.006, 0.22);
|
|
144
|
+
osc.connect(g);
|
|
145
|
+
g.connect(ctx.destination);
|
|
146
|
+
osc.start(t0);
|
|
147
|
+
osc.stop(t0 + 0.28);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case 'soft': {
|
|
151
|
+
const osc = ctx.createOscillator();
|
|
152
|
+
const g = ctx.createGain();
|
|
153
|
+
osc.type = 'sine';
|
|
154
|
+
osc.frequency.setValueAtTime(392, t0);
|
|
155
|
+
envelopeAttackRelease(g, ctx, t0, 0.05, 0.02, 0.2);
|
|
156
|
+
osc.connect(g);
|
|
157
|
+
g.connect(ctx.destination);
|
|
158
|
+
osc.start(t0);
|
|
159
|
+
osc.stop(t0 + 0.26);
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case 'pop': {
|
|
163
|
+
const osc = ctx.createOscillator();
|
|
164
|
+
const g = ctx.createGain();
|
|
165
|
+
osc.type = 'sine';
|
|
166
|
+
osc.frequency.setValueAtTime(320, t0);
|
|
167
|
+
envelopeAttackRelease(g, ctx, t0, 0.06, 0.002, 0.045);
|
|
168
|
+
osc.connect(g);
|
|
169
|
+
g.connect(ctx.destination);
|
|
170
|
+
osc.start(t0);
|
|
171
|
+
osc.stop(t0 + 0.08);
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
case 'pulse': {
|
|
175
|
+
const osc = ctx.createOscillator();
|
|
176
|
+
const g = ctx.createGain();
|
|
177
|
+
osc.type = 'sine';
|
|
178
|
+
osc.frequency.setValueAtTime(620, t0);
|
|
179
|
+
osc.frequency.exponentialRampToValueAtTime(920, t0 + 0.07);
|
|
180
|
+
envelopeAttackRelease(g, ctx, t0, 0.07, 0.003, 0.08);
|
|
181
|
+
osc.connect(g);
|
|
182
|
+
g.connect(ctx.destination);
|
|
183
|
+
osc.start(t0);
|
|
184
|
+
osc.stop(t0 + 0.12);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case 'airy':
|
|
188
|
+
default: {
|
|
189
|
+
const osc = ctx.createOscillator();
|
|
190
|
+
const g = ctx.createGain();
|
|
191
|
+
osc.type = 'sine';
|
|
192
|
+
osc.frequency.setValueAtTime(1760, t0);
|
|
193
|
+
envelopeAttackRelease(g, ctx, t0, 0.025, 0.002, 0.07);
|
|
194
|
+
osc.connect(g);
|
|
195
|
+
g.connect(ctx.destination);
|
|
196
|
+
osc.start(t0);
|
|
197
|
+
osc.stop(t0 + 0.1);
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
83
200
|
}
|
|
84
201
|
}
|
|
85
202
|
|
|
203
|
+
/** Schedules sound after the current frame so the toast paints first; handles suspended AudioContext. */
|
|
204
|
+
function scheduleToastSound() {
|
|
205
|
+
if (!currentFireWithSound || typeof window === 'undefined') return;
|
|
206
|
+
|
|
207
|
+
queueMicrotask(() => {
|
|
208
|
+
void (async () => {
|
|
209
|
+
const ctx = getSharedAudioContext();
|
|
210
|
+
if (!ctx) return;
|
|
211
|
+
try {
|
|
212
|
+
if (ctx.state === 'suspended') await ctx.resume();
|
|
213
|
+
playPresetSound(ctx, currentSoundPreset);
|
|
214
|
+
} catch {
|
|
215
|
+
// Ignore audio errors.
|
|
216
|
+
}
|
|
217
|
+
})();
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
86
221
|
export function updateToastConfig(config: {
|
|
87
222
|
duration?: number;
|
|
88
223
|
fireWithSound?: boolean;
|
|
224
|
+
soundPreset?: ToastSoundPreset;
|
|
89
225
|
theme?: ToastTheme;
|
|
90
226
|
richColors?: boolean;
|
|
91
227
|
}) {
|
|
92
228
|
if (config.duration !== undefined) currentDuration = config.duration;
|
|
93
229
|
if (config.fireWithSound !== undefined) currentFireWithSound = config.fireWithSound;
|
|
230
|
+
if (config.soundPreset !== undefined) currentSoundPreset = config.soundPreset;
|
|
94
231
|
}
|
|
95
232
|
|
|
96
233
|
export const toast = Object.assign(
|
|
97
234
|
(message: Renderable, options?: ToastOptions) => {
|
|
98
|
-
|
|
99
|
-
|
|
235
|
+
const id = originalToast(message, { duration: currentDuration, ...options });
|
|
236
|
+
scheduleToastSound();
|
|
237
|
+
return id;
|
|
100
238
|
},
|
|
101
239
|
{
|
|
102
240
|
success: (message: Renderable, options?: ToastOptions) => {
|
|
103
|
-
|
|
104
|
-
|
|
241
|
+
const id = originalToast.success(message, { duration: currentDuration, ...options });
|
|
242
|
+
scheduleToastSound();
|
|
243
|
+
return id;
|
|
105
244
|
},
|
|
106
245
|
error: (message: Renderable, options?: ToastOptions) => {
|
|
107
|
-
|
|
108
|
-
|
|
246
|
+
const id = originalToast.error(message, { duration: currentDuration, ...options });
|
|
247
|
+
scheduleToastSound();
|
|
248
|
+
return id;
|
|
109
249
|
},
|
|
110
250
|
loading: (message: Renderable, options?: ToastOptions) => {
|
|
111
|
-
|
|
112
|
-
|
|
251
|
+
const id = originalToast.loading(message, { duration: currentDuration, ...options });
|
|
252
|
+
scheduleToastSound();
|
|
253
|
+
return id;
|
|
113
254
|
},
|
|
114
255
|
promise: <T,>(
|
|
115
256
|
promise: Promise<T>,
|
|
@@ -120,8 +261,9 @@
|
|
|
120
261
|
},
|
|
121
262
|
opts?: DefaultToastOptions
|
|
122
263
|
) => {
|
|
123
|
-
|
|
124
|
-
|
|
264
|
+
const out = originalToast.promise(promise, msgs, opts);
|
|
265
|
+
scheduleToastSound();
|
|
266
|
+
return out;
|
|
125
267
|
},
|
|
126
268
|
dismiss: originalToast.dismiss.bind(originalToast),
|
|
127
269
|
remove: originalToast.remove.bind(originalToast),
|
|
@@ -154,7 +296,8 @@
|
|
|
154
296
|
expand = false,
|
|
155
297
|
gap = 8,
|
|
156
298
|
offset = '32px',
|
|
157
|
-
fireWithSound = false
|
|
299
|
+
fireWithSound = false,
|
|
300
|
+
soundPreset = 'chime'
|
|
158
301
|
}: ToasterProps = $props();
|
|
159
302
|
|
|
160
303
|
$effect(() => {
|
|
@@ -162,7 +305,7 @@
|
|
|
162
305
|
void theme;
|
|
163
306
|
void richColors;
|
|
164
307
|
void customIcons;
|
|
165
|
-
updateToastConfig({ duration, fireWithSound });
|
|
308
|
+
updateToastConfig({ duration, fireWithSound, soundPreset });
|
|
166
309
|
});
|
|
167
310
|
</script>
|
|
168
311
|
|
|
@@ -4,6 +4,7 @@ export { resolveValue };
|
|
|
4
4
|
export type { DefaultToastOptions, IconTheme, Toast, ToastOptions, ToastPosition, ToastType, Renderable, ValueFunction, ValueOrFunction } from 'svelte-french-toast';
|
|
5
5
|
export type CustomToastOptions = ToastOptions;
|
|
6
6
|
export type ToastTheme = Record<string, never>;
|
|
7
|
+
export type ToastSoundPreset = 'chime' | 'ding' | 'bell' | 'soft' | 'pop' | 'pulse' | 'airy';
|
|
7
8
|
export interface ToasterProps {
|
|
8
9
|
position?: ToastPosition;
|
|
9
10
|
duration?: number;
|
|
@@ -13,6 +14,8 @@ export interface ToasterProps {
|
|
|
13
14
|
containerStyle?: string;
|
|
14
15
|
containerClassName?: string;
|
|
15
16
|
fireWithSound?: boolean;
|
|
17
|
+
/** Web Audio preset when `fireWithSound` is true. Default `chime`. */
|
|
18
|
+
soundPreset?: ToastSoundPreset;
|
|
16
19
|
className?: string;
|
|
17
20
|
theme?: ToastTheme;
|
|
18
21
|
richColors?: boolean;
|
|
@@ -27,6 +30,7 @@ export interface ToasterProps {
|
|
|
27
30
|
export declare function updateToastConfig(config: {
|
|
28
31
|
duration?: number;
|
|
29
32
|
fireWithSound?: boolean;
|
|
33
|
+
soundPreset?: ToastSoundPreset;
|
|
30
34
|
theme?: ToastTheme;
|
|
31
35
|
richColors?: boolean;
|
|
32
36
|
}): void;
|
package/dist/ui/toast/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { default as Toaster, toast, originalToast, updateToastConfig } from './Toast.svelte';
|
|
2
|
-
export type { ToasterProps, ToastType, CustomToastOptions, ToastTheme, ToastOptions } from './Toast.svelte';
|
|
2
|
+
export type { ToasterProps, ToastSoundPreset, ToastType, CustomToastOptions, ToastTheme, ToastOptions } from './Toast.svelte';
|