zabi-components 8.0.0 → 9.0.0
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/CHANGELOG.md +2935 -0
- package/README.md +243 -10
- package/THEME.md +162 -332
- package/THEMING.md +1419 -259
- package/dist/atoms/ActionPanel.svelte +15 -5
- package/dist/atoms/Avatar.svelte +120 -0
- package/dist/atoms/Avatar.svelte.d.ts +46 -0
- package/dist/atoms/Badge.svelte +71 -13
- package/dist/atoms/Badge.svelte.d.ts +8 -2
- package/dist/atoms/Block.svelte +74 -0
- package/dist/atoms/Block.svelte.d.ts +29 -0
- package/dist/atoms/Button.svelte +217 -30
- package/dist/atoms/Button.svelte.d.ts +33 -4
- package/dist/atoms/Card.svelte +182 -26
- package/dist/atoms/Card.svelte.d.ts +55 -3
- package/dist/atoms/CardHeader.svelte +16 -4
- package/dist/atoms/Checkbox.svelte +81 -21
- package/dist/atoms/Checkbox.svelte.d.ts +31 -2
- package/dist/atoms/Chip.svelte +351 -0
- package/dist/atoms/Chip.svelte.d.ts +57 -0
- package/dist/atoms/CodeBlock.svelte +20 -2
- package/dist/atoms/CodeBlock.svelte.d.ts +4 -0
- package/dist/atoms/CodeField.svelte +385 -0
- package/dist/atoms/CodeField.svelte.d.ts +61 -0
- package/dist/atoms/ColorPicker.svelte +260 -49
- package/dist/atoms/ColorPicker.svelte.d.ts +3 -0
- package/dist/atoms/Container.svelte +16 -8
- package/dist/atoms/DateField.svelte +145 -0
- package/dist/atoms/DateField.svelte.d.ts +81 -0
- package/dist/atoms/FieldMessages.svelte +69 -0
- package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
- package/dist/atoms/FloatingActionButton.svelte +176 -0
- package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
- package/dist/atoms/Heading.svelte +63 -5
- package/dist/atoms/Heading.svelte.d.ts +14 -0
- package/dist/atoms/IconButton.svelte +294 -26
- package/dist/atoms/IconButton.svelte.d.ts +62 -4
- package/dist/atoms/Input.svelte +259 -53
- package/dist/atoms/Input.svelte.d.ts +33 -2
- package/dist/atoms/List.svelte +5 -1
- package/dist/atoms/ListItem.svelte +70 -13
- package/dist/atoms/ListItem.svelte.d.ts +11 -0
- package/dist/atoms/PickerField.svelte +239 -0
- package/dist/atoms/PickerField.svelte.d.ts +67 -0
- package/dist/atoms/Progress.svelte +99 -19
- package/dist/atoms/Progress.svelte.d.ts +15 -1
- package/dist/atoms/Radio.svelte +51 -3
- package/dist/atoms/Radio.svelte.d.ts +30 -2
- package/dist/atoms/Rating.svelte +620 -0
- package/dist/atoms/Rating.svelte.d.ts +77 -0
- package/dist/atoms/Select.svelte +438 -98
- package/dist/atoms/Select.svelte.d.ts +61 -3
- package/dist/atoms/SelectionControl.svelte +193 -23
- package/dist/atoms/SelectionControl.svelte.d.ts +29 -3
- package/dist/atoms/Skeleton.svelte +114 -18
- package/dist/atoms/Skeleton.svelte.d.ts +30 -3
- package/dist/atoms/SkeletonGroup.svelte +43 -0
- package/dist/atoms/SkeletonGroup.svelte.d.ts +20 -0
- package/dist/atoms/Slider.svelte +344 -0
- package/dist/atoms/Slider.svelte.d.ts +40 -0
- package/dist/atoms/Sparkline.svelte +109 -0
- package/dist/atoms/Sparkline.svelte.d.ts +27 -0
- package/dist/atoms/SpeechBubble.svelte +187 -0
- package/dist/atoms/SpeechBubble.svelte.d.ts +33 -0
- package/dist/atoms/Spinner.svelte +57 -0
- package/dist/atoms/Spinner.svelte.d.ts +19 -0
- package/dist/atoms/Stat.svelte +72 -0
- package/dist/atoms/Stat.svelte.d.ts +19 -0
- package/dist/atoms/Table.svelte +151 -6
- package/dist/atoms/Table.svelte.d.ts +15 -3
- package/dist/atoms/TemporalField.svelte +472 -0
- package/dist/atoms/TemporalField.svelte.d.ts +48 -0
- package/dist/atoms/Text.svelte +23 -11
- package/dist/atoms/Text.svelte.d.ts +6 -3
- package/dist/atoms/Textarea.svelte +150 -44
- package/dist/atoms/Textarea.svelte.d.ts +35 -2
- package/dist/atoms/ThemeToggle.svelte +223 -44
- package/dist/atoms/ThemeToggle.svelte.d.ts +26 -3
- package/dist/atoms/TimeColumn.svelte +150 -0
- package/dist/atoms/TimeColumn.svelte.d.ts +25 -0
- package/dist/atoms/TimeField.svelte +151 -0
- package/dist/atoms/TimeField.svelte.d.ts +85 -0
- package/dist/atoms/Toast.svelte +22 -8
- package/dist/atoms/Toast.svelte.d.ts +2 -0
- package/dist/atoms/Toggle.svelte +98 -14
- package/dist/atoms/Toggle.svelte.d.ts +26 -2
- package/dist/atoms/Tooltip.svelte +528 -24
- package/dist/atoms/Tooltip.svelte.d.ts +18 -0
- package/dist/atoms/ZabiStringsProvider.svelte +45 -0
- package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
- package/dist/atoms/index.d.ts +38 -0
- package/dist/atoms/index.js +29 -0
- package/dist/atoms/selection-control.styles.d.ts +47 -5
- package/dist/atoms/selection-control.styles.js +52 -10
- package/dist/components/atoms/index.d.ts +38 -0
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/atoms/selection-control.styles.d.ts +47 -5
- package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
- package/dist/components/index.d.ts +104 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/map.d.ts +11 -0
- package/dist/components/map.d.ts.map +1 -0
- package/dist/components/molecules/index.d.ts +51 -0
- package/dist/components/molecules/index.d.ts.map +1 -1
- package/dist/components/molecules/toast-store.d.ts +83 -7
- package/dist/components/molecules/toast-store.d.ts.map +1 -1
- package/dist/components/organisms/index.d.ts +10 -0
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/components/types/variants.d.ts +24 -1
- package/dist/components/types/variants.d.ts.map +1 -1
- package/dist/components/util/app-shell.d.ts +79 -0
- package/dist/components/util/app-shell.d.ts.map +1 -0
- package/dist/components/util/avatar.d.ts +39 -0
- package/dist/components/util/avatar.d.ts.map +1 -0
- package/dist/components/util/bottom-sheet.d.ts +30 -0
- package/dist/components/util/bottom-sheet.d.ts.map +1 -0
- package/dist/components/util/bottom-tab-bar.d.ts +41 -0
- package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
- package/dist/components/util/calendar.d.ts +56 -0
- package/dist/components/util/calendar.d.ts.map +1 -0
- package/dist/components/util/chart.d.ts +140 -0
- package/dist/components/util/chart.d.ts.map +1 -0
- package/dist/components/util/chip.d.ts +66 -0
- package/dist/components/util/chip.d.ts.map +1 -0
- package/dist/components/util/cn.d.ts.map +1 -1
- package/dist/components/util/code-field.d.ts +63 -0
- package/dist/components/util/code-field.d.ts.map +1 -0
- package/dist/components/util/collapsible.d.ts +64 -0
- package/dist/components/util/collapsible.d.ts.map +1 -0
- package/dist/components/util/confirm-dialog.d.ts +12 -0
- package/dist/components/util/confirm-dialog.d.ts.map +1 -0
- package/dist/components/util/date.d.ts +96 -0
- package/dist/components/util/date.d.ts.map +1 -0
- package/dist/components/util/display-voice.d.ts +10 -0
- package/dist/components/util/display-voice.d.ts.map +1 -0
- package/dist/components/util/drawer.d.ts +13 -0
- package/dist/components/util/drawer.d.ts.map +1 -0
- package/dist/components/util/dropdown.d.ts +47 -0
- package/dist/components/util/dropdown.d.ts.map +1 -0
- package/dist/components/util/field-control.d.ts +30 -0
- package/dist/components/util/field-control.d.ts.map +1 -0
- package/dist/components/util/field.d.ts +45 -0
- package/dist/components/util/field.d.ts.map +1 -0
- package/dist/components/util/fit-in-viewport.d.ts +201 -0
- package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
- package/dist/components/util/focus-utils.d.ts +77 -2
- package/dist/components/util/focus-utils.d.ts.map +1 -1
- package/dist/components/util/hydration.d.ts +45 -0
- package/dist/components/util/hydration.d.ts.map +1 -0
- package/dist/components/util/keyboard-inset.d.ts +18 -0
- package/dist/components/util/keyboard-inset.d.ts.map +1 -0
- package/dist/components/util/map-view.d.ts +144 -0
- package/dist/components/util/map-view.d.ts.map +1 -0
- package/dist/components/util/media-grid.d.ts +51 -0
- package/dist/components/util/media-grid.d.ts.map +1 -0
- package/dist/components/util/media.d.ts +6 -0
- package/dist/components/util/media.d.ts.map +1 -0
- package/dist/components/util/overlay-chrome.d.ts +53 -0
- package/dist/components/util/overlay-chrome.d.ts.map +1 -0
- package/dist/components/util/overlay.d.ts +72 -0
- package/dist/components/util/overlay.d.ts.map +1 -0
- package/dist/components/util/photo.d.ts +234 -0
- package/dist/components/util/photo.d.ts.map +1 -0
- package/dist/components/util/portal.d.ts +23 -0
- package/dist/components/util/portal.d.ts.map +1 -0
- package/dist/components/util/progress.d.ts +19 -0
- package/dist/components/util/progress.d.ts.map +1 -0
- package/dist/components/util/pull-to-refresh.d.ts +27 -0
- package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
- package/dist/components/util/radio-keys.d.ts +25 -0
- package/dist/components/util/radio-keys.d.ts.map +1 -0
- package/dist/components/util/rating.d.ts +23 -0
- package/dist/components/util/rating.d.ts.map +1 -0
- package/dist/components/util/ready-made-strings.d.ts +77 -0
- package/dist/components/util/ready-made-strings.d.ts.map +1 -0
- package/dist/components/util/scroll-edge.d.ts +33 -0
- package/dist/components/util/scroll-edge.d.ts.map +1 -0
- package/dist/components/util/scroll-padding.d.ts +20 -0
- package/dist/components/util/scroll-padding.d.ts.map +1 -0
- package/dist/components/util/segmented-control.d.ts +16 -0
- package/dist/components/util/segmented-control.d.ts.map +1 -0
- package/dist/components/util/select.d.ts +41 -0
- package/dist/components/util/select.d.ts.map +1 -0
- package/dist/components/util/sheet-drag.d.ts +84 -0
- package/dist/components/util/sheet-drag.d.ts.map +1 -0
- package/dist/components/util/sidebar.d.ts +63 -0
- package/dist/components/util/sidebar.d.ts.map +1 -0
- package/dist/components/util/skeleton.d.ts +15 -0
- package/dist/components/util/skeleton.d.ts.map +1 -0
- package/dist/components/util/sliding-indicator.d.ts +25 -0
- package/dist/components/util/sliding-indicator.d.ts.map +1 -0
- package/dist/components/util/sortable-list.d.ts +65 -0
- package/dist/components/util/sortable-list.d.ts.map +1 -0
- package/dist/components/util/stepper.d.ts +40 -0
- package/dist/components/util/stepper.d.ts.map +1 -0
- package/dist/components/util/swipeable-list-item.d.ts +36 -0
- package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
- package/dist/components/util/temporal-field.d.ts +89 -0
- package/dist/components/util/temporal-field.d.ts.map +1 -0
- package/dist/components/util/theme-mode.d.ts +80 -0
- package/dist/components/util/theme-mode.d.ts.map +1 -0
- package/dist/components/util/toaster.d.ts +133 -0
- package/dist/components/util/toaster.d.ts.map +1 -0
- package/dist/components/util/tone-surface.d.ts +34 -0
- package/dist/components/util/tone-surface.d.ts.map +1 -0
- package/dist/components/util/tooltip.d.ts +10 -0
- package/dist/components/util/tooltip.d.ts.map +1 -0
- package/dist/components/util/top-navbar.d.ts +35 -0
- package/dist/components/util/top-navbar.d.ts.map +1 -0
- package/dist/components/util/touch-target.d.ts +31 -0
- package/dist/components/util/touch-target.d.ts.map +1 -0
- package/dist/components/util/zabi-strings.d.ts +142 -0
- package/dist/components/util/zabi-strings.d.ts.map +1 -0
- package/dist/create-theme/cli.js +173 -0
- package/dist/create-theme/index.d.ts +190 -0
- package/dist/create-theme/index.js +1034 -0
- package/dist/create-theme/lib/ramp-math.js +128 -0
- package/dist/create-theme/lib/resolve.js +238 -0
- package/dist/create-theme/theme-data.js +2644 -0
- package/dist/index.d.ts +104 -2
- package/dist/index.js +75 -2
- package/dist/map.d.ts +10 -0
- package/dist/map.js +9 -0
- package/dist/molecules/Alert.svelte +63 -16
- package/dist/molecules/Alert.svelte.d.ts +14 -1
- package/dist/molecules/AppBar.svelte +674 -0
- package/dist/molecules/AppBar.svelte.d.ts +131 -0
- package/dist/molecules/AvatarGroup.svelte +116 -0
- package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
- package/dist/molecules/BarChart.svelte +381 -0
- package/dist/molecules/BarChart.svelte.d.ts +45 -0
- package/dist/molecules/BottomSheet.svelte +608 -0
- package/dist/molecules/BottomSheet.svelte.d.ts +109 -0
- package/dist/molecules/BottomTabBar.svelte +403 -0
- package/dist/molecules/BottomTabBar.svelte.d.ts +98 -0
- package/dist/molecules/Calendar.svelte +597 -0
- package/dist/molecules/Calendar.svelte.d.ts +72 -0
- package/dist/molecules/ChipGroup.svelte +325 -0
- package/dist/molecules/ChipGroup.svelte.d.ts +41 -0
- package/dist/molecules/Collapsible.svelte +347 -0
- package/dist/molecules/Collapsible.svelte.d.ts +61 -0
- package/dist/molecules/CollapsibleGroup.svelte +136 -0
- package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
- package/dist/molecules/ComponentDemo.svelte +16 -4
- package/dist/molecules/ComponentDemo.svelte.d.ts +3 -0
- package/dist/molecules/ConfirmDialog.svelte +257 -0
- package/dist/molecules/ConfirmDialog.svelte.d.ts +58 -0
- package/dist/molecules/ContactForm.svelte +34 -17
- package/dist/molecules/ContactForm.svelte.d.ts +8 -0
- package/dist/molecules/Drawer.svelte +415 -0
- package/dist/molecules/Drawer.svelte.d.ts +55 -0
- package/dist/molecules/Dropdown.svelte +517 -48
- package/dist/molecules/Dropdown.svelte.d.ts +47 -6
- package/dist/molecules/DropdownItem.svelte +122 -0
- package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
- package/dist/molecules/EmptyState.svelte +55 -10
- package/dist/molecules/EmptyState.svelte.d.ts +8 -0
- package/dist/molecules/FormField.svelte +9 -1
- package/dist/molecules/FormField.svelte.d.ts +2 -0
- package/dist/molecules/Header.svelte +1 -1
- package/dist/molecules/ImageUpload.svelte +492 -69
- package/dist/molecules/ImageUpload.svelte.d.ts +82 -2
- package/dist/molecules/LineChart.svelte +459 -0
- package/dist/molecules/LineChart.svelte.d.ts +53 -0
- package/dist/molecules/MapView.svelte +565 -0
- package/dist/molecules/MapView.svelte.d.ts +88 -0
- package/dist/molecules/MediaGrid.svelte +527 -0
- package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
- package/dist/molecules/Modal.svelte +293 -72
- package/dist/molecules/Modal.svelte.d.ts +51 -2
- package/dist/molecules/NavigationMenuContent.svelte +58 -1
- package/dist/molecules/NavigationMenuItem.svelte +0 -1
- package/dist/molecules/NavigationMenuLink.svelte +1 -1
- package/dist/molecules/NavigationMenuList.svelte +10 -3
- package/dist/molecules/NavigationMenuTrigger.svelte +2 -2
- package/dist/molecules/Page.svelte +32 -2
- package/dist/molecules/Page.svelte.d.ts +11 -0
- package/dist/molecules/PhotoGrid.svelte +523 -0
- package/dist/molecules/PhotoGrid.svelte.d.ts +73 -0
- package/dist/molecules/PhotoViewer.svelte +1213 -0
- package/dist/molecules/PhotoViewer.svelte.d.ts +74 -0
- package/dist/molecules/PieChart.svelte +421 -0
- package/dist/molecules/PieChart.svelte.d.ts +51 -0
- package/dist/molecules/PropsTable.svelte +22 -10
- package/dist/molecules/PropsTable.svelte.d.ts +3 -0
- package/dist/molecules/PullToRefresh.svelte +263 -0
- package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
- package/dist/molecules/RadioGroup.svelte +72 -18
- package/dist/molecules/RadioGroup.svelte.d.ts +19 -3
- package/dist/molecules/Section.svelte +1 -1
- package/dist/molecules/SegmentedControl.svelte +456 -0
- package/dist/molecules/SegmentedControl.svelte.d.ts +40 -0
- package/dist/molecules/SidebarBrandHeader.svelte +11 -1
- package/dist/molecules/SidebarBrandHeader.svelte.d.ts +3 -0
- package/dist/molecules/SidebarFooter.svelte +30 -7
- package/dist/molecules/SidebarFooter.svelte.d.ts +3 -0
- package/dist/molecules/SidebarNavSection.svelte +1 -1
- package/dist/molecules/SlideUp.svelte +220 -61
- package/dist/molecules/SlideUp.svelte.d.ts +26 -0
- package/dist/molecules/SortableList.svelte +633 -0
- package/dist/molecules/SortableList.svelte.d.ts +69 -0
- package/dist/molecules/Stepper.svelte +562 -0
- package/dist/molecules/Stepper.svelte.d.ts +39 -0
- package/dist/molecules/StickyActionBar.svelte +250 -0
- package/dist/molecules/StickyActionBar.svelte.d.ts +78 -0
- package/dist/molecules/SwipeableListItem.svelte +334 -0
- package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
- package/dist/molecules/Tabs.svelte +310 -9
- package/dist/molecules/Tabs.svelte.d.ts +7 -0
- package/dist/molecules/Toaster.svelte +514 -5
- package/dist/molecules/Toaster.svelte.d.ts +153 -2
- package/dist/molecules/ToasterToast.svelte +495 -77
- package/dist/molecules/ToasterToast.svelte.d.ts +15 -0
- package/dist/molecules/UnsavedChangesBar.svelte +223 -0
- package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
- package/dist/molecules/index.d.ts +51 -0
- package/dist/molecules/index.js +31 -0
- package/dist/molecules/toast-store.d.ts +83 -7
- package/dist/molecules/toast-store.js +25 -1
- package/dist/organisms/AppNavigation.svelte +440 -0
- package/dist/organisms/AppNavigation.svelte.d.ts +72 -0
- package/dist/organisms/AppShell.svelte +696 -0
- package/dist/organisms/AppShell.svelte.d.ts +157 -0
- package/dist/organisms/AppShellHeaderPart.svelte +163 -0
- package/dist/organisms/AppShellHeaderPart.svelte.d.ts +57 -0
- package/dist/organisms/SidebarAccountPanel.svelte +107 -12
- package/dist/organisms/SidebarAccountPanel.svelte.d.ts +19 -0
- package/dist/organisms/SidebarNavigation.svelte +111 -43
- package/dist/organisms/SidebarNavigation.svelte.d.ts +29 -1
- package/dist/organisms/SidebarPanel.svelte +34 -17
- package/dist/organisms/SidebarPanel.svelte.d.ts +1 -0
- package/dist/organisms/SidebarShell.svelte +164 -14
- package/dist/organisms/SidebarShell.svelte.d.ts +56 -2
- package/dist/organisms/TopNavbar.svelte +353 -39
- package/dist/organisms/TopNavbar.svelte.d.ts +47 -0
- package/dist/organisms/TopNavbarSheetContent.svelte +145 -0
- package/dist/organisms/TopNavbarSheetContent.svelte.d.ts +24 -0
- package/dist/organisms/index.d.ts +10 -0
- package/dist/organisms/index.js +7 -0
- package/dist/types/components.d.ts +21 -1
- package/dist/types/components.d.ts.map +1 -1
- package/dist/types/index.d.ts +1093 -26
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/variants.ts +28 -1
- package/dist/util/app-shell.d.ts +78 -0
- package/dist/util/app-shell.js +97 -0
- package/dist/util/avatar.d.ts +38 -0
- package/dist/util/avatar.js +52 -0
- package/dist/util/bottom-sheet.d.ts +29 -0
- package/dist/util/bottom-sheet.js +43 -0
- package/dist/util/bottom-tab-bar.d.ts +40 -0
- package/dist/util/bottom-tab-bar.js +54 -0
- package/dist/util/calendar.d.ts +55 -0
- package/dist/util/calendar.js +74 -0
- package/dist/util/chart.d.ts +139 -0
- package/dist/util/chart.js +305 -0
- package/dist/util/chip.d.ts +65 -0
- package/dist/util/chip.js +58 -0
- package/dist/util/cn.js +28 -2
- package/dist/util/code-field.d.ts +62 -0
- package/dist/util/code-field.js +112 -0
- package/dist/util/collapsible.d.ts +63 -0
- package/dist/util/collapsible.js +23 -0
- package/dist/util/confirm-dialog.d.ts +11 -0
- package/dist/util/confirm-dialog.js +2 -0
- package/dist/util/date.d.ts +95 -0
- package/dist/util/date.js +237 -0
- package/dist/util/display-voice.d.ts +9 -0
- package/dist/util/display-voice.js +9 -0
- package/dist/util/drawer.d.ts +12 -0
- package/dist/util/drawer.js +7 -0
- package/dist/util/dropdown.d.ts +46 -0
- package/dist/util/dropdown.js +21 -0
- package/dist/util/field-control.d.ts +29 -0
- package/dist/util/field-control.js +32 -0
- package/dist/util/field.d.ts +44 -0
- package/dist/util/field.js +38 -0
- package/dist/util/fit-in-viewport.d.ts +200 -0
- package/dist/util/fit-in-viewport.js +399 -0
- package/dist/util/focus-utils.d.ts +77 -2
- package/dist/util/focus-utils.js +186 -9
- package/dist/util/hydration.d.ts +44 -0
- package/dist/util/hydration.js +58 -0
- package/dist/util/keyboard-inset.d.ts +17 -0
- package/dist/util/keyboard-inset.js +53 -0
- package/dist/util/map-view.d.ts +143 -0
- package/dist/util/map-view.js +121 -0
- package/dist/util/media-grid.d.ts +50 -0
- package/dist/util/media-grid.js +81 -0
- package/dist/util/media.d.ts +5 -0
- package/dist/util/media.js +10 -0
- package/dist/util/overlay-chrome.d.ts +52 -0
- package/dist/util/overlay-chrome.js +56 -0
- package/dist/util/overlay.d.ts +71 -0
- package/dist/util/overlay.js +240 -0
- package/dist/util/photo.d.ts +233 -0
- package/dist/util/photo.js +209 -0
- package/dist/util/portal.d.ts +22 -0
- package/dist/util/portal.js +50 -0
- package/dist/util/progress.d.ts +18 -0
- package/dist/util/progress.js +17 -0
- package/dist/util/pull-to-refresh.d.ts +26 -0
- package/dist/util/pull-to-refresh.js +30 -0
- package/dist/util/radio-keys.d.ts +24 -0
- package/dist/util/radio-keys.js +49 -0
- package/dist/util/rating.d.ts +22 -0
- package/dist/util/rating.js +26 -0
- package/dist/util/ready-made-strings.d.ts +76 -0
- package/dist/util/ready-made-strings.js +67 -0
- package/dist/util/scroll-edge.d.ts +32 -0
- package/dist/util/scroll-edge.js +87 -0
- package/dist/util/scroll-padding.d.ts +19 -0
- package/dist/util/scroll-padding.js +44 -0
- package/dist/util/segmented-control.d.ts +15 -0
- package/dist/util/segmented-control.js +1 -0
- package/dist/util/select.d.ts +40 -0
- package/dist/util/select.js +63 -0
- package/dist/util/sheet-drag.d.ts +83 -0
- package/dist/util/sheet-drag.js +311 -0
- package/dist/util/sidebar.d.ts +62 -0
- package/dist/util/sidebar.js +38 -0
- package/dist/util/skeleton.d.ts +14 -0
- package/dist/util/skeleton.js +22 -0
- package/dist/util/sliding-indicator.d.ts +24 -0
- package/dist/util/sliding-indicator.js +44 -0
- package/dist/util/sortable-list.d.ts +64 -0
- package/dist/util/sortable-list.js +53 -0
- package/dist/util/stepper.d.ts +39 -0
- package/dist/util/stepper.js +38 -0
- package/dist/util/swipeable-list-item.d.ts +35 -0
- package/dist/util/swipeable-list-item.js +32 -0
- package/dist/util/temporal-field.d.ts +88 -0
- package/dist/util/temporal-field.js +136 -0
- package/dist/util/theme-mode.d.ts +79 -0
- package/dist/util/theme-mode.js +121 -0
- package/dist/util/toaster.d.ts +132 -0
- package/dist/util/toaster.js +130 -0
- package/dist/util/tone-surface.d.ts +33 -0
- package/dist/util/tone-surface.js +35 -0
- package/dist/util/tooltip.d.ts +9 -0
- package/dist/util/tooltip.js +18 -0
- package/dist/util/top-navbar.d.ts +34 -0
- package/dist/util/top-navbar.js +28 -0
- package/dist/util/touch-target.d.ts +30 -0
- package/dist/util/touch-target.js +30 -0
- package/dist/util/zabi-strings.d.ts +141 -0
- package/dist/util/zabi-strings.js +105 -0
- package/dist/zabi-components-colors.css +11 -2
- package/dist/zabi-components-theme-dark-only.css +870 -258
- package/dist/zabi-components-theme-dark.css +870 -258
- package/dist/zabi-components-theme-only.css +2419 -368
- package/dist/zabi-components-theme.css +2419 -368
- package/dist/zabi-components.css +6032 -711
- package/docs/lucide-icons.md +40 -17
- package/docs/theme-imports.md +81 -11
- package/package.json +45 -8
package/dist/atoms/Select.svelte
CHANGED
|
@@ -1,28 +1,94 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { mergeStrings } from "../util/ready-made-strings.js";
|
|
3
|
+
import { zabiCommonStrings, zabiStringsFor } from "../util/zabi-strings.js";
|
|
2
4
|
import Dropdown from "../molecules/Dropdown.svelte";
|
|
3
5
|
import Input from "./Input.svelte";
|
|
6
|
+
import Check from "@lucide/svelte/icons/check";
|
|
7
|
+
import ChevronDown from "@lucide/svelte/icons/chevron-down";
|
|
8
|
+
import { onMount, tick, untrack } from "svelte";
|
|
9
|
+
import type { HTMLButtonAttributes } from "svelte/elements";
|
|
4
10
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
DEFAULT_SELECT_STRINGS,
|
|
12
|
+
NATIVE_SELECT_ATTRIBUTE,
|
|
13
|
+
choiceBeforeMount,
|
|
14
|
+
type SelectPresentation,
|
|
15
|
+
type SelectStrings,
|
|
16
|
+
} from "../util/select.js";
|
|
10
17
|
import { generateId } from "../util/ssr-safe.js";
|
|
18
|
+
import { isInsideToastRegion } from "../util/focus-utils.js";
|
|
11
19
|
import { cn } from "../util/cn.js";
|
|
20
|
+
import { FIELD_CONTROL_SURFACE, fieldControlEdge, fieldControlSize } from "../util/field-control.js";
|
|
21
|
+
import { fieldDescribedBy, fieldMessageState } from "../util/field.js";
|
|
22
|
+
import FieldMessages from "./FieldMessages.svelte";
|
|
12
23
|
|
|
13
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Other attributes (`data-*`, `aria-*`, `onfocus`, ...) land on the
|
|
26
|
+
* trigger, which is a `<button>`: it is the control that takes focus and
|
|
27
|
+
* that the label names.
|
|
28
|
+
*
|
|
29
|
+
* The server's HTML also carries a native `<select>` with the same
|
|
30
|
+
* options. Until the component has mounted, and for good where there are
|
|
31
|
+
* no scripts, that is the control: it is what is seen, pressed and
|
|
32
|
+
* submitted. Once mounted the trigger and the list take over, and the
|
|
33
|
+
* native select stays, out of sight and out of reach, as the form control
|
|
34
|
+
* (`name`, `required`), kept at the same value. With
|
|
35
|
+
* `presentation="native"` it is the control throughout.
|
|
36
|
+
*/
|
|
37
|
+
type Props = Omit<
|
|
38
|
+
HTMLButtonAttributes,
|
|
39
|
+
| "class"
|
|
40
|
+
| "value"
|
|
41
|
+
| "name"
|
|
42
|
+
| "disabled"
|
|
43
|
+
| "type"
|
|
44
|
+
| "onchange"
|
|
45
|
+
| "onclick"
|
|
46
|
+
| "aria-describedby"
|
|
47
|
+
| "children"
|
|
48
|
+
> & {
|
|
14
49
|
/** Extra classes for the host element. */
|
|
15
50
|
class?: string;
|
|
51
|
+
/** Id of the trigger. Omit to auto-generate. */
|
|
52
|
+
id?: string;
|
|
16
53
|
value?: string | number | undefined;
|
|
17
54
|
options?: Array<{
|
|
18
55
|
value: string | number;
|
|
19
56
|
label: string;
|
|
20
57
|
disabled?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* The language of the label, as a BCP 47 tag (`"en"`), when it is
|
|
60
|
+
* not the page's (WCAG 3.1.2). Set on the option's row, on the
|
|
61
|
+
* trigger's text while it is chosen, and on the native `<option>`.
|
|
62
|
+
*/
|
|
63
|
+
lang?: string;
|
|
21
64
|
}>;
|
|
22
65
|
searchable?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* The component's own texts, for another language. Whatever is left
|
|
68
|
+
* out keeps its English default. The six older props for single texts
|
|
69
|
+
* (`placeholder`, `searchPlaceholder`, `noResultsText`,
|
|
70
|
+
* `loadingText`, `emptyStateTitle`, `emptyStateDescription`) win
|
|
71
|
+
* over it.
|
|
72
|
+
*/
|
|
73
|
+
strings?: Partial<SelectStrings>;
|
|
23
74
|
searchPlaceholder?: string;
|
|
24
75
|
maxMenuHeight?: string;
|
|
25
76
|
menuWidth?: string;
|
|
77
|
+
/**
|
|
78
|
+
* How the list is shown. `auto`: in a BottomSheet on a phone (a touch
|
|
79
|
+
* screen narrower than 640px), under the trigger everywhere else.
|
|
80
|
+
* `popover` and `sheet` are always the one or the other. `native`:
|
|
81
|
+
* only the browser's own `<select>`, styled as the field, with the
|
|
82
|
+
* platform's picker. It shows the options' labels and nothing else:
|
|
83
|
+
* no search field, and none of the loading or empty states.
|
|
84
|
+
*/
|
|
85
|
+
presentation?: SelectPresentation;
|
|
86
|
+
/**
|
|
87
|
+
* In a sheet, a short list (up to six options) without a search field
|
|
88
|
+
* gets a sheet as tall as the list, not half the screen. `false`
|
|
89
|
+
* keeps the half-height sheet.
|
|
90
|
+
*/
|
|
91
|
+
sheetFit?: boolean;
|
|
26
92
|
noResultsText?: string;
|
|
27
93
|
isLoading?: boolean;
|
|
28
94
|
loadingText?: string;
|
|
@@ -31,32 +97,48 @@
|
|
|
31
97
|
emptyStateActionLabel?: string;
|
|
32
98
|
placeholder?: string;
|
|
33
99
|
label?: string;
|
|
34
|
-
/**
|
|
100
|
+
/** The name the chosen value is submitted under, by the native `<select>`. */
|
|
35
101
|
name?: string;
|
|
102
|
+
/**
|
|
103
|
+
* The form is not sent while nothing is chosen. Checked by the
|
|
104
|
+
* browser on the native `<select>`; once mounted, the browser's own
|
|
105
|
+
* message is shown under the field and focus goes to the trigger.
|
|
106
|
+
*/
|
|
36
107
|
required?: boolean;
|
|
37
108
|
disabled?: boolean;
|
|
38
109
|
size?: "sm" | "md" | "lg";
|
|
39
110
|
variant?: "default" | "success" | "warning" | "error";
|
|
111
|
+
/** Status text under the field. Shown with a `success`, `warning` or `error` variant; for neutral help, use `hint`. */
|
|
40
112
|
message?: string;
|
|
113
|
+
/** Help text under the field, read out with it. */
|
|
114
|
+
hint?: string;
|
|
115
|
+
/** An error under the field. It marks the field invalid and is announced; it wins over `variant` and `message`. */
|
|
116
|
+
error?: string;
|
|
41
117
|
onchange?: (event: Event) => void;
|
|
42
118
|
onEmptyStateAction?: () => void;
|
|
43
|
-
|
|
119
|
+
/** Ids of other elements that describe the field. The hint and the message are added after them. */
|
|
120
|
+
"aria-describedby"?: string | null;
|
|
121
|
+
};
|
|
44
122
|
|
|
45
123
|
let {
|
|
46
124
|
class: className = "",
|
|
125
|
+
id: idProp,
|
|
47
126
|
value = $bindable(undefined),
|
|
48
127
|
options = [],
|
|
49
128
|
searchable = true,
|
|
50
|
-
|
|
51
|
-
|
|
129
|
+
strings,
|
|
130
|
+
searchPlaceholder,
|
|
131
|
+
maxMenuHeight = "60dvh",
|
|
52
132
|
menuWidth = "100%",
|
|
53
|
-
|
|
133
|
+
presentation = "auto",
|
|
134
|
+
sheetFit = true,
|
|
135
|
+
noResultsText,
|
|
54
136
|
isLoading = false,
|
|
55
|
-
loadingText
|
|
56
|
-
emptyStateTitle
|
|
57
|
-
emptyStateDescription
|
|
137
|
+
loadingText,
|
|
138
|
+
emptyStateTitle,
|
|
139
|
+
emptyStateDescription,
|
|
58
140
|
emptyStateActionLabel = "",
|
|
59
|
-
placeholder
|
|
141
|
+
placeholder,
|
|
60
142
|
label = "",
|
|
61
143
|
name = "",
|
|
62
144
|
required = false,
|
|
@@ -64,63 +146,100 @@
|
|
|
64
146
|
size = "md",
|
|
65
147
|
variant = "default",
|
|
66
148
|
message = "",
|
|
149
|
+
hint = "",
|
|
150
|
+
error = "",
|
|
67
151
|
onchange,
|
|
68
152
|
onEmptyStateAction,
|
|
153
|
+
"aria-describedby": describedBy,
|
|
69
154
|
...restProps
|
|
70
155
|
}: Props = $props();
|
|
71
156
|
|
|
157
|
+
/** The defaults, then a provider's words, then `strings`, then the older props where they are set. */
|
|
158
|
+
const provided = zabiStringsFor("select");
|
|
159
|
+
const common = zabiCommonStrings();
|
|
160
|
+
const text = $derived<SelectStrings>({
|
|
161
|
+
...mergeStrings(
|
|
162
|
+
{
|
|
163
|
+
...DEFAULT_SELECT_STRINGS,
|
|
164
|
+
// The sheet's three buttons say what every sheet's do.
|
|
165
|
+
closeLabel: common().close,
|
|
166
|
+
expandLabel: common().expand,
|
|
167
|
+
collapseLabel: common().collapse,
|
|
168
|
+
},
|
|
169
|
+
provided(),
|
|
170
|
+
strings,
|
|
171
|
+
),
|
|
172
|
+
...(placeholder !== undefined && { placeholder }),
|
|
173
|
+
...(searchPlaceholder !== undefined && { searchPlaceholder }),
|
|
174
|
+
...(noResultsText !== undefined && { noResults: noResultsText }),
|
|
175
|
+
...(loadingText !== undefined && { loading: loadingText }),
|
|
176
|
+
...(emptyStateTitle !== undefined && { emptyTitle: emptyStateTitle }),
|
|
177
|
+
...(emptyStateDescription !== undefined && { emptyDescription: emptyStateDescription }),
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const isNative = $derived(presentation === "native");
|
|
181
|
+
/**
|
|
182
|
+
* False on the server and until the component has mounted: the native
|
|
183
|
+
* select is the control. True after: the trigger is. The two have the
|
|
184
|
+
* same box, so nothing moves when one replaces the other.
|
|
185
|
+
*/
|
|
186
|
+
let mounted = $state(false);
|
|
187
|
+
/** Whether the native select is what is seen and pressed. */
|
|
188
|
+
const nativeShown = $derived(isNative || !mounted);
|
|
189
|
+
|
|
190
|
+
let nativeElement = $state<HTMLSelectElement | null>(null);
|
|
191
|
+
/** What the browser said was wrong with the hidden native select (`required`), in its own words. */
|
|
192
|
+
let nativeError = $state("");
|
|
193
|
+
|
|
194
|
+
const status = $derived(
|
|
195
|
+
fieldMessageState({ variant, message, hint, error: error || nativeError }),
|
|
196
|
+
);
|
|
197
|
+
|
|
72
198
|
let isOpen = $state(false);
|
|
73
199
|
let selectContainer: HTMLDivElement;
|
|
74
200
|
let searchQuery = $state("");
|
|
75
201
|
|
|
76
|
-
|
|
77
|
-
const sizeClass = $derived(() => {
|
|
78
|
-
if (size === "sm") return { box: "h-8 px-3", text: "text-sm" };
|
|
79
|
-
if (size === "lg") return { box: "h-12 px-4", text: "text-base" };
|
|
80
|
-
return { box: "h-10 px-3", text: "text-sm" };
|
|
81
|
-
});
|
|
202
|
+
const sizeClass = $derived(() => fieldControlSize(size));
|
|
82
203
|
|
|
83
|
-
const variantClass = $derived(() =>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
? "border-warning focus-visible:border-warning"
|
|
88
|
-
: variant === "error"
|
|
89
|
-
? "border-error focus-visible:border-error"
|
|
90
|
-
: "border-input-border";
|
|
91
|
-
});
|
|
204
|
+
const variantClass = $derived(() => fieldControlEdge(status.variant));
|
|
205
|
+
|
|
206
|
+
/** What the trigger and the visible native select share: the field's surface, edge and states. */
|
|
207
|
+
const fieldSurface = FIELD_CONTROL_SURFACE;
|
|
92
208
|
|
|
93
209
|
const triggerClasses = $derived(() => {
|
|
94
210
|
const sizeStyles = sizeClass();
|
|
95
|
-
const baseClasses =
|
|
96
|
-
"focus-ring flex w-full cursor-pointer items-center justify-between gap-2 rounded-control border bg-input text-body transition-colors duration-150 hover:bg-input-hover active:bg-input-focus focus-visible:bg-input-focus focus:outline-none focus-visible:outline-none disabled:cursor-not-allowed disabled:bg-input-disabled disabled:text-action-disabled-text";
|
|
211
|
+
const baseClasses = `${fieldSurface} flex items-center justify-between gap-2`;
|
|
97
212
|
|
|
98
|
-
return cn(
|
|
213
|
+
return cn(
|
|
214
|
+
`${baseClasses} ${sizeStyles.box} ${sizeStyles.text} ${variantClass()}`,
|
|
215
|
+
// Until it has mounted the native select stands in its place.
|
|
216
|
+
!mounted && "hidden",
|
|
217
|
+
);
|
|
99
218
|
});
|
|
100
219
|
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return "text-warning text-sm mt-1 flex items-center gap-2 w-full";
|
|
220
|
+
const nativeClasses = $derived.by(() => {
|
|
221
|
+
const sizeStyles = sizeClass();
|
|
222
|
+
if (!nativeShown) {
|
|
223
|
+
// Mounted, with a list of our own: still the form control, laid
|
|
224
|
+
// over the trigger so the browser's validation has a place to
|
|
225
|
+
// point at, but not seen, pressed or reached. `invisible`
|
|
226
|
+
// (`visibility: hidden`) is what takes it out of the tab order
|
|
227
|
+
// and the accessibility tree for certain; a form still sends and
|
|
228
|
+
// validates it.
|
|
229
|
+
return "pointer-events-none invisible absolute inset-0 h-full w-full";
|
|
112
230
|
}
|
|
113
|
-
return
|
|
231
|
+
return cn(
|
|
232
|
+
`${fieldSurface} block appearance-none truncate ${sizeStyles.native} ${sizeStyles.text} ${variantClass()}`,
|
|
233
|
+
!disabled && isEmpty() && "text-input-placeholder",
|
|
234
|
+
);
|
|
114
235
|
});
|
|
115
236
|
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (variant === "warning") return AlertTriangle;
|
|
120
|
-
return null;
|
|
121
|
-
});
|
|
237
|
+
const labelClasses = $derived(
|
|
238
|
+
() => "block text-sm font-medium text-label mb-2",
|
|
239
|
+
);
|
|
122
240
|
|
|
123
|
-
const
|
|
241
|
+
const fallbackId = generateId("select");
|
|
242
|
+
const selectId = $derived(idProp ?? fallbackId);
|
|
124
243
|
|
|
125
244
|
const isEmpty = $derived(() => {
|
|
126
245
|
return value === undefined || value === null || value === "";
|
|
@@ -164,8 +283,14 @@
|
|
|
164
283
|
|
|
165
284
|
function handleOptionClick(optionValue: string | number) {
|
|
166
285
|
if (disabled || isLoading) return;
|
|
167
|
-
value
|
|
286
|
+
if (options.find((option) => option.value === optionValue)?.disabled) return;
|
|
168
287
|
isOpen = false;
|
|
288
|
+
choose(optionValue);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Sets the value and tells the app, as a change of a form control does. */
|
|
292
|
+
function choose(optionValue: string | number | undefined) {
|
|
293
|
+
value = optionValue;
|
|
169
294
|
|
|
170
295
|
if (onchange) {
|
|
171
296
|
const syntheticEvent = new Event("change", { bubbles: true });
|
|
@@ -177,6 +302,124 @@
|
|
|
177
302
|
}
|
|
178
303
|
}
|
|
179
304
|
|
|
305
|
+
/**
|
|
306
|
+
* The native select's value as one of the options' own: a number stays a
|
|
307
|
+
* number. The empty first option is "nothing chosen".
|
|
308
|
+
*/
|
|
309
|
+
function fromNative(raw: string): string | number | undefined {
|
|
310
|
+
if (raw === "") return undefined;
|
|
311
|
+
return options.find((option) => String(option.value) === raw)?.value ?? raw;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** The native select was changed: by the visitor, in `native`, or by the browser restoring a form. */
|
|
315
|
+
function handleNativeChange(event: Event) {
|
|
316
|
+
const raw = (event.currentTarget as HTMLSelectElement).value;
|
|
317
|
+
if (raw === (isEmpty() ? "" : String(value))) return;
|
|
318
|
+
choose(fromNative(raw));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* The browser found the hidden native select invalid (`required`, and
|
|
323
|
+
* nothing chosen). Its own bubble would point at a control nobody can
|
|
324
|
+
* see, and focus would go into it: the message is shown under the field
|
|
325
|
+
* instead, in the browser's words, and focus goes to the trigger.
|
|
326
|
+
*/
|
|
327
|
+
function handleNativeInvalid(event: Event) {
|
|
328
|
+
if (nativeShown) return;
|
|
329
|
+
event.preventDefault();
|
|
330
|
+
const element = event.currentTarget as HTMLSelectElement;
|
|
331
|
+
nativeError = element.validationMessage;
|
|
332
|
+
// The browser tells every invalid control, and would focus the first.
|
|
333
|
+
// So does this: each Select shows its message, and only the first
|
|
334
|
+
// invalid control of the form takes focus.
|
|
335
|
+
const controls = [...(element.form?.elements ?? [])] as (Element & Partial<HTMLSelectElement>)[];
|
|
336
|
+
const first = controls.find((control) => control.willValidate && control.validity?.valid === false);
|
|
337
|
+
if (!first || first === element) document.getElementById(selectId)?.focus();
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
$effect(() => {
|
|
341
|
+
// A choice answers it.
|
|
342
|
+
void value;
|
|
343
|
+
nativeError = "";
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* The trigger takes over from the native select. If the keyboard was in
|
|
348
|
+
* the native select, which now leaves the tab order, focus goes to the
|
|
349
|
+
* trigger and does not fall to the page.
|
|
350
|
+
*/
|
|
351
|
+
function handOver() {
|
|
352
|
+
const hadFocus = !isNative && nativeElement !== null && document.activeElement === nativeElement;
|
|
353
|
+
mounted = true;
|
|
354
|
+
if (hadFocus) void tick().then(() => document.getElementById(selectId)?.focus());
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** The value the Select was rendered with: what a form reset goes back to. */
|
|
358
|
+
const initialValue = untrack(() => value);
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* The form around the Select was reset. The native select goes back to
|
|
362
|
+
* what its markup says by itself, which the component would not know of:
|
|
363
|
+
* the value is put back to the one it was rendered with, and that is
|
|
364
|
+
* reported once if it is a change. After the browser has done its own
|
|
365
|
+
* resetting, which is after the event.
|
|
366
|
+
*/
|
|
367
|
+
function handleFormReset(event: Event) {
|
|
368
|
+
setTimeout(() => {
|
|
369
|
+
if (event.defaultPrevented) return;
|
|
370
|
+
const initiallyEmpty = initialValue === undefined || initialValue === null || initialValue === "";
|
|
371
|
+
const unchanged = initiallyEmpty ? isEmpty() : isSameValue(value, initialValue);
|
|
372
|
+
if (!unchanged) choose(initiallyEmpty ? undefined : initialValue);
|
|
373
|
+
void tick().then(() => {
|
|
374
|
+
if (nativeElement) nativeElement.value = initiallyEmpty ? "" : String(initialValue);
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** Whether the browser's own list of the native select is open, where the browser can say (`:open`). */
|
|
380
|
+
function nativePickerOpen(element: HTMLSelectElement): boolean {
|
|
381
|
+
try {
|
|
382
|
+
return element.matches(":open");
|
|
383
|
+
} catch {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
onMount(() => {
|
|
389
|
+
// A choice made in the native select before the scripts arrived is
|
|
390
|
+
// the visitor's: it becomes the value, and is reported once. Only a
|
|
391
|
+
// choice: a select nobody touched keeps the value it was given.
|
|
392
|
+
const before = nativeElement ? choiceBeforeMount(nativeElement) : undefined;
|
|
393
|
+
if (before !== undefined && before !== (isEmpty() ? "" : String(value))) {
|
|
394
|
+
choose(fromNative(before));
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const element = nativeElement;
|
|
398
|
+
const form = element?.form ?? null;
|
|
399
|
+
form?.addEventListener("reset", handleFormReset);
|
|
400
|
+
const stopListening = () => form?.removeEventListener("reset", handleFormReset);
|
|
401
|
+
|
|
402
|
+
// Someone is choosing in the browser's own list right now: taking the
|
|
403
|
+
// select away would close it under their finger. They keep it until
|
|
404
|
+
// they have chosen or left, and the trigger takes over then.
|
|
405
|
+
if (!isNative && element && document.activeElement === element && nativePickerOpen(element)) {
|
|
406
|
+
const finish = () => {
|
|
407
|
+
element.removeEventListener("change", finish);
|
|
408
|
+
element.removeEventListener("blur", finish);
|
|
409
|
+
handOver();
|
|
410
|
+
};
|
|
411
|
+
element.addEventListener("change", finish);
|
|
412
|
+
element.addEventListener("blur", finish);
|
|
413
|
+
return () => {
|
|
414
|
+
element.removeEventListener("change", finish);
|
|
415
|
+
element.removeEventListener("blur", finish);
|
|
416
|
+
stopListening();
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
handOver();
|
|
420
|
+
return stopListening;
|
|
421
|
+
});
|
|
422
|
+
|
|
180
423
|
function handleTriggerClick(event: MouseEvent) {
|
|
181
424
|
if (disabled || isLoading) return;
|
|
182
425
|
event.stopPropagation();
|
|
@@ -187,6 +430,10 @@
|
|
|
187
430
|
if (!isOpen) return;
|
|
188
431
|
|
|
189
432
|
const target = event.target as HTMLElement;
|
|
433
|
+
if (isInsideToastRegion(target)) return;
|
|
434
|
+
// The list in a sheet is in `<body>`, not in this container: a press
|
|
435
|
+
// in that sheet (an option, the grip, the search field) is not outside.
|
|
436
|
+
if (target.closest('[role="dialog"]')?.querySelector("[data-dropdown-sheet]")) return;
|
|
190
437
|
const clickedSelectContainer = target.closest(".select-container");
|
|
191
438
|
|
|
192
439
|
if (
|
|
@@ -206,47 +453,137 @@
|
|
|
206
453
|
|
|
207
454
|
<svelte:window onclick={handleClickOutside} onkeydown={handleKeydown} />
|
|
208
455
|
|
|
209
|
-
<div bind:this={selectContainer} class={cn("w-full select-container", className)}>
|
|
456
|
+
<div bind:this={selectContainer} class={cn("w-full min-w-0 select-container", className)}>
|
|
210
457
|
{#if label}
|
|
211
458
|
<label for={selectId} class={labelClasses()}>{label}</label>
|
|
212
459
|
{/if}
|
|
213
460
|
|
|
461
|
+
<div class="relative w-full min-w-0">
|
|
462
|
+
<!-- The native select. While it is the control it has the field's id, so
|
|
463
|
+
the label and the messages are its; once the trigger has taken over it is
|
|
464
|
+
hidden from assistive technology and out of the tab order, and only the
|
|
465
|
+
form talks to it. A disabled Select has always sent its value, so the
|
|
466
|
+
hidden one is not disabled; what is not sent is its `required`. -->
|
|
467
|
+
<select
|
|
468
|
+
bind:this={nativeElement}
|
|
469
|
+
id={nativeShown ? selectId : `${selectId}-native`}
|
|
470
|
+
name={name || undefined}
|
|
471
|
+
class={nativeClasses}
|
|
472
|
+
required={required && (nativeShown || !(disabled || isLoading))}
|
|
473
|
+
disabled={nativeShown && (disabled || isLoading)}
|
|
474
|
+
tabindex={nativeShown ? undefined : -1}
|
|
475
|
+
aria-hidden={nativeShown ? undefined : "true"}
|
|
476
|
+
aria-invalid={nativeShown && status.variant === "error" ? "true" : undefined}
|
|
477
|
+
aria-describedby={nativeShown ? fieldDescribedBy(selectId, status, describedBy) : undefined}
|
|
478
|
+
onchange={handleNativeChange}
|
|
479
|
+
oninvalid={handleNativeInvalid}
|
|
480
|
+
aria-label={nativeShown ? restProps["aria-label"] : undefined}
|
|
481
|
+
aria-labelledby={nativeShown ? restProps["aria-labelledby"] : undefined}
|
|
482
|
+
{...{ [NATIVE_SELECT_ATTRIBUTE]: "" }}
|
|
483
|
+
{...isNative ? (restProps as Record<string, unknown>) : {}}
|
|
484
|
+
>
|
|
485
|
+
{#if isEmpty()}
|
|
486
|
+
<option value="" selected>{isLoading ? text.loading : text.placeholder}</option>
|
|
487
|
+
{:else if !selectedOption}
|
|
488
|
+
<!-- The value has no option of its own: its options are still on
|
|
489
|
+
their way, or it is not among them. It is still the value, so the
|
|
490
|
+
select holds it and a form sends it; it is not offered in the list
|
|
491
|
+
where the browser lets an option be hidden. -->
|
|
492
|
+
<option value={String(value)} selected hidden>{isLoading ? text.loading : String(value)}</option>
|
|
493
|
+
{/if}
|
|
494
|
+
{#each options as option (option.value)}
|
|
495
|
+
<option
|
|
496
|
+
value={String(option.value)}
|
|
497
|
+
disabled={option.disabled}
|
|
498
|
+
lang={option.lang}
|
|
499
|
+
selected={isSameValue(value, option.value)}
|
|
500
|
+
>
|
|
501
|
+
{option.label}
|
|
502
|
+
</option>
|
|
503
|
+
{/each}
|
|
504
|
+
</select>
|
|
505
|
+
{#if nativeShown}
|
|
506
|
+
<!-- `appearance-none` takes the browser's arrow away with its chrome. -->
|
|
507
|
+
<span
|
|
508
|
+
class={cn(
|
|
509
|
+
"pointer-events-none absolute top-1/2 me-px flex -translate-y-1/2 text-description",
|
|
510
|
+
sizeClass().chevron,
|
|
511
|
+
)}
|
|
512
|
+
aria-hidden="true"
|
|
513
|
+
>
|
|
514
|
+
<ChevronDown size={20} />
|
|
515
|
+
</span>
|
|
516
|
+
{/if}
|
|
517
|
+
|
|
518
|
+
{#if !isNative}
|
|
214
519
|
<Dropdown
|
|
215
520
|
bind:isOpen={isOpen}
|
|
216
521
|
placement="bottom-start"
|
|
217
522
|
selectedValue={value}
|
|
218
523
|
onOptionClick={handleOptionClick}
|
|
219
|
-
ariaLabel="
|
|
524
|
+
ariaLabel={strings?.listLabel || label || (restProps["aria-label"] as string | undefined) || text.listLabel}
|
|
525
|
+
ariaLabelledby={strings?.listLabel || label ? undefined : (restProps["aria-labelledby"] as string | undefined)}
|
|
220
526
|
menuRole="listbox"
|
|
527
|
+
fullWidth
|
|
528
|
+
presentation={presentation === "native" ? "popover" : presentation}
|
|
529
|
+
sheetTitle={label || text.placeholder}
|
|
530
|
+
sheetCloseLabel={text.closeLabel}
|
|
531
|
+
sheetExpandLabel={text.expandLabel}
|
|
532
|
+
sheetCollapseLabel={text.collapseLabel}
|
|
533
|
+
sheetSnap={searchable && options.length > 6 ? "full" : "half"}
|
|
534
|
+
sheetFit={sheetFit && !searchable && options.length <= 6}
|
|
221
535
|
>
|
|
222
536
|
{#snippet trigger(aria)}
|
|
537
|
+
<!-- A select-only combobox, as the ARIA practices describe it:
|
|
538
|
+
a button could not say that it is invalid or required
|
|
539
|
+
(`aria-invalid` and `aria-required` are not a button's), so a
|
|
540
|
+
Select with an error was announced like one without. The keys,
|
|
541
|
+
`aria-haspopup="listbox"`, `aria-expanded` and `aria-controls` are
|
|
542
|
+
as they were. -->
|
|
223
543
|
<button
|
|
224
544
|
type="button"
|
|
225
|
-
id={selectId}
|
|
545
|
+
id={mounted ? selectId : `${selectId}-trigger`}
|
|
226
546
|
class={triggerClasses()}
|
|
227
547
|
{disabled}
|
|
228
548
|
onclick={handleTriggerClick}
|
|
549
|
+
{...restProps}
|
|
229
550
|
{...aria}
|
|
230
|
-
|
|
551
|
+
role="combobox"
|
|
552
|
+
aria-invalid={status.variant === "error" ? "true" : undefined}
|
|
553
|
+
aria-required={required ? "true" : undefined}
|
|
554
|
+
aria-busy={isLoading ? "true" : undefined}
|
|
555
|
+
aria-describedby={fieldDescribedBy(selectId, status, describedBy)}
|
|
231
556
|
>
|
|
557
|
+
<!-- An empty trigger shows its placeholder in the placeholder
|
|
558
|
+
colour, as Input does. A value has no colour class of its own,
|
|
559
|
+
and a disabled trigger none at all: the button's `text-body`
|
|
560
|
+
and its disabled colour are inherited. A colour class here
|
|
561
|
+
beat the button's `disabled:` one, and a disabled Select read
|
|
562
|
+
as enabled. -->
|
|
563
|
+
<!-- The label wraps: cut off, there was nowhere on the closed
|
|
564
|
+
control to read the rest. The chevron stays in the middle of
|
|
565
|
+
the box, however many lines there are. -->
|
|
232
566
|
<span
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
567
|
+
lang={isLoading || isEmpty() ? undefined : selectedOption?.lang}
|
|
568
|
+
class="min-w-0 flex-1 text-start [overflow-wrap:anywhere] {disabled
|
|
569
|
+
? ''
|
|
570
|
+
: isLoading
|
|
571
|
+
? 'text-description'
|
|
572
|
+
: isEmpty()
|
|
573
|
+
? 'text-input-placeholder'
|
|
574
|
+
: ''}"
|
|
238
575
|
>
|
|
239
576
|
{#if isLoading}
|
|
240
|
-
{
|
|
577
|
+
{text.loading}
|
|
241
578
|
{:else}
|
|
242
579
|
{isEmpty()
|
|
243
|
-
? placeholder
|
|
244
|
-
: selectedOption?.label || placeholder}
|
|
580
|
+
? text.placeholder
|
|
581
|
+
: selectedOption?.label || text.placeholder}
|
|
245
582
|
{/if}
|
|
246
583
|
</span>
|
|
247
584
|
<ChevronDown
|
|
248
585
|
size={20}
|
|
249
|
-
class="text-description transition-transform duration-
|
|
586
|
+
class="shrink-0 text-description transition-transform duration-(--duration-moderate) motion-reduce:transition-none {isOpen
|
|
250
587
|
? 'rotate-180'
|
|
251
588
|
: ''}"
|
|
252
589
|
/>
|
|
@@ -255,30 +592,37 @@
|
|
|
255
592
|
{#snippet header()}
|
|
256
593
|
{#if searchable && !isLoading}
|
|
257
594
|
<!-- Outside role="listbox": a textbox is not a valid listbox child. -->
|
|
258
|
-
|
|
595
|
+
<!-- 0.5rem and the panel's 1px border from the corner, as the
|
|
596
|
+
options below and a Dropdown's items are: an 8px field 9px
|
|
597
|
+
inside the panel's 16px corner is concentric with it. -->
|
|
598
|
+
<div class="px-2 pb-2 pt-1" style:width={menuWidth} data-menu-width>
|
|
259
599
|
<Input
|
|
260
600
|
type="text"
|
|
261
601
|
size="sm"
|
|
262
602
|
class="min-w-0"
|
|
263
|
-
placeholder={searchPlaceholder}
|
|
603
|
+
placeholder={text.searchPlaceholder}
|
|
264
604
|
bind:value={searchQuery}
|
|
265
|
-
aria-label={searchPlaceholder}
|
|
605
|
+
aria-label={text.searchPlaceholder}
|
|
266
606
|
{disabled}
|
|
267
607
|
/>
|
|
268
608
|
</div>
|
|
269
609
|
{/if}
|
|
270
610
|
{/snippet}
|
|
271
611
|
{#snippet children()}
|
|
272
|
-
|
|
612
|
+
<!-- `px-1` twice: the inner one is room for an option's focus
|
|
613
|
+
ring inside the scrolling box, and together with the panel's
|
|
614
|
+
1px rim they put the options 9px from its corner. -->
|
|
615
|
+
<div class="px-1 pb-2 pt-1" style:width={menuWidth} data-menu-width>
|
|
273
616
|
<div
|
|
274
617
|
class="overflow-y-auto px-1"
|
|
618
|
+
data-menu-scroll
|
|
275
619
|
style:max-height={maxMenuHeight}
|
|
276
620
|
>
|
|
277
621
|
{#if isLoading}
|
|
278
622
|
<div class="space-y-2 px-2 py-2" role="status" aria-live="polite">
|
|
279
623
|
<div class="h-8 w-full animate-pulse rounded-control bg-base-200"></div>
|
|
280
624
|
<div class="h-8 w-full animate-pulse rounded-control bg-base-200"></div>
|
|
281
|
-
<p class="text-xs text-description">{
|
|
625
|
+
<p class="text-xs text-description">{text.loading}</p>
|
|
282
626
|
</div>
|
|
283
627
|
{:else if filteredOptions().length > 0}
|
|
284
628
|
{#each filteredOptions() as option (option.value)}
|
|
@@ -286,35 +630,45 @@
|
|
|
286
630
|
"data-value": String(option.value),
|
|
287
631
|
} as Record<string, string>}
|
|
288
632
|
<div class="w-full my-1">
|
|
633
|
+
<!-- `aria-disabled`, not `disabled`: a natively disabled button cannot take focus, so the arrow keys stopped at the option before it. The click is refused in `handleOptionClick`. -->
|
|
289
634
|
<button
|
|
290
635
|
type="button"
|
|
291
636
|
role="option"
|
|
292
|
-
aria-selected={isSameValue(value, option.value)
|
|
293
|
-
|
|
294
|
-
: undefined}
|
|
295
|
-
class="focus-ring flex w-full items-center justify-start rounded-control border-2 px-3 py-2 text-left text-sm font-medium transition-colors focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 {isSameValue(
|
|
637
|
+
aria-selected={isSameValue(value, option.value)}
|
|
638
|
+
class="focus-ring flex w-full items-center justify-start gap-2 rounded-control border-2 px-3 py-2 pointer-coarse:py-3 text-start text-sm font-medium transition-colors focus:outline-none cursor-pointer aria-disabled:cursor-not-allowed aria-disabled:opacity-50 {isSameValue(
|
|
296
639
|
value,
|
|
297
640
|
option.value,
|
|
298
641
|
)
|
|
299
|
-
? 'border-
|
|
300
|
-
:
|
|
301
|
-
|
|
642
|
+
? 'border-transparent bg-surface-overlay-hover text-headline active:bg-surface-active'
|
|
643
|
+
: option.disabled
|
|
644
|
+
? 'border-transparent bg-transparent text-body'
|
|
645
|
+
: 'border-transparent bg-transparent text-body hover:bg-surface-overlay-hover active:bg-surface-active'}"
|
|
646
|
+
aria-disabled={option.disabled ? "true" : undefined}
|
|
647
|
+
lang={option.lang}
|
|
302
648
|
{...buttonRestProps}
|
|
303
649
|
onclick={() =>
|
|
304
650
|
handleOptionClick(option.value)}
|
|
305
651
|
>
|
|
306
|
-
{option.label}
|
|
652
|
+
<span class="min-w-0 flex-1">{option.label}</span>
|
|
653
|
+
<!-- The chosen option's mark: a check and a
|
|
654
|
+
fill. It used to be an edge in the action
|
|
655
|
+
colour, which is what the focus ring draws:
|
|
656
|
+
the chosen and the focused option could
|
|
657
|
+
not be told apart. -->
|
|
658
|
+
{#if isSameValue(value, option.value)}
|
|
659
|
+
<Check size={16} class="shrink-0" aria-hidden="true" />
|
|
660
|
+
{/if}
|
|
307
661
|
</button>
|
|
308
662
|
</div>
|
|
309
663
|
{/each}
|
|
310
664
|
{:else if hasSearchQuery()}
|
|
311
665
|
<div class="px-3 py-2 text-sm text-description">
|
|
312
|
-
{
|
|
666
|
+
{text.noResults}
|
|
313
667
|
</div>
|
|
314
668
|
{:else}
|
|
315
669
|
<div class="rounded-control border border-border bg-surface-overlay-hover px-3 py-3 text-sm">
|
|
316
|
-
<p class="font-medium text-headline">{
|
|
317
|
-
<p class="mt-1 text-description">{
|
|
670
|
+
<p class="font-medium text-headline">{text.emptyTitle}</p>
|
|
671
|
+
<p class="mt-1 text-description">{text.emptyDescription}</p>
|
|
318
672
|
{#if emptyStateActionLabel && onEmptyStateAction}
|
|
319
673
|
<button
|
|
320
674
|
type="button"
|
|
@@ -330,21 +684,7 @@
|
|
|
330
684
|
</div>
|
|
331
685
|
{/snippet}
|
|
332
686
|
</Dropdown>
|
|
333
|
-
{#if name}
|
|
334
|
-
<input type="hidden" {name} value={isEmpty() ? "" : String(value)} />
|
|
335
|
-
{/if}
|
|
336
|
-
{#if message && variant !== "default"}
|
|
337
|
-
<p
|
|
338
|
-
id={`${selectId}-message`}
|
|
339
|
-
class={messageClasses()}
|
|
340
|
-
role={variant === "error" ? "alert" : "status"}
|
|
341
|
-
aria-live={variant === "error" ? "assertive" : "polite"}
|
|
342
|
-
>
|
|
343
|
-
{#if getIcon()}
|
|
344
|
-
{@const Icon = getIcon()}
|
|
345
|
-
<Icon size={14} class="shrink-0" />
|
|
346
|
-
{/if}
|
|
347
|
-
<span>{message}</span>
|
|
348
|
-
</p>
|
|
349
687
|
{/if}
|
|
688
|
+
</div>
|
|
689
|
+
<FieldMessages fieldId={selectId} {status} {hint} gap="mt-1" messageClass="w-full" />
|
|
350
690
|
</div>
|