@rimelight/ui 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (310) hide show
  1. package/package.json +36 -6
  2. package/src/components/BlogPost.astro +181 -0
  3. package/src/components/DynamicShowcase.astro +681 -0
  4. package/src/components/ExtendedCarousel.astro +107 -0
  5. package/src/components/Head.astro +150 -0
  6. package/src/components/LanguageSwitcher.astro +128 -0
  7. package/src/components/ProjectPost.astro +183 -0
  8. package/src/components/RLHead.astro +3 -0
  9. package/src/components/ThemeToggle.astro +30 -42
  10. package/src/components/astro/RLAButton.astro +85 -0
  11. package/src/components/astro/RLAHeader.astro +75 -0
  12. package/src/components/vue/RLVButton.vue +90 -0
  13. package/src/components/vue/RLVHeader.vue +78 -0
  14. package/src/composables/index.ts +1 -0
  15. package/src/composables/useUi.ts +27 -0
  16. package/src/config/index.ts +0 -2
  17. package/src/config/site.config.ts +1 -1
  18. package/src/env.d.ts +9 -42
  19. package/src/integrations/index.ts +2 -0
  20. package/src/integrations/ui.ts +87 -0
  21. package/src/themes/button.theme.ts +361 -0
  22. package/src/themes/header.theme.ts +33 -0
  23. package/src/themes/index.ts +13 -0
  24. package/src/utils/index.ts +3 -2
  25. package/i18n-data.json +0 -186
  26. package/src/components/accordion/Accordion.astro +0 -257
  27. package/src/components/accordion/AccordionContent.astro +0 -34
  28. package/src/components/accordion/AccordionItem.astro +0 -29
  29. package/src/components/accordion/AccordionTrigger.astro +0 -33
  30. package/src/components/alert/Alert.astro +0 -34
  31. package/src/components/alert/AlertDescription.astro +0 -15
  32. package/src/components/alert/AlertTitle.astro +0 -17
  33. package/src/components/alert-dialog/AlertDialog.astro +0 -275
  34. package/src/components/alert-dialog/AlertDialogAction.astro +0 -46
  35. package/src/components/alert-dialog/AlertDialogCancel.astro +0 -46
  36. package/src/components/alert-dialog/AlertDialogContent.astro +0 -52
  37. package/src/components/alert-dialog/AlertDialogDescription.astro +0 -18
  38. package/src/components/alert-dialog/AlertDialogFooter.astro +0 -16
  39. package/src/components/alert-dialog/AlertDialogHeader.astro +0 -14
  40. package/src/components/alert-dialog/AlertDialogTitle.astro +0 -20
  41. package/src/components/alert-dialog/AlertDialogTrigger.astro +0 -47
  42. package/src/components/aspect-ratio/AspectRatio.astro +0 -32
  43. package/src/components/avatar/Avatar.astro +0 -29
  44. package/src/components/avatar/AvatarFallback.astro +0 -18
  45. package/src/components/avatar/AvatarImage.astro +0 -66
  46. package/src/components/badge/Badge.astro +0 -57
  47. package/src/components/breadcrumb/Breadcrumb.astro +0 -12
  48. package/src/components/breadcrumb/BreadcrumbEllipsis.astro +0 -29
  49. package/src/components/breadcrumb/BreadcrumbItem.astro +0 -15
  50. package/src/components/breadcrumb/BreadcrumbLink.astro +0 -24
  51. package/src/components/breadcrumb/BreadcrumbList.astro +0 -17
  52. package/src/components/breadcrumb/BreadcrumbPage.astro +0 -22
  53. package/src/components/breadcrumb/BreadcrumbSeparator.astro +0 -24
  54. package/src/components/button/Button.astro +0 -58
  55. package/src/components/button-group/ButtonGroup.astro +0 -64
  56. package/src/components/button-group/ButtonGroupSeparator.astro +0 -20
  57. package/src/components/button-group/ButtonGroupText.astro +0 -19
  58. package/src/components/card/Card.astro +0 -36
  59. package/src/components/card/CardAction.astro +0 -16
  60. package/src/components/card/CardContent.astro +0 -16
  61. package/src/components/card/CardDescription.astro +0 -16
  62. package/src/components/card/CardFooter.astro +0 -16
  63. package/src/components/card/CardHeader.astro +0 -19
  64. package/src/components/card/CardTitle.astro +0 -16
  65. package/src/components/carousel/Carousel.astro +0 -55
  66. package/src/components/carousel/CarouselContent.astro +0 -26
  67. package/src/components/carousel/CarouselItem.astro +0 -26
  68. package/src/components/carousel/CarouselNext.astro +0 -39
  69. package/src/components/carousel/CarouselPrevious.astro +0 -41
  70. package/src/components/carousel/carousel-script.ts +0 -188
  71. package/src/components/carousel/index.ts +0 -1
  72. package/src/components/checkbox/Checkbox.astro +0 -133
  73. package/src/components/collapsible/Collapsible.astro +0 -161
  74. package/src/components/collapsible/CollapsibleContent.astro +0 -22
  75. package/src/components/collapsible/CollapsibleTrigger.astro +0 -44
  76. package/src/components/dialog/Dialog.astro +0 -389
  77. package/src/components/dialog/DialogClose.astro +0 -35
  78. package/src/components/dialog/DialogContent.astro +0 -78
  79. package/src/components/dialog/DialogDescription.astro +0 -14
  80. package/src/components/dialog/DialogFooter.astro +0 -14
  81. package/src/components/dialog/DialogHeader.astro +0 -14
  82. package/src/components/dialog/DialogTitle.astro +0 -18
  83. package/src/components/dialog/DialogTrigger.astro +0 -47
  84. package/src/components/dropdown/Dropdown.astro +0 -604
  85. package/src/components/dropdown/DropdownContent.astro +0 -118
  86. package/src/components/dropdown/DropdownItem.astro +0 -48
  87. package/src/components/dropdown/DropdownLabel.astro +0 -29
  88. package/src/components/dropdown/DropdownSeparator.astro +0 -21
  89. package/src/components/dropdown/DropdownShortcut.astro +0 -17
  90. package/src/components/dropdown/DropdownSub.astro +0 -15
  91. package/src/components/dropdown/DropdownSubContent.astro +0 -39
  92. package/src/components/dropdown/DropdownSubTrigger.astro +0 -34
  93. package/src/components/dropdown/DropdownTrigger.astro +0 -52
  94. package/src/components/dropzone/Dropzone.astro +0 -236
  95. package/src/components/dropzone/DropzoneFilesList.astro +0 -26
  96. package/src/components/dropzone/DropzoneLoadingIndicator.astro +0 -10
  97. package/src/components/dropzone/DropzoneUploadIndicator.astro +0 -10
  98. package/src/components/embeds/YoutubeEmbed.astro +0 -94
  99. package/src/components/hero/Hero.astro +0 -166
  100. package/src/components/icons/Flag.astro +0 -62
  101. package/src/components/image/ExtendedImage.astro +0 -46
  102. package/src/components/image/Image.astro +0 -25
  103. package/src/components/input/Input.astro +0 -27
  104. package/src/components/input-group/InputGroup.astro +0 -28
  105. package/src/components/input-group/InputGroupAddon.astro +0 -76
  106. package/src/components/input-group/InputGroupButton.astro +0 -36
  107. package/src/components/input-group/InputGroupInput.astro +0 -18
  108. package/src/components/input-group/InputGroupText.astro +0 -17
  109. package/src/components/input-group/InputGroupTextarea.astro +0 -22
  110. package/src/components/input-otp/InputOtp.astro +0 -352
  111. package/src/components/input-otp/InputOtpGroup.astro +0 -16
  112. package/src/components/input-otp/InputOtpSeparator.astro +0 -25
  113. package/src/components/input-otp/InputOtpSlot.astro +0 -50
  114. package/src/components/input-otp/InputOtpTypes.ts +0 -6
  115. package/src/components/item/Item.astro +0 -52
  116. package/src/components/item/ItemActions.astro +0 -16
  117. package/src/components/item/ItemContent.astro +0 -16
  118. package/src/components/item/ItemDescription.astro +0 -19
  119. package/src/components/item/ItemFooter.astro +0 -16
  120. package/src/components/item/ItemGroup.astro +0 -16
  121. package/src/components/item/ItemHeader.astro +0 -16
  122. package/src/components/item/ItemMedia.astro +0 -40
  123. package/src/components/item/ItemSeparator.astro +0 -23
  124. package/src/components/item/ItemTitle.astro +0 -16
  125. package/src/components/kbd/Kbd.astro +0 -21
  126. package/src/components/kbd/KbdGroup.astro +0 -16
  127. package/src/components/label/Label.astro +0 -24
  128. package/src/components/marquee/Marquee.astro +0 -73
  129. package/src/components/native-select/NativeSelect.astro +0 -68
  130. package/src/components/native-select/NativeSelectOptGroup.astro +0 -15
  131. package/src/components/native-select/NativeSelectOption.astro +0 -15
  132. package/src/components/pagination/Pagination.astro +0 -20
  133. package/src/components/pagination/PaginationContent.astro +0 -16
  134. package/src/components/pagination/PaginationEllipsis.astro +0 -37
  135. package/src/components/pagination/PaginationItem.astro +0 -16
  136. package/src/components/pagination/PaginationLink.astro +0 -27
  137. package/src/components/pagination/PaginationNext.astro +0 -33
  138. package/src/components/pagination/PaginationPrevious.astro +0 -33
  139. package/src/components/popover/Popover.astro +0 -717
  140. package/src/components/popover/PopoverContent.astro +0 -102
  141. package/src/components/popover/PopoverDescription.astro +0 -14
  142. package/src/components/popover/PopoverHeader.astro +0 -14
  143. package/src/components/popover/PopoverTitle.astro +0 -14
  144. package/src/components/popover/PopoverTrigger.astro +0 -51
  145. package/src/components/progress/Progress.astro +0 -154
  146. package/src/components/prose/Prose.astro +0 -619
  147. package/src/components/radio-group/RadioGroup.astro +0 -162
  148. package/src/components/radio-group/RadioGroupItem.astro +0 -133
  149. package/src/components/radio-group/RadioGroupTypes.ts +0 -6
  150. package/src/components/section/Section.astro +0 -167
  151. package/src/components/select/Select.astro +0 -752
  152. package/src/components/select/SelectContent.astro +0 -94
  153. package/src/components/select/SelectGroup.astro +0 -9
  154. package/src/components/select/SelectItem.astro +0 -51
  155. package/src/components/select/SelectLabel.astro +0 -14
  156. package/src/components/select/SelectSearch.astro +0 -49
  157. package/src/components/select/SelectSeparator.astro +0 -12
  158. package/src/components/select/SelectTrigger.astro +0 -56
  159. package/src/components/select/SelectTypes.ts +0 -13
  160. package/src/components/select/SelectValue.astro +0 -19
  161. package/src/components/separator/Separator.astro +0 -36
  162. package/src/components/sheet/Sheet.astro +0 -15
  163. package/src/components/sheet/SheetClose.astro +0 -15
  164. package/src/components/sheet/SheetContent.astro +0 -89
  165. package/src/components/sheet/SheetDescription.astro +0 -16
  166. package/src/components/sheet/SheetFooter.astro +0 -16
  167. package/src/components/sheet/SheetHeader.astro +0 -16
  168. package/src/components/sheet/SheetTitle.astro +0 -16
  169. package/src/components/sheet/SheetTrigger.astro +0 -15
  170. package/src/components/sidebar/Sidebar.astro +0 -231
  171. package/src/components/sidebar/SidebarContent.astro +0 -25
  172. package/src/components/sidebar/SidebarFooter.astro +0 -22
  173. package/src/components/sidebar/SidebarGroup.astro +0 -22
  174. package/src/components/sidebar/SidebarGroupContent.astro +0 -22
  175. package/src/components/sidebar/SidebarGroupLabel.astro +0 -53
  176. package/src/components/sidebar/SidebarHeader.astro +0 -22
  177. package/src/components/sidebar/SidebarInput.astro +0 -24
  178. package/src/components/sidebar/SidebarInset.astro +0 -22
  179. package/src/components/sidebar/SidebarMenu.astro +0 -22
  180. package/src/components/sidebar/SidebarMenuAction.astro +0 -63
  181. package/src/components/sidebar/SidebarMenuBadge.astro +0 -31
  182. package/src/components/sidebar/SidebarMenuButton.astro +0 -124
  183. package/src/components/sidebar/SidebarMenuItem.astro +0 -22
  184. package/src/components/sidebar/SidebarMenuSkeleton.astro +0 -43
  185. package/src/components/sidebar/SidebarMenuSub.astro +0 -25
  186. package/src/components/sidebar/SidebarMenuSubButton.astro +0 -52
  187. package/src/components/sidebar/SidebarMenuSubItem.astro +0 -17
  188. package/src/components/sidebar/SidebarProvider.astro +0 -215
  189. package/src/components/sidebar/SidebarRail.astro +0 -72
  190. package/src/components/sidebar/SidebarSeparator.astro +0 -25
  191. package/src/components/sidebar/SidebarTrigger.astro +0 -69
  192. package/src/components/skeleton/Skeleton.astro +0 -14
  193. package/src/components/slider/Slider.astro +0 -425
  194. package/src/components/spinner/Spinner.astro +0 -23
  195. package/src/components/switch/Switch.astro +0 -192
  196. package/src/components/switch/SwitchTypes.ts +0 -6
  197. package/src/components/table/Table.astro +0 -20
  198. package/src/components/table/TableBody.astro +0 -18
  199. package/src/components/table/TableCaption.astro +0 -18
  200. package/src/components/table/TableCell.astro +0 -18
  201. package/src/components/table/TableFoot.astro +0 -18
  202. package/src/components/table/TableHead.astro +0 -18
  203. package/src/components/table/TableHeader.astro +0 -18
  204. package/src/components/table/TableRow.astro +0 -17
  205. package/src/components/tabs/Tabs.astro +0 -270
  206. package/src/components/tabs/TabsContent.astro +0 -28
  207. package/src/components/tabs/TabsList.astro +0 -23
  208. package/src/components/tabs/TabsTrigger.astro +0 -34
  209. package/src/components/textarea/Textarea.astro +0 -31
  210. package/src/components/toast/ToastDescription.astro +0 -22
  211. package/src/components/toast/ToastItem.astro +0 -56
  212. package/src/components/toast/ToastTemplate.astro +0 -27
  213. package/src/components/toast/ToastTitle.astro +0 -59
  214. package/src/components/toast/Toaster.astro +0 -981
  215. package/src/components/toast/index.ts +0 -1
  216. package/src/components/toast/toast-manager.ts +0 -234
  217. package/src/components/toggle/Toggle.astro +0 -174
  218. package/src/components/toggle/ToggleTypes.ts +0 -14
  219. package/src/components/tooltip/Tooltip.astro +0 -422
  220. package/src/components/tooltip/TooltipContent.astro +0 -83
  221. package/src/components/tooltip/TooltipTrigger.astro +0 -13
  222. package/src/components/video/Video.astro +0 -121
  223. package/src/config/cookies.ts +0 -5
  224. package/src/config/links.ts +0 -4
  225. package/src/domain/cms/index.ts +0 -1
  226. package/src/domain/cms/types.ts +0 -20
  227. package/src/domain/i18n/constants.ts +0 -21
  228. package/src/domain/i18n/cookie-storage.ts +0 -13
  229. package/src/domain/i18n/country-to-locale-map.ts +0 -67
  230. package/src/domain/i18n/fallbacks.generated.ts +0 -230
  231. package/src/domain/i18n/fetcher.ts +0 -278
  232. package/src/domain/i18n/flags.ts +0 -16
  233. package/src/domain/i18n/format.ts +0 -90
  234. package/src/domain/i18n/index.ts +0 -10
  235. package/src/domain/i18n/middleware/i18n.ts +0 -161
  236. package/src/domain/i18n/resolve-locale.ts +0 -55
  237. package/src/domain/i18n/runtime-constants.ts +0 -14
  238. package/src/domain/i18n/schema.ts +0 -29
  239. package/src/domain/index.ts +0 -4
  240. package/src/domain/seo/constants.ts +0 -6
  241. package/src/domain/seo/index.ts +0 -3
  242. package/src/domain/seo/services/index.ts +0 -3
  243. package/src/domain/seo/services/llms.ts +0 -56
  244. package/src/domain/seo/services/robots.ts +0 -12
  245. package/src/domain/seo/services/sitemap.ts +0 -56
  246. package/src/domain/seo/utils.ts +0 -36
  247. package/src/domain/theme/constants.ts +0 -6
  248. package/src/domain/theme/index.ts +0 -2
  249. package/src/domain/theme/schema.ts +0 -10
  250. package/src/i18n.generated.d.ts +0 -202
  251. package/src/lib/component-preview.ts +0 -73
  252. package/src/lib/index.ts +0 -6
  253. package/src/lib/remark/remark-modified-time.ts +0 -13
  254. package/src/lib/showcase-preview-elements.ts +0 -408
  255. package/src/lib/showcase-renderer.ts +0 -218
  256. package/src/lib/showcase-utils.ts +0 -189
  257. package/src/lib/turnstile.ts +0 -35
  258. package/src/lib/utils/starwind/positioning.ts +0 -318
  259. package/src/locales/de/blog.json +0 -9
  260. package/src/locales/de/common.json +0 -19
  261. package/src/locales/de/errors.json +0 -6
  262. package/src/locales/de/home.json +0 -20
  263. package/src/locales/de/messages.json +0 -8
  264. package/src/locales/de/nav.json +0 -13
  265. package/src/locales/de/projects.json +0 -9
  266. package/src/locales/de/services.json +0 -43
  267. package/src/locales/en/blog.json +0 -9
  268. package/src/locales/en/chat.json +0 -29
  269. package/src/locales/en/common.json +0 -19
  270. package/src/locales/en/cookie_notice.json +0 -11
  271. package/src/locales/en/editor.json +0 -24
  272. package/src/locales/en/errors.json +0 -6
  273. package/src/locales/en/home.json +0 -20
  274. package/src/locales/en/messages.json +0 -8
  275. package/src/locales/en/nav.json +0 -13
  276. package/src/locales/en/projects.json +0 -9
  277. package/src/locales/en/services.json +0 -43
  278. package/src/locales/es/blog.json +0 -9
  279. package/src/locales/es/common.json +0 -19
  280. package/src/locales/es/errors.json +0 -6
  281. package/src/locales/es/home.json +0 -20
  282. package/src/locales/es/messages.json +0 -8
  283. package/src/locales/es/nav.json +0 -13
  284. package/src/locales/es/projects.json +0 -9
  285. package/src/locales/es/services.json +0 -43
  286. package/src/locales/ja/blog.json +0 -9
  287. package/src/locales/ja/common.json +0 -19
  288. package/src/locales/ja/errors.json +0 -6
  289. package/src/locales/ja/home.json +0 -20
  290. package/src/locales/ja/messages.json +0 -8
  291. package/src/locales/ja/nav.json +0 -13
  292. package/src/locales/ja/projects.json +0 -9
  293. package/src/locales/ja/services.json +0 -43
  294. package/src/locales/pt/blog.json +0 -9
  295. package/src/locales/pt/chat.json +0 -29
  296. package/src/locales/pt/common.json +0 -19
  297. package/src/locales/pt/cookie_notice.json +0 -11
  298. package/src/locales/pt/editor.json +0 -24
  299. package/src/locales/pt/errors.json +0 -6
  300. package/src/locales/pt/home.json +0 -20
  301. package/src/locales/pt/messages.json +0 -8
  302. package/src/locales/pt/nav.json +0 -13
  303. package/src/locales/pt/projects.json +0 -9
  304. package/src/locales/pt/services.json +0 -43
  305. package/src/utils/server/cookies.ts +0 -98
  306. package/src/utils/server/index.ts +0 -1
  307. package/src/utils/shared/index.ts +0 -3
  308. /package/src/utils/{shared/deep-merge.ts → deep-merge.ts} +0 -0
  309. /package/src/utils/{shared/embed.ts → embed.ts} +0 -0
  310. /package/src/utils/{shared/parse-boolean.ts → parse-boolean.ts} +0 -0
