@starwind-ui/core 1.14.0 → 1.15.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.
- package/package.json +1 -1
- package/dist/index.d.ts +0 -28
- package/dist/index.js +0 -85
- package/dist/index.js.map +0 -1
- package/dist/src/components/accordion/Accordion.astro +0 -254
- package/dist/src/components/accordion/AccordionContent.astro +0 -33
- package/dist/src/components/accordion/AccordionItem.astro +0 -27
- package/dist/src/components/accordion/AccordionTrigger.astro +0 -32
- package/dist/src/components/accordion/index.ts +0 -15
- package/dist/src/components/alert/Alert.astro +0 -31
- package/dist/src/components/alert/AlertDescription.astro +0 -14
- package/dist/src/components/alert/AlertTitle.astro +0 -16
- package/dist/src/components/alert/index.ts +0 -13
- package/dist/src/components/alert-dialog/AlertDialog.astro +0 -275
- package/dist/src/components/alert-dialog/AlertDialogAction.astro +0 -44
- package/dist/src/components/alert-dialog/AlertDialogCancel.astro +0 -45
- package/dist/src/components/alert-dialog/AlertDialogContent.astro +0 -52
- package/dist/src/components/alert-dialog/AlertDialogDescription.astro +0 -18
- package/dist/src/components/alert-dialog/AlertDialogFooter.astro +0 -16
- package/dist/src/components/alert-dialog/AlertDialogHeader.astro +0 -14
- package/dist/src/components/alert-dialog/AlertDialogTitle.astro +0 -20
- package/dist/src/components/alert-dialog/AlertDialogTrigger.astro +0 -47
- package/dist/src/components/alert-dialog/index.ts +0 -46
- package/dist/src/components/aspect-ratio/AspectRatio.astro +0 -32
- package/dist/src/components/aspect-ratio/index.ts +0 -7
- package/dist/src/components/avatar/Avatar.astro +0 -29
- package/dist/src/components/avatar/AvatarFallback.astro +0 -18
- package/dist/src/components/avatar/AvatarImage.astro +0 -49
- package/dist/src/components/avatar/index.ts +0 -13
- package/dist/src/components/badge/Badge.astro +0 -49
- package/dist/src/components/badge/index.ts +0 -7
- package/dist/src/components/breadcrumb/Breadcrumb.astro +0 -11
- package/dist/src/components/breadcrumb/BreadcrumbEllipsis.astro +0 -28
- package/dist/src/components/breadcrumb/BreadcrumbItem.astro +0 -14
- package/dist/src/components/breadcrumb/BreadcrumbLink.astro +0 -22
- package/dist/src/components/breadcrumb/BreadcrumbList.astro +0 -16
- package/dist/src/components/breadcrumb/BreadcrumbPage.astro +0 -21
- package/dist/src/components/breadcrumb/BreadcrumbSeparator.astro +0 -23
- package/dist/src/components/breadcrumb/index.ts +0 -37
- package/dist/src/components/button/Button.astro +0 -54
- package/dist/src/components/button/index.ts +0 -7
- package/dist/src/components/button-group/ButtonGroup.astro +0 -62
- package/dist/src/components/button-group/ButtonGroupSeparator.astro +0 -27
- package/dist/src/components/button-group/ButtonGroupText.astro +0 -19
- package/dist/src/components/button-group/index.ts +0 -17
- package/dist/src/components/card/Card.astro +0 -14
- package/dist/src/components/card/CardContent.astro +0 -14
- package/dist/src/components/card/CardDescription.astro +0 -14
- package/dist/src/components/card/CardFooter.astro +0 -14
- package/dist/src/components/card/CardHeader.astro +0 -14
- package/dist/src/components/card/CardTitle.astro +0 -14
- package/dist/src/components/card/index.ts +0 -26
- package/dist/src/components/carousel/Carousel.astro +0 -55
- package/dist/src/components/carousel/CarouselContent.astro +0 -26
- package/dist/src/components/carousel/CarouselItem.astro +0 -26
- package/dist/src/components/carousel/CarouselNext.astro +0 -37
- package/dist/src/components/carousel/CarouselPrevious.astro +0 -37
- package/dist/src/components/carousel/carousel-script.ts +0 -191
- package/dist/src/components/carousel/index.ts +0 -32
- package/dist/src/components/checkbox/Checkbox.astro +0 -128
- package/dist/src/components/checkbox/index.ts +0 -7
- package/dist/src/components/dialog/Dialog.astro +0 -355
- package/dist/src/components/dialog/DialogClose.astro +0 -35
- package/dist/src/components/dialog/DialogContent.astro +0 -78
- package/dist/src/components/dialog/DialogDescription.astro +0 -14
- package/dist/src/components/dialog/DialogFooter.astro +0 -14
- package/dist/src/components/dialog/DialogHeader.astro +0 -14
- package/dist/src/components/dialog/DialogTitle.astro +0 -22
- package/dist/src/components/dialog/DialogTrigger.astro +0 -47
- package/dist/src/components/dialog/index.ts +0 -45
- package/dist/src/components/dropdown/Dropdown.astro +0 -377
- package/dist/src/components/dropdown/DropdownContent.astro +0 -81
- package/dist/src/components/dropdown/DropdownItem.astro +0 -48
- package/dist/src/components/dropdown/DropdownLabel.astro +0 -29
- package/dist/src/components/dropdown/DropdownSeparator.astro +0 -21
- package/dist/src/components/dropdown/DropdownTrigger.astro +0 -52
- package/dist/src/components/dropdown/index.ts +0 -33
- package/dist/src/components/dropzone/Dropzone.astro +0 -236
- package/dist/src/components/dropzone/DropzoneFilesList.astro +0 -26
- package/dist/src/components/dropzone/DropzoneLoadingIndicator.astro +0 -10
- package/dist/src/components/dropzone/DropzoneUploadIndicator.astro +0 -10
- package/dist/src/components/dropzone/index.ts +0 -24
- package/dist/src/components/image/Image.astro +0 -24
- package/dist/src/components/image/index.ts +0 -9
- package/dist/src/components/input/Input.astro +0 -25
- package/dist/src/components/input/index.ts +0 -7
- package/dist/src/components/item/Item.astro +0 -52
- package/dist/src/components/item/ItemActions.astro +0 -16
- package/dist/src/components/item/ItemContent.astro +0 -16
- package/dist/src/components/item/ItemDescription.astro +0 -19
- package/dist/src/components/item/ItemFooter.astro +0 -16
- package/dist/src/components/item/ItemGroup.astro +0 -16
- package/dist/src/components/item/ItemHeader.astro +0 -16
- package/dist/src/components/item/ItemMedia.astro +0 -40
- package/dist/src/components/item/ItemSeparator.astro +0 -21
- package/dist/src/components/item/ItemTitle.astro +0 -16
- package/dist/src/components/item/index.ts +0 -50
- package/dist/src/components/kbd/Kbd.astro +0 -21
- package/dist/src/components/kbd/KbdGroup.astro +0 -16
- package/dist/src/components/kbd/index.ts +0 -11
- package/dist/src/components/label/Label.astro +0 -22
- package/dist/src/components/label/index.ts +0 -7
- package/dist/src/components/pagination/Pagination.astro +0 -20
- package/dist/src/components/pagination/PaginationContent.astro +0 -16
- package/dist/src/components/pagination/PaginationEllipsis.astro +0 -35
- package/dist/src/components/pagination/PaginationItem.astro +0 -16
- package/dist/src/components/pagination/PaginationLink.astro +0 -24
- package/dist/src/components/pagination/PaginationNext.astro +0 -30
- package/dist/src/components/pagination/PaginationPrevious.astro +0 -30
- package/dist/src/components/pagination/index.ts +0 -38
- package/dist/src/components/progress/Progress.astro +0 -155
- package/dist/src/components/progress/index.ts +0 -10
- package/dist/src/components/radio-group/RadioGroup.astro +0 -162
- package/dist/src/components/radio-group/RadioGroupItem.astro +0 -129
- package/dist/src/components/radio-group/RadioGroupTypes.ts +0 -6
- package/dist/src/components/radio-group/index.ts +0 -23
- package/dist/src/components/select/Select.astro +0 -751
- package/dist/src/components/select/SelectContent.astro +0 -94
- package/dist/src/components/select/SelectGroup.astro +0 -9
- package/dist/src/components/select/SelectItem.astro +0 -51
- package/dist/src/components/select/SelectLabel.astro +0 -14
- package/dist/src/components/select/SelectSearch.astro +0 -49
- package/dist/src/components/select/SelectSeparator.astro +0 -12
- package/dist/src/components/select/SelectTrigger.astro +0 -54
- package/dist/src/components/select/SelectTypes.ts +0 -13
- package/dist/src/components/select/SelectValue.astro +0 -19
- package/dist/src/components/select/index.ts +0 -49
- package/dist/src/components/separator/Separator.astro +0 -36
- package/dist/src/components/separator/index.ts +0 -7
- package/dist/src/components/sheet/Sheet.astro +0 -13
- package/dist/src/components/sheet/SheetClose.astro +0 -13
- package/dist/src/components/sheet/SheetContent.astro +0 -92
- package/dist/src/components/sheet/SheetDescription.astro +0 -16
- package/dist/src/components/sheet/SheetFooter.astro +0 -16
- package/dist/src/components/sheet/SheetHeader.astro +0 -16
- package/dist/src/components/sheet/SheetTitle.astro +0 -16
- package/dist/src/components/sheet/SheetTrigger.astro +0 -13
- package/dist/src/components/sheet/index.ts +0 -41
- package/dist/src/components/skeleton/Skeleton.astro +0 -14
- package/dist/src/components/skeleton/index.ts +0 -9
- package/dist/src/components/slider/Slider.astro +0 -411
- package/dist/src/components/slider/index.ts +0 -9
- package/dist/src/components/spinner/Spinner.astro +0 -21
- package/dist/src/components/spinner/index.ts +0 -7
- package/dist/src/components/switch/Switch.astro +0 -192
- package/dist/src/components/switch/SwitchTypes.ts +0 -6
- package/dist/src/components/switch/index.ts +0 -12
- package/dist/src/components/table/Table.astro +0 -18
- package/dist/src/components/table/TableBody.astro +0 -16
- package/dist/src/components/table/TableCaption.astro +0 -16
- package/dist/src/components/table/TableCell.astro +0 -16
- package/dist/src/components/table/TableFoot.astro +0 -16
- package/dist/src/components/table/TableHead.astro +0 -16
- package/dist/src/components/table/TableHeader.astro +0 -16
- package/dist/src/components/table/TableRow.astro +0 -16
- package/dist/src/components/table/index.ts +0 -42
- package/dist/src/components/tabs/Tabs.astro +0 -271
- package/dist/src/components/tabs/TabsContent.astro +0 -28
- package/dist/src/components/tabs/TabsList.astro +0 -22
- package/dist/src/components/tabs/TabsTrigger.astro +0 -34
- package/dist/src/components/tabs/index.ts +0 -20
- package/dist/src/components/textarea/Textarea.astro +0 -29
- package/dist/src/components/textarea/index.ts +0 -9
- package/dist/src/components/toast/ToastDescription.astro +0 -21
- package/dist/src/components/toast/ToastItem.astro +0 -54
- package/dist/src/components/toast/ToastTemplate.astro +0 -25
- package/dist/src/components/toast/ToastTitle.astro +0 -57
- package/dist/src/components/toast/Toaster.astro +0 -982
- package/dist/src/components/toast/index.ts +0 -29
- package/dist/src/components/toast/toast-manager.ts +0 -216
- package/dist/src/components/toggle/Toggle.astro +0 -174
- package/dist/src/components/toggle/ToggleTypes.ts +0 -14
- package/dist/src/components/toggle/index.ts +0 -8
- package/dist/src/components/tooltip/Tooltip.astro +0 -239
- package/dist/src/components/tooltip/TooltipContent.astro +0 -114
- package/dist/src/components/tooltip/TooltipTrigger.astro +0 -10
- package/dist/src/components/tooltip/index.ts +0 -16
- package/dist/src/components/video/Video.astro +0 -120
- package/dist/src/components/video/index.ts +0 -9
|
@@ -1,751 +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
|
-
* Whether the select field is required in a form context
|
|
15
|
-
*/
|
|
16
|
-
required?: boolean;
|
|
17
|
-
|
|
18
|
-
children: any;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const { class: className, name, defaultValue, required, ...rest } = Astro.props;
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
<div
|
|
25
|
-
class:list={["starwind-select", "relative", className]}
|
|
26
|
-
data-name={name}
|
|
27
|
-
data-value={defaultValue}
|
|
28
|
-
data-required={required}
|
|
29
|
-
data-slot="select"
|
|
30
|
-
{...rest}
|
|
31
|
-
>
|
|
32
|
-
<slot />
|
|
33
|
-
</div>
|
|
34
|
-
|
|
35
|
-
<script>
|
|
36
|
-
import type { SelectChangeEvent, SelectEvent } from "./SelectTypes";
|
|
37
|
-
|
|
38
|
-
class SelectHandler {
|
|
39
|
-
private select: HTMLElement;
|
|
40
|
-
private trigger: HTMLButtonElement | null;
|
|
41
|
-
private content: HTMLElement | null;
|
|
42
|
-
private searchInput: HTMLInputElement | null = null;
|
|
43
|
-
private emptyElement: HTMLElement | null = null;
|
|
44
|
-
private isOpen: boolean = false;
|
|
45
|
-
private selectedItem: HTMLElement | null = null;
|
|
46
|
-
private activeItem: HTMLElement | null = null;
|
|
47
|
-
private animationDuration = 150;
|
|
48
|
-
private typeaheadTimerRef: number | null = null;
|
|
49
|
-
private typeaheadSearch = "";
|
|
50
|
-
private returnFocusOnClose: boolean = true;
|
|
51
|
-
private required: boolean = false;
|
|
52
|
-
|
|
53
|
-
constructor(select: HTMLElement, selectIdx: number) {
|
|
54
|
-
this.select = select;
|
|
55
|
-
this.trigger = select.querySelector(".starwind-select-trigger");
|
|
56
|
-
this.content = select.querySelector(".starwind-select-content");
|
|
57
|
-
this.searchInput = select.querySelector('[data-slot="select-search"]');
|
|
58
|
-
this.emptyElement = select.querySelector('[data-slot="select-empty"]');
|
|
59
|
-
|
|
60
|
-
if (!this.trigger || !this.content) return;
|
|
61
|
-
|
|
62
|
-
this.required = this.select.getAttribute("data-required") === "true";
|
|
63
|
-
|
|
64
|
-
// animationDuration is set with inline styles through passed prop to SelectContent
|
|
65
|
-
const animationDurationString = this.content.style.animationDuration;
|
|
66
|
-
if (animationDurationString.endsWith("ms")) {
|
|
67
|
-
this.animationDuration = parseFloat(animationDurationString);
|
|
68
|
-
} else if (animationDurationString.endsWith("s")) {
|
|
69
|
-
// using something like @playform/compress might optimize to use "s" instead of "ms"
|
|
70
|
-
this.animationDuration = parseFloat(animationDurationString) * 1000;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
this.init(selectIdx);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
private init(selectIdx: number) {
|
|
77
|
-
this.setupAccessibility(selectIdx);
|
|
78
|
-
this.setupEvents();
|
|
79
|
-
this.setupSelectField();
|
|
80
|
-
this.setInitialState();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
private setupSelectField() {
|
|
84
|
-
if (!this.trigger || !this.content) return;
|
|
85
|
-
// build the standard select field
|
|
86
|
-
const selectField = document.createElement("select");
|
|
87
|
-
selectField.tabIndex = -1;
|
|
88
|
-
selectField.setAttribute("aria-hidden", "true");
|
|
89
|
-
selectField.setAttribute("placeholder", "select");
|
|
90
|
-
const selectName = this.select.getAttribute("data-name");
|
|
91
|
-
|
|
92
|
-
if (this.required) {
|
|
93
|
-
selectField.required = true;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (selectName) {
|
|
97
|
-
selectField.name = selectName;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// you can comment out this "sr-only" class line below if you want to see the native select in action
|
|
101
|
-
selectField.classList.add("starwind-sr-only");
|
|
102
|
-
|
|
103
|
-
// The first option is a placeholder
|
|
104
|
-
const placeholderOption = document.createElement("option");
|
|
105
|
-
placeholderOption.value = "";
|
|
106
|
-
placeholderOption.textContent = "Select";
|
|
107
|
-
placeholderOption.disabled = true;
|
|
108
|
-
placeholderOption.selected = true;
|
|
109
|
-
selectField.appendChild(placeholderOption);
|
|
110
|
-
|
|
111
|
-
// add all options to the select field
|
|
112
|
-
this.content.querySelectorAll('[role="option"]').forEach((option) => {
|
|
113
|
-
const optionValue = option.getAttribute("data-value");
|
|
114
|
-
const optionText = option.textContent;
|
|
115
|
-
const optionElement = document.createElement("option");
|
|
116
|
-
optionElement.value = optionValue || "";
|
|
117
|
-
optionElement.textContent = optionText || "";
|
|
118
|
-
selectField.appendChild(optionElement);
|
|
119
|
-
});
|
|
120
|
-
this.trigger.appendChild(selectField);
|
|
121
|
-
|
|
122
|
-
// add this select field right after the trigger
|
|
123
|
-
this.trigger.parentElement?.insertBefore(selectField, this.trigger.nextSibling);
|
|
124
|
-
|
|
125
|
-
// Intercept invalid event to show error on trigger instead of hidden select
|
|
126
|
-
if (this.required) {
|
|
127
|
-
selectField.addEventListener("invalid", (e) => {
|
|
128
|
-
this.showValidationError();
|
|
129
|
-
e.preventDefault();
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
this.setSize();
|
|
134
|
-
this.content.style.width = "var(--starwind-select-trigger-width)";
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
private setupAccessibility(selectIdx: number) {
|
|
138
|
-
if (!this.trigger || !this.content) return;
|
|
139
|
-
|
|
140
|
-
// Generate unique IDs for accessibility
|
|
141
|
-
this.trigger.id = `starwind-select${selectIdx}-trigger`;
|
|
142
|
-
this.content.id = `starwind-select${selectIdx}-content`;
|
|
143
|
-
|
|
144
|
-
// Set up additional ARIA attributes
|
|
145
|
-
this.trigger.setAttribute("aria-controls", this.content.id);
|
|
146
|
-
this.trigger.setAttribute("aria-required", this.required ? "true" : "false");
|
|
147
|
-
this.content.setAttribute("aria-labelledby", this.trigger.id);
|
|
148
|
-
|
|
149
|
-
// If search input exists, add IDs to all items for aria-activedescendant
|
|
150
|
-
if (this.searchInput) {
|
|
151
|
-
if (!this.searchInput.id) {
|
|
152
|
-
this.searchInput.id = `starwind-select${selectIdx}-search`;
|
|
153
|
-
}
|
|
154
|
-
// Link search input to the listbox it filters
|
|
155
|
-
this.searchInput.setAttribute("aria-controls", this.content.id);
|
|
156
|
-
|
|
157
|
-
const items = this.content.querySelectorAll('[role="option"]');
|
|
158
|
-
items.forEach((item, index) => {
|
|
159
|
-
if (!item.id) {
|
|
160
|
-
item.id = `starwind-select${selectIdx}-option${index}`;
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
private setupEvents() {
|
|
167
|
-
if (!this.trigger || !this.content) return;
|
|
168
|
-
|
|
169
|
-
// Handle search input if it exists
|
|
170
|
-
if (this.searchInput) {
|
|
171
|
-
this.setupSearchInput();
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Handle pointerdown
|
|
175
|
-
this.trigger.addEventListener("pointerdown", (e) => {
|
|
176
|
-
// prevent implicit pointer capture
|
|
177
|
-
// https://www.w3.org/TR/pointerevents3/#implicit-pointer-capture
|
|
178
|
-
const target = e.target as HTMLElement;
|
|
179
|
-
if (target.hasPointerCapture(e.pointerId)) {
|
|
180
|
-
target.releasePointerCapture(e.pointerId);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// prevent trigger from stealing focus from the active item after opening.
|
|
184
|
-
e.preventDefault();
|
|
185
|
-
|
|
186
|
-
// only call handler if it's the left button (mousedown gets triggered by all mouse buttons)
|
|
187
|
-
// but not when the control key is pressed (avoiding MacOS right click); also not for touch
|
|
188
|
-
// devices because that would open the menu on scroll. (pen devices behave as touch on iOS).
|
|
189
|
-
if (e.button === 0 && e.ctrlKey === false && e.pointerType === "mouse") {
|
|
190
|
-
this.returnFocusOnClose = true;
|
|
191
|
-
this.toggleSelect();
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
// Handle click event for mobile devices
|
|
196
|
-
this.trigger.addEventListener("click", (e) => {
|
|
197
|
-
if (window.matchMedia("(pointer: coarse)").matches) {
|
|
198
|
-
e.preventDefault();
|
|
199
|
-
this.returnFocusOnClose = true;
|
|
200
|
-
this.toggleSelect();
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
// add enter or space key to select trigger to toggle select
|
|
205
|
-
this.trigger.addEventListener("keydown", (e) => {
|
|
206
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
207
|
-
e.preventDefault();
|
|
208
|
-
this.returnFocusOnClose = true;
|
|
209
|
-
this.toggleSelect();
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// Handle keyboard navigation inside select content
|
|
214
|
-
this.content.addEventListener("keydown", (e) => {
|
|
215
|
-
// Check if the event originated from the search input
|
|
216
|
-
const isFromSearchInput = e.target === this.searchInput;
|
|
217
|
-
|
|
218
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
219
|
-
// Only handle selection if not typing in search input
|
|
220
|
-
if (!isFromSearchInput) {
|
|
221
|
-
// set element based on current focused element
|
|
222
|
-
const activeElement = document.activeElement;
|
|
223
|
-
this.returnFocusOnClose = true;
|
|
224
|
-
this.handleSelection(activeElement as HTMLElement);
|
|
225
|
-
}
|
|
226
|
-
// If from search input, don't handle it here - let the input handle it naturally
|
|
227
|
-
} else if (e.key === "Escape" && this.isOpen) {
|
|
228
|
-
this.returnFocusOnClose = true;
|
|
229
|
-
this.closeSelect();
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// add key navigation for accessibility
|
|
233
|
-
// "Home" goes to first element
|
|
234
|
-
// "End" goes to last element
|
|
235
|
-
// "ArrowUp" goes to previous element
|
|
236
|
-
// "ArrowDown" goes to next element
|
|
237
|
-
else if (["ArrowUp", "ArrowDown", "Home", "End"].includes(e.key)) {
|
|
238
|
-
this.handleNavigationKeys(e);
|
|
239
|
-
e.preventDefault();
|
|
240
|
-
} else {
|
|
241
|
-
const isModifierKey = e.ctrlKey || e.altKey || e.metaKey;
|
|
242
|
-
|
|
243
|
-
// select should not be navigated using tab key so we prevent it
|
|
244
|
-
if (e.key === "Tab") e.preventDefault();
|
|
245
|
-
|
|
246
|
-
if (!isModifierKey && e.key.length === 1 && !this.searchInput) {
|
|
247
|
-
this.handleTypeahead(e.key);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
// Handle hover on select items
|
|
253
|
-
this.content.addEventListener("mouseover", (e) => {
|
|
254
|
-
const target = e.target as HTMLElement;
|
|
255
|
-
const option = target.closest('[role="option"]');
|
|
256
|
-
if (option && option instanceof HTMLElement && this.isOpen === true) {
|
|
257
|
-
this.setActiveItem(option);
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
// handle pointerdown outside select content to close
|
|
262
|
-
document.addEventListener("pointerdown", (e) => {
|
|
263
|
-
// only close if not a mouse pointer
|
|
264
|
-
if (!window.matchMedia("(pointer: coarse)").matches) {
|
|
265
|
-
if (
|
|
266
|
-
!(
|
|
267
|
-
this.trigger?.contains(e.target as Node) || this.content?.contains(e.target as Node)
|
|
268
|
-
) &&
|
|
269
|
-
this.isOpen
|
|
270
|
-
) {
|
|
271
|
-
this.returnFocusOnClose = false;
|
|
272
|
-
this.closeSelect();
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
// Handle click outside select content to close
|
|
278
|
-
document.addEventListener("click", (e) => {
|
|
279
|
-
if (
|
|
280
|
-
!(this.trigger?.contains(e.target as Node) || this.content?.contains(e.target as Node)) &&
|
|
281
|
-
this.isOpen
|
|
282
|
-
) {
|
|
283
|
-
this.returnFocusOnClose = false;
|
|
284
|
-
this.closeSelect();
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
// Handle selection of items
|
|
289
|
-
this.content?.addEventListener("click", (e) => {
|
|
290
|
-
const item = (e.target as HTMLElement).closest("[role='option']");
|
|
291
|
-
if (item instanceof HTMLElement) {
|
|
292
|
-
this.returnFocusOnClose = true;
|
|
293
|
-
this.handleSelection(item);
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
// passive resize listener to call setSize()
|
|
298
|
-
window.addEventListener("resize", () => this.setSize(), { passive: true });
|
|
299
|
-
|
|
300
|
-
// Listen for programmatic selection events
|
|
301
|
-
document.addEventListener("starwind-select:select", (e: Event) => {
|
|
302
|
-
const selectEvent = e as SelectEvent;
|
|
303
|
-
const selectId = selectEvent.detail.selectId;
|
|
304
|
-
const selectName = selectEvent.detail.selectName;
|
|
305
|
-
const selectValue = selectEvent.detail.value;
|
|
306
|
-
|
|
307
|
-
// Check if this event is for this select
|
|
308
|
-
if (
|
|
309
|
-
(selectId && this.select.id === selectId) ||
|
|
310
|
-
(selectName && this.select.getAttribute("data-name") === selectName)
|
|
311
|
-
) {
|
|
312
|
-
this.programmaticallySelect(selectValue);
|
|
313
|
-
}
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
private setActiveItem(item: HTMLElement | null) {
|
|
318
|
-
if (!this.content) return;
|
|
319
|
-
|
|
320
|
-
// Remove active state from previous item
|
|
321
|
-
if (this.activeItem) {
|
|
322
|
-
this.activeItem.removeAttribute("data-active");
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Set new active item
|
|
326
|
-
this.activeItem = item;
|
|
327
|
-
if (item) {
|
|
328
|
-
item.setAttribute("data-active", "true");
|
|
329
|
-
|
|
330
|
-
// Scroll item into view if needed
|
|
331
|
-
item.scrollIntoView({ block: "nearest" });
|
|
332
|
-
|
|
333
|
-
// For search mode, set aria-activedescendant for assistive technologies
|
|
334
|
-
if (this.searchInput) {
|
|
335
|
-
// Item should already have an ID from setupAccessibility
|
|
336
|
-
this.searchInput.setAttribute("aria-activedescendant", item.id);
|
|
337
|
-
} else {
|
|
338
|
-
// For non-search mode, set focus for keyboard accessibility
|
|
339
|
-
item.focus();
|
|
340
|
-
}
|
|
341
|
-
} else if (this.searchInput) {
|
|
342
|
-
// Clear aria-activedescendant when no item is active
|
|
343
|
-
this.searchInput.removeAttribute("aria-activedescendant");
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
private findNavigableItem(
|
|
348
|
-
items: NodeListOf<Element>,
|
|
349
|
-
startIndex: number,
|
|
350
|
-
direction: "forward" | "backward",
|
|
351
|
-
): HTMLElement | null {
|
|
352
|
-
const step = direction === "forward" ? 1 : -1;
|
|
353
|
-
const end = direction === "forward" ? items.length : -1;
|
|
354
|
-
|
|
355
|
-
for (let i = startIndex; i !== end; i += step) {
|
|
356
|
-
const item = items[i] as HTMLElement;
|
|
357
|
-
if (
|
|
358
|
-
item.getAttribute("data-disabled") !== "true" &&
|
|
359
|
-
item.getAttribute("data-filtered") !== "true"
|
|
360
|
-
) {
|
|
361
|
-
return item;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
return null;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
private handleNavigationKeys(e: KeyboardEvent) {
|
|
368
|
-
if (!this.content) return;
|
|
369
|
-
const items = this.content.querySelectorAll('[role="option"]');
|
|
370
|
-
const currentIndex = Array.from(items).indexOf(this.activeItem as HTMLElement);
|
|
371
|
-
|
|
372
|
-
let targetItem: HTMLElement | null = null;
|
|
373
|
-
|
|
374
|
-
switch (e.key) {
|
|
375
|
-
case "Home":
|
|
376
|
-
targetItem = this.findNavigableItem(items, 0, "forward");
|
|
377
|
-
break;
|
|
378
|
-
|
|
379
|
-
case "End":
|
|
380
|
-
targetItem = this.findNavigableItem(items, items.length - 1, "backward");
|
|
381
|
-
break;
|
|
382
|
-
|
|
383
|
-
case "ArrowUp":
|
|
384
|
-
if (currentIndex > 0) {
|
|
385
|
-
targetItem = this.findNavigableItem(items, currentIndex - 1, "backward");
|
|
386
|
-
}
|
|
387
|
-
break;
|
|
388
|
-
|
|
389
|
-
case "ArrowDown":
|
|
390
|
-
if (currentIndex < items.length - 1) {
|
|
391
|
-
targetItem = this.findNavigableItem(items, currentIndex + 1, "forward");
|
|
392
|
-
}
|
|
393
|
-
break;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
if (targetItem) {
|
|
397
|
-
this.setActiveItem(targetItem);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
private setupSearchInput() {
|
|
402
|
-
if (!this.searchInput || !this.content) return;
|
|
403
|
-
|
|
404
|
-
this.searchInput.addEventListener("input", (e) => {
|
|
405
|
-
const target = e.target as HTMLInputElement;
|
|
406
|
-
const searchValue = target.value.toLowerCase().trim();
|
|
407
|
-
this.filterItems(searchValue);
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
// Handle keyboard navigation from search input
|
|
411
|
-
this.searchInput.addEventListener("keydown", (e) => {
|
|
412
|
-
if (e.key === "Escape") {
|
|
413
|
-
e.stopPropagation();
|
|
414
|
-
this.returnFocusOnClose = true;
|
|
415
|
-
this.closeSelect();
|
|
416
|
-
}
|
|
417
|
-
// Allow arrow keys to navigate to items
|
|
418
|
-
else if (["ArrowUp", "ArrowDown", "Home", "End"].includes(e.key)) {
|
|
419
|
-
e.preventDefault();
|
|
420
|
-
e.stopPropagation();
|
|
421
|
-
this.handleNavigationKeys(e);
|
|
422
|
-
}
|
|
423
|
-
// Handle Enter to select the active or first visible item
|
|
424
|
-
else if (e.key === "Enter") {
|
|
425
|
-
e.preventDefault();
|
|
426
|
-
const itemToSelect = this.activeItem || this.getFirstVisibleItem();
|
|
427
|
-
if (itemToSelect) {
|
|
428
|
-
this.returnFocusOnClose = true;
|
|
429
|
-
this.handleSelection(itemToSelect);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
// Prevent space from scrolling the page, but allow it to be typed
|
|
433
|
-
else if (e.key === " ") {
|
|
434
|
-
e.stopPropagation();
|
|
435
|
-
}
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
private getFirstVisibleItem(): HTMLElement | null {
|
|
440
|
-
if (!this.content) return null;
|
|
441
|
-
|
|
442
|
-
const items = this.content.querySelectorAll('[role="option"]');
|
|
443
|
-
for (const item of items) {
|
|
444
|
-
if (
|
|
445
|
-
item.getAttribute("data-disabled") !== "true" &&
|
|
446
|
-
item.getAttribute("data-filtered") !== "true"
|
|
447
|
-
) {
|
|
448
|
-
return item as HTMLElement;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
return null;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
private filterItems(searchValue: string) {
|
|
455
|
-
if (!this.content) return;
|
|
456
|
-
|
|
457
|
-
const items = this.content.querySelectorAll('[role="option"]');
|
|
458
|
-
let visibleCount = 0;
|
|
459
|
-
let firstVisibleItem: HTMLElement | null = null;
|
|
460
|
-
|
|
461
|
-
items.forEach((item) => {
|
|
462
|
-
const itemText = item.textContent?.toLowerCase().trim() || "";
|
|
463
|
-
const matches = itemText.includes(searchValue);
|
|
464
|
-
|
|
465
|
-
if (matches || searchValue === "") {
|
|
466
|
-
item.classList.remove("starwind-sr-only");
|
|
467
|
-
item.removeAttribute("data-filtered");
|
|
468
|
-
visibleCount++;
|
|
469
|
-
if (!firstVisibleItem) {
|
|
470
|
-
firstVisibleItem = item as HTMLElement;
|
|
471
|
-
}
|
|
472
|
-
} else {
|
|
473
|
-
item.classList.add("starwind-sr-only");
|
|
474
|
-
item.setAttribute("data-filtered", "true");
|
|
475
|
-
}
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
// Update active item to first visible item after filtering
|
|
479
|
-
if (this.searchInput && firstVisibleItem) {
|
|
480
|
-
this.setActiveItem(firstVisibleItem);
|
|
481
|
-
} else if (this.searchInput && visibleCount === 0) {
|
|
482
|
-
this.setActiveItem(null);
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// Show/hide empty message
|
|
486
|
-
if (this.emptyElement) {
|
|
487
|
-
if (visibleCount === 0 && searchValue !== "") {
|
|
488
|
-
this.emptyElement.classList.remove("hidden");
|
|
489
|
-
} else {
|
|
490
|
-
this.emptyElement.classList.add("hidden");
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
private handleTypeahead(key: string) {
|
|
496
|
-
if (!this.content) return;
|
|
497
|
-
const search = this.typeaheadSearch + key;
|
|
498
|
-
const items = this.content.querySelectorAll('[role="option"]');
|
|
499
|
-
|
|
500
|
-
// find and set active the first matching option
|
|
501
|
-
const matches = Array.from(items).filter((item) =>
|
|
502
|
-
item.textContent?.toLowerCase().trim().startsWith(search.toLowerCase()),
|
|
503
|
-
) as HTMLElement[];
|
|
504
|
-
if (matches.length > 0) {
|
|
505
|
-
this.setActiveItem(matches[0]);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// update the typeahead search and reset the timer
|
|
509
|
-
this.typeaheadSearch = search;
|
|
510
|
-
if (this.typeaheadTimerRef) {
|
|
511
|
-
window.clearTimeout(this.typeaheadTimerRef);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// set a timer to clear the search after 1 second
|
|
515
|
-
this.typeaheadTimerRef = window.setTimeout(() => {
|
|
516
|
-
this.typeaheadSearch = "";
|
|
517
|
-
this.typeaheadTimerRef = null;
|
|
518
|
-
}, 1000);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
private setSize() {
|
|
522
|
-
if (!this.trigger || !this.content) return;
|
|
523
|
-
this.content.style.setProperty(
|
|
524
|
-
"--starwind-select-content-width",
|
|
525
|
-
`${this.content.offsetWidth}px`,
|
|
526
|
-
);
|
|
527
|
-
|
|
528
|
-
this.content.style.setProperty(
|
|
529
|
-
"--starwind-select-trigger-width",
|
|
530
|
-
`${this.trigger.offsetWidth}px`,
|
|
531
|
-
);
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
private toggleSelect() {
|
|
535
|
-
if (this.isOpen) {
|
|
536
|
-
this.closeSelect();
|
|
537
|
-
} else {
|
|
538
|
-
this.openSelect();
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
private openSelect() {
|
|
543
|
-
if (!this.content || !this.trigger || this.trigger.disabled) return;
|
|
544
|
-
|
|
545
|
-
this.isOpen = true;
|
|
546
|
-
this.content.setAttribute("data-state", "open");
|
|
547
|
-
this.trigger.setAttribute("aria-expanded", "true");
|
|
548
|
-
this.content.style.removeProperty("display");
|
|
549
|
-
|
|
550
|
-
// If search input exists, focus it and clear any previous search
|
|
551
|
-
if (this.searchInput) {
|
|
552
|
-
this.searchInput.value = "";
|
|
553
|
-
this.filterItems("");
|
|
554
|
-
|
|
555
|
-
// Set the selected item or first item as active
|
|
556
|
-
const initialItem = this.selectedItem || this.getFirstVisibleItem();
|
|
557
|
-
if (initialItem) {
|
|
558
|
-
this.setActiveItem(initialItem);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
requestAnimationFrame(() => {
|
|
562
|
-
this.searchInput?.focus();
|
|
563
|
-
});
|
|
564
|
-
} else {
|
|
565
|
-
// set active on the current selected item
|
|
566
|
-
if (this.selectedItem) {
|
|
567
|
-
this.setActiveItem(this.selectedItem);
|
|
568
|
-
} else {
|
|
569
|
-
// if no item is selected, set active on the first item
|
|
570
|
-
const firstItem = this.content.querySelector('[role="option"]') as HTMLElement;
|
|
571
|
-
if (firstItem) {
|
|
572
|
-
this.setActiveItem(firstItem);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
private closeSelect() {
|
|
579
|
-
if (!this.content || !this.trigger) return;
|
|
580
|
-
|
|
581
|
-
this.isOpen = false;
|
|
582
|
-
this.content.setAttribute("data-state", "closed");
|
|
583
|
-
|
|
584
|
-
// Remove focus from any currently focused element
|
|
585
|
-
const activeElement = document.activeElement;
|
|
586
|
-
if (activeElement instanceof HTMLElement) {
|
|
587
|
-
activeElement.blur();
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
// Set focus on trigger if returnFocusOnClose is true
|
|
591
|
-
if (this.returnFocusOnClose) {
|
|
592
|
-
requestAnimationFrame(() => {
|
|
593
|
-
if (!this.trigger) return;
|
|
594
|
-
this.trigger.focus();
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
// give the content time to animate before hiding
|
|
599
|
-
setTimeout(() => {
|
|
600
|
-
if (!this.content) return;
|
|
601
|
-
this.content.style.display = "none";
|
|
602
|
-
|
|
603
|
-
// Clear search and show all items after animation completes
|
|
604
|
-
if (this.searchInput) {
|
|
605
|
-
this.searchInput.value = "";
|
|
606
|
-
this.filterItems("");
|
|
607
|
-
this.setActiveItem(null);
|
|
608
|
-
}
|
|
609
|
-
}, this.animationDuration);
|
|
610
|
-
|
|
611
|
-
this.trigger.setAttribute("aria-expanded", "false");
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
private handleSelection(item: HTMLElement) {
|
|
615
|
-
if (!this.trigger) return;
|
|
616
|
-
|
|
617
|
-
// Clear any validation error when a selection is made
|
|
618
|
-
this.clearValidationError();
|
|
619
|
-
|
|
620
|
-
// update the hidden select field
|
|
621
|
-
const selectField = this.select.querySelector("select");
|
|
622
|
-
if (selectField) {
|
|
623
|
-
const newValue = item.getAttribute("data-value") || "";
|
|
624
|
-
selectField.value = newValue;
|
|
625
|
-
|
|
626
|
-
// Dispatch custom event with the new value
|
|
627
|
-
const event = new CustomEvent<SelectChangeEvent["detail"]>("starwind-select:change", {
|
|
628
|
-
detail: { value: newValue, selectId: this.select.id, label: item.textContent || "" },
|
|
629
|
-
bubbles: true,
|
|
630
|
-
cancelable: true,
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
selectField.dispatchEvent(event);
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
// Update trigger content
|
|
637
|
-
const triggerSpan = this.trigger.firstElementChild as HTMLSpanElement;
|
|
638
|
-
if (triggerSpan) {
|
|
639
|
-
triggerSpan.textContent = item.textContent;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
// Update selected states after select finishes closing
|
|
643
|
-
setTimeout(() => {
|
|
644
|
-
if (this.selectedItem) {
|
|
645
|
-
this.selectedItem.setAttribute("aria-selected", "false");
|
|
646
|
-
}
|
|
647
|
-
item.setAttribute("aria-selected", "true");
|
|
648
|
-
this.selectedItem = item;
|
|
649
|
-
}, this.animationDuration);
|
|
650
|
-
|
|
651
|
-
// Close the select
|
|
652
|
-
this.closeSelect();
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
/**
|
|
656
|
-
* Sets the initial state based on the default value attribute
|
|
657
|
-
*/
|
|
658
|
-
private setInitialState(): void {
|
|
659
|
-
const defaultValue = this.select.dataset.value;
|
|
660
|
-
if (defaultValue) {
|
|
661
|
-
const item = this.content?.querySelector(`[data-value="${defaultValue}"]`);
|
|
662
|
-
|
|
663
|
-
if (item && item instanceof HTMLElement) {
|
|
664
|
-
this.returnFocusOnClose = false;
|
|
665
|
-
this.handleSelection(item);
|
|
666
|
-
this.selectedItem = item;
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
/**
|
|
672
|
-
* Programmatically selects an option by value
|
|
673
|
-
*/
|
|
674
|
-
private programmaticallySelect(value: string): void {
|
|
675
|
-
if (!this.content) return;
|
|
676
|
-
|
|
677
|
-
// Skip if already selected (prevents infinite loops with synced selects)
|
|
678
|
-
if (this.selectedItem?.getAttribute("data-value") === value) return;
|
|
679
|
-
|
|
680
|
-
const item = this.content.querySelector(`[data-value="${value}"]`);
|
|
681
|
-
if (item instanceof HTMLElement) {
|
|
682
|
-
this.returnFocusOnClose = false;
|
|
683
|
-
|
|
684
|
-
// Update aria-selected attributes immediately
|
|
685
|
-
if (this.selectedItem) {
|
|
686
|
-
this.selectedItem.setAttribute("aria-selected", "false");
|
|
687
|
-
}
|
|
688
|
-
item.setAttribute("aria-selected", "true");
|
|
689
|
-
|
|
690
|
-
// Then call handleSelection which will update the rest
|
|
691
|
-
this.handleSelection(item);
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
/**
|
|
696
|
-
* Shows validation error on the trigger
|
|
697
|
-
*/
|
|
698
|
-
private showValidationError(): void {
|
|
699
|
-
if (!this.trigger) return;
|
|
700
|
-
|
|
701
|
-
// Add error state to trigger
|
|
702
|
-
this.trigger.setAttribute("aria-invalid", "true");
|
|
703
|
-
|
|
704
|
-
// scroll trigger into view
|
|
705
|
-
this.trigger.scrollIntoView({ behavior: "auto", block: "center" });
|
|
706
|
-
|
|
707
|
-
// Focus the trigger so user knows where the error is
|
|
708
|
-
this.trigger.focus();
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
/**
|
|
712
|
-
* Clears validation error from the trigger
|
|
713
|
-
*/
|
|
714
|
-
private clearValidationError(): void {
|
|
715
|
-
if (!this.trigger) return;
|
|
716
|
-
|
|
717
|
-
this.trigger.setAttribute("aria-invalid", "false");
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
// Store instances in a WeakMap to avoid memory leaks
|
|
722
|
-
const selectInstances = new WeakMap<HTMLElement, SelectHandler>();
|
|
723
|
-
let selectCounter = 0;
|
|
724
|
-
|
|
725
|
-
// Initialize selects
|
|
726
|
-
const initSelects = () => {
|
|
727
|
-
document.querySelectorAll(".starwind-select").forEach((select) => {
|
|
728
|
-
if (select instanceof HTMLElement && !selectInstances.has(select)) {
|
|
729
|
-
selectInstances.set(select, new SelectHandler(select, selectCounter++));
|
|
730
|
-
}
|
|
731
|
-
});
|
|
732
|
-
};
|
|
733
|
-
|
|
734
|
-
initSelects();
|
|
735
|
-
document.addEventListener("astro:after-swap", initSelects);
|
|
736
|
-
document.addEventListener("starwind:init", initSelects);
|
|
737
|
-
</script>
|
|
738
|
-
|
|
739
|
-
<style is:global>
|
|
740
|
-
.starwind-sr-only {
|
|
741
|
-
position: absolute;
|
|
742
|
-
width: 1px;
|
|
743
|
-
height: 1px;
|
|
744
|
-
padding: 0;
|
|
745
|
-
margin: -1px;
|
|
746
|
-
overflow: hidden;
|
|
747
|
-
clip: rect(0, 0, 0, 0);
|
|
748
|
-
white-space: nowrap;
|
|
749
|
-
border-width: 0;
|
|
750
|
-
}
|
|
751
|
-
</style>
|