@rimelight/ui 0.0.5 → 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 (308) hide show
  1. package/package.json +17 -8
  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 +32 -42
  11. package/src/components/astro/RLAHeader.astro +75 -0
  12. package/src/components/vue/RLVButton.vue +50 -37
  13. package/src/components/vue/RLVHeader.vue +78 -0
  14. package/src/composables/index.ts +1 -1
  15. package/src/composables/{useComponentUI.ts → useUi.ts} +2 -2
  16. package/src/config/index.ts +0 -2
  17. package/src/config/site.config.ts +1 -1
  18. package/src/integrations/ui.ts +6 -0
  19. package/src/themes/button.theme.ts +350 -34
  20. package/src/themes/header.theme.ts +33 -0
  21. package/src/themes/index.ts +3 -1
  22. package/src/utils/index.ts +3 -2
  23. package/i18n-data.json +0 -186
  24. package/src/components/accordion/Accordion.astro +0 -257
  25. package/src/components/accordion/AccordionContent.astro +0 -34
  26. package/src/components/accordion/AccordionItem.astro +0 -29
  27. package/src/components/accordion/AccordionTrigger.astro +0 -33
  28. package/src/components/alert/Alert.astro +0 -34
  29. package/src/components/alert/AlertDescription.astro +0 -15
  30. package/src/components/alert/AlertTitle.astro +0 -17
  31. package/src/components/alert-dialog/AlertDialog.astro +0 -275
  32. package/src/components/alert-dialog/AlertDialogAction.astro +0 -46
  33. package/src/components/alert-dialog/AlertDialogCancel.astro +0 -46
  34. package/src/components/alert-dialog/AlertDialogContent.astro +0 -52
  35. package/src/components/alert-dialog/AlertDialogDescription.astro +0 -18
  36. package/src/components/alert-dialog/AlertDialogFooter.astro +0 -16
  37. package/src/components/alert-dialog/AlertDialogHeader.astro +0 -14
  38. package/src/components/alert-dialog/AlertDialogTitle.astro +0 -20
  39. package/src/components/alert-dialog/AlertDialogTrigger.astro +0 -47
  40. package/src/components/aspect-ratio/AspectRatio.astro +0 -32
  41. package/src/components/avatar/Avatar.astro +0 -29
  42. package/src/components/avatar/AvatarFallback.astro +0 -18
  43. package/src/components/avatar/AvatarImage.astro +0 -66
  44. package/src/components/badge/Badge.astro +0 -57
  45. package/src/components/breadcrumb/Breadcrumb.astro +0 -12
  46. package/src/components/breadcrumb/BreadcrumbEllipsis.astro +0 -29
  47. package/src/components/breadcrumb/BreadcrumbItem.astro +0 -15
  48. package/src/components/breadcrumb/BreadcrumbLink.astro +0 -24
  49. package/src/components/breadcrumb/BreadcrumbList.astro +0 -17
  50. package/src/components/breadcrumb/BreadcrumbPage.astro +0 -22
  51. package/src/components/breadcrumb/BreadcrumbSeparator.astro +0 -24
  52. package/src/components/button/Button.astro +0 -58
  53. package/src/components/button-group/ButtonGroup.astro +0 -64
  54. package/src/components/button-group/ButtonGroupSeparator.astro +0 -20
  55. package/src/components/button-group/ButtonGroupText.astro +0 -19
  56. package/src/components/card/Card.astro +0 -36
  57. package/src/components/card/CardAction.astro +0 -16
  58. package/src/components/card/CardContent.astro +0 -16
  59. package/src/components/card/CardDescription.astro +0 -16
  60. package/src/components/card/CardFooter.astro +0 -16
  61. package/src/components/card/CardHeader.astro +0 -19
  62. package/src/components/card/CardTitle.astro +0 -16
  63. package/src/components/carousel/Carousel.astro +0 -55
  64. package/src/components/carousel/CarouselContent.astro +0 -26
  65. package/src/components/carousel/CarouselItem.astro +0 -26
  66. package/src/components/carousel/CarouselNext.astro +0 -39
  67. package/src/components/carousel/CarouselPrevious.astro +0 -41
  68. package/src/components/carousel/carousel-script.ts +0 -188
  69. package/src/components/carousel/index.ts +0 -1
  70. package/src/components/checkbox/Checkbox.astro +0 -133
  71. package/src/components/collapsible/Collapsible.astro +0 -161
  72. package/src/components/collapsible/CollapsibleContent.astro +0 -22
  73. package/src/components/collapsible/CollapsibleTrigger.astro +0 -44
  74. package/src/components/dialog/Dialog.astro +0 -389
  75. package/src/components/dialog/DialogClose.astro +0 -35
  76. package/src/components/dialog/DialogContent.astro +0 -78
  77. package/src/components/dialog/DialogDescription.astro +0 -14
  78. package/src/components/dialog/DialogFooter.astro +0 -14
  79. package/src/components/dialog/DialogHeader.astro +0 -14
  80. package/src/components/dialog/DialogTitle.astro +0 -18
  81. package/src/components/dialog/DialogTrigger.astro +0 -47
  82. package/src/components/dropdown/Dropdown.astro +0 -604
  83. package/src/components/dropdown/DropdownContent.astro +0 -118
  84. package/src/components/dropdown/DropdownItem.astro +0 -48
  85. package/src/components/dropdown/DropdownLabel.astro +0 -29
  86. package/src/components/dropdown/DropdownSeparator.astro +0 -21
  87. package/src/components/dropdown/DropdownShortcut.astro +0 -17
  88. package/src/components/dropdown/DropdownSub.astro +0 -15
  89. package/src/components/dropdown/DropdownSubContent.astro +0 -39
  90. package/src/components/dropdown/DropdownSubTrigger.astro +0 -34
  91. package/src/components/dropdown/DropdownTrigger.astro +0 -52
  92. package/src/components/dropzone/Dropzone.astro +0 -236
  93. package/src/components/dropzone/DropzoneFilesList.astro +0 -26
  94. package/src/components/dropzone/DropzoneLoadingIndicator.astro +0 -10
  95. package/src/components/dropzone/DropzoneUploadIndicator.astro +0 -10
  96. package/src/components/embeds/YoutubeEmbed.astro +0 -94
  97. package/src/components/hero/Hero.astro +0 -166
  98. package/src/components/icons/Flag.astro +0 -62
  99. package/src/components/image/ExtendedImage.astro +0 -46
  100. package/src/components/image/Image.astro +0 -25
  101. package/src/components/input/Input.astro +0 -27
  102. package/src/components/input-group/InputGroup.astro +0 -28
  103. package/src/components/input-group/InputGroupAddon.astro +0 -76
  104. package/src/components/input-group/InputGroupButton.astro +0 -36
  105. package/src/components/input-group/InputGroupInput.astro +0 -18
  106. package/src/components/input-group/InputGroupText.astro +0 -17
  107. package/src/components/input-group/InputGroupTextarea.astro +0 -22
  108. package/src/components/input-otp/InputOtp.astro +0 -352
  109. package/src/components/input-otp/InputOtpGroup.astro +0 -16
  110. package/src/components/input-otp/InputOtpSeparator.astro +0 -25
  111. package/src/components/input-otp/InputOtpSlot.astro +0 -50
  112. package/src/components/input-otp/InputOtpTypes.ts +0 -6
  113. package/src/components/item/Item.astro +0 -52
  114. package/src/components/item/ItemActions.astro +0 -16
  115. package/src/components/item/ItemContent.astro +0 -16
  116. package/src/components/item/ItemDescription.astro +0 -19
  117. package/src/components/item/ItemFooter.astro +0 -16
  118. package/src/components/item/ItemGroup.astro +0 -16
  119. package/src/components/item/ItemHeader.astro +0 -16
  120. package/src/components/item/ItemMedia.astro +0 -40
  121. package/src/components/item/ItemSeparator.astro +0 -23
  122. package/src/components/item/ItemTitle.astro +0 -16
  123. package/src/components/kbd/Kbd.astro +0 -21
  124. package/src/components/kbd/KbdGroup.astro +0 -16
  125. package/src/components/label/Label.astro +0 -24
  126. package/src/components/marquee/Marquee.astro +0 -73
  127. package/src/components/native-select/NativeSelect.astro +0 -68
  128. package/src/components/native-select/NativeSelectOptGroup.astro +0 -15
  129. package/src/components/native-select/NativeSelectOption.astro +0 -15
  130. package/src/components/pagination/Pagination.astro +0 -20
  131. package/src/components/pagination/PaginationContent.astro +0 -16
  132. package/src/components/pagination/PaginationEllipsis.astro +0 -37
  133. package/src/components/pagination/PaginationItem.astro +0 -16
  134. package/src/components/pagination/PaginationLink.astro +0 -27
  135. package/src/components/pagination/PaginationNext.astro +0 -33
  136. package/src/components/pagination/PaginationPrevious.astro +0 -33
  137. package/src/components/popover/Popover.astro +0 -717
  138. package/src/components/popover/PopoverContent.astro +0 -102
  139. package/src/components/popover/PopoverDescription.astro +0 -14
  140. package/src/components/popover/PopoverHeader.astro +0 -14
  141. package/src/components/popover/PopoverTitle.astro +0 -14
  142. package/src/components/popover/PopoverTrigger.astro +0 -51
  143. package/src/components/progress/Progress.astro +0 -154
  144. package/src/components/prose/Prose.astro +0 -619
  145. package/src/components/radio-group/RadioGroup.astro +0 -162
  146. package/src/components/radio-group/RadioGroupItem.astro +0 -133
  147. package/src/components/radio-group/RadioGroupTypes.ts +0 -6
  148. package/src/components/section/Section.astro +0 -167
  149. package/src/components/select/Select.astro +0 -752
  150. package/src/components/select/SelectContent.astro +0 -94
  151. package/src/components/select/SelectGroup.astro +0 -9
  152. package/src/components/select/SelectItem.astro +0 -51
  153. package/src/components/select/SelectLabel.astro +0 -14
  154. package/src/components/select/SelectSearch.astro +0 -49
  155. package/src/components/select/SelectSeparator.astro +0 -12
  156. package/src/components/select/SelectTrigger.astro +0 -56
  157. package/src/components/select/SelectTypes.ts +0 -13
  158. package/src/components/select/SelectValue.astro +0 -19
  159. package/src/components/separator/Separator.astro +0 -36
  160. package/src/components/sheet/Sheet.astro +0 -15
  161. package/src/components/sheet/SheetClose.astro +0 -15
  162. package/src/components/sheet/SheetContent.astro +0 -89
  163. package/src/components/sheet/SheetDescription.astro +0 -16
  164. package/src/components/sheet/SheetFooter.astro +0 -16
  165. package/src/components/sheet/SheetHeader.astro +0 -16
  166. package/src/components/sheet/SheetTitle.astro +0 -16
  167. package/src/components/sheet/SheetTrigger.astro +0 -15
  168. package/src/components/sidebar/Sidebar.astro +0 -231
  169. package/src/components/sidebar/SidebarContent.astro +0 -25
  170. package/src/components/sidebar/SidebarFooter.astro +0 -22
  171. package/src/components/sidebar/SidebarGroup.astro +0 -22
  172. package/src/components/sidebar/SidebarGroupContent.astro +0 -22
  173. package/src/components/sidebar/SidebarGroupLabel.astro +0 -53
  174. package/src/components/sidebar/SidebarHeader.astro +0 -22
  175. package/src/components/sidebar/SidebarInput.astro +0 -24
  176. package/src/components/sidebar/SidebarInset.astro +0 -22
  177. package/src/components/sidebar/SidebarMenu.astro +0 -22
  178. package/src/components/sidebar/SidebarMenuAction.astro +0 -63
  179. package/src/components/sidebar/SidebarMenuBadge.astro +0 -31
  180. package/src/components/sidebar/SidebarMenuButton.astro +0 -124
  181. package/src/components/sidebar/SidebarMenuItem.astro +0 -22
  182. package/src/components/sidebar/SidebarMenuSkeleton.astro +0 -43
  183. package/src/components/sidebar/SidebarMenuSub.astro +0 -25
  184. package/src/components/sidebar/SidebarMenuSubButton.astro +0 -52
  185. package/src/components/sidebar/SidebarMenuSubItem.astro +0 -17
  186. package/src/components/sidebar/SidebarProvider.astro +0 -215
  187. package/src/components/sidebar/SidebarRail.astro +0 -72
  188. package/src/components/sidebar/SidebarSeparator.astro +0 -25
  189. package/src/components/sidebar/SidebarTrigger.astro +0 -69
  190. package/src/components/skeleton/Skeleton.astro +0 -14
  191. package/src/components/slider/Slider.astro +0 -425
  192. package/src/components/spinner/Spinner.astro +0 -23
  193. package/src/components/switch/Switch.astro +0 -192
  194. package/src/components/switch/SwitchTypes.ts +0 -6
  195. package/src/components/table/Table.astro +0 -20
  196. package/src/components/table/TableBody.astro +0 -18
  197. package/src/components/table/TableCaption.astro +0 -18
  198. package/src/components/table/TableCell.astro +0 -18
  199. package/src/components/table/TableFoot.astro +0 -18
  200. package/src/components/table/TableHead.astro +0 -18
  201. package/src/components/table/TableHeader.astro +0 -18
  202. package/src/components/table/TableRow.astro +0 -17
  203. package/src/components/tabs/Tabs.astro +0 -270
  204. package/src/components/tabs/TabsContent.astro +0 -28
  205. package/src/components/tabs/TabsList.astro +0 -23
  206. package/src/components/tabs/TabsTrigger.astro +0 -34
  207. package/src/components/textarea/Textarea.astro +0 -31
  208. package/src/components/toast/ToastDescription.astro +0 -22
  209. package/src/components/toast/ToastItem.astro +0 -56
  210. package/src/components/toast/ToastTemplate.astro +0 -27
  211. package/src/components/toast/ToastTitle.astro +0 -59
  212. package/src/components/toast/Toaster.astro +0 -981
  213. package/src/components/toast/index.ts +0 -1
  214. package/src/components/toast/toast-manager.ts +0 -234
  215. package/src/components/toggle/Toggle.astro +0 -174
  216. package/src/components/toggle/ToggleTypes.ts +0 -14
  217. package/src/components/tooltip/Tooltip.astro +0 -422
  218. package/src/components/tooltip/TooltipContent.astro +0 -83
  219. package/src/components/tooltip/TooltipTrigger.astro +0 -13
  220. package/src/components/video/Video.astro +0 -121
  221. package/src/config/cookies.ts +0 -5
  222. package/src/config/links.ts +0 -4
  223. package/src/domain/cms/index.ts +0 -1
  224. package/src/domain/cms/types.ts +0 -20
  225. package/src/domain/i18n/constants.ts +0 -21
  226. package/src/domain/i18n/cookie-storage.ts +0 -13
  227. package/src/domain/i18n/country-to-locale-map.ts +0 -67
  228. package/src/domain/i18n/fallbacks.generated.ts +0 -230
  229. package/src/domain/i18n/fetcher.ts +0 -278
  230. package/src/domain/i18n/flags.ts +0 -16
  231. package/src/domain/i18n/format.ts +0 -90
  232. package/src/domain/i18n/index.ts +0 -10
  233. package/src/domain/i18n/middleware/i18n.ts +0 -161
  234. package/src/domain/i18n/resolve-locale.ts +0 -55
  235. package/src/domain/i18n/runtime-constants.ts +0 -14
  236. package/src/domain/i18n/schema.ts +0 -29
  237. package/src/domain/index.ts +0 -4
  238. package/src/domain/seo/constants.ts +0 -6
  239. package/src/domain/seo/index.ts +0 -3
  240. package/src/domain/seo/services/index.ts +0 -3
  241. package/src/domain/seo/services/llms.ts +0 -56
  242. package/src/domain/seo/services/robots.ts +0 -12
  243. package/src/domain/seo/services/sitemap.ts +0 -56
  244. package/src/domain/seo/utils.ts +0 -36
  245. package/src/domain/theme/constants.ts +0 -6
  246. package/src/domain/theme/index.ts +0 -2
  247. package/src/domain/theme/schema.ts +0 -10
  248. package/src/i18n.generated.d.ts +0 -202
  249. package/src/lib/component-preview.ts +0 -73
  250. package/src/lib/index.ts +0 -6
  251. package/src/lib/remark/remark-modified-time.ts +0 -13
  252. package/src/lib/showcase-preview-elements.ts +0 -408
  253. package/src/lib/showcase-renderer.ts +0 -218
  254. package/src/lib/showcase-utils.ts +0 -189
  255. package/src/lib/turnstile.ts +0 -35
  256. package/src/lib/utils/starwind/positioning.ts +0 -318
  257. package/src/locales/de/blog.json +0 -9
  258. package/src/locales/de/common.json +0 -19
  259. package/src/locales/de/errors.json +0 -6
  260. package/src/locales/de/home.json +0 -20
  261. package/src/locales/de/messages.json +0 -8
  262. package/src/locales/de/nav.json +0 -13
  263. package/src/locales/de/projects.json +0 -9
  264. package/src/locales/de/services.json +0 -43
  265. package/src/locales/en/blog.json +0 -9
  266. package/src/locales/en/chat.json +0 -29
  267. package/src/locales/en/common.json +0 -19
  268. package/src/locales/en/cookie_notice.json +0 -11
  269. package/src/locales/en/editor.json +0 -24
  270. package/src/locales/en/errors.json +0 -6
  271. package/src/locales/en/home.json +0 -20
  272. package/src/locales/en/messages.json +0 -8
  273. package/src/locales/en/nav.json +0 -13
  274. package/src/locales/en/projects.json +0 -9
  275. package/src/locales/en/services.json +0 -43
  276. package/src/locales/es/blog.json +0 -9
  277. package/src/locales/es/common.json +0 -19
  278. package/src/locales/es/errors.json +0 -6
  279. package/src/locales/es/home.json +0 -20
  280. package/src/locales/es/messages.json +0 -8
  281. package/src/locales/es/nav.json +0 -13
  282. package/src/locales/es/projects.json +0 -9
  283. package/src/locales/es/services.json +0 -43
  284. package/src/locales/ja/blog.json +0 -9
  285. package/src/locales/ja/common.json +0 -19
  286. package/src/locales/ja/errors.json +0 -6
  287. package/src/locales/ja/home.json +0 -20
  288. package/src/locales/ja/messages.json +0 -8
  289. package/src/locales/ja/nav.json +0 -13
  290. package/src/locales/ja/projects.json +0 -9
  291. package/src/locales/ja/services.json +0 -43
  292. package/src/locales/pt/blog.json +0 -9
  293. package/src/locales/pt/chat.json +0 -29
  294. package/src/locales/pt/common.json +0 -19
  295. package/src/locales/pt/cookie_notice.json +0 -11
  296. package/src/locales/pt/editor.json +0 -24
  297. package/src/locales/pt/errors.json +0 -6
  298. package/src/locales/pt/home.json +0 -20
  299. package/src/locales/pt/messages.json +0 -8
  300. package/src/locales/pt/nav.json +0 -13
  301. package/src/locales/pt/projects.json +0 -9
  302. package/src/locales/pt/services.json +0 -43
  303. package/src/utils/server/cookies.ts +0 -98
  304. package/src/utils/server/index.ts +0 -1
  305. package/src/utils/shared/index.ts +0 -3
  306. /package/src/utils/{shared/deep-merge.ts → deep-merge.ts} +0 -0
  307. /package/src/utils/{shared/embed.ts → embed.ts} +0 -0
  308. /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>