@starwind-ui/core 1.11.2 → 1.12.1

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 (158) hide show
  1. package/package.json +1 -1
  2. package/dist/index.d.ts +0 -28
  3. package/dist/index.js +0 -74
  4. package/dist/index.js.map +0 -1
  5. package/dist/src/components/accordion/Accordion.astro +0 -247
  6. package/dist/src/components/accordion/AccordionContent.astro +0 -33
  7. package/dist/src/components/accordion/AccordionItem.astro +0 -27
  8. package/dist/src/components/accordion/AccordionTrigger.astro +0 -32
  9. package/dist/src/components/accordion/index.ts +0 -15
  10. package/dist/src/components/alert/Alert.astro +0 -31
  11. package/dist/src/components/alert/AlertDescription.astro +0 -14
  12. package/dist/src/components/alert/AlertTitle.astro +0 -16
  13. package/dist/src/components/alert/index.ts +0 -13
  14. package/dist/src/components/alert-dialog/AlertDialog.astro +0 -273
  15. package/dist/src/components/alert-dialog/AlertDialogAction.astro +0 -44
  16. package/dist/src/components/alert-dialog/AlertDialogCancel.astro +0 -45
  17. package/dist/src/components/alert-dialog/AlertDialogContent.astro +0 -52
  18. package/dist/src/components/alert-dialog/AlertDialogDescription.astro +0 -18
  19. package/dist/src/components/alert-dialog/AlertDialogFooter.astro +0 -16
  20. package/dist/src/components/alert-dialog/AlertDialogHeader.astro +0 -14
  21. package/dist/src/components/alert-dialog/AlertDialogTitle.astro +0 -20
  22. package/dist/src/components/alert-dialog/AlertDialogTrigger.astro +0 -47
  23. package/dist/src/components/alert-dialog/index.ts +0 -46
  24. package/dist/src/components/aspect-ratio/AspectRatio.astro +0 -32
  25. package/dist/src/components/aspect-ratio/index.ts +0 -7
  26. package/dist/src/components/avatar/Avatar.astro +0 -29
  27. package/dist/src/components/avatar/AvatarFallback.astro +0 -18
  28. package/dist/src/components/avatar/AvatarImage.astro +0 -49
  29. package/dist/src/components/avatar/index.ts +0 -13
  30. package/dist/src/components/badge/Badge.astro +0 -51
  31. package/dist/src/components/badge/index.ts +0 -7
  32. package/dist/src/components/breadcrumb/Breadcrumb.astro +0 -11
  33. package/dist/src/components/breadcrumb/BreadcrumbEllipsis.astro +0 -28
  34. package/dist/src/components/breadcrumb/BreadcrumbItem.astro +0 -14
  35. package/dist/src/components/breadcrumb/BreadcrumbLink.astro +0 -22
  36. package/dist/src/components/breadcrumb/BreadcrumbList.astro +0 -16
  37. package/dist/src/components/breadcrumb/BreadcrumbPage.astro +0 -21
  38. package/dist/src/components/breadcrumb/BreadcrumbSeparator.astro +0 -23
  39. package/dist/src/components/breadcrumb/index.ts +0 -37
  40. package/dist/src/components/button/Button.astro +0 -53
  41. package/dist/src/components/button/index.ts +0 -7
  42. package/dist/src/components/card/Card.astro +0 -14
  43. package/dist/src/components/card/CardContent.astro +0 -14
  44. package/dist/src/components/card/CardDescription.astro +0 -14
  45. package/dist/src/components/card/CardFooter.astro +0 -14
  46. package/dist/src/components/card/CardHeader.astro +0 -14
  47. package/dist/src/components/card/CardTitle.astro +0 -14
  48. package/dist/src/components/card/index.ts +0 -26
  49. package/dist/src/components/carousel/Carousel.astro +0 -55
  50. package/dist/src/components/carousel/CarouselContent.astro +0 -26
  51. package/dist/src/components/carousel/CarouselItem.astro +0 -26
  52. package/dist/src/components/carousel/CarouselNext.astro +0 -37
  53. package/dist/src/components/carousel/CarouselPrevious.astro +0 -37
  54. package/dist/src/components/carousel/carousel-script.ts +0 -191
  55. package/dist/src/components/carousel/index.ts +0 -32
  56. package/dist/src/components/checkbox/Checkbox.astro +0 -127
  57. package/dist/src/components/checkbox/index.ts +0 -7
  58. package/dist/src/components/dialog/Dialog.astro +0 -263
  59. package/dist/src/components/dialog/DialogClose.astro +0 -35
  60. package/dist/src/components/dialog/DialogContent.astro +0 -67
  61. package/dist/src/components/dialog/DialogDescription.astro +0 -14
  62. package/dist/src/components/dialog/DialogFooter.astro +0 -14
  63. package/dist/src/components/dialog/DialogHeader.astro +0 -14
  64. package/dist/src/components/dialog/DialogTitle.astro +0 -20
  65. package/dist/src/components/dialog/DialogTrigger.astro +0 -47
  66. package/dist/src/components/dialog/index.ts +0 -45
  67. package/dist/src/components/dropdown/Dropdown.astro +0 -375
  68. package/dist/src/components/dropdown/DropdownContent.astro +0 -81
  69. package/dist/src/components/dropdown/DropdownItem.astro +0 -48
  70. package/dist/src/components/dropdown/DropdownLabel.astro +0 -29
  71. package/dist/src/components/dropdown/DropdownSeparator.astro +0 -21
  72. package/dist/src/components/dropdown/DropdownTrigger.astro +0 -52
  73. package/dist/src/components/dropdown/index.ts +0 -33
  74. package/dist/src/components/dropzone/Dropzone.astro +0 -233
  75. package/dist/src/components/dropzone/DropzoneFilesList.astro +0 -26
  76. package/dist/src/components/dropzone/DropzoneLoadingIndicator.astro +0 -10
  77. package/dist/src/components/dropzone/DropzoneUploadIndicator.astro +0 -10
  78. package/dist/src/components/dropzone/index.ts +0 -24
  79. package/dist/src/components/input/Input.astro +0 -24
  80. package/dist/src/components/input/index.ts +0 -7
  81. package/dist/src/components/item/Item.astro +0 -52
  82. package/dist/src/components/item/ItemActions.astro +0 -16
  83. package/dist/src/components/item/ItemContent.astro +0 -16
  84. package/dist/src/components/item/ItemDescription.astro +0 -19
  85. package/dist/src/components/item/ItemFooter.astro +0 -16
  86. package/dist/src/components/item/ItemGroup.astro +0 -16
  87. package/dist/src/components/item/ItemHeader.astro +0 -16
  88. package/dist/src/components/item/ItemMedia.astro +0 -40
  89. package/dist/src/components/item/ItemSeparator.astro +0 -21
  90. package/dist/src/components/item/ItemTitle.astro +0 -16
  91. package/dist/src/components/item/index.ts +0 -50
  92. package/dist/src/components/kbd/Kbd.astro +0 -21
  93. package/dist/src/components/kbd/KbdGroup.astro +0 -16
  94. package/dist/src/components/kbd/index.ts +0 -11
  95. package/dist/src/components/label/Label.astro +0 -22
  96. package/dist/src/components/label/index.ts +0 -7
  97. package/dist/src/components/pagination/Pagination.astro +0 -20
  98. package/dist/src/components/pagination/PaginationContent.astro +0 -16
  99. package/dist/src/components/pagination/PaginationEllipsis.astro +0 -25
  100. package/dist/src/components/pagination/PaginationItem.astro +0 -16
  101. package/dist/src/components/pagination/PaginationLink.astro +0 -24
  102. package/dist/src/components/pagination/PaginationNext.astro +0 -26
  103. package/dist/src/components/pagination/PaginationPrevious.astro +0 -26
  104. package/dist/src/components/pagination/index.ts +0 -38
  105. package/dist/src/components/progress/Progress.astro +0 -154
  106. package/dist/src/components/progress/index.ts +0 -10
  107. package/dist/src/components/radio-group/RadioGroup.astro +0 -157
  108. package/dist/src/components/radio-group/RadioGroupItem.astro +0 -129
  109. package/dist/src/components/radio-group/RadioGroupTypes.ts +0 -6
  110. package/dist/src/components/radio-group/index.ts +0 -23
  111. package/dist/src/components/select/Select.astro +0 -534
  112. package/dist/src/components/select/SelectContent.astro +0 -83
  113. package/dist/src/components/select/SelectGroup.astro +0 -9
  114. package/dist/src/components/select/SelectItem.astro +0 -49
  115. package/dist/src/components/select/SelectLabel.astro +0 -14
  116. package/dist/src/components/select/SelectSeparator.astro +0 -12
  117. package/dist/src/components/select/SelectTrigger.astro +0 -48
  118. package/dist/src/components/select/SelectTypes.ts +0 -13
  119. package/dist/src/components/select/SelectValue.astro +0 -19
  120. package/dist/src/components/select/index.ts +0 -45
  121. package/dist/src/components/separator/Separator.astro +0 -36
  122. package/dist/src/components/separator/index.ts +0 -7
  123. package/dist/src/components/sheet/Sheet.astro +0 -13
  124. package/dist/src/components/sheet/SheetClose.astro +0 -13
  125. package/dist/src/components/sheet/SheetContent.astro +0 -92
  126. package/dist/src/components/sheet/SheetDescription.astro +0 -16
  127. package/dist/src/components/sheet/SheetFooter.astro +0 -16
  128. package/dist/src/components/sheet/SheetHeader.astro +0 -16
  129. package/dist/src/components/sheet/SheetTitle.astro +0 -16
  130. package/dist/src/components/sheet/SheetTrigger.astro +0 -13
  131. package/dist/src/components/sheet/index.ts +0 -41
  132. package/dist/src/components/skeleton/Skeleton.astro +0 -14
  133. package/dist/src/components/skeleton/index.ts +0 -9
  134. package/dist/src/components/spinner/Spinner.astro +0 -21
  135. package/dist/src/components/spinner/index.ts +0 -7
  136. package/dist/src/components/switch/Switch.astro +0 -191
  137. package/dist/src/components/switch/SwitchTypes.ts +0 -6
  138. package/dist/src/components/switch/index.ts +0 -12
  139. package/dist/src/components/table/Table.astro +0 -18
  140. package/dist/src/components/table/TableBody.astro +0 -16
  141. package/dist/src/components/table/TableCaption.astro +0 -16
  142. package/dist/src/components/table/TableCell.astro +0 -16
  143. package/dist/src/components/table/TableFoot.astro +0 -16
  144. package/dist/src/components/table/TableHead.astro +0 -16
  145. package/dist/src/components/table/TableHeader.astro +0 -16
  146. package/dist/src/components/table/TableRow.astro +0 -16
  147. package/dist/src/components/table/index.ts +0 -42
  148. package/dist/src/components/tabs/Tabs.astro +0 -269
  149. package/dist/src/components/tabs/TabsContent.astro +0 -28
  150. package/dist/src/components/tabs/TabsList.astro +0 -22
  151. package/dist/src/components/tabs/TabsTrigger.astro +0 -34
  152. package/dist/src/components/tabs/index.ts +0 -20
  153. package/dist/src/components/textarea/Textarea.astro +0 -28
  154. package/dist/src/components/textarea/index.ts +0 -9
  155. package/dist/src/components/tooltip/Tooltip.astro +0 -237
  156. package/dist/src/components/tooltip/TooltipContent.astro +0 -114
  157. package/dist/src/components/tooltip/TooltipTrigger.astro +0 -10
  158. package/dist/src/components/tooltip/index.ts +0 -16
