varsel 0.5.1 → 0.5.2
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/dist/VarselItem.svelte +900 -0
- package/dist/VarselItem.svelte.d.ts +23 -0
- package/dist/VarselItem.svelte.d.ts.map +1 -0
- package/dist/VarselManager.svelte +431 -0
- package/dist/VarselManager.svelte.d.ts +18 -0
- package/dist/VarselManager.svelte.d.ts.map +1 -0
- package/dist/VarselToaster.svelte +98 -0
- package/dist/VarselToaster.svelte.d.ts +32 -0
- package/dist/VarselToaster.svelte.d.ts.map +1 -0
- package/dist/core/accessibility.d.ts +6 -0
- package/dist/core/accessibility.d.ts.map +1 -0
- package/dist/core/accessibility.js +12 -0
- package/dist/core/animations.d.ts +29 -0
- package/dist/core/animations.d.ts.map +1 -0
- package/dist/core/animations.js +28 -0
- package/dist/core/positions.d.ts +71 -0
- package/dist/core/positions.d.ts.map +1 -0
- package/dist/core/positions.js +30 -0
- package/dist/core/swipe.d.ts +20 -0
- package/dist/core/swipe.d.ts.map +1 -0
- package/dist/core/swipe.js +30 -0
- package/dist/core/toast-factory.d.ts +3 -0
- package/dist/core/toast-factory.d.ts.map +1 -0
- package/dist/core/toast-factory.js +123 -0
- package/dist/core/toast-state.d.ts +49 -0
- package/dist/core/toast-state.d.ts.map +1 -0
- package/dist/core/toast-state.js +80 -0
- package/dist/core/toaster-instances.d.ts +27 -0
- package/dist/core/toaster-instances.d.ts.map +1 -0
- package/dist/core/toaster-instances.js +38 -0
- package/dist/core/types.d.ts +144 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +1 -0
- package/dist/core/utils.d.ts +10 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/core/utils.js +9 -0
- package/dist/core/variants.d.ts +18 -0
- package/dist/core/variants.d.ts.map +1 -0
- package/dist/core/variants.js +56 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/internals.d.ts +16 -0
- package/dist/internals.d.ts.map +1 -0
- package/dist/internals.js +14 -0
- package/dist/styles.css +195 -0
- package/dist/variant-icons.d.ts +108 -0
- package/dist/variant-icons.d.ts.map +1 -0
- package/dist/variant-icons.js +45 -0
- package/package.json +6 -4
|
@@ -0,0 +1,900 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* @component
|
|
4
|
+
* @description
|
|
5
|
+
* Individual toast component. Handles its own enter/exit animations,
|
|
6
|
+
* swipe-to-dismiss gestures, auto-closing timer, and rendering of content.
|
|
7
|
+
*/
|
|
8
|
+
import { onDestroy, onMount } from "svelte";
|
|
9
|
+
import {
|
|
10
|
+
ANIMATION_CONFIG,
|
|
11
|
+
FOCUSABLE_SELECTORS,
|
|
12
|
+
POSITION_CONFIGS,
|
|
13
|
+
SWIPE_DISMISS_THRESHOLD,
|
|
14
|
+
SWIPE_DISMISS_VELOCITY,
|
|
15
|
+
SWIPE_EXIT_DISTANCE,
|
|
16
|
+
cn,
|
|
17
|
+
getDefaultSwipeDirections,
|
|
18
|
+
toastContainerVariants,
|
|
19
|
+
toastContentVariants,
|
|
20
|
+
toastState,
|
|
21
|
+
type PositionedToast,
|
|
22
|
+
type ToastPosition,
|
|
23
|
+
type SwipeAxis,
|
|
24
|
+
type SwipeDirection,
|
|
25
|
+
} from "./internals";
|
|
26
|
+
import {
|
|
27
|
+
hasVariantIcon,
|
|
28
|
+
variantIconMap,
|
|
29
|
+
} from "./variant-icons";
|
|
30
|
+
|
|
31
|
+
let {
|
|
32
|
+
toast,
|
|
33
|
+
onRemove,
|
|
34
|
+
isGroupHovered = false,
|
|
35
|
+
expandedOffset = 0,
|
|
36
|
+
expandedGap = ANIMATION_CONFIG.EXPANDED_GAP,
|
|
37
|
+
collapsedOffset = undefined,
|
|
38
|
+
hiddenCollapsedOffset = undefined,
|
|
39
|
+
onHeightChange = undefined,
|
|
40
|
+
onGroupHoverEnter = undefined,
|
|
41
|
+
onGroupHoldChange = undefined,
|
|
42
|
+
defaultDuration = 5000,
|
|
43
|
+
defaultShowClose = true,
|
|
44
|
+
pauseOnHover = true,
|
|
45
|
+
offset = undefined,
|
|
46
|
+
expand = true,
|
|
47
|
+
visibleToasts = ANIMATION_CONFIG.MAX_VISIBLE_TOASTS
|
|
48
|
+
}: {
|
|
49
|
+
toast: PositionedToast;
|
|
50
|
+
onRemove: (id: string) => void;
|
|
51
|
+
isGroupHovered?: boolean;
|
|
52
|
+
expandedOffset?: number;
|
|
53
|
+
expandedGap?: number;
|
|
54
|
+
collapsedOffset?: number;
|
|
55
|
+
hiddenCollapsedOffset?: number;
|
|
56
|
+
onHeightChange?: (id: string, height: number) => void;
|
|
57
|
+
onGroupHoverEnter?: () => void;
|
|
58
|
+
onGroupHoldChange?: (holding: boolean) => void;
|
|
59
|
+
defaultDuration?: number;
|
|
60
|
+
defaultShowClose?: boolean;
|
|
61
|
+
pauseOnHover?: boolean;
|
|
62
|
+
offset?: number | string;
|
|
63
|
+
expand?: boolean;
|
|
64
|
+
visibleToasts?: number;
|
|
65
|
+
} = $props();
|
|
66
|
+
|
|
67
|
+
let id = $derived(toast.id);
|
|
68
|
+
let title = $derived(toast.title);
|
|
69
|
+
let description = $derived(toast.description);
|
|
70
|
+
let variant = $derived(toast.variant || "default");
|
|
71
|
+
let duration = $derived(toast.duration ?? defaultDuration);
|
|
72
|
+
let action = $derived(toast.action);
|
|
73
|
+
let isLoading = $derived(toast.isLoading || false);
|
|
74
|
+
let index = $derived(toast.index);
|
|
75
|
+
let renderIndex = $derived(toast.renderIndex);
|
|
76
|
+
let shouldClose = $derived(toast.shouldClose);
|
|
77
|
+
let position = $derived(toast.position || "bottom-center");
|
|
78
|
+
let className = $derived(toast.className || "");
|
|
79
|
+
let onAutoClose = $derived(toast.onAutoClose);
|
|
80
|
+
let onDismiss = $derived(toast.onDismiss);
|
|
81
|
+
let showClose = $derived(toast.showClose ?? defaultShowClose);
|
|
82
|
+
|
|
83
|
+
let toastRef = $state<HTMLDivElement | null>(null);
|
|
84
|
+
let isItemHovered = $state(false);
|
|
85
|
+
let isSwiping = $state(false);
|
|
86
|
+
let swipeDismissDirection = $state<SwipeDirection | null>(null);
|
|
87
|
+
let animationState = $state<"entering" | "entered" | "exiting" | "stacking">(
|
|
88
|
+
"entering",
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
let timeoutRef: ReturnType<typeof setTimeout> | null = null;
|
|
92
|
+
let timerStartRef: number | null = null;
|
|
93
|
+
let remainingTime = $state<number | null>(Number.NaN);
|
|
94
|
+
let enterAnimationFrame: number | null = null;
|
|
95
|
+
let focusTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
96
|
+
let pointerStart: { x: number; y: number } | null = null;
|
|
97
|
+
let dragStartTime: number | null = null;
|
|
98
|
+
let swipeAxis: SwipeAxis | null = null;
|
|
99
|
+
let lastSwipe = { x: 0, y: 0 };
|
|
100
|
+
let mounted = $state(false);
|
|
101
|
+
let prevShouldClose = false;
|
|
102
|
+
let previousDuration: number | undefined;
|
|
103
|
+
let isExiting = $state(false);
|
|
104
|
+
let exitAnimationComplete = false;
|
|
105
|
+
let hasAnimatedIn = $state(false);
|
|
106
|
+
let isPointerHeld = false;
|
|
107
|
+
type SpinnerState = "hidden" | "loading" | "finishing";
|
|
108
|
+
let spinnerState = $state<SpinnerState>("hidden");
|
|
109
|
+
let spinnerFinishTimer: ReturnType<typeof setTimeout> | null = null;
|
|
110
|
+
let hasShownSpinner = $state(false);
|
|
111
|
+
|
|
112
|
+
$effect(() => {
|
|
113
|
+
if (isLoading) {
|
|
114
|
+
hasShownSpinner = true;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
$effect(() => {
|
|
119
|
+
if (isLoading) {
|
|
120
|
+
spinnerState = "loading";
|
|
121
|
+
} else if (spinnerState === "loading") {
|
|
122
|
+
spinnerState = "finishing";
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
$effect(() => {
|
|
127
|
+
if (spinnerState === "finishing") {
|
|
128
|
+
if (!spinnerFinishTimer) {
|
|
129
|
+
spinnerFinishTimer = setTimeout(() => {
|
|
130
|
+
spinnerState = "hidden";
|
|
131
|
+
spinnerFinishTimer = null;
|
|
132
|
+
}, 420);
|
|
133
|
+
}
|
|
134
|
+
} else if (spinnerFinishTimer) {
|
|
135
|
+
clearTimeout(spinnerFinishTimer);
|
|
136
|
+
spinnerFinishTimer = null;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
let shouldRenderSpinner = $derived(spinnerState !== "hidden");
|
|
141
|
+
|
|
142
|
+
const handleSpinnerAnimationEnd = (event: AnimationEvent) => {
|
|
143
|
+
if (event.animationName !== "vs-spinner-finish") return;
|
|
144
|
+
spinnerState = "hidden";
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
let iconConfig = $derived(
|
|
148
|
+
hasVariantIcon(variant) ? variantIconMap[variant] : undefined,
|
|
149
|
+
);
|
|
150
|
+
let showStatusIcon = $derived(isLoading || Boolean(iconConfig));
|
|
151
|
+
|
|
152
|
+
let iconStateClass = $derived(
|
|
153
|
+
!iconConfig
|
|
154
|
+
? undefined
|
|
155
|
+
: !hasShownSpinner
|
|
156
|
+
? "vs-icon--static"
|
|
157
|
+
: isLoading
|
|
158
|
+
? "vs-icon--waiting"
|
|
159
|
+
: "vs-icon--pop",
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const clearSwipeRefs = () => {
|
|
163
|
+
pointerStart = null;
|
|
164
|
+
dragStartTime = null;
|
|
165
|
+
swipeAxis = null;
|
|
166
|
+
lastSwipe = { x: 0, y: 0 };
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const getFocusableElements = () => {
|
|
170
|
+
if (!toastRef) return [];
|
|
171
|
+
return Array.from(
|
|
172
|
+
toastRef.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTORS),
|
|
173
|
+
);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
type CloseReason = "auto" | "dismiss" | null;
|
|
177
|
+
let closeReason: CloseReason = null;
|
|
178
|
+
|
|
179
|
+
const handleTransitionEnd = (event: TransitionEvent) => {
|
|
180
|
+
if (event.target !== toastRef) return;
|
|
181
|
+
if (event.propertyName !== "opacity" && event.propertyName !== "transform")
|
|
182
|
+
return;
|
|
183
|
+
if (animationState !== "exiting") return;
|
|
184
|
+
if (exitAnimationComplete) return;
|
|
185
|
+
|
|
186
|
+
exitAnimationComplete = true;
|
|
187
|
+
if (closeReason === "auto") {
|
|
188
|
+
onAutoClose?.();
|
|
189
|
+
} else if (closeReason === "dismiss") {
|
|
190
|
+
onDismiss?.();
|
|
191
|
+
}
|
|
192
|
+
onRemove(id);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const handleClose = (reason: Exclude<CloseReason, null> = "dismiss") => {
|
|
196
|
+
if (!toastRef || isExiting) return;
|
|
197
|
+
|
|
198
|
+
isExiting = true;
|
|
199
|
+
exitAnimationComplete = false;
|
|
200
|
+
closeReason = reason;
|
|
201
|
+
|
|
202
|
+
toastState.update(id, { shouldClose: true });
|
|
203
|
+
|
|
204
|
+
if (enterAnimationFrame) {
|
|
205
|
+
cancelAnimationFrame(enterAnimationFrame);
|
|
206
|
+
enterAnimationFrame = null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (timeoutRef) {
|
|
210
|
+
clearTimeout(timeoutRef);
|
|
211
|
+
timeoutRef = null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
animationState = "exiting";
|
|
215
|
+
toastState.update(id, { shouldClose: true, isLeaving: true });
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
$effect(() => {
|
|
219
|
+
const desiredClose = Boolean(shouldClose);
|
|
220
|
+
if (desiredClose && !prevShouldClose) {
|
|
221
|
+
handleClose();
|
|
222
|
+
}
|
|
223
|
+
prevShouldClose = desiredClose;
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
onMount(() => {
|
|
227
|
+
mounted = true;
|
|
228
|
+
return () => {
|
|
229
|
+
mounted = false;
|
|
230
|
+
if (enterAnimationFrame) cancelAnimationFrame(enterAnimationFrame);
|
|
231
|
+
if (timeoutRef) clearTimeout(timeoutRef);
|
|
232
|
+
if (focusTimeout) clearTimeout(focusTimeout);
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
onDestroy(() => {
|
|
237
|
+
if (enterAnimationFrame) cancelAnimationFrame(enterAnimationFrame);
|
|
238
|
+
if (timeoutRef) clearTimeout(timeoutRef);
|
|
239
|
+
if (focusTimeout) clearTimeout(focusTimeout);
|
|
240
|
+
if (spinnerFinishTimer) {
|
|
241
|
+
clearTimeout(spinnerFinishTimer);
|
|
242
|
+
spinnerFinishTimer = null;
|
|
243
|
+
}
|
|
244
|
+
if (isPointerHeld) {
|
|
245
|
+
isPointerHeld = false;
|
|
246
|
+
onGroupHoldChange?.(false);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
$effect(() => {
|
|
251
|
+
if (mounted && duration !== previousDuration) {
|
|
252
|
+
remainingTime = duration;
|
|
253
|
+
previousDuration = duration;
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
$effect(() => {
|
|
258
|
+
if (mounted) {
|
|
259
|
+
if (!toastRef || !onHeightChange) {
|
|
260
|
+
// do nothing
|
|
261
|
+
} else {
|
|
262
|
+
const el = toastRef;
|
|
263
|
+
const notify = () => onHeightChange?.(id, el.offsetHeight);
|
|
264
|
+
const ro = new ResizeObserver(() => notify());
|
|
265
|
+
ro.observe(el);
|
|
266
|
+
notify();
|
|
267
|
+
return () => ro.disconnect();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const setFocusToToast = () => {
|
|
273
|
+
if (!toastRef) return;
|
|
274
|
+
const focusableElements = getFocusableElements();
|
|
275
|
+
const firstFocusable = focusableElements[0];
|
|
276
|
+
if (firstFocusable) {
|
|
277
|
+
firstFocusable.focus();
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
toastRef.focus();
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
$effect(() => {
|
|
284
|
+
if (mounted && toastRef && !isExiting) {
|
|
285
|
+
if (!hasAnimatedIn && isLatest) {
|
|
286
|
+
hasAnimatedIn = true;
|
|
287
|
+
animationState = "entering";
|
|
288
|
+
if (enterAnimationFrame) {
|
|
289
|
+
cancelAnimationFrame(enterAnimationFrame);
|
|
290
|
+
}
|
|
291
|
+
enterAnimationFrame = requestAnimationFrame(() => {
|
|
292
|
+
enterAnimationFrame = requestAnimationFrame(() => {
|
|
293
|
+
animationState = "entered";
|
|
294
|
+
if (action) {
|
|
295
|
+
if (focusTimeout) clearTimeout(focusTimeout);
|
|
296
|
+
focusTimeout = setTimeout(
|
|
297
|
+
() => setFocusToToast(),
|
|
298
|
+
ANIMATION_CONFIG.ENTER_DURATION * 1000,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
} else if (hasAnimatedIn) {
|
|
304
|
+
if (animationState !== "stacking" || index > 0) {
|
|
305
|
+
animationState = "stacking";
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
animationState = "stacking";
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
$effect(() => {
|
|
314
|
+
if (mounted) {
|
|
315
|
+
if (shouldClose || !hasAnimatedIn || duration <= 0) {
|
|
316
|
+
if (timeoutRef) {
|
|
317
|
+
clearTimeout(timeoutRef);
|
|
318
|
+
timeoutRef = null;
|
|
319
|
+
}
|
|
320
|
+
timerStartRef = null;
|
|
321
|
+
} else {
|
|
322
|
+
if (remainingTime == null || Number.isNaN(remainingTime)) {
|
|
323
|
+
remainingTime = duration;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const isHovering = isGroupHovered || isItemHovered;
|
|
327
|
+
const isPaused =
|
|
328
|
+
(pauseOnHover && isHovering) || isSwiping || hiddenByStacking;
|
|
329
|
+
|
|
330
|
+
if (isPaused) {
|
|
331
|
+
if (timeoutRef) {
|
|
332
|
+
clearTimeout(timeoutRef);
|
|
333
|
+
timeoutRef = null;
|
|
334
|
+
}
|
|
335
|
+
if (timerStartRef !== null) {
|
|
336
|
+
const elapsed = Date.now() - timerStartRef;
|
|
337
|
+
remainingTime = Math.max(0, (remainingTime ?? duration) - elapsed);
|
|
338
|
+
timerStartRef = null;
|
|
339
|
+
}
|
|
340
|
+
} else {
|
|
341
|
+
if (!timeoutRef) {
|
|
342
|
+
const ms = Math.max(0, remainingTime ?? duration);
|
|
343
|
+
if (!Number.isFinite(ms)) {
|
|
344
|
+
// Do not set timeout for infinite duration
|
|
345
|
+
} else if (ms === 0) {
|
|
346
|
+
handleClose("auto");
|
|
347
|
+
} else {
|
|
348
|
+
timerStartRef = Date.now();
|
|
349
|
+
timeoutRef = setTimeout(() => {
|
|
350
|
+
handleClose("auto");
|
|
351
|
+
}, ms);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
$effect(() => {
|
|
360
|
+
if (mounted && toastRef && !isSwiping && !swipeDismissDirection) {
|
|
361
|
+
toastRef.style.setProperty("--swipe-translate-x", "0px");
|
|
362
|
+
toastRef.style.setProperty("--swipe-translate-y", "0px");
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
let swipeDirections = $derived(
|
|
367
|
+
showClose ? getDefaultSwipeDirections(position) : [],
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
const handlePointerDown = (event: PointerEvent) => {
|
|
371
|
+
if (!showClose) return;
|
|
372
|
+
if (event.pointerType === "mouse" && event.button !== 0) return;
|
|
373
|
+
if (event.button === 2) return;
|
|
374
|
+
if (isExiting) return;
|
|
375
|
+
|
|
376
|
+
const target = event.target as HTMLElement;
|
|
377
|
+
if (target.closest("button, a, input, textarea, select")) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
clearSwipeRefs();
|
|
382
|
+
pointerStart = { x: event.clientX, y: event.clientY };
|
|
383
|
+
dragStartTime = Date.now();
|
|
384
|
+
if (toastRef) {
|
|
385
|
+
toastRef.style.setProperty("--swipe-translate-x", "0px");
|
|
386
|
+
toastRef.style.setProperty("--swipe-translate-y", "0px");
|
|
387
|
+
}
|
|
388
|
+
swipeDismissDirection = null;
|
|
389
|
+
isSwiping = true;
|
|
390
|
+
if (!isPointerHeld) {
|
|
391
|
+
isPointerHeld = true;
|
|
392
|
+
onGroupHoldChange?.(true);
|
|
393
|
+
}
|
|
394
|
+
const currentTarget = event.currentTarget as HTMLElement | null;
|
|
395
|
+
currentTarget?.setPointerCapture(event.pointerId);
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const handlePointerMove = (event: PointerEvent) => {
|
|
399
|
+
if (!showClose) return;
|
|
400
|
+
if (!pointerStart) return;
|
|
401
|
+
if (isExiting) return;
|
|
402
|
+
|
|
403
|
+
if (event.pointerType === "touch") {
|
|
404
|
+
event.preventDefault();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const xDelta = event.clientX - pointerStart.x;
|
|
408
|
+
const yDelta = event.clientY - pointerStart.y;
|
|
409
|
+
|
|
410
|
+
let axis = swipeAxis;
|
|
411
|
+
if (!axis) {
|
|
412
|
+
if (Math.abs(xDelta) > 1 || Math.abs(yDelta) > 1) {
|
|
413
|
+
axis = Math.abs(xDelta) > Math.abs(yDelta) ? "x" : "y";
|
|
414
|
+
swipeAxis = axis;
|
|
415
|
+
} else {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const dampen = (delta: number) => {
|
|
421
|
+
const factor = Math.abs(delta) / 20;
|
|
422
|
+
return delta * (1 / (1.5 + factor));
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
let nextX = 0;
|
|
426
|
+
let nextY = 0;
|
|
427
|
+
|
|
428
|
+
if (axis === "x") {
|
|
429
|
+
const allowLeft = swipeDirections.includes("left");
|
|
430
|
+
const allowRight = swipeDirections.includes("right");
|
|
431
|
+
if (!allowLeft && !allowRight) {
|
|
432
|
+
swipeAxis = "y";
|
|
433
|
+
axis = "y";
|
|
434
|
+
} else if ((allowLeft && xDelta < 0) || (allowRight && xDelta > 0)) {
|
|
435
|
+
nextX = xDelta;
|
|
436
|
+
} else {
|
|
437
|
+
nextX = dampen(xDelta);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (axis === "y") {
|
|
442
|
+
const allowTop = swipeDirections.includes("top");
|
|
443
|
+
const allowBottom = swipeDirections.includes("bottom");
|
|
444
|
+
if (!allowTop && !allowBottom) {
|
|
445
|
+
swipeAxis = "x";
|
|
446
|
+
axis = "x";
|
|
447
|
+
} else if ((allowTop && yDelta < 0) || (allowBottom && yDelta > 0)) {
|
|
448
|
+
nextY = yDelta;
|
|
449
|
+
} else {
|
|
450
|
+
nextY = dampen(yDelta);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
lastSwipe = { x: nextX, y: nextY };
|
|
455
|
+
if (toastRef) {
|
|
456
|
+
toastRef.style.setProperty("--swipe-translate-x", `${nextX}px`);
|
|
457
|
+
toastRef.style.setProperty("--swipe-translate-y", `${nextY}px`);
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const handlePointerUp = (event: PointerEvent) => {
|
|
462
|
+
if (!showClose) return;
|
|
463
|
+
const currentTarget = event.currentTarget as HTMLElement | null;
|
|
464
|
+
if (currentTarget?.hasPointerCapture(event.pointerId)) {
|
|
465
|
+
currentTarget.releasePointerCapture(event.pointerId);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (!pointerStart) {
|
|
469
|
+
swipeDismissDirection = null;
|
|
470
|
+
isSwiping = false;
|
|
471
|
+
clearSwipeRefs();
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const elapsed = dragStartTime ? Date.now() - dragStartTime : 0;
|
|
476
|
+
|
|
477
|
+
const axis = swipeAxis;
|
|
478
|
+
const { x, y } = lastSwipe;
|
|
479
|
+
let dismissed = false;
|
|
480
|
+
|
|
481
|
+
if (axis) {
|
|
482
|
+
const distance = axis === "x" ? x : y;
|
|
483
|
+
const velocity = elapsed > 0 ? Math.abs(distance) / elapsed : 0;
|
|
484
|
+
const meetsThreshold =
|
|
485
|
+
Math.abs(distance) >= SWIPE_DISMISS_THRESHOLD ||
|
|
486
|
+
velocity > SWIPE_DISMISS_VELOCITY;
|
|
487
|
+
|
|
488
|
+
if (meetsThreshold && Math.abs(distance) > 0) {
|
|
489
|
+
let direction: SwipeDirection;
|
|
490
|
+
if (axis === "x") {
|
|
491
|
+
direction = distance > 0 ? "right" : "left";
|
|
492
|
+
} else {
|
|
493
|
+
direction = distance > 0 ? "bottom" : "top";
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (swipeDirections.includes(direction)) {
|
|
497
|
+
swipeDismissDirection = direction;
|
|
498
|
+
dismissed = true;
|
|
499
|
+
handleClose();
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (!dismissed) {
|
|
505
|
+
swipeDismissDirection = null;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
isSwiping = false;
|
|
509
|
+
clearSwipeRefs();
|
|
510
|
+
if (isPointerHeld) {
|
|
511
|
+
isPointerHeld = false;
|
|
512
|
+
onGroupHoldChange?.(false);
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
const handlePointerCancel = (event: PointerEvent) => {
|
|
517
|
+
if (!showClose) return;
|
|
518
|
+
const currentTarget = event.currentTarget as HTMLElement | null;
|
|
519
|
+
if (currentTarget?.hasPointerCapture(event.pointerId)) {
|
|
520
|
+
currentTarget.releasePointerCapture(event.pointerId);
|
|
521
|
+
}
|
|
522
|
+
swipeDismissDirection = null;
|
|
523
|
+
isSwiping = false;
|
|
524
|
+
clearSwipeRefs();
|
|
525
|
+
if (isPointerHeld) {
|
|
526
|
+
isPointerHeld = false;
|
|
527
|
+
onGroupHoldChange?.(false);
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
const zIndexBase = Number(ANIMATION_CONFIG.Z_INDEX_BASE);
|
|
532
|
+
|
|
533
|
+
let isTopPosition = $derived(position?.startsWith("top-") ?? false);
|
|
534
|
+
let maxVisibleIndex = $derived(
|
|
535
|
+
Math.max(0, visibleToasts - 1),
|
|
536
|
+
);
|
|
537
|
+
let visibleIndex = $derived(Math.min(index, maxVisibleIndex));
|
|
538
|
+
let defaultCollapsedOffset = $derived(
|
|
539
|
+
isTopPosition
|
|
540
|
+
? index * ANIMATION_CONFIG.STACK_OFFSET
|
|
541
|
+
: -(index * ANIMATION_CONFIG.STACK_OFFSET),
|
|
542
|
+
);
|
|
543
|
+
let resolvedCollapsedOffset = $derived(
|
|
544
|
+
typeof collapsedOffset === "number" && Number.isFinite(collapsedOffset)
|
|
545
|
+
? collapsedOffset
|
|
546
|
+
: defaultCollapsedOffset,
|
|
547
|
+
);
|
|
548
|
+
let resolvedHiddenCollapsedOffset = $derived(
|
|
549
|
+
typeof hiddenCollapsedOffset === "number" &&
|
|
550
|
+
Number.isFinite(hiddenCollapsedOffset)
|
|
551
|
+
? hiddenCollapsedOffset
|
|
552
|
+
: resolvedCollapsedOffset,
|
|
553
|
+
);
|
|
554
|
+
let scale = $derived(
|
|
555
|
+
Math.max(ANIMATION_CONFIG.MIN_SCALE, 1 - index * ANIMATION_CONFIG.SCALE_FACTOR),
|
|
556
|
+
);
|
|
557
|
+
let visibleScale = $derived(
|
|
558
|
+
Math.max(
|
|
559
|
+
ANIMATION_CONFIG.MIN_SCALE,
|
|
560
|
+
1 - visibleIndex * ANIMATION_CONFIG.SCALE_FACTOR,
|
|
561
|
+
),
|
|
562
|
+
);
|
|
563
|
+
let zIndex = $derived(zIndexBase - renderIndex);
|
|
564
|
+
let stackHidden = $derived(index >= visibleToasts);
|
|
565
|
+
let hiddenByStacking = $derived(stackHidden && animationState !== "exiting");
|
|
566
|
+
let isStackLeader = $derived(index === 0);
|
|
567
|
+
let isLatest = $derived(isStackLeader && !shouldClose);
|
|
568
|
+
type PositionConfig = (typeof POSITION_CONFIGS)[ToastPosition];
|
|
569
|
+
let config = $derived(
|
|
570
|
+
POSITION_CONFIGS[(position ?? "bottom-center") as ToastPosition],
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
let transformStyle = $derived.by(() => {
|
|
574
|
+
const baseOffsetY = stackHidden
|
|
575
|
+
? resolvedHiddenCollapsedOffset
|
|
576
|
+
: resolvedCollapsedOffset;
|
|
577
|
+
const promotionOffset =
|
|
578
|
+
typeof expandedGap === "number"
|
|
579
|
+
? expandedGap
|
|
580
|
+
: ANIMATION_CONFIG.EXPANDED_GAP;
|
|
581
|
+
const expandedTranslateY = isTopPosition ? expandedOffset : -expandedOffset;
|
|
582
|
+
const hiddenExpandedTranslateY = expandedTranslateY - promotionOffset;
|
|
583
|
+
|
|
584
|
+
let translateX = 0;
|
|
585
|
+
let translateY = baseOffsetY;
|
|
586
|
+
let scaleValue = stackHidden
|
|
587
|
+
? visibleIndex === 0
|
|
588
|
+
? 1
|
|
589
|
+
: visibleScale
|
|
590
|
+
: isStackLeader
|
|
591
|
+
? 1
|
|
592
|
+
: scale;
|
|
593
|
+
let opacityValue = stackHidden ? 0 : 1;
|
|
594
|
+
|
|
595
|
+
if (stackHidden) {
|
|
596
|
+
if (expand && isGroupHovered && animationState !== "exiting") {
|
|
597
|
+
translateX = 0;
|
|
598
|
+
translateY = hiddenExpandedTranslateY;
|
|
599
|
+
scaleValue = 1;
|
|
600
|
+
}
|
|
601
|
+
} else if (expand && isGroupHovered && animationState !== "exiting") {
|
|
602
|
+
translateX = 0;
|
|
603
|
+
translateY = expandedTranslateY;
|
|
604
|
+
scaleValue = 1;
|
|
605
|
+
opacityValue = 1;
|
|
606
|
+
} else {
|
|
607
|
+
switch (animationState) {
|
|
608
|
+
case "entering":
|
|
609
|
+
translateX = config.animateIn.x;
|
|
610
|
+
translateY = config.animateIn.y;
|
|
611
|
+
scaleValue = 1;
|
|
612
|
+
opacityValue = 0;
|
|
613
|
+
break;
|
|
614
|
+
case "entered":
|
|
615
|
+
translateX = 0;
|
|
616
|
+
translateY = baseOffsetY;
|
|
617
|
+
scaleValue = 1;
|
|
618
|
+
opacityValue = 1;
|
|
619
|
+
break;
|
|
620
|
+
case "exiting": {
|
|
621
|
+
scaleValue = 1;
|
|
622
|
+
opacityValue = 0;
|
|
623
|
+
if (swipeDismissDirection) {
|
|
624
|
+
switch (swipeDismissDirection) {
|
|
625
|
+
case "left":
|
|
626
|
+
translateX = -SWIPE_EXIT_DISTANCE;
|
|
627
|
+
translateY = 0;
|
|
628
|
+
break;
|
|
629
|
+
case "right":
|
|
630
|
+
translateX = SWIPE_EXIT_DISTANCE;
|
|
631
|
+
translateY = 0;
|
|
632
|
+
break;
|
|
633
|
+
case "top":
|
|
634
|
+
translateX = 0;
|
|
635
|
+
translateY = -SWIPE_EXIT_DISTANCE;
|
|
636
|
+
break;
|
|
637
|
+
case "bottom":
|
|
638
|
+
translateX = 0;
|
|
639
|
+
translateY = SWIPE_EXIT_DISTANCE;
|
|
640
|
+
break;
|
|
641
|
+
default:
|
|
642
|
+
translateX = config.animateOut.x;
|
|
643
|
+
translateY = config.animateOut.y;
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
} else {
|
|
647
|
+
translateX = config.animateOut.x;
|
|
648
|
+
translateY = config.animateOut.y;
|
|
649
|
+
}
|
|
650
|
+
break;
|
|
651
|
+
}
|
|
652
|
+
default:
|
|
653
|
+
translateX = 0;
|
|
654
|
+
translateY = baseOffsetY;
|
|
655
|
+
scaleValue = isStackLeader ? 1 : scale;
|
|
656
|
+
opacityValue = stackHidden ? 0 : 1;
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const transform = `translate(calc(${translateX}px + var(--swipe-translate-x, 0px)), calc(${translateY}px + var(--swipe-translate-y, 0px))) scale(${scaleValue})`;
|
|
662
|
+
|
|
663
|
+
return {
|
|
664
|
+
transform,
|
|
665
|
+
opacity: opacityValue,
|
|
666
|
+
};
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
let transitionDuration = $derived.by(() => {
|
|
670
|
+
switch (animationState) {
|
|
671
|
+
case "entering":
|
|
672
|
+
case "entered":
|
|
673
|
+
return `${ANIMATION_CONFIG.ENTER_DURATION}s`;
|
|
674
|
+
case "exiting":
|
|
675
|
+
return `${ANIMATION_CONFIG.EXIT_DURATION}s`;
|
|
676
|
+
default:
|
|
677
|
+
return `${ANIMATION_CONFIG.STACK_DURATION}s`;
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
let transitionTimingFunction = $derived(
|
|
682
|
+
animationState === "exiting"
|
|
683
|
+
? ANIMATION_CONFIG.EASING_EXIT
|
|
684
|
+
: ANIMATION_CONFIG.EASING_DEFAULT,
|
|
685
|
+
);
|
|
686
|
+
|
|
687
|
+
let canSwipe = $derived(showClose && swipeDirections.length > 0);
|
|
688
|
+
let swipeCursorClass = $derived(
|
|
689
|
+
canSwipe ? (isSwiping ? "cursor-grabbing" : "cursor-grab") : undefined,
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
let titleId = $derived(title ? `${id}-title` : undefined);
|
|
693
|
+
let descriptionId = $derived(description ? `${id}-desc` : undefined);
|
|
694
|
+
let liveRole = $derived(variant === "destructive" ? "alert" : "status");
|
|
695
|
+
let livePoliteness = $derived(
|
|
696
|
+
(variant === "destructive" ? "assertive" : "polite") as "assertive" | "polite",
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
let offsetStyle = $derived.by(() => {
|
|
700
|
+
if (offset === undefined) return undefined;
|
|
701
|
+
const val = typeof offset === "number" ? `${offset}px` : offset;
|
|
702
|
+
if (position.startsWith("top")) return `top: ${val};`;
|
|
703
|
+
if (position.startsWith("bottom")) return `bottom: ${val};`;
|
|
704
|
+
return undefined;
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
const handleBlurCapture = (event: FocusEvent) => {
|
|
708
|
+
const next = event.relatedTarget as Node | null;
|
|
709
|
+
if (!toastRef || !next || !toastRef.contains(next)) {
|
|
710
|
+
isItemHovered = false;
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
</script>
|
|
714
|
+
|
|
715
|
+
<div
|
|
716
|
+
bind:this={toastRef}
|
|
717
|
+
class={cn(
|
|
718
|
+
toastContainerVariants({ position, variant }),
|
|
719
|
+
className,
|
|
720
|
+
swipeCursorClass,
|
|
721
|
+
stackHidden && "pointer-events-none",
|
|
722
|
+
)}
|
|
723
|
+
style:transform-origin={position?.startsWith("top-")
|
|
724
|
+
? "center top"
|
|
725
|
+
: "center bottom"}
|
|
726
|
+
style:z-index={zIndex}
|
|
727
|
+
style:transition={isSwiping
|
|
728
|
+
? `transform 0s linear, opacity ${transitionDuration} ${transitionTimingFunction}`
|
|
729
|
+
: `transform ${transitionDuration} ${transitionTimingFunction}, opacity ${transitionDuration} ${transitionTimingFunction}`}
|
|
730
|
+
style:transform={transformStyle.transform}
|
|
731
|
+
style:opacity={transformStyle.opacity}
|
|
732
|
+
style={offsetStyle}
|
|
733
|
+
role={stackHidden ? undefined : liveRole}
|
|
734
|
+
aria-live={stackHidden ? undefined : livePoliteness}
|
|
735
|
+
aria-atomic={stackHidden ? undefined : "true"}
|
|
736
|
+
aria-describedby={stackHidden ? undefined : descriptionId}
|
|
737
|
+
aria-hidden={stackHidden ? true : undefined}
|
|
738
|
+
tabindex="-1"
|
|
739
|
+
ontransitionend={handleTransitionEnd}
|
|
740
|
+
data-toast-id={id}
|
|
741
|
+
>
|
|
742
|
+
<div
|
|
743
|
+
role="presentation"
|
|
744
|
+
class={cn(swipeCursorClass, "touch-none")}
|
|
745
|
+
aria-busy={isLoading ? "true" : undefined}
|
|
746
|
+
onpointerdown={handlePointerDown}
|
|
747
|
+
onpointermove={handlePointerMove}
|
|
748
|
+
onpointerup={handlePointerUp}
|
|
749
|
+
onpointercancel={handlePointerCancel}
|
|
750
|
+
onmouseenter={() => {
|
|
751
|
+
isItemHovered = true;
|
|
752
|
+
onGroupHoverEnter?.();
|
|
753
|
+
}}
|
|
754
|
+
onmouseleave={() => (isItemHovered = false)}
|
|
755
|
+
onfocuscapture={() => (isItemHovered = true)}
|
|
756
|
+
onblurcapture={handleBlurCapture}
|
|
757
|
+
>
|
|
758
|
+
{#if toast.component}
|
|
759
|
+
{@const Component = toast.component}
|
|
760
|
+
<Component {id} {toast} {...toast.componentProps} />
|
|
761
|
+
{:else}
|
|
762
|
+
<div class={cn(toastContentVariants({ variant }))}>
|
|
763
|
+
{#if showClose}
|
|
764
|
+
<button
|
|
765
|
+
type="button"
|
|
766
|
+
onclick={() => handleClose()}
|
|
767
|
+
class={cn(
|
|
768
|
+
"absolute top-2 end-2 cursor-pointer rounded-vs-sm p-1 text-vs-foreground/45 hover:bg-vs-popover-muted hover:text-vs-foreground/70 transition-[background-color,color,box-shadow] ease-vs-button duration-100 focus-visible:ring-1 focus-visible:ring-vs-ring/50 focus-visible:outline-none",
|
|
769
|
+
)}
|
|
770
|
+
aria-label="Close toast"
|
|
771
|
+
>
|
|
772
|
+
<svg
|
|
773
|
+
aria-hidden="true"
|
|
774
|
+
class="h-4 w-4"
|
|
775
|
+
viewBox="0 0 24 24"
|
|
776
|
+
fill="none"
|
|
777
|
+
stroke="currentColor"
|
|
778
|
+
stroke-width="2"
|
|
779
|
+
stroke-linecap="round"
|
|
780
|
+
stroke-linejoin="round"
|
|
781
|
+
>
|
|
782
|
+
<line x1="18" x2="6" y1="6" y2="18" />
|
|
783
|
+
<line x1="6" x2="18" y1="6" y2="18" />
|
|
784
|
+
</svg>
|
|
785
|
+
</button>
|
|
786
|
+
{/if}
|
|
787
|
+
|
|
788
|
+
<div class="p-4 pe-8">
|
|
789
|
+
<div class="flex gap-3">
|
|
790
|
+
{#if showStatusIcon}
|
|
791
|
+
<span class="relative inline-flex h-4 w-4 shrink-0 items-center justify-center">
|
|
792
|
+
{#if shouldRenderSpinner}
|
|
793
|
+
<span
|
|
794
|
+
class={cn(
|
|
795
|
+
"vs-spinner absolute inset-0",
|
|
796
|
+
spinnerState === "loading"
|
|
797
|
+
? "vs-spinner--active"
|
|
798
|
+
: "vs-spinner--finish",
|
|
799
|
+
)}
|
|
800
|
+
role={spinnerState === "loading" ? "status" : undefined}
|
|
801
|
+
aria-label={spinnerState === "loading" ? "Loading..." : undefined}
|
|
802
|
+
aria-live={spinnerState === "loading" ? "assertive" : undefined}
|
|
803
|
+
onanimationend={handleSpinnerAnimationEnd}
|
|
804
|
+
>
|
|
805
|
+
<svg
|
|
806
|
+
viewBox="0 0 256 256"
|
|
807
|
+
fill="none"
|
|
808
|
+
stroke="currentColor"
|
|
809
|
+
stroke-width="24"
|
|
810
|
+
stroke-linecap="round"
|
|
811
|
+
stroke-linejoin="round"
|
|
812
|
+
>
|
|
813
|
+
<line x1="128" y1="32" x2="128" y2="64" />
|
|
814
|
+
<line x1="195.9" y1="60.1" x2="173.3" y2="82.7" />
|
|
815
|
+
<line x1="224" y1="128" x2="192" y2="128" />
|
|
816
|
+
<line x1="195.9" y1="195.9" x2="173.3" y2="173.3" />
|
|
817
|
+
<line x1="128" y1="224" x2="128" y2="192" />
|
|
818
|
+
<line x1="60.1" y1="195.9" x2="82.7" y2="173.3" />
|
|
819
|
+
<line x1="32" y1="128" x2="64" y2="128" />
|
|
820
|
+
<line x1="60.1" y1="60.1" x2="82.7" y2="82.7" />
|
|
821
|
+
</svg>
|
|
822
|
+
</span>
|
|
823
|
+
{/if}
|
|
824
|
+
{#if iconConfig}
|
|
825
|
+
<span
|
|
826
|
+
class={cn(
|
|
827
|
+
"vs-icon absolute inset-0 flex items-center justify-center",
|
|
828
|
+
iconStateClass,
|
|
829
|
+
)}
|
|
830
|
+
aria-hidden="true"
|
|
831
|
+
>
|
|
832
|
+
<svg
|
|
833
|
+
viewBox={iconConfig.viewBox}
|
|
834
|
+
fill="none"
|
|
835
|
+
stroke="currentColor"
|
|
836
|
+
stroke-width="2"
|
|
837
|
+
stroke-linecap="round"
|
|
838
|
+
stroke-linejoin="round"
|
|
839
|
+
>
|
|
840
|
+
{#each iconConfig.elements as element, elementIndex (elementIndex)}
|
|
841
|
+
{#if element.tag === "path"}
|
|
842
|
+
<path d={element.d} />
|
|
843
|
+
{:else if element.tag === "line"}
|
|
844
|
+
<line
|
|
845
|
+
x1={element.x1}
|
|
846
|
+
y1={element.y1}
|
|
847
|
+
x2={element.x2}
|
|
848
|
+
y2={element.y2}
|
|
849
|
+
/>
|
|
850
|
+
{:else if element.tag === "circle"}
|
|
851
|
+
<circle
|
|
852
|
+
cx={element.cx}
|
|
853
|
+
cy={element.cy}
|
|
854
|
+
r={element.r}
|
|
855
|
+
/>
|
|
856
|
+
{/if}
|
|
857
|
+
{/each}
|
|
858
|
+
</svg>
|
|
859
|
+
</span>
|
|
860
|
+
{/if}
|
|
861
|
+
</span>
|
|
862
|
+
{/if}
|
|
863
|
+
<div class="min-w-0">
|
|
864
|
+
{#if title}
|
|
865
|
+
<div
|
|
866
|
+
id={titleId}
|
|
867
|
+
class="mb-1 text-sm leading-none font-medium select-none"
|
|
868
|
+
>
|
|
869
|
+
{title}
|
|
870
|
+
</div>
|
|
871
|
+
{/if}
|
|
872
|
+
{#if description}
|
|
873
|
+
<div
|
|
874
|
+
id={descriptionId}
|
|
875
|
+
class="text-sm leading-snug text-vs-foreground/70 text-balance select-none"
|
|
876
|
+
>
|
|
877
|
+
{description}
|
|
878
|
+
</div>
|
|
879
|
+
{/if}
|
|
880
|
+
{#if action}
|
|
881
|
+
<div class="mt-3">
|
|
882
|
+
<button
|
|
883
|
+
type="button"
|
|
884
|
+
onclick={() => {
|
|
885
|
+
action.onClick();
|
|
886
|
+
handleClose();
|
|
887
|
+
}}
|
|
888
|
+
class="relative inline-flex cursor-pointer items-center justify-center rounded-vs-md px-3 py-1.5 text-sm font-medium bg-vs-foreground text-vs-popover shadow-vs-button transition-[background-color,color,box-shadow] ease-vs-button duration-100 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:ring-offset-vs-ring-offset/50 focus-visible:outline-none focus-visible:ring-vs-ring/50"
|
|
889
|
+
>
|
|
890
|
+
{action.label}
|
|
891
|
+
</button>
|
|
892
|
+
</div>
|
|
893
|
+
{/if}
|
|
894
|
+
</div>
|
|
895
|
+
</div>
|
|
896
|
+
</div>
|
|
897
|
+
</div>
|
|
898
|
+
{/if}
|
|
899
|
+
</div>
|
|
900
|
+
</div>
|