@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,604 +0,0 @@
1
- ---
2
- import type { HTMLAttributes } from "astro/types";
3
-
4
- type Props = HTMLAttributes<"div"> & {
5
- /**
6
- * When true, the dropdown will open on hover in addition to click
7
- */
8
- openOnHover?: boolean;
9
- /**
10
- * Time in milliseconds to wait before closing when hover open is enabled
11
- * @default 200
12
- */
13
- closeDelay?: number;
14
-
15
- children: any;
16
- };
17
-
18
- const { class: className, openOnHover = false, closeDelay = 200, ...rest } = Astro.props;
19
- ---
20
-
21
- <div
22
- class:list={["starwind-dropdown", "relative", className]}
23
- data-open-on-hover={openOnHover ? "true" : undefined}
24
- data-close-delay={closeDelay}
25
- {...rest}
26
- data-slot="dropdown"
27
- >
28
- <slot />
29
- </div>
30
-
31
- <script>
32
- class DropdownHandler {
33
- private dropdown: HTMLElement;
34
- private trigger: HTMLElement | null;
35
- private content: HTMLElement | null;
36
- private items: HTMLElement[] = [];
37
- private currentFocusIndex: number = -1;
38
- private isOpen: boolean = false;
39
- private isClosing: boolean = false;
40
- private animationDuration = 150;
41
- private openOnHover: boolean;
42
- private closeDelay: number;
43
- private closeTimerRef: number | null = null;
44
- private lastOpenSource: "keyboard" | "mouse" = "keyboard";
45
- private lastCloseSource: "keyboard" | "mouse" = "keyboard";
46
- private isSubmenu: boolean;
47
- private rootDropdown: HTMLElement;
48
- private contentPlaceholder: Comment | null = null;
49
- private cleanupAutoUpdate: (() => void) | null = null;
50
- private openChildCount: number = 0;
51
- private parentHandler: DropdownHandler | null = null;
52
-
53
- constructor(dropdown: HTMLElement, dropdownIdx: number) {
54
- this.dropdown = dropdown;
55
- this.isSubmenu = dropdown.classList.contains("starwind-dropdown-sub");
56
- this.openOnHover = dropdown.getAttribute("data-open-on-hover") === "true" || this.isSubmenu;
57
- this.closeDelay = parseInt(dropdown.getAttribute("data-close-delay") || "200");
58
-
59
- // Find the trigger and content that belong to this dropdown, avoiding nested ones
60
- this.trigger = this.findOwnElement('[data-slot="dropdown-trigger"], [data-sub-trigger]');
61
-
62
- // if trigger is set with asChild, use the first child element for trigger button
63
- if (this.trigger?.hasAttribute("data-as-child")) {
64
- this.trigger = this.trigger.firstElementChild as HTMLElement;
65
- }
66
-
67
- this.content = this.findOwnElement('[data-slot="dropdown-content"]');
68
-
69
- // Resolve root dropdown before any portaling (DOM is in original state)
70
- this.rootDropdown =
71
- (this.dropdown.closest(".starwind-dropdown") as HTMLElement) || this.dropdown;
72
-
73
- // Store handler reference and root dropdown on content element for lookups
74
- if (this.content) {
75
- (this.content as any).__dropdownHandler = this;
76
- (this.content as any).__rootDropdown = this.rootDropdown;
77
- }
78
-
79
- if (!this.trigger || !this.content) return;
80
-
81
- // Get animation duration from inline styles if available
82
- const animationDurationString = this.content.style.animationDuration;
83
- if (animationDurationString.endsWith("ms")) {
84
- this.animationDuration = parseFloat(animationDurationString);
85
- } else if (animationDurationString.endsWith("s")) {
86
- this.animationDuration = parseFloat(animationDurationString) * 1000;
87
- }
88
-
89
- this.init(dropdownIdx);
90
- }
91
-
92
- private findOwnElement(selector: string): HTMLElement | null {
93
- const elements = this.dropdown.querySelectorAll(selector);
94
- for (const el of elements) {
95
- if (el.closest(".starwind-dropdown, .starwind-dropdown-sub") === this.dropdown) {
96
- return el as HTMLElement;
97
- }
98
- }
99
- return null;
100
- }
101
-
102
- private init(dropdownIdx: number) {
103
- this.setupAccessibility(dropdownIdx);
104
- this.setupEvents();
105
- }
106
-
107
- private setupAccessibility(dropdownIdx: number) {
108
- if (!this.trigger || !this.content) return;
109
-
110
- // Generate unique IDs for accessibility
111
- this.trigger.id = `starwind-dropdown${dropdownIdx}-trigger`;
112
- this.content.id = `starwind-dropdown${dropdownIdx}-content`;
113
-
114
- // Set up additional ARIA attributes
115
- this.trigger.setAttribute("aria-controls", this.content.id);
116
- this.content.setAttribute("aria-labelledby", this.trigger.id);
117
- }
118
-
119
- private setupEvents() {
120
- if (!this.trigger || !this.content) return;
121
-
122
- // Handle trigger click
123
- this.trigger.addEventListener("click", (e) => {
124
- e.preventDefault();
125
- this.lastOpenSource = e.detail === 0 ? "keyboard" : "mouse";
126
- this.toggleDropdown();
127
- });
128
-
129
- // Handle keyboard navigation on trigger
130
- this.trigger.addEventListener("keydown", (e) => {
131
- if (e.key === "Enter" || e.key === " ") {
132
- e.preventDefault();
133
- e.stopPropagation();
134
- this.lastOpenSource = "keyboard";
135
- if (this.isSubmenu) {
136
- this.openDropdown();
137
- } else {
138
- this.toggleDropdown();
139
- }
140
- } else if (e.key === "Escape" && this.isOpen) {
141
- e.preventDefault();
142
- e.stopPropagation();
143
- this.lastCloseSource = "keyboard";
144
- // Close all menus in the group, focus root trigger
145
- this.rootDropdown.dispatchEvent(
146
- new CustomEvent("starwind-dropdown:close-all", { detail: { focusRoot: true } }),
147
- );
148
- } else if (this.isOpen && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
149
- e.preventDefault();
150
- e.stopPropagation();
151
- this.lastOpenSource = "keyboard";
152
- this.updateDropdownItems();
153
- if (e.key === "ArrowDown") {
154
- this.focusItem(0); // Focus first item when opening with arrow down
155
- } else {
156
- this.focusItem(this.items.length - 1); // Focus last item when opening with arrow up
157
- }
158
- } else if (e.key === "ArrowRight" && this.isSubmenu) {
159
- e.preventDefault();
160
- e.stopPropagation();
161
- if (!this.isOpen) {
162
- this.lastOpenSource = "keyboard";
163
- this.openDropdown();
164
- } else {
165
- this.updateDropdownItems();
166
- this.focusItem(0);
167
- }
168
- }
169
- });
170
-
171
- // Listen for close-all event (routed through rootDropdown to work across portaled content)
172
- this.rootDropdown.addEventListener("starwind-dropdown:close-all", ((e: CustomEvent) => {
173
- this.closeDropdown({ skipFocus: !!(e.detail?.focusRoot && this.isSubmenu) });
174
- }) as EventListener);
175
-
176
- // Listen for cancel-close event (routed through rootDropdown)
177
- this.rootDropdown.addEventListener("starwind-dropdown:cancel-close", () => {
178
- this.clearCloseTimer();
179
- });
180
-
181
- // Close dropdown when clicking outside for mouse
182
- document.addEventListener("pointerdown", (e) => {
183
- if (
184
- this.isOpen &&
185
- !this.dropdown.contains(e.target as Node) &&
186
- !this.content?.contains(e.target as Node) &&
187
- !this.isTargetInGroup(e.target as Node)
188
- ) {
189
- if (e.button === 0 && e.ctrlKey === false && e.pointerType === "mouse") {
190
- this.closeDropdown();
191
- }
192
- }
193
- });
194
-
195
- // Handle click outside select content to close for mobile
196
- document.addEventListener("click", (e) => {
197
- if (
198
- this.isOpen &&
199
- !this.trigger?.contains(e.target as Node) &&
200
- !this.content?.contains(e.target as Node) &&
201
- !this.isTargetInGroup(e.target as Node)
202
- ) {
203
- this.closeDropdown();
204
- }
205
- });
206
-
207
- // Handle keyboard navigation and item selection within dropdown
208
- this.content.addEventListener("keydown", (e) => {
209
- if (e.key === "Escape") {
210
- e.preventDefault();
211
- e.stopPropagation();
212
- // Close all menus in the group, focus root trigger
213
- this.rootDropdown.dispatchEvent(
214
- new CustomEvent("starwind-dropdown:close-all", { detail: { focusRoot: true } }),
215
- );
216
- } else if (this.isOpen) {
217
- this.handleMenuKeydown(e);
218
- }
219
- });
220
-
221
- // Handle item selection
222
- this.content.addEventListener("click", (e) => {
223
- const target = e.target as HTMLElement;
224
- const item = target.closest('[role="menuitem"]');
225
- if (item && !(item as HTMLElement).hasAttribute("data-disabled")) {
226
- if (item.hasAttribute("aria-haspopup")) return;
227
-
228
- // Close the dropdown after item selection
229
- this.closeDropdown();
230
-
231
- // Dispatch close-all through root to close all menus in the group
232
- this.rootDropdown.dispatchEvent(new CustomEvent("starwind-dropdown:close-all"));
233
- }
234
- });
235
-
236
- // Handle hover on dropdown items
237
- this.content.addEventListener("mouseover", (e) => {
238
- const target = e.target as HTMLElement;
239
- const menuItem = target.closest('[role="menuitem"]');
240
- if (menuItem && menuItem instanceof HTMLElement && this.isOpen === true) {
241
- // Update items list before focusing to ensure the index is correct
242
- this.updateDropdownItems();
243
-
244
- // Focus the item when hovering
245
- menuItem.focus();
246
-
247
- // Update the current focus index
248
- this.currentFocusIndex = this.items.indexOf(menuItem);
249
- }
250
- });
251
-
252
- if (this.openOnHover) {
253
- this.trigger.addEventListener("pointerenter", (e) => {
254
- if (e.pointerType !== "mouse") return;
255
- if (this.isClosing) return;
256
- if (!this.isOpen) {
257
- this.lastOpenSource = "mouse";
258
- this.openDropdown();
259
- } else {
260
- // If the dropdown is already open, make sure to clear any close timer
261
- this.clearCloseTimer();
262
- }
263
- });
264
-
265
- this.dropdown.addEventListener("pointerleave", (e) => {
266
- if (e.pointerType !== "mouse") return;
267
- if (this.openChildCount > 0) return;
268
- if (this.isOpen) {
269
- this.lastCloseSource = "mouse";
270
- this.closeDropdownDelayed();
271
- }
272
- });
273
-
274
- this.content.addEventListener("pointerenter", (e) => {
275
- if (e.pointerType !== "mouse") return;
276
- // If the user moves the mouse to the content, cancel the close timer
277
- this.clearCloseTimer();
278
- });
279
- }
280
- }
281
-
282
- private handleMenuKeydown(e: KeyboardEvent) {
283
- // Make sure we've got an updated list of menu items
284
- this.updateDropdownItems();
285
-
286
- // Skip if no items
287
- if (this.items.length === 0) return;
288
-
289
- const currentIdx = this.currentFocusIndex;
290
- const currentItem = this.items[currentIdx];
291
-
292
- switch (e.key) {
293
- case "ArrowDown":
294
- e.preventDefault();
295
- e.stopPropagation();
296
- this.focusItem(currentIdx === -1 ? 0 : currentIdx + 1);
297
- break;
298
- case "ArrowUp":
299
- e.preventDefault();
300
- e.stopPropagation();
301
- this.focusItem(currentIdx === -1 ? this.items.length - 1 : currentIdx - 1);
302
- break;
303
- case "ArrowRight":
304
- if (currentItem?.getAttribute("aria-haspopup") === "true") {
305
- e.preventDefault();
306
- e.stopPropagation();
307
- currentItem.click();
308
- }
309
- break;
310
- case "ArrowLeft":
311
- if (this.isSubmenu) {
312
- e.preventDefault();
313
- e.stopPropagation();
314
- this.closeDropdown();
315
- }
316
- break;
317
- case "Home":
318
- e.preventDefault();
319
- e.stopPropagation();
320
- this.focusItem(0);
321
- break;
322
- case "End":
323
- e.preventDefault();
324
- e.stopPropagation();
325
- this.focusItem(this.items.length - 1);
326
- break;
327
- case "Enter":
328
- case " ":
329
- if (currentIdx !== -1 && this.items[currentIdx]) {
330
- e.preventDefault();
331
- e.stopPropagation();
332
- this.items[currentIdx]?.click();
333
- }
334
- break;
335
- }
336
- }
337
-
338
- private isTargetInGroup(target: Node): boolean {
339
- const el = target instanceof HTMLElement ? target : target.parentElement;
340
- if (!el) return false;
341
- const contentEl = el.closest('[data-slot="dropdown-content"]');
342
- return !!contentEl && (contentEl as any).__rootDropdown === this.rootDropdown;
343
- }
344
-
345
- private updateDropdownItems() {
346
- if (!this.content) return;
347
- // Get all interactive menuitem elements belonging to this menu level only
348
- this.items = (
349
- Array.from(
350
- this.content.querySelectorAll('[role="menuitem"]:not([data-disabled="true"])'),
351
- ) as HTMLElement[]
352
- ).filter((item) => item.closest('[role="menu"]') === this.content);
353
- }
354
-
355
- private focusItem(idx: number) {
356
- // Ensure the index wraps around properly
357
- const targetIdx = (idx + this.items.length) % this.items.length;
358
-
359
- if (this.items[targetIdx]) {
360
- this.items[targetIdx].focus();
361
- this.currentFocusIndex = targetIdx;
362
- }
363
- }
364
-
365
- private toggleDropdown() {
366
- if (this.isOpen) {
367
- this.closeDropdown();
368
- } else {
369
- this.openDropdown();
370
- }
371
- }
372
-
373
- private openDropdown() {
374
- if (this.isClosing) return;
375
- if (!this.content || !this.trigger || (this.trigger as any).disabled) return;
376
-
377
- this.isOpen = true;
378
- this.trigger.setAttribute("aria-expanded", "true");
379
- this.content.style.removeProperty("display");
380
-
381
- // Portal sub-content to body before triggering animation
382
- if (this.isSubmenu) {
383
- this.portalContent();
384
- }
385
-
386
- this.content.setAttribute("data-state", "open");
387
-
388
- // Update the list of dropdown items
389
- this.updateDropdownItems();
390
-
391
- // Reset focus index when opening
392
- this.currentFocusIndex = -1;
393
-
394
- this.positionContent();
395
-
396
- // For submenus opened by keyboard, focus the first item
397
- if (this.isSubmenu && this.lastOpenSource === "keyboard") {
398
- requestAnimationFrame(() => {
399
- this.focusItem(0);
400
- });
401
- }
402
- }
403
-
404
- private closeDropdown(options?: { skipFocus?: boolean }) {
405
- if (!this.isOpen) return;
406
- if (!this.content || !this.trigger) return;
407
-
408
- this.isClosing = true;
409
- this.isOpen = false;
410
- this.content.setAttribute("data-state", "closed");
411
-
412
- // Set focus back on trigger only if opened or closed by keyboard
413
- // Skip focus if a close-all with focusRoot is closing this submenu (root will handle focus)
414
- if (
415
- !options?.skipFocus &&
416
- (!this.openOnHover ||
417
- this.lastOpenSource === "keyboard" ||
418
- this.lastCloseSource === "keyboard")
419
- ) {
420
- requestAnimationFrame(() => {
421
- if (!this.trigger) return;
422
- this.trigger.focus();
423
- });
424
- }
425
-
426
- // Give the content time to animate before hiding
427
- setTimeout(() => {
428
- if (!this.content) return;
429
- if (this.isSubmenu) {
430
- this.unportalContent();
431
- }
432
- this.content.style.display = "none";
433
- this.isClosing = false;
434
- }, this.animationDuration);
435
-
436
- this.trigger.setAttribute("aria-expanded", "false");
437
-
438
- // Reset focus index when closing
439
- this.currentFocusIndex = -1;
440
- }
441
-
442
- private closeDropdownDelayed() {
443
- if (!this.content || !this.trigger) return;
444
-
445
- // Clear any existing close timer
446
- this.clearCloseTimer();
447
-
448
- // Set a new timer to close the dropdown after the delay
449
- this.closeTimerRef = window.setTimeout(() => {
450
- if (this.isOpen) {
451
- this.closeDropdown();
452
- }
453
- this.closeTimerRef = null;
454
- }, this.closeDelay);
455
- }
456
-
457
- private clearCloseTimer() {
458
- if (this.closeTimerRef !== null) {
459
- window.clearTimeout(this.closeTimerRef);
460
- this.closeTimerRef = null;
461
- }
462
- }
463
-
464
- private portalContent() {
465
- if (!this.content || !this.trigger) return;
466
-
467
- // Save the original position with a placeholder
468
- this.contentPlaceholder = document.createComment("dropdown-sub-placeholder");
469
- this.content.parentNode?.insertBefore(this.contentPlaceholder, this.content);
470
-
471
- // Move content to body
472
- document.body.appendChild(this.content);
473
-
474
- // Apply fixed positioning (overrides the CSS absolute class)
475
- this.content.style.position = "fixed";
476
- this.content.style.zIndex = "50";
477
-
478
- // Position relative to trigger
479
- this.positionSubContent();
480
-
481
- // Set up auto-update for scroll/resize
482
- const updatePosition = () => this.positionSubContent();
483
- window.addEventListener("scroll", updatePosition, true);
484
- window.addEventListener("resize", updatePosition);
485
-
486
- // Find and link to parent handler for child-tracking
487
- const parentContent = this.trigger.closest('[data-slot="dropdown-content"]');
488
- if (parentContent) {
489
- this.parentHandler = (parentContent as any).__dropdownHandler || null;
490
- if (this.parentHandler) {
491
- this.parentHandler.openChildCount++;
492
- }
493
- }
494
-
495
- // Handle hover interactions for portaled content
496
- const onPointerEnter = (e: PointerEvent) => {
497
- if (e.pointerType !== "mouse") return;
498
- this.clearCloseTimer();
499
- // Cancel close timers for all handlers in the group (parents included)
500
- this.rootDropdown.dispatchEvent(new CustomEvent("starwind-dropdown:cancel-close"));
501
- };
502
-
503
- const onPointerLeave = (e: PointerEvent) => {
504
- if (e.pointerType !== "mouse") return;
505
- // Don't close if a child submenu is still open
506
- if (this.openChildCount > 0) return;
507
- this.lastCloseSource = "mouse";
508
- this.closeDropdownDelayed();
509
- };
510
-
511
- this.content.addEventListener("pointerenter", onPointerEnter);
512
- this.content.addEventListener("pointerleave", onPointerLeave);
513
-
514
- this.cleanupAutoUpdate = () => {
515
- window.removeEventListener("scroll", updatePosition, true);
516
- window.removeEventListener("resize", updatePosition);
517
- this.content?.removeEventListener("pointerenter", onPointerEnter);
518
- this.content?.removeEventListener("pointerleave", onPointerLeave);
519
- };
520
- }
521
-
522
- private unportalContent() {
523
- if (!this.content || !this.contentPlaceholder) return;
524
-
525
- // Decrement parent's open child count
526
- if (this.parentHandler) {
527
- this.parentHandler.openChildCount--;
528
- this.parentHandler = null;
529
- }
530
-
531
- // Clean up auto-update and hover listeners
532
- this.cleanupAutoUpdate?.();
533
- this.cleanupAutoUpdate = null;
534
-
535
- // Move content back to its original position
536
- this.contentPlaceholder.parentNode?.insertBefore(this.content, this.contentPlaceholder);
537
- this.contentPlaceholder.remove();
538
- this.contentPlaceholder = null;
539
-
540
- // Remove fixed positioning styles
541
- this.content.style.removeProperty("position");
542
- this.content.style.removeProperty("z-index");
543
- this.content.style.removeProperty("top");
544
- this.content.style.removeProperty("left");
545
- }
546
-
547
- private positionSubContent() {
548
- if (!this.content || !this.trigger) return;
549
-
550
- const triggerRect = this.trigger.getBoundingClientRect();
551
-
552
- // Position to the right of the trigger by default
553
- let top = triggerRect.top;
554
- let left = triggerRect.right;
555
-
556
- // Measure content dimensions
557
- const contentWidth = this.content.offsetWidth;
558
- const contentHeight = this.content.offsetHeight;
559
- const viewportWidth = window.innerWidth;
560
- const viewportHeight = window.innerHeight;
561
-
562
- // If it would overflow on the right, flip to the left
563
- if (left + contentWidth > viewportWidth) {
564
- left = triggerRect.left - contentWidth;
565
- }
566
-
567
- // If it would overflow on the bottom, shift up
568
- if (top + contentHeight > viewportHeight) {
569
- top = Math.max(0, viewportHeight - contentHeight);
570
- }
571
-
572
- this.content.style.top = `${top}px`;
573
- this.content.style.left = `${left}px`;
574
- }
575
-
576
- private positionContent() {
577
- if (!this.content || !this.trigger || this.isSubmenu) return;
578
-
579
- // Set content width to match trigger width
580
- this.content.style.width = "var(--starwind-dropdown-trigger-width)";
581
- this.content.style.setProperty(
582
- "--starwind-dropdown-trigger-width",
583
- `${this.trigger.offsetWidth}px`,
584
- );
585
- }
586
- }
587
-
588
- // Store instances in a WeakMap to avoid memory leaks
589
- const dropdownInstances = new WeakMap<HTMLElement, DropdownHandler>();
590
- let dropdownCounter = 0;
591
-
592
- // Initialize dropdowns
593
- const initDropdowns = () => {
594
- document.querySelectorAll(".starwind-dropdown, .starwind-dropdown-sub").forEach((dropdown) => {
595
- if (dropdown instanceof HTMLElement && !dropdownInstances.has(dropdown)) {
596
- dropdownInstances.set(dropdown, new DropdownHandler(dropdown, dropdownCounter++));
597
- }
598
- });
599
- };
600
-
601
- initDropdowns();
602
- document.addEventListener("astro:after-swap", initDropdowns);
603
- document.addEventListener("starwind:init", initDropdowns);
604
- </script>