zabi-components 8.0.0 → 8.1.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 +1924 -0
- package/README.md +243 -10
- package/THEME.md +149 -332
- package/THEMING.md +1192 -266
- package/dist/atoms/ActionPanel.svelte +15 -5
- package/dist/atoms/Avatar.svelte +104 -0
- package/dist/atoms/Avatar.svelte.d.ts +34 -0
- package/dist/atoms/Badge.svelte +36 -7
- package/dist/atoms/Badge.svelte.d.ts +2 -2
- package/dist/atoms/Button.svelte +177 -27
- package/dist/atoms/Button.svelte.d.ts +30 -2
- package/dist/atoms/Card.svelte +4 -4
- package/dist/atoms/CardHeader.svelte +16 -4
- package/dist/atoms/Checkbox.svelte +48 -21
- package/dist/atoms/Checkbox.svelte.d.ts +4 -2
- package/dist/atoms/CodeBlock.svelte +20 -2
- package/dist/atoms/CodeBlock.svelte.d.ts +4 -0
- package/dist/atoms/ColorPicker.svelte +259 -49
- package/dist/atoms/ColorPicker.svelte.d.ts +3 -0
- package/dist/atoms/Container.svelte +16 -8
- package/dist/atoms/DateField.svelte +107 -0
- package/dist/atoms/DateField.svelte.d.ts +53 -0
- package/dist/atoms/FieldMessages.svelte +69 -0
- package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
- package/dist/atoms/FloatingActionButton.svelte +174 -0
- package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
- package/dist/atoms/Heading.svelte +37 -5
- package/dist/atoms/Heading.svelte.d.ts +7 -0
- package/dist/atoms/IconButton.svelte +220 -24
- package/dist/atoms/IconButton.svelte.d.ts +49 -4
- package/dist/atoms/Input.svelte +253 -52
- package/dist/atoms/Input.svelte.d.ts +32 -2
- package/dist/atoms/List.svelte +4 -1
- package/dist/atoms/ListItem.svelte +44 -11
- package/dist/atoms/ListItem.svelte.d.ts +5 -0
- package/dist/atoms/Progress.svelte +27 -8
- package/dist/atoms/Progress.svelte.d.ts +4 -0
- package/dist/atoms/Radio.svelte +19 -3
- package/dist/atoms/Radio.svelte.d.ts +4 -2
- package/dist/atoms/Rating.svelte +494 -0
- package/dist/atoms/Rating.svelte.d.ts +56 -0
- package/dist/atoms/Select.svelte +433 -91
- package/dist/atoms/Select.svelte.d.ts +49 -3
- package/dist/atoms/SelectionControl.svelte +94 -17
- package/dist/atoms/SelectionControl.svelte.d.ts +4 -3
- package/dist/atoms/Skeleton.svelte +1 -1
- package/dist/atoms/Slider.svelte +309 -0
- package/dist/atoms/Slider.svelte.d.ts +40 -0
- package/dist/atoms/Spinner.svelte +57 -0
- package/dist/atoms/Spinner.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 +140 -0
- package/dist/atoms/TemporalField.svelte.d.ts +32 -0
- package/dist/atoms/Text.svelte +23 -11
- package/dist/atoms/Text.svelte.d.ts +6 -3
- package/dist/atoms/Textarea.svelte +55 -44
- package/dist/atoms/Textarea.svelte.d.ts +14 -2
- package/dist/atoms/ThemeToggle.svelte +223 -44
- package/dist/atoms/ThemeToggle.svelte.d.ts +26 -3
- package/dist/atoms/TimeField.svelte +108 -0
- package/dist/atoms/TimeField.svelte.d.ts +54 -0
- package/dist/atoms/Toast.svelte +17 -4
- package/dist/atoms/Toast.svelte.d.ts +2 -0
- package/dist/atoms/Toggle.svelte +41 -8
- package/dist/atoms/Toggle.svelte.d.ts +11 -2
- package/dist/atoms/Tooltip.svelte +508 -23
- package/dist/atoms/Tooltip.svelte.d.ts +11 -0
- package/dist/atoms/ZabiStringsProvider.svelte +45 -0
- package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
- package/dist/atoms/index.d.ts +17 -0
- package/dist/atoms/index.js +12 -0
- package/dist/atoms/selection-control.styles.d.ts +36 -5
- package/dist/atoms/selection-control.styles.js +40 -9
- package/dist/components/atoms/index.d.ts +17 -0
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/atoms/selection-control.styles.d.ts +36 -5
- package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
- package/dist/components/index.d.ts +80 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/molecules/index.d.ts +46 -0
- package/dist/components/molecules/index.d.ts.map +1 -1
- package/dist/components/molecules/toast-store.d.ts +53 -7
- package/dist/components/molecules/toast-store.d.ts.map +1 -1
- package/dist/components/organisms/index.d.ts +5 -0
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/components/types/variants.d.ts +15 -1
- package/dist/components/types/variants.d.ts.map +1 -1
- package/dist/components/util/app-shell.d.ts +28 -0
- package/dist/components/util/app-shell.d.ts.map +1 -0
- package/dist/components/util/avatar.d.ts +36 -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 +35 -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/collapsible.d.ts +58 -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/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.d.ts +39 -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 +75 -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/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 +215 -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/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-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 +60 -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/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/theme-mode.d.ts +80 -0
- package/dist/components/util/theme-mode.d.ts.map +1 -0
- package/dist/components/util/toaster.d.ts +93 -0
- package/dist/components/util/toaster.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 +11 -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 +133 -0
- package/dist/components/util/zabi-strings.d.ts.map +1 -0
- package/dist/create-theme/cli.js +169 -0
- package/dist/create-theme/index.d.ts +176 -0
- package/dist/create-theme/index.js +817 -0
- package/dist/create-theme/lib/ramp-math.js +128 -0
- package/dist/create-theme/lib/resolve.js +141 -0
- package/dist/create-theme/theme-data.js +1116 -0
- package/dist/index.d.ts +80 -2
- package/dist/index.js +57 -2
- package/dist/molecules/Alert.svelte +33 -5
- package/dist/molecules/Alert.svelte.d.ts +2 -0
- package/dist/molecules/AppBar.svelte +415 -0
- package/dist/molecules/AppBar.svelte.d.ts +69 -0
- package/dist/molecules/AvatarGroup.svelte +116 -0
- package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
- package/dist/molecules/BottomSheet.svelte +540 -0
- package/dist/molecules/BottomSheet.svelte.d.ts +93 -0
- package/dist/molecules/BottomTabBar.svelte +297 -0
- package/dist/molecules/BottomTabBar.svelte.d.ts +64 -0
- package/dist/molecules/Calendar.svelte +597 -0
- package/dist/molecules/Calendar.svelte.d.ts +72 -0
- package/dist/molecules/Collapsible.svelte +243 -0
- package/dist/molecules/Collapsible.svelte.d.ts +44 -0
- package/dist/molecules/CollapsibleGroup.svelte +126 -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 +253 -0
- package/dist/molecules/ConfirmDialog.svelte.d.ts +55 -0
- package/dist/molecules/ContactForm.svelte +34 -17
- package/dist/molecules/ContactForm.svelte.d.ts +8 -0
- package/dist/molecules/Drawer.svelte +394 -0
- package/dist/molecules/Drawer.svelte.d.ts +55 -0
- package/dist/molecules/Dropdown.svelte +509 -48
- package/dist/molecules/Dropdown.svelte.d.ts +45 -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 +383 -70
- package/dist/molecules/ImageUpload.svelte.d.ts +47 -2
- package/dist/molecules/MediaGrid.svelte +527 -0
- package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
- package/dist/molecules/Modal.svelte +288 -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 +433 -0
- package/dist/molecules/PhotoGrid.svelte.d.ts +62 -0
- package/dist/molecules/PhotoViewer.svelte +1116 -0
- package/dist/molecules/PhotoViewer.svelte.d.ts +56 -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 +52 -14
- package/dist/molecules/RadioGroup.svelte.d.ts +14 -3
- package/dist/molecules/Section.svelte +1 -1
- package/dist/molecules/SegmentedControl.svelte +323 -0
- package/dist/molecules/SegmentedControl.svelte.d.ts +34 -0
- package/dist/molecules/SidebarBrandHeader.svelte +11 -1
- package/dist/molecules/SidebarBrandHeader.svelte.d.ts +3 -0
- package/dist/molecules/SidebarFooter.svelte +29 -6
- package/dist/molecules/SidebarFooter.svelte.d.ts +3 -0
- package/dist/molecules/SlideUp.svelte +220 -61
- package/dist/molecules/SlideUp.svelte.d.ts +26 -0
- package/dist/molecules/SortableList.svelte +632 -0
- package/dist/molecules/SortableList.svelte.d.ts +69 -0
- package/dist/molecules/Stepper.svelte +548 -0
- package/dist/molecules/Stepper.svelte.d.ts +39 -0
- package/dist/molecules/StickyActionBar.svelte +191 -0
- package/dist/molecules/StickyActionBar.svelte.d.ts +65 -0
- package/dist/molecules/SwipeableListItem.svelte +334 -0
- package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
- package/dist/molecules/Tabs.svelte +233 -9
- package/dist/molecules/Tabs.svelte.d.ts +7 -0
- package/dist/molecules/Toaster.svelte +350 -5
- package/dist/molecules/Toaster.svelte.d.ts +95 -2
- package/dist/molecules/ToasterToast.svelte +253 -67
- package/dist/molecules/ToasterToast.svelte.d.ts +9 -0
- package/dist/molecules/UnsavedChangesBar.svelte +212 -0
- package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
- package/dist/molecules/index.d.ts +46 -0
- package/dist/molecules/index.js +27 -0
- package/dist/molecules/toast-store.d.ts +53 -7
- package/dist/molecules/toast-store.js +21 -0
- package/dist/organisms/AppShell.svelte +196 -0
- package/dist/organisms/AppShell.svelte.d.ts +61 -0
- package/dist/organisms/SidebarAccountPanel.svelte +107 -12
- package/dist/organisms/SidebarAccountPanel.svelte.d.ts +19 -0
- package/dist/organisms/SidebarNavigation.svelte +96 -15
- package/dist/organisms/SidebarNavigation.svelte.d.ts +29 -1
- package/dist/organisms/SidebarPanel.svelte +31 -10
- package/dist/organisms/SidebarShell.svelte +162 -11
- package/dist/organisms/SidebarShell.svelte.d.ts +56 -2
- package/dist/organisms/TopNavbar.svelte +256 -31
- package/dist/organisms/TopNavbar.svelte.d.ts +26 -0
- package/dist/organisms/index.d.ts +5 -0
- package/dist/organisms/index.js +3 -0
- package/dist/types/components.d.ts +4 -0
- package/dist/types/components.d.ts.map +1 -1
- package/dist/types/index.d.ts +605 -25
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/variants.ts +17 -1
- package/dist/util/app-shell.d.ts +27 -0
- package/dist/util/app-shell.js +72 -0
- package/dist/util/avatar.d.ts +35 -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 +34 -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/collapsible.d.ts +57 -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/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.d.ts +38 -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 +75 -2
- package/dist/util/focus-utils.js +182 -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/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 +214 -0
- package/dist/util/photo.js +206 -0
- package/dist/util/portal.d.ts +22 -0
- package/dist/util/portal.js +50 -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-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 +59 -0
- package/dist/util/sheet-drag.js +241 -0
- package/dist/util/sidebar.d.ts +62 -0
- package/dist/util/sidebar.js +38 -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/theme-mode.d.ts +79 -0
- package/dist/util/theme-mode.js +121 -0
- package/dist/util/toaster.d.ts +92 -0
- package/dist/util/toaster.js +75 -0
- package/dist/util/tooltip.d.ts +9 -0
- package/dist/util/tooltip.js +18 -0
- package/dist/util/top-navbar.d.ts +10 -0
- package/dist/util/top-navbar.js +5 -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 +132 -0
- package/dist/util/zabi-strings.js +105 -0
- package/dist/zabi-components-colors.css +5 -2
- package/dist/zabi-components-theme-dark-only.css +543 -241
- package/dist/zabi-components-theme-dark.css +543 -241
- package/dist/zabi-components-theme-only.css +1584 -367
- package/dist/zabi-components-theme.css +1584 -367
- package/dist/zabi-components.css +3885 -518
- package/docs/lucide-icons.md +40 -17
- package/docs/theme-imports.md +81 -11
- package/package.json +35 -8
|
@@ -1,6 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
import type { HTMLButtonAttributes } from "svelte/elements";
|
|
2
|
+
import { type SelectPresentation, type SelectStrings } from "../util/select.js";
|
|
3
|
+
/**
|
|
4
|
+
* Other attributes (`data-*`, `aria-*`, `onfocus`, ...) land on the
|
|
5
|
+
* trigger, which is a `<button>`: it is the control that takes focus and
|
|
6
|
+
* that the label names.
|
|
7
|
+
*
|
|
8
|
+
* The server's HTML also carries a native `<select>` with the same
|
|
9
|
+
* options. Until the component has mounted, and for good where there are
|
|
10
|
+
* no scripts, that is the control: it is what is seen, pressed and
|
|
11
|
+
* submitted. Once mounted the trigger and the list take over, and the
|
|
12
|
+
* native select stays, out of sight and out of reach, as the form control
|
|
13
|
+
* (`name`, `required`), kept at the same value. With
|
|
14
|
+
* `presentation="native"` it is the control throughout.
|
|
15
|
+
*/
|
|
16
|
+
type Props = Omit<HTMLButtonAttributes, "class" | "value" | "name" | "disabled" | "type" | "onchange" | "onclick" | "aria-describedby" | "children"> & {
|
|
2
17
|
/** Extra classes for the host element. */
|
|
3
18
|
class?: string;
|
|
19
|
+
/** Id of the trigger. Omit to auto-generate. */
|
|
20
|
+
id?: string;
|
|
4
21
|
value?: string | number | undefined;
|
|
5
22
|
options?: Array<{
|
|
6
23
|
value: string | number;
|
|
@@ -8,9 +25,26 @@ interface Props {
|
|
|
8
25
|
disabled?: boolean;
|
|
9
26
|
}>;
|
|
10
27
|
searchable?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* The component's own texts, for another language. Whatever is left
|
|
30
|
+
* out keeps its English default. The six older props for single texts
|
|
31
|
+
* (`placeholder`, `searchPlaceholder`, `noResultsText`,
|
|
32
|
+
* `loadingText`, `emptyStateTitle`, `emptyStateDescription`) win
|
|
33
|
+
* over it.
|
|
34
|
+
*/
|
|
35
|
+
strings?: Partial<SelectStrings>;
|
|
11
36
|
searchPlaceholder?: string;
|
|
12
37
|
maxMenuHeight?: string;
|
|
13
38
|
menuWidth?: string;
|
|
39
|
+
/**
|
|
40
|
+
* How the list is shown. `auto`: in a BottomSheet on a phone (a touch
|
|
41
|
+
* screen narrower than 640px), under the trigger everywhere else.
|
|
42
|
+
* `popover` and `sheet` are always the one or the other. `native`:
|
|
43
|
+
* only the browser's own `<select>`, styled as the field, with the
|
|
44
|
+
* platform's picker. It shows the options' labels and nothing else:
|
|
45
|
+
* no search field, and none of the loading or empty states.
|
|
46
|
+
*/
|
|
47
|
+
presentation?: SelectPresentation;
|
|
14
48
|
noResultsText?: string;
|
|
15
49
|
isLoading?: boolean;
|
|
16
50
|
loadingText?: string;
|
|
@@ -19,16 +53,28 @@ interface Props {
|
|
|
19
53
|
emptyStateActionLabel?: string;
|
|
20
54
|
placeholder?: string;
|
|
21
55
|
label?: string;
|
|
22
|
-
/**
|
|
56
|
+
/** The name the chosen value is submitted under, by the native `<select>`. */
|
|
23
57
|
name?: string;
|
|
58
|
+
/**
|
|
59
|
+
* The form is not sent while nothing is chosen. Checked by the
|
|
60
|
+
* browser on the native `<select>`; once mounted, the browser's own
|
|
61
|
+
* message is shown under the field and focus goes to the trigger.
|
|
62
|
+
*/
|
|
24
63
|
required?: boolean;
|
|
25
64
|
disabled?: boolean;
|
|
26
65
|
size?: "sm" | "md" | "lg";
|
|
27
66
|
variant?: "default" | "success" | "warning" | "error";
|
|
67
|
+
/** Status text under the field. Shown with a `success`, `warning` or `error` variant; for neutral help, use `hint`. */
|
|
28
68
|
message?: string;
|
|
69
|
+
/** Help text under the field, read out with it. */
|
|
70
|
+
hint?: string;
|
|
71
|
+
/** An error under the field. It marks the field invalid and is announced; it wins over `variant` and `message`. */
|
|
72
|
+
error?: string;
|
|
29
73
|
onchange?: (event: Event) => void;
|
|
30
74
|
onEmptyStateAction?: () => void;
|
|
31
|
-
|
|
75
|
+
/** Ids of other elements that describe the field. The hint and the message are added after them. */
|
|
76
|
+
"aria-describedby"?: string | null;
|
|
77
|
+
};
|
|
32
78
|
declare const Select: import("svelte").Component<Props, {}, "value">;
|
|
33
79
|
type Select = ReturnType<typeof Select>;
|
|
34
80
|
export default Select;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type
|
|
2
|
+
import { onMount, tick, type Snippet } from "svelte";
|
|
3
|
+
import type { HTMLInputAttributes } from "svelte/elements";
|
|
3
4
|
import { generateId } from "../util/ssr-safe.js";
|
|
4
5
|
import { cn } from "../util/cn.js";
|
|
6
|
+
import { groupBinding } from "../util/hydration.js";
|
|
5
7
|
import {
|
|
8
|
+
SELECTION_CONTROL_INPUT,
|
|
6
9
|
SELECTION_CONTROL_LABEL_ROW,
|
|
7
10
|
selectionControlRingOverlayClasses,
|
|
8
11
|
selectionControlShellClasses,
|
|
@@ -17,7 +20,10 @@
|
|
|
17
20
|
disabled: boolean;
|
|
18
21
|
};
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
type Props = Omit<
|
|
24
|
+
HTMLInputAttributes,
|
|
25
|
+
"class" | "id" | "name" | "value" | "disabled" | "checked" | "type" | "onchange"
|
|
26
|
+
> & {
|
|
21
27
|
/** Extra classes for the host element. */
|
|
22
28
|
class?: string;
|
|
23
29
|
id?: string;
|
|
@@ -33,7 +39,7 @@
|
|
|
33
39
|
onChange?: (event: Event) => void;
|
|
34
40
|
onchange?: (event: Event) => void;
|
|
35
41
|
mark?: Snippet<[SelectionControlMarkProps]>;
|
|
36
|
-
}
|
|
42
|
+
};
|
|
37
43
|
|
|
38
44
|
let {
|
|
39
45
|
class: className = "",
|
|
@@ -46,13 +52,24 @@
|
|
|
46
52
|
disabled = false,
|
|
47
53
|
loading = false,
|
|
48
54
|
defaultChecked = false,
|
|
49
|
-
checked = $bindable(
|
|
55
|
+
checked = $bindable<Exclude<Props["checked"], undefined>>(),
|
|
50
56
|
onChange,
|
|
51
57
|
onchange,
|
|
52
58
|
mark,
|
|
53
59
|
...restProps
|
|
54
60
|
}: Props = $props();
|
|
55
61
|
|
|
62
|
+
// No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
|
|
63
|
+
// one that has a fallback (`props_invalid_value`), and a page that throws
|
|
64
|
+
// while it hydrates never becomes interactive. The default is applied
|
|
65
|
+
// here instead: at once, for the server and the first render, and again
|
|
66
|
+
// whenever a parent hands back `undefined`.
|
|
67
|
+
const applyDefaults = () => {
|
|
68
|
+
if (checked === undefined) checked = defaultChecked;
|
|
69
|
+
};
|
|
70
|
+
applyDefaults();
|
|
71
|
+
$effect.pre(applyDefaults);
|
|
72
|
+
|
|
56
73
|
/** Call once per mount — `generateId()` in `$derived` would churn ids every tick. */
|
|
57
74
|
const fallbackId = generateId("selection-control");
|
|
58
75
|
const controlId = $derived(idProp ?? fallbackId);
|
|
@@ -64,8 +81,50 @@
|
|
|
64
81
|
|
|
65
82
|
const controlRingClasses = $derived(selectionControlRingOverlayClasses(shape));
|
|
66
83
|
|
|
84
|
+
let input: HTMLInputElement | undefined = $state();
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The input is bound, so a tick made before the page hydrated is kept:
|
|
88
|
+
* Svelte's binding finds the input changed and hands its state over here
|
|
89
|
+
* instead of overwriting it (see util/hydration.ts). A form reset arrives
|
|
90
|
+
* the same way.
|
|
91
|
+
*
|
|
92
|
+
* A choice that is adopted like that came without a `change` event that
|
|
93
|
+
* anything heard, and a parent that only listens (`onchange`, no
|
|
94
|
+
* `bind:checked`) would never learn of it. So while the component
|
|
95
|
+
* settles, a change made through the binding is noted, and reported once
|
|
96
|
+
* with a real `change` event on the input: the callbacks get the event
|
|
97
|
+
* they always get, and a form around the control hears it too.
|
|
98
|
+
*/
|
|
99
|
+
let settling = true;
|
|
100
|
+
let adopted = false;
|
|
101
|
+
|
|
102
|
+
function take(next: boolean) {
|
|
103
|
+
if (next === !!checked) return;
|
|
104
|
+
checked = next;
|
|
105
|
+
if (settling) adopted = true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** What a single radio's group is bound to: its own value while it is checked. */
|
|
109
|
+
const NOT_CHOSEN = {};
|
|
110
|
+
const radio = groupBinding<unknown>(
|
|
111
|
+
() => (checked ? value : NOT_CHOSEN),
|
|
112
|
+
(chosen) => take(chosen === value),
|
|
113
|
+
() => take(false),
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
onMount(() => {
|
|
117
|
+
// The bindings adopt in a microtask of their own, queued before this one.
|
|
118
|
+
void tick().then(() => {
|
|
119
|
+
settling = false;
|
|
120
|
+
if (adopted) input?.dispatchEvent(new Event("change", { bubbles: true }));
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
67
124
|
function handleChange(event: Event) {
|
|
68
125
|
if (isDisabled) return;
|
|
126
|
+
// A real change is its own report: nothing is left to tell.
|
|
127
|
+
adopted = false;
|
|
69
128
|
const target = event.target as HTMLInputElement;
|
|
70
129
|
checked = target.checked;
|
|
71
130
|
onChange?.(event);
|
|
@@ -76,21 +135,39 @@
|
|
|
76
135
|
<div class={cn("flex items-center gap-2", className)}>
|
|
77
136
|
<label for={controlId} class={labelWrapperClasses}>
|
|
78
137
|
<span class={controlContainerClasses}>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
138
|
+
<!-- One branch per type: a binding needs a type it can read. -->
|
|
139
|
+
{#if type === "checkbox"}
|
|
140
|
+
<input
|
|
141
|
+
bind:this={input}
|
|
142
|
+
type="checkbox"
|
|
143
|
+
id={controlId}
|
|
144
|
+
{name}
|
|
145
|
+
{value}
|
|
146
|
+
bind:checked={() => !!checked, take}
|
|
147
|
+
disabled={isDisabled}
|
|
148
|
+
class={SELECTION_CONTROL_INPUT}
|
|
149
|
+
aria-busy={loading ? "true" : undefined}
|
|
150
|
+
onchange={handleChange}
|
|
151
|
+
{...restProps}
|
|
152
|
+
/>
|
|
153
|
+
{:else}
|
|
154
|
+
<input
|
|
155
|
+
bind:this={input}
|
|
156
|
+
type="radio"
|
|
157
|
+
id={controlId}
|
|
158
|
+
{name}
|
|
159
|
+
{value}
|
|
160
|
+
bind:group={radio.value}
|
|
161
|
+
disabled={isDisabled}
|
|
162
|
+
class={SELECTION_CONTROL_INPUT}
|
|
163
|
+
aria-busy={loading ? "true" : undefined}
|
|
164
|
+
onchange={handleChange}
|
|
165
|
+
{...restProps}
|
|
166
|
+
/>
|
|
167
|
+
{/if}
|
|
91
168
|
<span class={controlRingClasses}></span>
|
|
92
169
|
{#if mark}
|
|
93
|
-
{@render mark({ checked, loading, disabled: isDisabled })}
|
|
170
|
+
{@render mark({ checked: !!checked, loading, disabled: isDisabled })}
|
|
94
171
|
{/if}
|
|
95
172
|
</span>
|
|
96
173
|
{#if label}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Snippet } from "svelte";
|
|
2
|
+
import type { HTMLInputAttributes } from "svelte/elements";
|
|
2
3
|
import { type SelectionControlShape } from "./selection-control.styles";
|
|
3
4
|
type SelectionControlType = "checkbox" | "radio";
|
|
4
5
|
export type SelectionControlMarkProps = {
|
|
@@ -6,7 +7,7 @@ export type SelectionControlMarkProps = {
|
|
|
6
7
|
loading: boolean;
|
|
7
8
|
disabled: boolean;
|
|
8
9
|
};
|
|
9
|
-
|
|
10
|
+
type Props = Omit<HTMLInputAttributes, "class" | "id" | "name" | "value" | "disabled" | "checked" | "type" | "onchange"> & {
|
|
10
11
|
/** Extra classes for the host element. */
|
|
11
12
|
class?: string;
|
|
12
13
|
id?: string;
|
|
@@ -22,7 +23,7 @@ interface Props {
|
|
|
22
23
|
onChange?: (event: Event) => void;
|
|
23
24
|
onchange?: (event: Event) => void;
|
|
24
25
|
mark?: Snippet<[SelectionControlMarkProps]>;
|
|
25
|
-
}
|
|
26
|
+
};
|
|
26
27
|
declare const SelectionControl: import("svelte").Component<Props, {}, "checked">;
|
|
27
28
|
type SelectionControl = ReturnType<typeof SelectionControl>;
|
|
28
29
|
export default SelectionControl;
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
</script>
|
|
49
49
|
|
|
50
50
|
<div
|
|
51
|
-
class={cn("animate-pulse bg-
|
|
51
|
+
class={cn("animate-pulse bg-neutral-subtle", defaultShape[variant], radius[variant], className)}
|
|
52
52
|
style={inlineStyle || undefined}
|
|
53
53
|
role="status"
|
|
54
54
|
aria-busy="true"
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLInputAttributes } from "svelte/elements";
|
|
3
|
+
import type { SemanticVariant, SizeVariant } from "../types/variants.js";
|
|
4
|
+
import { generateId } from "../util/ssr-safe.js";
|
|
5
|
+
import { cn } from "../util/cn.js";
|
|
6
|
+
|
|
7
|
+
/** Other attributes (`data-*`, `aria-*`, `list`, ...) land on the `<input>`. */
|
|
8
|
+
type Props = Omit<
|
|
9
|
+
HTMLInputAttributes,
|
|
10
|
+
"class" | "size" | "type" | "value" | "min" | "max" | "step" | "id"
|
|
11
|
+
> & {
|
|
12
|
+
/** Omit to auto-generate; pair with FormField `id` when used inside FormField. */
|
|
13
|
+
id?: string;
|
|
14
|
+
/** Current value; supports `bind:value`. Starts at `min` when not given. */
|
|
15
|
+
value?: number;
|
|
16
|
+
min?: number;
|
|
17
|
+
max?: number;
|
|
18
|
+
step?: number;
|
|
19
|
+
name?: string;
|
|
20
|
+
/** Extra classes for the host element. */
|
|
21
|
+
class?: string;
|
|
22
|
+
label?: string;
|
|
23
|
+
/** True when FormField supplies the visible `<label>`. */
|
|
24
|
+
hideLabel?: boolean;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
/** Height of the row, on the scale Input and Button use. */
|
|
27
|
+
size?: SizeVariant;
|
|
28
|
+
/** Colours the message; `error` also marks the input invalid. */
|
|
29
|
+
variant?: SemanticVariant;
|
|
30
|
+
/** Helper or error text below the slider. */
|
|
31
|
+
message?: string;
|
|
32
|
+
/** Shows the current value beside the label. */
|
|
33
|
+
showValue?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Turns the value into text ("40 %", "2 h"). It is what `showValue`
|
|
36
|
+
* shows, and what assistive technology reads instead of the bare
|
|
37
|
+
* number.
|
|
38
|
+
*/
|
|
39
|
+
formatValue?: (value: number) => string;
|
|
40
|
+
/** Native `input` event: fires while the thumb moves. */
|
|
41
|
+
oninput?: (event: Event) => void;
|
|
42
|
+
/** Native `change` event: fires when the thumb is released. */
|
|
43
|
+
onchange?: (event: Event) => void;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
let {
|
|
47
|
+
id: idProp,
|
|
48
|
+
min = 0,
|
|
49
|
+
max = 100,
|
|
50
|
+
step = 1,
|
|
51
|
+
value = $bindable<Exclude<Props["value"], undefined>>(),
|
|
52
|
+
name = "",
|
|
53
|
+
class: className = "",
|
|
54
|
+
label = "",
|
|
55
|
+
hideLabel = false,
|
|
56
|
+
disabled = false,
|
|
57
|
+
size = "md",
|
|
58
|
+
variant = "default",
|
|
59
|
+
message = "",
|
|
60
|
+
showValue = false,
|
|
61
|
+
formatValue,
|
|
62
|
+
oninput,
|
|
63
|
+
onchange,
|
|
64
|
+
...restProps
|
|
65
|
+
}: Props = $props();
|
|
66
|
+
|
|
67
|
+
// No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
|
|
68
|
+
// one that has a fallback (`props_invalid_value`), and a page that throws
|
|
69
|
+
// while it hydrates never becomes interactive. The default is applied
|
|
70
|
+
// here instead: at once, for the server and the first render, and again
|
|
71
|
+
// whenever a parent hands back `undefined`.
|
|
72
|
+
const applyDefaults = () => {
|
|
73
|
+
if (value === undefined) value = min;
|
|
74
|
+
};
|
|
75
|
+
applyDefaults();
|
|
76
|
+
$effect.pre(applyDefaults);
|
|
77
|
+
|
|
78
|
+
const fallbackId = generateId("slider");
|
|
79
|
+
const inputId = $derived(idProp ?? fallbackId);
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Row heights on the shared control scale (32 / 40 / 48), so a slider
|
|
83
|
+
* lines up with an Input or a Button of the same size beside it. The
|
|
84
|
+
* `<input>` itself is 44px tall in the two smaller rows, pulled back in
|
|
85
|
+
* with a negative margin: the whole input takes the pointer, so that is
|
|
86
|
+
* the touch target, and the row does not grow for it.
|
|
87
|
+
*/
|
|
88
|
+
const sizeClass = $derived.by(() => {
|
|
89
|
+
if (size === "sm") return { box: "h-8 pointer-coarse:min-h-11", input: "h-11 -my-1.5" };
|
|
90
|
+
if (size === "lg") return { box: "h-12", input: "h-12" };
|
|
91
|
+
return { box: "h-10 pointer-coarse:min-h-11", input: "h-11 -my-0.5" };
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
/** Where the thumb sits, 0 to 1; drives the filled part of the track. */
|
|
95
|
+
const ratio = $derived.by(() => {
|
|
96
|
+
if (!(max > min)) return 0;
|
|
97
|
+
return Math.max(0, Math.min(1, (value - min) / (max - min)));
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const valueText = $derived(formatValue ? formatValue(value) : String(value));
|
|
101
|
+
const labelVisible = $derived(!!label && !hideLabel);
|
|
102
|
+
|
|
103
|
+
// The `-text` step clears 4.5:1 on a page surface, as in Input.
|
|
104
|
+
const messageClasses = $derived.by(() => {
|
|
105
|
+
const base = "text-sm mt-2";
|
|
106
|
+
if (variant === "error") return `text-error-text ${base}`;
|
|
107
|
+
if (variant === "success") return `text-success-text ${base}`;
|
|
108
|
+
if (variant === "warning") return `text-warning-text ${base}`;
|
|
109
|
+
return `text-description ${base}`;
|
|
110
|
+
});
|
|
111
|
+
</script>
|
|
112
|
+
|
|
113
|
+
{#snippet valueOutput()}
|
|
114
|
+
<output
|
|
115
|
+
for={inputId}
|
|
116
|
+
class={cn(
|
|
117
|
+
"shrink-0 text-sm tabular-nums",
|
|
118
|
+
disabled ? "text-action-disabled-text" : "text-description",
|
|
119
|
+
)}
|
|
120
|
+
data-slider-value
|
|
121
|
+
>
|
|
122
|
+
{valueText}
|
|
123
|
+
</output>
|
|
124
|
+
{/snippet}
|
|
125
|
+
|
|
126
|
+
<div class={cn(className)}>
|
|
127
|
+
{#if labelVisible}
|
|
128
|
+
<div class="mb-2 flex items-baseline justify-between gap-3">
|
|
129
|
+
<label for={inputId} class="block text-sm font-medium text-label">{label}</label>
|
|
130
|
+
{#if showValue}
|
|
131
|
+
{@render valueOutput()}
|
|
132
|
+
{/if}
|
|
133
|
+
</div>
|
|
134
|
+
{/if}
|
|
135
|
+
<div class={cn("flex items-center gap-3", sizeClass.box)}>
|
|
136
|
+
<!-- A native range input: keyboard, touch, right-to-left and form
|
|
137
|
+
submission come with it. Only its looks are replaced, below. -->
|
|
138
|
+
<input
|
|
139
|
+
type="range"
|
|
140
|
+
id={inputId}
|
|
141
|
+
class={cn("slider min-w-0 flex-1", sizeClass.input)}
|
|
142
|
+
style:--zabi-slider-ratio={ratio}
|
|
143
|
+
data-size={size}
|
|
144
|
+
bind:value
|
|
145
|
+
{min}
|
|
146
|
+
{max}
|
|
147
|
+
{step}
|
|
148
|
+
name={name || undefined}
|
|
149
|
+
{disabled}
|
|
150
|
+
{oninput}
|
|
151
|
+
{onchange}
|
|
152
|
+
aria-valuetext={formatValue ? valueText : undefined}
|
|
153
|
+
aria-invalid={variant === "error" ? "true" : undefined}
|
|
154
|
+
aria-describedby={message ? `${inputId}-message` : undefined}
|
|
155
|
+
{...restProps}
|
|
156
|
+
/>
|
|
157
|
+
{#if showValue && !labelVisible}
|
|
158
|
+
{@render valueOutput()}
|
|
159
|
+
{/if}
|
|
160
|
+
</div>
|
|
161
|
+
{#if message}
|
|
162
|
+
<p
|
|
163
|
+
id={`${inputId}-message`}
|
|
164
|
+
class={messageClasses}
|
|
165
|
+
role={variant === "error" ? "alert" : undefined}
|
|
166
|
+
>
|
|
167
|
+
{message}
|
|
168
|
+
</p>
|
|
169
|
+
{/if}
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<style>
|
|
173
|
+
/*
|
|
174
|
+
* The parts of a range input are vendor pseudo-elements, which utilities
|
|
175
|
+
* cannot reach without an arbitrary variant per declaration. WebKit/Blink
|
|
176
|
+
* and Gecko each get their own rules: one unknown pseudo-element in a
|
|
177
|
+
* selector list drops the whole rule.
|
|
178
|
+
*/
|
|
179
|
+
.slider {
|
|
180
|
+
--zabi-slider-thumb: 1.25rem;
|
|
181
|
+
--zabi-slider-track: 0.375rem;
|
|
182
|
+
--zabi-slider-fill: var(--color-action-primary);
|
|
183
|
+
--zabi-slider-rest: var(--color-control-track);
|
|
184
|
+
--zabi-slider-halo: 0 0 0 0 transparent;
|
|
185
|
+
/* The fill ends under the centre of the thumb, which travels less
|
|
186
|
+
than the full width. */
|
|
187
|
+
--zabi-slider-stop: calc(
|
|
188
|
+
var(--zabi-slider-ratio, 0) * (100% - var(--zabi-slider-thumb)) +
|
|
189
|
+
var(--zabi-slider-thumb) / 2
|
|
190
|
+
);
|
|
191
|
+
-webkit-appearance: none;
|
|
192
|
+
-moz-appearance: none;
|
|
193
|
+
appearance: none;
|
|
194
|
+
background: transparent;
|
|
195
|
+
cursor: pointer;
|
|
196
|
+
outline: none;
|
|
197
|
+
/* A touch that starts on the slider moves the thumb; vertical scrolling stays with the page. */
|
|
198
|
+
touch-action: pan-y;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.slider[data-size="sm"] {
|
|
202
|
+
--zabi-slider-thumb: 1rem;
|
|
203
|
+
--zabi-slider-track: 0.25rem;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.slider[data-size="lg"] {
|
|
207
|
+
--zabi-slider-thumb: 1.5rem;
|
|
208
|
+
--zabi-slider-track: 0.5rem;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/* Only where a pointer can hover: after a tap, `:hover` stays on the slider until the next tap elsewhere. */
|
|
212
|
+
@media (hover: hover) {
|
|
213
|
+
.slider:hover {
|
|
214
|
+
--zabi-slider-fill: var(--color-action-primary-hover);
|
|
215
|
+
--zabi-slider-rest: var(--color-control-track-hover);
|
|
216
|
+
--zabi-slider-halo: 0 0 0 4px var(--color-surface-hover);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.slider:active {
|
|
221
|
+
--zabi-slider-fill: var(--color-action-primary-active);
|
|
222
|
+
--zabi-slider-rest: var(--color-control-track-active);
|
|
223
|
+
--zabi-slider-halo: 0 0 0 6px var(--color-surface-active);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* Same geometry as `.focus-ring`: a 2px gap, then a 2px ring, on the thumb. */
|
|
227
|
+
.slider:focus-visible {
|
|
228
|
+
--zabi-slider-halo:
|
|
229
|
+
0 0 0 2px var(--color-focus-ring-offset), 0 0 0 4px var(--color-focus-ring);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.slider:disabled {
|
|
233
|
+
--zabi-slider-fill: var(--color-action-disabled-text);
|
|
234
|
+
--zabi-slider-rest: var(--color-action-disabled);
|
|
235
|
+
--zabi-slider-halo: 0 0 0 0 transparent;
|
|
236
|
+
cursor: not-allowed;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/* WebKit and Blink: one track, painted in two parts. */
|
|
240
|
+
.slider::-webkit-slider-runnable-track {
|
|
241
|
+
height: var(--zabi-slider-track);
|
|
242
|
+
border-radius: 9999px;
|
|
243
|
+
background: linear-gradient(
|
|
244
|
+
to right,
|
|
245
|
+
var(--zabi-slider-fill) 0 var(--zabi-slider-stop),
|
|
246
|
+
var(--zabi-slider-rest) var(--zabi-slider-stop) 100%
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.slider:dir(rtl)::-webkit-slider-runnable-track {
|
|
251
|
+
background: linear-gradient(
|
|
252
|
+
to left,
|
|
253
|
+
var(--zabi-slider-fill) 0 var(--zabi-slider-stop),
|
|
254
|
+
var(--zabi-slider-rest) var(--zabi-slider-stop) 100%
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.slider::-webkit-slider-thumb {
|
|
259
|
+
-webkit-appearance: none;
|
|
260
|
+
appearance: none;
|
|
261
|
+
box-sizing: border-box;
|
|
262
|
+
width: var(--zabi-slider-thumb);
|
|
263
|
+
height: var(--zabi-slider-thumb);
|
|
264
|
+
margin-top: calc((var(--zabi-slider-track) - var(--zabi-slider-thumb)) / 2);
|
|
265
|
+
border: 2px solid var(--zabi-slider-fill);
|
|
266
|
+
border-radius: 9999px;
|
|
267
|
+
background: var(--color-card);
|
|
268
|
+
box-shadow: var(--zabi-slider-halo);
|
|
269
|
+
-webkit-transition: box-shadow 150ms;
|
|
270
|
+
transition: box-shadow 150ms;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* Gecko: the filled part is its own pseudo-element and follows the direction. */
|
|
274
|
+
.slider::-moz-range-track {
|
|
275
|
+
height: var(--zabi-slider-track);
|
|
276
|
+
border-radius: 9999px;
|
|
277
|
+
background: var(--zabi-slider-rest);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.slider::-moz-range-progress {
|
|
281
|
+
height: var(--zabi-slider-track);
|
|
282
|
+
border-radius: 9999px;
|
|
283
|
+
background: var(--zabi-slider-fill);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.slider::-moz-range-thumb {
|
|
287
|
+
box-sizing: border-box;
|
|
288
|
+
width: var(--zabi-slider-thumb);
|
|
289
|
+
height: var(--zabi-slider-thumb);
|
|
290
|
+
border: 2px solid var(--zabi-slider-fill);
|
|
291
|
+
border-radius: 9999px;
|
|
292
|
+
background: var(--color-card);
|
|
293
|
+
box-shadow: var(--zabi-slider-halo);
|
|
294
|
+
-moz-transition: box-shadow 150ms;
|
|
295
|
+
transition: box-shadow 150ms;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
@media (prefers-reduced-motion: reduce) {
|
|
299
|
+
.slider::-webkit-slider-thumb {
|
|
300
|
+
-webkit-transition: none;
|
|
301
|
+
transition: none;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.slider::-moz-range-thumb {
|
|
305
|
+
-moz-transition: none;
|
|
306
|
+
transition: none;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
</style>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from "svelte/elements";
|
|
2
|
+
import type { SemanticVariant, SizeVariant } from "../types/variants.js";
|
|
3
|
+
/** Other attributes (`data-*`, `aria-*`, `list`, ...) land on the `<input>`. */
|
|
4
|
+
type Props = Omit<HTMLInputAttributes, "class" | "size" | "type" | "value" | "min" | "max" | "step" | "id"> & {
|
|
5
|
+
/** Omit to auto-generate; pair with FormField `id` when used inside FormField. */
|
|
6
|
+
id?: string;
|
|
7
|
+
/** Current value; supports `bind:value`. Starts at `min` when not given. */
|
|
8
|
+
value?: number;
|
|
9
|
+
min?: number;
|
|
10
|
+
max?: number;
|
|
11
|
+
step?: number;
|
|
12
|
+
name?: string;
|
|
13
|
+
/** Extra classes for the host element. */
|
|
14
|
+
class?: string;
|
|
15
|
+
label?: string;
|
|
16
|
+
/** True when FormField supplies the visible `<label>`. */
|
|
17
|
+
hideLabel?: boolean;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
/** Height of the row, on the scale Input and Button use. */
|
|
20
|
+
size?: SizeVariant;
|
|
21
|
+
/** Colours the message; `error` also marks the input invalid. */
|
|
22
|
+
variant?: SemanticVariant;
|
|
23
|
+
/** Helper or error text below the slider. */
|
|
24
|
+
message?: string;
|
|
25
|
+
/** Shows the current value beside the label. */
|
|
26
|
+
showValue?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Turns the value into text ("40 %", "2 h"). It is what `showValue`
|
|
29
|
+
* shows, and what assistive technology reads instead of the bare
|
|
30
|
+
* number.
|
|
31
|
+
*/
|
|
32
|
+
formatValue?: (value: number) => string;
|
|
33
|
+
/** Native `input` event: fires while the thumb moves. */
|
|
34
|
+
oninput?: (event: Event) => void;
|
|
35
|
+
/** Native `change` event: fires when the thumb is released. */
|
|
36
|
+
onchange?: (event: Event) => void;
|
|
37
|
+
};
|
|
38
|
+
declare const Slider: import("svelte").Component<Props, {}, "value">;
|
|
39
|
+
type Slider = ReturnType<typeof Slider>;
|
|
40
|
+
export default Slider;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
import { cn } from "../util/cn.js";
|
|
4
|
+
|
|
5
|
+
type Props = Omit<HTMLAttributes<HTMLSpanElement>, "class"> & {
|
|
6
|
+
/**
|
|
7
|
+
* Diameter, on the scale the built-in loading states use: 12, 14, 16
|
|
8
|
+
* and 20px. For another size pass a `size-*` utility through `class`.
|
|
9
|
+
*/
|
|
10
|
+
size?: "xs" | "sm" | "md" | "lg";
|
|
11
|
+
/**
|
|
12
|
+
* What is loading. With a label the spinner is a `status` and the
|
|
13
|
+
* label is read out; without one it is decorative and hidden from
|
|
14
|
+
* assistive technology, for use beside text that already says so.
|
|
15
|
+
*/
|
|
16
|
+
label?: string;
|
|
17
|
+
/** Extra classes for the ring: a size, a colour, an opacity. */
|
|
18
|
+
class?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
size = "md",
|
|
23
|
+
label = "",
|
|
24
|
+
class: className = "",
|
|
25
|
+
...restProps
|
|
26
|
+
}: Props = $props();
|
|
27
|
+
|
|
28
|
+
const SIZES = {
|
|
29
|
+
xs: "size-3",
|
|
30
|
+
sm: "size-3.5",
|
|
31
|
+
md: "size-4",
|
|
32
|
+
lg: "size-5",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The same ring Button, IconButton and Input draw while loading, in the
|
|
37
|
+
* text colour. Rotation is the motion a reduced-motion preference is
|
|
38
|
+
* about, so there the ring stays still and fades in and out instead:
|
|
39
|
+
* something must still show that work is going on.
|
|
40
|
+
*/
|
|
41
|
+
const ringClasses = $derived(
|
|
42
|
+
cn(
|
|
43
|
+
"inline-block shrink-0 animate-spin rounded-full border-2 border-current border-t-transparent opacity-80 motion-reduce:animate-pulse",
|
|
44
|
+
SIZES[size],
|
|
45
|
+
className,
|
|
46
|
+
),
|
|
47
|
+
);
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
{#if label}
|
|
51
|
+
<span class="inline-flex shrink-0" role="status" {...restProps}>
|
|
52
|
+
<span class={ringClasses} aria-hidden="true"></span>
|
|
53
|
+
<span class="sr-only">{label}</span>
|
|
54
|
+
</span>
|
|
55
|
+
{:else}
|
|
56
|
+
<span class={ringClasses} aria-hidden="true" {...restProps}></span>
|
|
57
|
+
{/if}
|