@@ -1,981 +0,0 @@
1
- ---
2
- import type { HTMLAttributes } from "astro/types";
3
- import { cv } from "css-variants";
4
-
5
- import ToastTemplate from "./ToastTemplate.astro";
6
-
7
- export const toastViewport = cv({
8
- base: [
9
- "starwind-toast-viewport fixed z-50 flex w-5xl gap-5xs outline-none",
10
- "data-[position=bottom-center]:bottom-2xl data-[position=bottom-center]:left-1/2 data-[position=bottom-center]:-translate-x-1/2",
11
- "data-[position=bottom-left]:bottom-2xl data-[position=bottom-left]:left-2xl",
12
- "data-[position=bottom-right]:right-2xl data-[position=bottom-right]:bottom-2xl",
13
- "data-[position=top-center]:top-2xl data-[position=top-center]:left-1/2 data-[position=top-center]:-translate-x-1/2",
14
- "data-[position=top-left]:top-2xl data-[position=top-left]:left-2xl",
15
- "data-[position=top-right]:top-2xl data-[position=top-right]:right-2xl",
16
- ],
17
- });
18
-
19
- type Position =
20
- | "top-left"
21
- | "top-center"
22
- | "top-right"
23
- | "bottom-left"
24
- | "bottom-center"
25
- | "bottom-right";
26
-
27
- export interface ToasterProps extends HTMLAttributes<"div"> {
28
- position?: Position;
29
- limit?: number;
30
- gap?: string;
31
- peek?: string;
32
- duration?: number;
33
- }
34
-
35
- type Props = ToasterProps;
36
-
37
- const {
38
- class: className,
39
- position = "bottom-right",
40
- gap = "0.75rem",
41
- peek = "1.5rem",
42
- limit = 3,
43
- duration = 5000,
44
- ...rest
45
- } = Astro.props;
46
- ---
47
-
48
- <div
49
- class:list={[toastViewport(), className]}
50
- data-position={position}
51
- data-limit={limit}
52
- data-duration={duration}
53
- role="region"
54
- aria-live="polite"
55
- aria-atomic="false"
56
- aria-relevant="additions text"
57
- aria-label="Notifications"
58
- tabindex={-1}
59
- style={`--gap: ${gap}; --peek: ${peek};`}
60
- {...rest}
61
- data-slot="toast-viewport"
62
- >
63
- <!-- Hidden templates for each variant - JS will clone these -->
64
- <slot>
65
- <ToastTemplate variant="default" />
66
- <ToastTemplate variant="success" />
67
- <ToastTemplate variant="error" />
68
- <ToastTemplate variant="warning" />
69
- <ToastTemplate variant="info" />
70
- <ToastTemplate variant="loading" />
71
- </slot>
72
- </div>
73
-
74
- <style is:global>
75
- .starwind-toast-viewport [data-slot="toast"] {
76
- --scale: max(0.9, 1 - (var(--toast-index, 0) * 0.05));
77
-
78
- z-index: calc(1000 - var(--toast-index, 0));
79
- transform: translateX(var(--toast-swipe-movement-x, 0px))
80
- translateY(calc(var(--toast-swipe-movement-y, 0px) - (var(--toast-index, 0) * var(--peek))))
81
- scale(var(--scale));
82
- }
83
-
84
- .starwind-toast-viewport [data-slot="toast"][data-swiping] {
85
- transition: none;
86
- }
87
-
88
- .starwind-toast-viewport [data-slot="toast"][data-expanded] {
89
- --scale: 1;
90
-
91
- transform: translateX(var(--toast-swipe-movement-x, 0px))
92
- translateY(calc(var(--toast-swipe-movement-y, 0px) - var(--toast-offset-y, 0px)));
93
- }
94
-
95
- /* Exit animations by swipe direction */
96
- .starwind-toast-viewport [data-slot="toast"][data-state="closed"][data-swipe-direction="down"] {
97
- transform: translateY(calc(var(--toast-swipe-movement-y, 0px) + 150%));
98
- opacity: 0;
99
- }
100
-
101
- .starwind-toast-viewport [data-slot="toast"][data-state="closed"][data-swipe-direction="up"] {
102
- transform: translateY(calc(var(--toast-swipe-movement-y, 0px) - 150%));
103
- opacity: 0;
104
- }
105
-
106
- .starwind-toast-viewport [data-slot="toast"][data-state="closed"][data-swipe-direction="right"] {
107
- transform: translateX(calc(var(--toast-swipe-movement-x, 0px) + 150%))
108
- translateY(calc(var(--toast-offset-y, 0px) * -1));
109
- opacity: 0;
110
- }
111
-
112
- .starwind-toast-viewport [data-slot="toast"][data-state="closed"][data-swipe-direction="left"] {
113
- transform: translateX(calc(var(--toast-swipe-movement-x, 0px) - 150%))
114
- translateY(calc(var(--toast-offset-y, 0px) * -1));
115
- opacity: 0;
116
- }
117
-
118
- /* Position-specific overrides for top positions */
119
- .starwind-toast-viewport[data-position^="top"] [data-slot="toast"] {
120
- top: 0;
121
- bottom: auto;
122
- transform-origin: top center;
123
- transform: translateX(var(--toast-swipe-movement-x, 0px))
124
- translateY(calc(var(--toast-swipe-movement-y, 0px) + (var(--toast-index, 0) * var(--peek))))
125
- scale(var(--scale));
126
- }
127
-
128
- .starwind-toast-viewport[data-position^="top"] [data-slot="toast"][data-expanded] {
129
- transform: translateX(var(--toast-swipe-movement-x, 0px))
130
- translateY(calc(var(--toast-swipe-movement-y, 0px) + var(--toast-offset-y, 0px)));
131
- }
132
-
133
- .starwind-toast-viewport[data-position^="top"]
134
- [data-slot="toast"][data-state="closed"][data-swipe-direction="up"] {
135
- transform: translateY(calc(var(--toast-swipe-movement-y, 0px) - 150%));
136
- }
137
-
138
- .starwind-toast-viewport[data-position^="top"]
139
- [data-slot="toast"][data-state="closed"][data-swipe-direction="down"] {
140
- transform: translateY(calc(var(--toast-swipe-movement-y, 0px) + 150%));
141
- }
142
-
143
- .starwind-toast-viewport[data-position^="top"]
144
- [data-slot="toast"][data-state="closed"][data-swipe-direction="left"] {
145
- transform: translateX(calc(var(--toast-swipe-movement-x, 0px) - 150%))
146
- translateY(var(--toast-offset-y, 0px));
147
- opacity: 0;
148
- }
149
-
150
- .starwind-toast-viewport[data-position^="top"]
151
- [data-slot="toast"][data-state="closed"][data-swipe-direction="right"] {
152
- transform: translateX(calc(var(--toast-swipe-movement-x, 0px) + 150%))
153
- translateY(var(--toast-offset-y, 0px));
154
- opacity: 0;
155
- }
156
-
157
- /* Entry animation - starting state (must come after base transforms) */
158
- .starwind-toast-viewport [data-slot="toast"][data-starting-style] {
159
- transform: translateY(150%);
160
- opacity: 0;
161
- }
162
-
163
- /* Default exit animation - slide down (for non-swipe closes) */
164
- .starwind-toast-viewport [data-slot="toast"][data-state="closed"]:not([data-swipe-direction]) {
165
- transform: translateY(150%);
166
- opacity: 0;
167
- }
168
-
169
- /* Top position entry - slide from top */
170
- .starwind-toast-viewport[data-position^="top"] [data-slot="toast"][data-starting-style] {
171
- transform: translateY(-150%);
172
- opacity: 0;
173
- }
174
-
175
- /* Top position default exit - slide up */
176
- .starwind-toast-viewport[data-position^="top"]
177
- [data-slot="toast"][data-state="closed"]:not([data-swipe-direction]) {
178
- transform: translateY(-150%);
179
- opacity: 0;
180
- }
181
- </style>
182
-
183
- <script>
184
- type Variant = "default" | "success" | "error" | "warning" | "info" | "loading";
185
-
186
- interface ToastOptions {
187
- id?: string;
188
- title?: string;
189
- description?: string;
190
- variant?: Variant;
191
- duration?: number;
192
- action?: {
193
- label: string;
194
- onClick: () => void;
195
- };
196
- onClose?: () => void;
197
- onRemove?: () => void;
198
- }
199
-
200
- interface ToastState extends ToastOptions {
201
- id: string;
202
- element?: HTMLElement;
203
- timeoutId?: number | undefined;
204
- height?: number;
205
- closing?: boolean;
206
- // Swipe state
207
- swiping?: boolean;
208
- swipeX?: number;
209
- swipeY?: number;
210
- swipeDirection?: "up" | "down" | "left" | "right";
211
- }
212
-
213
- // Swipe constants
214
- const SWIPE_THRESHOLD = 40;
215
- const DAMPING_FACTOR = 0.1;
216
- const LONG_PRESS_DURATION = 300;
217
-
218
- type SwipeDirection = "up" | "down" | "left" | "right";
219
-
220
- class StarwindToastManager {
221
- readonly viewport: HTMLElement;
222
- private toasts: ToastState[] = [];
223
- private limit: number;
224
- private defaultDuration: number;
225
- private expanded: boolean = false;
226
- private expandedByTouch: boolean = false;
227
- private counter: number = 0;
228
- private swipeDirections: SwipeDirection[] = ["down", "right"];
229
- private destroyed: boolean = false;
230
-
231
- // Swipe tracking
232
- private activeSwipe: {
233
- toast: ToastState;
234
- startX: number;
235
- startY: number;
236
- startTime: number;
237
- didMove: boolean;
238
- lockedDirection: "horizontal" | "vertical" | null;
239
- } | null = null;
240
-
241
- // Bound event handlers for cleanup
242
- private boundHandlers: {
243
- mouseenter: () => void;
244
- mouseleave: (e: MouseEvent) => void;
245
- focusin: () => void;
246
- focusout: (e: FocusEvent) => void;
247
- documentMousemove: (e: MouseEvent) => void;
248
- documentPointerdown: (e: PointerEvent) => void;
249
- };
250
-
251
- constructor(viewport: HTMLElement) {
252
- this.viewport = viewport;
253
- this.limit = Number(viewport.dataset.limit) || 3;
254
- this.defaultDuration = Number(viewport.dataset.duration) || 5000;
255
-
256
- // Determine swipe directions based on position
257
- const position = viewport.dataset.position || "bottom-right";
258
- if (position.startsWith("top")) {
259
- this.swipeDirections = ["up", "right"];
260
- }
261
-
262
- // Initialize bound handlers
263
- this.boundHandlers = {
264
- mouseenter: this.handleMouseenter.bind(this),
265
- mouseleave: this.handleMouseleave.bind(this),
266
- focusin: this.handleFocusin.bind(this),
267
- focusout: this.handleFocusout.bind(this),
268
- documentMousemove: this.handleDocumentMousemove.bind(this),
269
- documentPointerdown: this.handleDocumentPointerdown.bind(this),
270
- };
271
-
272
- this.setupEvents();
273
- }
274
-
275
- private handleMouseenter(): void {
276
- this.expanded = true;
277
- this.updateExpanded();
278
- this.pauseAllTimers();
279
- }
280
-
281
- private handleMouseleave(e: MouseEvent): void {
282
- // Don't collapse if expanded by touch - let the document pointerdown handle it
283
- if (this.expandedByTouch) return;
284
-
285
- // Check if mouse is still within viewport bounds (handles case where toast is removed)
286
- const rect = this.viewport.getBoundingClientRect();
287
- const isStillInside =
288
- e.clientX >= rect.left &&
289
- e.clientX <= rect.right &&
290
- e.clientY >= rect.top &&
291
- e.clientY <= rect.bottom;
292
-
293
- if (!isStillInside) {
294
- this.expanded = false;
295
- this.updateExpanded();
296
- this.resumeAllTimers();
297
- }
298
- }
299
-
300
- private handleFocusin(): void {
301
- this.expanded = true;
302
- this.updateExpanded();
303
- this.pauseAllTimers();
304
- }
305
-
306
- private handleFocusout(e: FocusEvent): void {
307
- // Don't collapse if expanded by touch - let the document pointerdown handle it
308
- if (this.expandedByTouch) return;
309
-
310
- // Check if mouse is still over viewport before collapsing
311
- // This handles the case where focus leaves because a toast was closed
312
- const rect = this.viewport.getBoundingClientRect();
313
- const mouseEvent = (window as any).__lastMousePosition;
314
- const isMouseInside =
315
- mouseEvent &&
316
- mouseEvent.x >= rect.left &&
317
- mouseEvent.x <= rect.right &&
318
- mouseEvent.y >= rect.top &&
319
- mouseEvent.y <= rect.bottom;
320
-
321
- if (!this.viewport.contains(e.relatedTarget as Node) && !isMouseInside) {
322
- this.expanded = false;
323
- this.updateExpanded();
324
- this.resumeAllTimers();
325
- }
326
- }
327
-
328
- private handleDocumentMousemove(e: MouseEvent): void {
329
- (window as any).__lastMousePosition = { x: e.clientX, y: e.clientY };
330
- }
331
-
332
- private handleDocumentPointerdown(e: PointerEvent): void {
333
- if (!this.expandedByTouch || !this.expanded) return;
334
-
335
- const target = e.target as HTMLElement;
336
- if (!this.viewport.contains(target)) {
337
- this.expandedByTouch = false;
338
- this.expanded = false;
339
- this.updateExpanded();
340
- this.resumeAllTimers();
341
- }
342
- }
343
-
344
- private setupEvents(): void {
345
- this.viewport.addEventListener("mouseenter", this.boundHandlers.mouseenter);
346
- this.viewport.addEventListener("mouseleave", this.boundHandlers.mouseleave);
347
- this.viewport.addEventListener("focusin", this.boundHandlers.focusin);
348
- this.viewport.addEventListener("focusout", this.boundHandlers.focusout);
349
- document.addEventListener("mousemove", this.boundHandlers.documentMousemove);
350
- document.addEventListener("pointerdown", this.boundHandlers.documentPointerdown);
351
- }
352
-
353
- private removeEvents(): void {
354
- this.viewport.removeEventListener("mouseenter", this.boundHandlers.mouseenter);
355
- this.viewport.removeEventListener("mouseleave", this.boundHandlers.mouseleave);
356
- this.viewport.removeEventListener("focusin", this.boundHandlers.focusin);
357
- this.viewport.removeEventListener("focusout", this.boundHandlers.focusout);
358
- document.removeEventListener("mousemove", this.boundHandlers.documentMousemove);
359
- document.removeEventListener("pointerdown", this.boundHandlers.documentPointerdown);
360
- }
361
-
362
- /**
363
- * Destroy the manager and clean up all resources
364
- */
365
- public destroy(): void {
366
- if (this.destroyed) return;
367
- this.destroyed = true;
368
-
369
- // Clear all toasts and their timers
370
- this.toasts.forEach((toast) => {
371
- if (toast.timeoutId) {
372
- clearTimeout(toast.timeoutId);
373
- }
374
- toast.element?.remove();
375
- });
376
- this.toasts = [];
377
-
378
- // Remove all event listeners
379
- this.removeEvents();
380
-
381
- // Clear active swipe state
382
- this.activeSwipe = null;
383
-
384
- // Null out the scoped namespace entry
385
- if ((window as any).__starwind__?.toast === this) {
386
- (window as any).__starwind__.toast = null;
387
- }
388
- }
389
-
390
- private updateExpanded(): void {
391
- // Set expanded state on viewport
392
- if (this.expanded) {
393
- this.viewport.setAttribute("data-expanded", "");
394
- // Calculate total height needed for expanded state
395
- this.updateViewportHeight();
396
- } else {
397
- this.viewport.removeAttribute("data-expanded");
398
- this.viewport.style.removeProperty("height");
399
- }
400
-
401
- this.toasts.forEach((toast) => {
402
- if (toast.element) {
403
- const contentEl = toast.element.querySelector('[data-slot="toast-content"]');
404
- if (this.expanded) {
405
- toast.element.setAttribute("data-expanded", "");
406
- contentEl?.setAttribute("data-expanded", "");
407
- } else {
408
- toast.element.removeAttribute("data-expanded");
409
- contentEl?.removeAttribute("data-expanded");
410
- }
411
- }
412
- });
413
- this.updatePositions();
414
- }
415
-
416
- private updateViewportHeight(): void {
417
- if (!this.expanded || this.toasts.length === 0) return;
418
-
419
- const gapValue = this.getCssPixelValue("--gap");
420
- const visibleToasts = this.toasts.slice(0, this.limit);
421
- const totalHeight = visibleToasts.reduce((sum, toast) => sum + (toast.height || 0), 0);
422
- const totalGaps = (visibleToasts.length - 1) * gapValue;
423
-
424
- this.viewport.style.height = `${totalHeight + totalGaps}px`;
425
- }
426
-
427
- private getCssPixelValue(property: string): number {
428
- const value = getComputedStyle(this.viewport).getPropertyValue(property).trim();
429
- if (!value) return 16;
430
-
431
- // Create a temporary element to convert CSS value to pixels
432
- const temp = document.createElement("div");
433
- temp.style.position = "absolute";
434
- temp.style.visibility = "hidden";
435
- temp.style.width = value;
436
- document.body.appendChild(temp);
437
- const pixels = temp.offsetWidth;
438
- document.body.removeChild(temp);
439
- return pixels || 16;
440
- }
441
-
442
- private pauseAllTimers(): void {
443
- this.toasts.forEach((toast) => {
444
- if (toast.timeoutId) {
445
- clearTimeout(toast.timeoutId);
446
- toast.timeoutId = undefined;
447
- }
448
- });
449
- }
450
-
451
- private resumeAllTimers(): void {
452
- this.toasts.forEach((toast) => {
453
- const duration = toast.duration ?? this.defaultDuration;
454
- if (duration > 0) {
455
- toast.timeoutId = window.setTimeout(() => {
456
- this.close(toast.id);
457
- }, duration);
458
- }
459
- });
460
- }
461
-
462
- /**
463
- * Add a new toast
464
- */
465
- public add(options: ToastOptions): string {
466
- const id = options.id || `toast-${++this.counter}`;
467
- const duration = options.duration ?? this.defaultDuration;
468
-
469
- const toastState: ToastState = {
470
- ...options,
471
- id,
472
- duration,
473
- };
474
-
475
- // Always add the new toast at the front (newest first)
476
- this.renderToast(toastState);
477
- this.toasts.unshift(toastState);
478
-
479
- // updatePositions will handle hiding toasts beyond the limit via data-limited
480
- // They'll become visible again when other toasts close
481
-
482
- return id;
483
- }
484
-
485
- /**
486
- * Update an existing toast
487
- */
488
- public update(id: string, options: Partial<ToastOptions>): void {
489
- const toastIndex = this.toasts.findIndex((t) => t.id === id);
490
- if (toastIndex === -1) return;
491
-
492
- const toast = this.toasts[toastIndex]!;
493
- const oldVariant = toast.variant;
494
- Object.assign(toast, options);
495
-
496
- // If variant changed, we need to re-render the toast with new template
497
- if (options.variant && options.variant !== oldVariant && toast.element) {
498
- this.rerenderToast(toast);
499
- } else if (toast.element) {
500
- this.updateToastContent(toast);
501
- }
502
-
503
- // Restart timer if duration changed or variant changed (new default duration)
504
- if (toast.element && (options.duration !== undefined || options.variant)) {
505
- if (toast.timeoutId) {
506
- clearTimeout(toast.timeoutId);
507
- toast.timeoutId = undefined;
508
- }
509
- const duration = toast.duration ?? this.defaultDuration;
510
- if (duration > 0 && !this.expanded) {
511
- toast.timeoutId = window.setTimeout(() => {
512
- this.close(toast.id);
513
- }, duration);
514
- }
515
- }
516
- }
517
-
518
- /**
519
- * Close a toast
520
- */
521
- public close(id: string): void {
522
- const toast = this.toasts.find((t) => t.id === id);
523
- if (!toast) return;
524
-
525
- // Guard against closing a toast that's already closing
526
- if (toast.closing) return;
527
- toast.closing = true;
528
-
529
- if (toast.timeoutId) {
530
- clearTimeout(toast.timeoutId);
531
- toast.timeoutId = undefined;
532
- }
533
-
534
- toast.onClose?.();
535
-
536
- if (toast.element) {
537
- toast.element.setAttribute("data-state", "closed");
538
-
539
- setTimeout(() => {
540
- toast.element?.remove();
541
- toast.onRemove?.();
542
-
543
- // Find current index (may have changed since close was called)
544
- const currentIndex = this.toasts.findIndex((t) => t.id === id);
545
- if (currentIndex !== -1) {
546
- this.toasts.splice(currentIndex, 1);
547
- }
548
- this.updatePositions();
549
- }, 200);
550
- } else {
551
- // No element, just remove from array immediately
552
- const index = this.toasts.findIndex((t) => t.id === id);
553
- if (index !== -1) {
554
- this.toasts.splice(index, 1);
555
- }
556
- }
557
- }
558
-
559
- /**
560
- * Close all toasts
561
- */
562
- public closeAll(): void {
563
- [...this.toasts].forEach((toast) => this.close(toast.id));
564
- }
565
-
566
- private renderToast(toast: ToastState): void {
567
- const variant = toast.variant || "default";
568
- const template = this.viewport.querySelector(
569
- `template[data-toast-template="${variant}"]`,
570
- ) as HTMLTemplateElement;
571
-
572
- if (!template) {
573
- console.error(`Toast template for variant "${variant}" not found`);
574
- return;
575
- }
576
-
577
- const clone = template.content.cloneNode(true) as DocumentFragment;
578
- const element = clone.firstElementChild as HTMLElement;
579
-
580
- if (!element) return;
581
-
582
- element.setAttribute("data-toast-id", toast.id);
583
- element.setAttribute("data-starting-style", "");
584
-
585
- // Update title
586
- const titleEl = element.querySelector("[data-toast-title]");
587
- const titleTextEl = element.querySelector("[data-toast-title-text]");
588
- if (titleEl) {
589
- if (toast.title) {
590
- if (titleTextEl) titleTextEl.textContent = toast.title;
591
- } else {
592
- titleEl.remove();
593
- }
594
- }
595
-
596
- // Update description
597
- const descEl = element.querySelector("[data-toast-description]");
598
- if (descEl) {
599
- if (toast.description) {
600
- descEl.textContent = toast.description;
601
- } else {
602
- descEl.remove();
603
- }
604
- }
605
-
606
- // Setup close button
607
- const closeBtn = element.querySelector('[data-slot="toast-close"]');
608
- closeBtn?.addEventListener("click", () => this.close(toast.id));
609
-
610
- // Setup swipe handlers
611
- this.setupSwipeHandlers(element, toast);
612
-
613
- // Initialize swipe CSS variables
614
- element.style.setProperty("--toast-swipe-movement-x", "0px");
615
- element.style.setProperty("--toast-swipe-movement-y", "0px");
616
-
617
- // Insert at beginning (newest on top visually)
618
- this.viewport.insertBefore(element, this.viewport.firstElementChild);
619
- toast.element = element;
620
-
621
- // Trigger entry animation
622
- requestAnimationFrame(() => {
623
- toast.height = element.offsetHeight;
624
- this.updatePositions();
625
-
626
- // Remove starting style after a frame to trigger animation
627
- requestAnimationFrame(() => {
628
- element.removeAttribute("data-starting-style");
629
- });
630
- });
631
-
632
- // Start auto-dismiss timer
633
- const duration = toast.duration ?? this.defaultDuration;
634
- if (duration > 0 && !this.expanded) {
635
- toast.timeoutId = window.setTimeout(() => {
636
- this.close(toast.id);
637
- }, duration);
638
- }
639
- }
640
-
641
- private updateToastContent(toast: ToastState): void {
642
- if (!toast.element) return;
643
-
644
- const titleEl = toast.element.querySelector("[data-toast-title]");
645
- const descEl = toast.element.querySelector("[data-toast-description]");
646
-
647
- if (titleEl && toast.title) {
648
- const titleTextEl = titleEl.querySelector("[data-toast-title-text]");
649
- if (titleTextEl) titleTextEl.textContent = toast.title;
650
- }
651
- if (descEl && toast.description) {
652
- descEl.textContent = toast.description;
653
- }
654
- }
655
-
656
- /**
657
- * Re-render a toast with a new template (for variant changes)
658
- */
659
- private rerenderToast(toast: ToastState): void {
660
- if (!toast.element) return;
661
-
662
- const variant = toast.variant || "default";
663
- const template = this.viewport.querySelector(
664
- `template[data-toast-template="${variant}"]`,
665
- ) as HTMLTemplateElement;
666
-
667
- if (!template) {
668
- console.error(`Toast template for variant "${variant}" not found`);
669
- return;
670
- }
671
-
672
- const clone = template.content.cloneNode(true) as DocumentFragment;
673
- const newElement = clone.firstElementChild as HTMLElement;
674
-
675
- if (!newElement) return;
676
-
677
- // Copy over attributes and state from old element
678
- newElement.setAttribute("data-toast-id", toast.id);
679
- newElement.setAttribute("data-state", "open");
680
- if (this.expanded) {
681
- newElement.setAttribute("data-expanded", "");
682
- }
683
-
684
- // Copy CSS variables
685
- const oldStyle = toast.element.style;
686
- newElement.style.setProperty("--toast-index", oldStyle.getPropertyValue("--toast-index"));
687
- newElement.style.setProperty(
688
- "--toast-offset-y",
689
- oldStyle.getPropertyValue("--toast-offset-y"),
690
- );
691
- newElement.style.setProperty("--toast-swipe-movement-x", "0px");
692
- newElement.style.setProperty("--toast-swipe-movement-y", "0px");
693
-
694
- // Update title
695
- const titleTextEl = newElement.querySelector("[data-toast-title-text]");
696
- if (titleTextEl && toast.title) {
697
- titleTextEl.textContent = toast.title;
698
- }
699
-
700
- // Update description
701
- const descEl = newElement.querySelector("[data-toast-description]");
702
- if (descEl && toast.description) {
703
- descEl.textContent = toast.description;
704
- }
705
-
706
- // Setup close button
707
- const closeBtn = newElement.querySelector('[data-slot="toast-close"]');
708
- closeBtn?.addEventListener("click", () => this.close(toast.id));
709
-
710
- // Setup swipe handlers
711
- this.setupSwipeHandlers(newElement, toast);
712
-
713
- // Replace old element with new one
714
- toast.element.replaceWith(newElement);
715
- toast.element = newElement;
716
-
717
- // Update height
718
- toast.height = newElement.offsetHeight;
719
- }
720
-
721
- private updatePositions(): void {
722
- let cumulativeOffset = 0;
723
-
724
- // Iterate through toasts - index 0 is newest (front)
725
- this.toasts.forEach((toast, index) => {
726
- if (!toast.element) return;
727
-
728
- const contentEl = toast.element.querySelector('[data-slot="toast-content"]');
729
-
730
- // Set CSS variables for this toast
731
- toast.element.style.setProperty("--toast-index", String(index));
732
- toast.element.style.setProperty("--toast-offset-y", `${cumulativeOffset}px`);
733
-
734
- // Set data-behind on content for toasts behind the frontmost
735
- if (index > 0) {
736
- contentEl?.setAttribute("data-behind", "");
737
- } else {
738
- contentEl?.removeAttribute("data-behind");
739
- }
740
-
741
- // Mark as limited if beyond limit
742
- if (index >= this.limit) {
743
- toast.element.setAttribute("data-limited", "");
744
- } else {
745
- toast.element.removeAttribute("data-limited");
746
- }
747
-
748
- // Accumulate offset for expanded state (height + gap)
749
- const gapValue = this.getCssPixelValue("--gap");
750
- cumulativeOffset += (toast.height || 0) + gapValue;
751
- });
752
- }
753
-
754
- // Swipe handlers
755
- private handlePointerDown(e: PointerEvent, toast: ToastState): void {
756
- if (e.button !== 0) return;
757
-
758
- const target = e.target as HTMLElement;
759
- const isInteractive = target.closest(
760
- 'button, a, input, textarea, [role="button"], [data-swipe-ignore]',
761
- );
762
- if (isInteractive) return;
763
-
764
- // Treat touch/pointer down as hover to expand stacked toasts
765
- // Always mark as touch-expanded for long press detection
766
- this.expandedByTouch = true;
767
- if (!this.expanded) {
768
- this.expanded = true;
769
- this.updateExpanded();
770
- this.pauseAllTimers();
771
- }
772
-
773
- this.activeSwipe = {
774
- toast,
775
- startX: e.clientX,
776
- startY: e.clientY,
777
- startTime: Date.now(),
778
- didMove: false,
779
- lockedDirection: null,
780
- };
781
-
782
- toast.swiping = true;
783
- toast.swipeX = 0;
784
- toast.swipeY = 0;
785
- toast.element?.setAttribute("data-swiping", "");
786
- toast.element?.setPointerCapture(e.pointerId);
787
-
788
- // Pause timer while swiping
789
- if (toast.timeoutId) {
790
- clearTimeout(toast.timeoutId);
791
- toast.timeoutId = undefined;
792
- }
793
- }
794
-
795
- private handlePointerMove(e: PointerEvent): void {
796
- if (!this.activeSwipe) return;
797
-
798
- e.preventDefault();
799
-
800
- const { toast, startX, startY } = this.activeSwipe;
801
- const deltaX = e.clientX - startX;
802
- const deltaY = e.clientY - startY;
803
-
804
- // Lock direction on first significant movement
805
- if (!this.activeSwipe.lockedDirection) {
806
- const absX = Math.abs(deltaX);
807
- const absY = Math.abs(deltaY);
808
- if (absX > 5 || absY > 5) {
809
- this.activeSwipe.didMove = true;
810
- const hasHorizontal =
811
- this.swipeDirections.includes("left") || this.swipeDirections.includes("right");
812
- const hasVertical =
813
- this.swipeDirections.includes("up") || this.swipeDirections.includes("down");
814
- if (hasHorizontal && hasVertical) {
815
- this.activeSwipe.lockedDirection = absX > absY ? "horizontal" : "vertical";
816
- } else if (hasHorizontal) {
817
- this.activeSwipe.lockedDirection = "horizontal";
818
- } else {
819
- this.activeSwipe.lockedDirection = "vertical";
820
- }
821
- }
822
- }
823
-
824
- // Apply damping for non-allowed directions
825
- let swipeX = 0;
826
- let swipeY = 0;
827
-
828
- if (this.activeSwipe.lockedDirection === "horizontal") {
829
- if (this.swipeDirections.includes("right") && deltaX > 0) {
830
- swipeX = deltaX;
831
- } else if (this.swipeDirections.includes("left") && deltaX < 0) {
832
- swipeX = deltaX;
833
- } else {
834
- swipeX = deltaX * DAMPING_FACTOR;
835
- }
836
- } else if (this.activeSwipe.lockedDirection === "vertical") {
837
- if (this.swipeDirections.includes("down") && deltaY > 0) {
838
- swipeY = deltaY;
839
- } else if (this.swipeDirections.includes("up") && deltaY < 0) {
840
- swipeY = deltaY;
841
- } else {
842
- swipeY = deltaY * DAMPING_FACTOR;
843
- }
844
- }
845
-
846
- toast.swipeX = swipeX;
847
- toast.swipeY = swipeY;
848
- toast.element?.style.setProperty("--toast-swipe-movement-x", `${swipeX}px`);
849
- toast.element?.style.setProperty("--toast-swipe-movement-y", `${swipeY}px`);
850
- }
851
-
852
- private handlePointerUp(e: PointerEvent): void {
853
- if (!this.activeSwipe) return;
854
-
855
- const { toast } = this.activeSwipe;
856
- toast.element?.releasePointerCapture(e.pointerId);
857
- toast.element?.removeAttribute("data-swiping");
858
- toast.swiping = false;
859
-
860
- const swipeX = toast.swipeX || 0;
861
- const swipeY = toast.swipeY || 0;
862
-
863
- // Check if swipe exceeds threshold based on locked direction
864
- let shouldClose = false;
865
- let direction: SwipeDirection | undefined;
866
- const lockedDir = this.activeSwipe.lockedDirection;
867
-
868
- if (lockedDir === "horizontal") {
869
- if (swipeX > SWIPE_THRESHOLD && this.swipeDirections.includes("right")) {
870
- shouldClose = true;
871
- direction = "right";
872
- } else if (swipeX < -SWIPE_THRESHOLD && this.swipeDirections.includes("left")) {
873
- shouldClose = true;
874
- direction = "left";
875
- }
876
- } else if (lockedDir === "vertical") {
877
- if (swipeY > SWIPE_THRESHOLD && this.swipeDirections.includes("down")) {
878
- shouldClose = true;
879
- direction = "down";
880
- } else if (swipeY < -SWIPE_THRESHOLD && this.swipeDirections.includes("up")) {
881
- shouldClose = true;
882
- direction = "up";
883
- }
884
- }
885
-
886
- // Check if this was a long press or if user moved (swiped)
887
- const pressDuration = Date.now() - this.activeSwipe.startTime;
888
- const isLongPress = pressDuration >= LONG_PRESS_DURATION;
889
- const didMove = this.activeSwipe.didMove;
890
-
891
- // Check if pointer is outside viewport bounds
892
- const rect = this.viewport.getBoundingClientRect();
893
- const isOutsideViewport =
894
- e.clientX < rect.left ||
895
- e.clientX > rect.right ||
896
- e.clientY < rect.top ||
897
- e.clientY > rect.bottom;
898
-
899
- if (shouldClose && direction) {
900
- toast.swipeDirection = direction;
901
- toast.element?.setAttribute("data-swipe-direction", direction);
902
- this.close(toast.id);
903
- } else {
904
- // Reset position
905
- toast.swipeX = 0;
906
- toast.swipeY = 0;
907
- toast.element?.style.setProperty("--toast-swipe-movement-x", "0px");
908
- toast.element?.style.setProperty("--toast-swipe-movement-y", "0px");
909
-
910
- // Collapse if: short tap without movement, OR pointer ended outside viewport
911
- if ((!isLongPress && !didMove && this.expandedByTouch) || isOutsideViewport) {
912
- this.expandedByTouch = false;
913
- this.expanded = false;
914
- this.updateExpanded();
915
- this.resumeAllTimers();
916
- } else {
917
- // Resume timer for this toast only
918
- const duration = toast.duration ?? this.defaultDuration;
919
- if (duration > 0 && !this.expanded) {
920
- toast.timeoutId = window.setTimeout(() => {
921
- this.close(toast.id);
922
- }, duration);
923
- }
924
- }
925
- }
926
-
927
- this.activeSwipe = null;
928
- }
929
-
930
- private setupSwipeHandlers(element: HTMLElement, toast: ToastState): void {
931
- element.addEventListener("pointerdown", (e) => this.handlePointerDown(e, toast));
932
- element.addEventListener("pointermove", (e) => this.handlePointerMove(e));
933
- element.addEventListener("pointerup", (e) => this.handlePointerUp(e));
934
- element.addEventListener("pointercancel", (e) => this.handlePointerUp(e));
935
-
936
- // Prevent default touch behavior to avoid scroll interference
937
- element.addEventListener(
938
- "touchmove",
939
- (e) => {
940
- if (this.activeSwipe?.toast === toast) {
941
- e.preventDefault();
942
- }
943
- },
944
- { passive: false },
945
- );
946
- }
947
- }
948
-
949
- // Initialize scoped namespace
950
- (window as any).__starwind__ = (window as any).__starwind__ || {};
951
-
952
- let currentManager: StarwindToastManager | null = null;
953
-
954
- const setupToasts = () => {
955
- // Find the viewport in the current DOM
956
- const viewport = document.querySelector(".starwind-toast-viewport") as HTMLElement | null;
957
-
958
- if (!viewport) {
959
- // No viewport on this page, destroy existing manager and clear references
960
- currentManager?.destroy();
961
- currentManager = null;
962
- return;
963
- }
964
-
965
- // Check if we already have a manager for this exact element
966
- if (currentManager && currentManager.viewport === viewport) {
967
- return;
968
- }
969
-
970
- // Destroy previous manager before creating new one (viewport differs)
971
- currentManager?.destroy();
972
-
973
- // Create new manager for the viewport
974
- currentManager = new StarwindToastManager(viewport);
975
- (window as any).__starwind__.toast = currentManager;
976
- };
977
-
978
- setupToasts();
979
- document.addEventListener("astro:after-swap", setupToasts);
980
- document.addEventListener("starwind:init", setupToasts);
981
- </script>