@@ -1,534 +0,0 @@
1
- ---
2
- import type { HTMLAttributes } from "astro/types";
3
-
4
- type Props = HTMLAttributes<"div"> & {
5
- /**
6
- * The name for the hidden <select> element - used for standard form handling
7
- */
8
- name?: string;
9
- /**
10
- * The value of the item that should be selected by default
11
- */
12
- defaultValue?: string;
13
-
14
- children: any;
15
- };
16
-
17
- const { class: className, name, defaultValue, ...rest } = Astro.props;
18
- ---
19
-
20
- <div
21
- class:list={["starwind-select", "relative", className]}
22
- data-name={name}
23
- data-value={defaultValue}
24
- data-slot="select"
25
- {...rest}
26
- >
27
- <slot />
28
- </div>
29
-
30
- <script>
31
- import type { SelectChangeEvent, SelectEvent } from "./SelectTypes";
32
-
33
- class SelectHandler {
34
- private select: HTMLElement;
35
- private trigger: HTMLButtonElement | null;
36
- private content: HTMLElement | null;
37
- private isOpen: boolean = false;
38
- private selectedItem: HTMLElement | null = null;
39
- private animationDuration = 150;
40
- private typeaheadTimerRef: number | null = null;
41
- private typeaheadSearch = "";
42
- private returnFocusOnClose: boolean = true;
43
-
44
- constructor(select: HTMLElement, selectIdx: number) {
45
- this.select = select;
46
- this.trigger = select.querySelector(".starwind-select-trigger");
47
- this.content = select.querySelector(".starwind-select-content");
48
-
49
- if (!this.trigger || !this.content) return;
50
-
51
- // animationDuration is set with inline styles through passed prop to SelectContent
52
- const animationDurationString = this.content.style.animationDuration;
53
- if (animationDurationString.endsWith("ms")) {
54
- this.animationDuration = parseFloat(animationDurationString);
55
- } else if (animationDurationString.endsWith("s")) {
56
- // using something like @playform/compress might optimize to use "s" instead of "ms"
57
- this.animationDuration = parseFloat(animationDurationString) * 1000;
58
- }
59
-
60
- this.init(selectIdx);
61
- }
62
-
63
- private init(selectIdx: number) {
64
- this.setupAccessibility(selectIdx);
65
- this.setupEvents();
66
- this.setupSelectField();
67
- this.setInitialState();
68
- }
69
-
70
- private setupSelectField() {
71
- if (!this.trigger || !this.content) return;
72
- // build the standard select field
73
- const selectField = document.createElement("select");
74
- selectField.tabIndex = -1;
75
- selectField.setAttribute("aria-hidden", "true");
76
- selectField.setAttribute("placeholder", "select");
77
- const selectName = this.select.getAttribute("data-name");
78
- if (selectName) {
79
- selectField.name = selectName;
80
- }
81
-
82
- // you can comment out this "sr-only" class line below if you want to see the native select in action
83
- selectField.classList.add("starwind-sr-only");
84
-
85
- // The first option is a placeholder
86
- const placeholderOption = document.createElement("option");
87
- placeholderOption.value = "";
88
- placeholderOption.textContent = "Select";
89
- placeholderOption.disabled = true;
90
- placeholderOption.selected = true;
91
- selectField.appendChild(placeholderOption);
92
-
93
- // add all options to the select field
94
- this.content.querySelectorAll('[role="option"]').forEach((option) => {
95
- const optionValue = option.getAttribute("data-value");
96
- const optionText = option.textContent;
97
- const optionElement = document.createElement("option");
98
- optionElement.value = optionValue || "";
99
- optionElement.textContent = optionText || "";
100
- selectField.appendChild(optionElement);
101
- });
102
- this.trigger.appendChild(selectField);
103
-
104
- // add this select field right after the trigger
105
- this.trigger.parentElement?.insertBefore(selectField, this.trigger.nextSibling);
106
-
107
- this.setSize();
108
- this.content.style.width = "var(--starwind-select-trigger-width)";
109
- }
110
-
111
- private setupAccessibility(selectIdx: number) {
112
- if (!this.trigger || !this.content) return;
113
-
114
- // Generate unique IDs for accessibility
115
- this.trigger.id = `starwind-select${selectIdx}-trigger`;
116
- this.content.id = `starwind-select${selectIdx}-content`;
117
-
118
- // Set up additional ARIA attributes
119
- this.trigger.setAttribute("aria-controls", this.content.id);
120
- this.content.setAttribute("aria-labelledby", this.trigger.id);
121
- }
122
-
123
- private setupEvents() {
124
- if (!this.trigger || !this.content) return;
125
-
126
- // Handle pointerdown
127
- this.trigger.addEventListener("pointerdown", (e) => {
128
- // prevent implicit pointer capture
129
- // https://www.w3.org/TR/pointerevents3/#implicit-pointer-capture
130
- const target = e.target as HTMLElement;
131
- if (target.hasPointerCapture(e.pointerId)) {
132
- target.releasePointerCapture(e.pointerId);
133
- }
134
-
135
- // prevent trigger from stealing focus from the active item after opening.
136
- e.preventDefault();
137
-
138
- // only call handler if it's the left button (mousedown gets triggered by all mouse buttons)
139
- // but not when the control key is pressed (avoiding MacOS right click); also not for touch
140
- // devices because that would open the menu on scroll. (pen devices behave as touch on iOS).
141
- if (e.button === 0 && e.ctrlKey === false && e.pointerType === "mouse") {
142
- this.returnFocusOnClose = true;
143
- this.toggleSelect();
144
- }
145
- });
146
-
147
- // Handle click event for mobile devices
148
- this.trigger.addEventListener("click", (e) => {
149
- if (window.matchMedia("(pointer: coarse)").matches) {
150
- e.preventDefault();
151
- this.returnFocusOnClose = true;
152
- this.toggleSelect();
153
- }
154
- });
155
-
156
- // add enter or space key to select trigger to toggle select
157
- this.trigger.addEventListener("keydown", (e) => {
158
- if (e.key === "Enter" || e.key === " ") {
159
- e.preventDefault();
160
- this.returnFocusOnClose = true;
161
- this.toggleSelect();
162
- }
163
- });
164
-
165
- // Handle keyboard navigation inside select content
166
- this.content.addEventListener("keydown", (e) => {
167
- if (e.key === "Enter" || e.key === " ") {
168
- // set element based on current focused element
169
- const activeElement = document.activeElement;
170
- this.returnFocusOnClose = true;
171
- this.handleSelection(activeElement as HTMLElement);
172
- } else if (e.key === "Escape" && this.isOpen) {
173
- this.returnFocusOnClose = true;
174
- this.closeSelect();
175
- }
176
-
177
- // add key navigation for accessibility
178
- // "Home" goes to first element
179
- // "End" goes to last element
180
- // "ArrowUp" goes to previous element
181
- // "ArrowDown" goes to next element
182
- else if (["ArrowUp", "ArrowDown", "Home", "End"].includes(e.key)) {
183
- this.handleNavigationKeys(e);
184
- e.preventDefault();
185
- } else {
186
- const isModifierKey = e.ctrlKey || e.altKey || e.metaKey;
187
-
188
- // select should not be navigated using tab key so we prevent it
189
- if (e.key === "Tab") e.preventDefault();
190
-
191
- if (!isModifierKey && e.key.length === 1) {
192
- this.handleTypeahead(e.key);
193
- }
194
- }
195
- });
196
-
197
- // Handle hover on select items
198
- this.content.addEventListener("mouseover", (e) => {
199
- const target = e.target as HTMLElement;
200
- const option = target.closest('[role="option"]');
201
- if (option && option instanceof HTMLElement && this.isOpen === true) {
202
- option.focus();
203
- }
204
- });
205
-
206
- // handle pointerdown outside select content to close
207
- document.addEventListener("pointerdown", (e) => {
208
- // only close if not a mouse pointer
209
- if (!window.matchMedia("(pointer: coarse)").matches) {
210
- if (
211
- !(
212
- this.trigger?.contains(e.target as Node) || this.content?.contains(e.target as Node)
213
- ) &&
214
- this.isOpen
215
- ) {
216
- this.returnFocusOnClose = false;
217
- this.closeSelect();
218
- }
219
- }
220
- });
221
-
222
- // Handle click outside select content to close
223
- document.addEventListener("click", (e) => {
224
- if (
225
- !(this.trigger?.contains(e.target as Node) || this.content?.contains(e.target as Node)) &&
226
- this.isOpen
227
- ) {
228
- this.returnFocusOnClose = false;
229
- this.closeSelect();
230
- }
231
- });
232
-
233
- // Handle selection of items
234
- this.content?.addEventListener("click", (e) => {
235
- const item = (e.target as HTMLElement).closest("[role='option']");
236
- if (item instanceof HTMLElement) {
237
- this.returnFocusOnClose = true;
238
- this.handleSelection(item);
239
- }
240
- });
241
-
242
- // passive resize listener to call setSize()
243
- window.addEventListener("resize", () => this.setSize(), { passive: true });
244
-
245
- // Listen for programmatic selection events
246
- document.addEventListener("starwind-select:select", (e: Event) => {
247
- const selectEvent = e as SelectEvent;
248
- const selectId = selectEvent.detail.selectId;
249
- const selectName = selectEvent.detail.selectName;
250
- const selectValue = selectEvent.detail.value;
251
-
252
- // Check if this event is for this select
253
- if (
254
- (selectId && this.select.id === selectId) ||
255
- (selectName && this.select.getAttribute("data-name") === selectName)
256
- ) {
257
- this.programmaticallySelect(selectValue);
258
- }
259
- });
260
- }
261
-
262
- private handleNavigationKeys(e: KeyboardEvent) {
263
- if (!this.content) return;
264
- const items = this.content.querySelectorAll('[role="option"]');
265
-
266
- // current, or first item, is focused upon opening the select
267
- const activeElement = document.activeElement;
268
- const currentIndex = Array.from(items).indexOf(activeElement as HTMLElement);
269
- if (e.key === "Home") {
270
- const firstEnabledItem = Array.from(items).find(
271
- (item) => item.getAttribute("data-disabled") !== "true",
272
- ) as HTMLElement;
273
- if (firstEnabledItem) {
274
- firstEnabledItem.focus();
275
- }
276
- return;
277
- }
278
- if (e.key === "End") {
279
- const lastEnabledItem = Array.from(items)
280
- .reverse()
281
- .find((item) => item.getAttribute("data-disabled") !== "true") as HTMLElement;
282
- if (lastEnabledItem) {
283
- lastEnabledItem.focus();
284
- }
285
- return;
286
- }
287
- if (e.key === "ArrowUp" && currentIndex > 0) {
288
- for (let i = currentIndex - 1; i >= 0; i--) {
289
- const item = items[i] as HTMLElement;
290
- if (item.getAttribute("data-disabled") !== "true") {
291
- item.focus();
292
- break;
293
- }
294
- }
295
- return;
296
- }
297
- if (e.key === "ArrowDown" && currentIndex < items.length - 1) {
298
- for (let i = currentIndex + 1; i < items.length; i++) {
299
- const item = items[i] as HTMLElement;
300
- if (item.getAttribute("data-disabled") !== "true") {
301
- item.focus();
302
- break;
303
- }
304
- }
305
- return;
306
- }
307
- }
308
-
309
- private handleTypeahead(key: string) {
310
- if (!this.content) return;
311
- const search = this.typeaheadSearch + key;
312
- const items = this.content.querySelectorAll('[role="option"]');
313
-
314
- // find and focus the first matching option
315
- const matches = Array.from(items).filter((item) =>
316
- item.textContent?.toLowerCase().trim().startsWith(search.toLowerCase()),
317
- ) as HTMLElement[];
318
- if (matches.length > 0) {
319
- matches[0].focus();
320
- }
321
-
322
- // update the typeahead search and reset the timer
323
- this.typeaheadSearch = search;
324
- if (this.typeaheadTimerRef) {
325
- window.clearTimeout(this.typeaheadTimerRef);
326
- }
327
-
328
- // set a timer to clear the search after 1 second
329
- this.typeaheadTimerRef = window.setTimeout(() => {
330
- this.typeaheadSearch = "";
331
- this.typeaheadTimerRef = null;
332
- }, 1000);
333
- }
334
-
335
- private setSize() {
336
- if (!this.trigger || !this.content) return;
337
- this.content.style.setProperty(
338
- "--starwind-select-content-width",
339
- `${this.content.offsetWidth}px`,
340
- );
341
-
342
- this.content.style.setProperty(
343
- "--starwind-select-trigger-width",
344
- `${this.trigger.offsetWidth}px`,
345
- );
346
- }
347
-
348
- private toggleSelect() {
349
- if (this.isOpen) {
350
- this.closeSelect();
351
- } else {
352
- this.openSelect();
353
- }
354
- }
355
-
356
- private openSelect() {
357
- if (!this.content || !this.trigger || this.trigger.disabled) return;
358
-
359
- this.isOpen = true;
360
- this.content.setAttribute("data-state", "open");
361
- this.trigger.setAttribute("aria-expanded", "true");
362
- this.content.style.removeProperty("display");
363
-
364
- // set focus on the current selected item
365
- if (this.selectedItem) {
366
- this.selectedItem.focus();
367
- } else {
368
- // if no item is selected, focus on the first item
369
- const firstItem = this.content.querySelector('[role="option"]') as HTMLElement;
370
- if (firstItem) {
371
- firstItem.focus();
372
- }
373
- }
374
-
375
- this.positionContent();
376
- }
377
-
378
- private closeSelect() {
379
- if (!this.content || !this.trigger) return;
380
-
381
- this.isOpen = false;
382
- this.content.setAttribute("data-state", "closed");
383
-
384
- // Remove focus from any currently focused element
385
- const activeElement = document.activeElement;
386
- if (activeElement instanceof HTMLElement) {
387
- activeElement.blur();
388
- }
389
-
390
- // Set focus on trigger if returnFocusOnClose is true
391
- if (this.returnFocusOnClose) {
392
- requestAnimationFrame(() => {
393
- if (!this.trigger) return;
394
- this.trigger.focus();
395
- });
396
- }
397
-
398
- // give the content time to animate before hiding
399
- setTimeout(() => {
400
- if (!this.content) return;
401
- this.content.style.display = "none";
402
- }, this.animationDuration);
403
-
404
- this.trigger.setAttribute("aria-expanded", "false");
405
- }
406
-
407
- private handleSelection(item: HTMLElement) {
408
- if (!this.trigger) return;
409
-
410
- // update the hidden select field
411
- const selectField = this.select.querySelector("select");
412
- if (selectField) {
413
- const newValue = item.getAttribute("data-value") || "";
414
- selectField.value = newValue;
415
-
416
- // Dispatch custom event with the new value
417
- const event = new CustomEvent<SelectChangeEvent["detail"]>("starwind-select:change", {
418
- detail: { value: newValue, selectId: this.select.id, label: item.textContent || "" },
419
- bubbles: true,
420
- cancelable: true,
421
- });
422
-
423
- selectField.dispatchEvent(event);
424
- }
425
-
426
- // Update trigger content
427
- const triggerSpan = this.trigger.firstElementChild as HTMLSpanElement;
428
- if (triggerSpan) {
429
- triggerSpan.textContent = item.textContent;
430
- }
431
-
432
- // Update selected states after select finishes closing
433
- setTimeout(() => {
434
- if (this.selectedItem) {
435
- this.selectedItem.setAttribute("aria-selected", "false");
436
- }
437
- item.setAttribute("aria-selected", "true");
438
- this.selectedItem = item;
439
- }, this.animationDuration);
440
-
441
- // Close the select
442
- this.closeSelect();
443
- }
444
-
445
- /**
446
- * Sets the initial state based on the default value attribute
447
- */
448
- private setInitialState(): void {
449
- const defaultValue = this.select.dataset.value;
450
- if (defaultValue) {
451
- const item = this.content?.querySelector(`[data-value="${defaultValue}"]`);
452
-
453
- if (item && item instanceof HTMLElement) {
454
- this.returnFocusOnClose = false;
455
- this.handleSelection(item);
456
- this.selectedItem = item;
457
- }
458
- }
459
- }
460
-
461
- /**
462
- * Programmatically selects an option by value
463
- */
464
- private programmaticallySelect(value: string): void {
465
- if (!this.content) return;
466
-
467
- const item = this.content.querySelector(`[data-value="${value}"]`);
468
- if (item instanceof HTMLElement) {
469
- this.returnFocusOnClose = false;
470
-
471
- // Update aria-selected attributes immediately
472
- if (this.selectedItem) {
473
- this.selectedItem.setAttribute("aria-selected", "false");
474
- }
475
- item.setAttribute("aria-selected", "true");
476
-
477
- // Then call handleSelection which will update the rest
478
- this.handleSelection(item);
479
- }
480
- }
481
-
482
- /**
483
- * TODO: add position logic to avoid collisions with window boundary
484
- * It will need to switch to top or bottom depending on space available
485
- * It will also need to set the content max height so it doesn't overflow the viewport
486
- */
487
- private positionContent() {
488
- // if (!this.content || !this.trigger) return;
489
- // const triggerRect = this.trigger.getBoundingClientRect();
490
- // const contentRect = this.content.getBoundingClientRect();
491
- // const viewportHeight = window.innerHeight;
492
- // // Position the content below the trigger by default
493
- // let top = triggerRect.bottom;
494
- // // If there's not enough space below, position it above
495
- // if (top + contentRect.height > viewportHeight) {
496
- // top = triggerRect.top - contentRect.height;
497
- // }
498
- // this.content.style.position = "absolute";
499
- // this.content.style.top = `${top}px`;
500
- // this.content.style.left = `${triggerRect.left}px`;
501
- // this.content.style.width = `${triggerRect.width}px`;
502
- // this.content.style.zIndex = "50";
503
- }
504
- }
505
-
506
- // Store instances in a WeakMap to avoid memory leaks
507
- const selectInstances = new WeakMap<HTMLElement, SelectHandler>();
508
-
509
- // Initialize selects
510
- const initSelects = () => {
511
- document.querySelectorAll(".starwind-select").forEach((select, idx) => {
512
- if (select instanceof HTMLElement && !selectInstances.has(select)) {
513
- selectInstances.set(select, new SelectHandler(select, idx));
514
- }
515
- });
516
- };
517
-
518
- initSelects();
519
- document.addEventListener("astro:after-swap", initSelects);
520
- </script>
521
-
522
- <style is:global>
523
- .starwind-sr-only {
524
- position: absolute;
525
- width: 1px;
526
- height: 1px;
527
- padding: 0;
528
- margin: -1px;
529
- overflow: hidden;
530
- clip: rect(0, 0, 0, 0);
531
- white-space: nowrap;
532
- border-width: 0;
533
- }
534
- </style>
@@ -1,83 +0,0 @@
1
- ---
2
- import type { HTMLAttributes } from "astro/types";
3
- import { tv } from "tailwind-variants";
4
-
5
- type Props = HTMLAttributes<"div"> & {
6
- /**
7
- * Side of the tooltip
8
- * @default bottom
9
- */
10
- side?: "top" | "bottom";
11
- /**
12
- * Alignment of the dropdown
13
- * @default start
14
- */
15
- align?: "start" | "center" | "end";
16
- /**
17
- * Offset distance in pixels
18
- * @default 4
19
- */
20
- sideOffset?: number;
21
- /**
22
- * Open and close animation duration in milliseconds
23
- * @default 150
24
- */
25
- animationDuration?: number;
26
- };
27
-
28
- export const selectContent = tv({
29
- base: [
30
- "starwind-select-content",
31
- "bg-popover text-popover-foreground absolute z-50 min-w-[8rem] rounded-md border shadow-md",
32
- "data-[state=open]:animate-in fade-in zoom-in-95 overflow-hidden will-change-transform",
33
- "data-[state=closed]:animate-out data-[state=closed]:fill-mode-forwards fade-out zoom-out-95",
34
- ],
35
- variants: {
36
- side: {
37
- bottom: "slide-in-from-top-2 slide-out-to-top-2 top-full",
38
- top: "slide-in-from-bottom-2 slide-out-to-bottom-2 bottom-full",
39
- },
40
- align: {
41
- start: "slide-in-from-left-1 slide-out-to-left-1 left-0",
42
- center: "left-1/2 -translate-x-1/2",
43
- end: "slide-in-from-right-1 slide-out-to-right-1 right-0",
44
- },
45
- },
46
- defaultVariants: { side: "bottom", align: "start" },
47
- });
48
-
49
- export const selectContentInner = tv({
50
- base: "max-h-96 w-full min-w-(--select-trigger-width) overflow-y-auto p-1",
51
- });
52
-
53
- const {
54
- class: className,
55
- side = "bottom",
56
- align = "start",
57
- sideOffset = 4,
58
- animationDuration = 150,
59
- ...rest
60
- } = Astro.props;
61
- ---
62
-
63
- <div
64
- class={selectContent({ side, align, class: className })}
65
- role="listbox"
66
- data-side={side}
67
- data-align={align}
68
- data-state="closed"
69
- data-slot="select-content"
70
- tabindex="-1"
71
- style={{
72
- // hide the content initially. Script will remove this
73
- display: "none",
74
- animationDuration: `${animationDuration}ms`,
75
- marginTop: side === "bottom" ? `${sideOffset}px` : undefined,
76
- marginBottom: side === "top" ? `${sideOffset}px` : undefined,
77
- }}
78
- {...rest}
79
- >
80
- <div class={selectContentInner()}>
81
- <slot />
82
- </div>
83
- </div>
@@ -1,9 +0,0 @@
1
- ---
2
- /**
3
- * This current doesn't do anything
4
- */
5
-
6
- type Props = { children: any };
7
- ---
8
-
9
- <slot />
@@ -1,49 +0,0 @@
1
- ---
2
- import Check from "@tabler/icons/outline/check.svg";
3
- import type { HTMLAttributes } from "astro/types";
4
- import { tv } from "tailwind-variants";
5
-
6
- type Props = HTMLAttributes<"div"> & {
7
- /**
8
- * The value associated with this select item
9
- */
10
- value: string;
11
- /**
12
- * Whether this select item is disabled and cannot be selected
13
- */
14
- disabled?: boolean;
15
- };
16
-
17
- export const selectItem = tv({
18
- base: [
19
- "relative flex w-full cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 outline-none select-none",
20
- "focus:bg-accent focus:text-accent-foreground",
21
- "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
22
- "not-aria-selected:[&_svg]:hidden aria-selected:[&_svg]:flex",
23
- ],
24
- });
25
-
26
- export const selectItemIcon = tv({ base: "absolute left-2 size-3.5 items-center justify-center" });
27
-
28
- const { class: className, value, disabled, ...rest } = Astro.props;
29
- ---
30
-
31
- <div
32
- class={selectItem({ class: className })}
33
- data-value={value}
34
- data-disabled={disabled}
35
- data-slot="select-item"
36
- aria-selected="false"
37
- role="option"
38
- tabindex="0"
39
- {...rest}
40
- >
41
- <span class={selectItemIcon()}>
42
- <slot name="icon">
43
- <Check class="size-4" />
44
- </slot>
45
- </span>
46
- <span>
47
- <slot />
48
- </span>
49
- </div>
@@ -1,14 +0,0 @@
1
- ---
2
- import type { HTMLAttributes } from "astro/types";
3
- import { tv } from "tailwind-variants";
4
-
5
- type Props = HTMLAttributes<"div">;
6
-
7
- export const selectLabel = tv({ base: "text-muted-foreground py-1.5 pr-2 pl-8 text-sm" });
8
-
9
- const { class: className, ...rest } = Astro.props;
10
- ---
11
-
12
- <div class={selectLabel({ class: className })} data-slot="select-label" {...rest}>
13
- <slot />
14
- </div>
@@ -1,12 +0,0 @@
1
- ---
2
- import type { HTMLAttributes } from "astro/types";
3
- import { tv } from "tailwind-variants";
4
-
5
- type Props = HTMLAttributes<"div">;
6
-
7
- export const selectSeparator = tv({ base: "bg-muted -mx-1 my-1 h-px" });
8
-
9
- const { class: className, ...rest } = Astro.props;
10
- ---
11
-
12
- <div class={selectSeparator({ class: className })} data-slot="select-separator" {...rest}></div>