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
|
@@ -1,63 +1,153 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { onMount } from 'svelte';
|
|
2
|
+
import { onMount, untrack } from 'svelte';
|
|
3
3
|
import { cubicOut } from 'svelte/easing';
|
|
4
4
|
import { slide, fade, fly } from 'svelte/transition';
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
X,
|
|
12
|
-
} from '@lucide/svelte';
|
|
5
|
+
import AlertCircle from '@lucide/svelte/icons/circle-alert';
|
|
6
|
+
import AlertTriangle from '@lucide/svelte/icons/triangle-alert';
|
|
7
|
+
import CheckCircle from '@lucide/svelte/icons/circle-check-big';
|
|
8
|
+
import ChevronDown from '@lucide/svelte/icons/chevron-down';
|
|
9
|
+
import Info from '@lucide/svelte/icons/info';
|
|
10
|
+
import X from '@lucide/svelte/icons/x';
|
|
13
11
|
import Button from '../atoms/Button.svelte';
|
|
14
12
|
import { toastStore, type ToastItem } from './toast-store.js';
|
|
15
13
|
import { cn } from "../util/cn.js";
|
|
14
|
+
import { createVelocityTracker, DRAG_SLOP, FLICK_VELOCITY, prefersReducedMotion, timeOf } from "../util/sheet-drag.js";
|
|
15
|
+
import {
|
|
16
|
+
DEFAULT_TOASTER_STRINGS,
|
|
17
|
+
easeStandard,
|
|
18
|
+
resolveToastDuration,
|
|
19
|
+
toastsReplaced,
|
|
20
|
+
toastTexts,
|
|
21
|
+
TOAST_SECONDS_AFTER_HOLD,
|
|
22
|
+
TOAST_SWIPE_DISMISS_FRACTION,
|
|
23
|
+
type ToastDuration,
|
|
24
|
+
type ToasterStrings,
|
|
25
|
+
type ToastPauseChange,
|
|
26
|
+
} from "../util/toaster.js";
|
|
16
27
|
|
|
17
28
|
interface Props {
|
|
18
29
|
/** Extra classes for the host element. */
|
|
19
30
|
class?: string;
|
|
20
31
|
toast: ToastItem;
|
|
32
|
+
/** Every word the toast says by itself; the Toaster passes its own. */
|
|
33
|
+
strings?: ToasterStrings;
|
|
34
|
+
/** Shows the time left as a sentence, with a button that stops the timer. */
|
|
35
|
+
showCountdown?: boolean;
|
|
36
|
+
/** What a toast pushed without a duration gets, in place of medium; the Toaster passes its own. */
|
|
37
|
+
defaultDuration?: ToastDuration;
|
|
38
|
+
/** Called when a pointer, a finger or focus starts or stops holding the timer. */
|
|
39
|
+
onpausechange?: (detail: ToastPauseChange) => void;
|
|
40
|
+
/** The compact form for a phone: one row, docked, sliding out from the edge it sits on. */
|
|
41
|
+
snackbar?: boolean;
|
|
42
|
+
/** A touch or a pen drags the toast away, sideways (and down, in the snackbar). */
|
|
43
|
+
swipeToDismiss?: boolean;
|
|
44
|
+
/** Draws the time bar (`true`/`false`); unset, it is left out of a snackbar toast with nothing to press. */
|
|
45
|
+
timeLine?: boolean;
|
|
21
46
|
}
|
|
22
47
|
|
|
23
|
-
let {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
48
|
+
let {
|
|
49
|
+
class: className = "",
|
|
50
|
+
toast,
|
|
51
|
+
strings = DEFAULT_TOASTER_STRINGS,
|
|
52
|
+
showCountdown = false,
|
|
53
|
+
defaultDuration,
|
|
54
|
+
onpausechange,
|
|
55
|
+
snackbar = false,
|
|
56
|
+
swipeToDismiss = true,
|
|
57
|
+
timeLine,
|
|
58
|
+
}: Props = $props();
|
|
33
59
|
|
|
34
60
|
let count = $state(0);
|
|
35
61
|
let timerActive = $state(false);
|
|
36
62
|
let isExpanded = $state(false);
|
|
37
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* What holds the timer (WCAG 2.2.1): a mouse or a stylus over the toast,
|
|
65
|
+
* a finger on it, keyboard focus inside it.
|
|
66
|
+
*/
|
|
38
67
|
let hovered = $state(false);
|
|
68
|
+
let held = $state(false);
|
|
39
69
|
let focusWithin = $state(false);
|
|
40
|
-
const paused = $derived(hovered || focusWithin);
|
|
70
|
+
const paused = $derived(hovered || held || focusWithin);
|
|
41
71
|
|
|
42
72
|
let intervalRef: ReturnType<typeof setInterval> | undefined;
|
|
43
73
|
|
|
44
|
-
const
|
|
74
|
+
const texts = $derived(toastTexts(toast, strings));
|
|
75
|
+
const title = $derived(texts.title);
|
|
76
|
+
const message = $derived(texts.message);
|
|
77
|
+
const headerTitle = $derived(texts.headerTitle);
|
|
78
|
+
const secondLine = $derived(texts.secondLine);
|
|
45
79
|
|
|
80
|
+
/**
|
|
81
|
+
* The part that opens and closes is `detail`, when the toast was given
|
|
82
|
+
* one: the long version. The message is never folded away.
|
|
83
|
+
*/
|
|
84
|
+
const expandableText = $derived((toast.detail ?? "").trim());
|
|
46
85
|
const hasExpandable = $derived(expandableText.length > 0);
|
|
47
86
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Seconds until the toast closes by itself; `0` is never. What the app
|
|
89
|
+
* said for this toast wins. Otherwise it goes by what the toast is and by
|
|
90
|
+
* how much there is to read in it: the two lines that are shown, not the
|
|
91
|
+
* detail behind its button.
|
|
92
|
+
*/
|
|
93
|
+
const maxSeconds = $derived.by(() => {
|
|
94
|
+
const ms = resolveToastDuration(
|
|
95
|
+
{
|
|
96
|
+
duration: toast.duration,
|
|
97
|
+
type: toast.type,
|
|
98
|
+
hasAction: !!toast.action,
|
|
99
|
+
textLength: headerTitle.length + secondLine.length,
|
|
100
|
+
},
|
|
101
|
+
defaultDuration,
|
|
102
|
+
);
|
|
103
|
+
return ms <= 0 ? 0 : Math.max(1, Math.ceil(ms / 1000));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* A short timed toast pushed with `closable: false` has no control at
|
|
108
|
+
* all. A toast that stays, or has a button of any kind to press, keeps
|
|
109
|
+
* its close button: there must always be a way to dismiss that is not a
|
|
110
|
+
* gesture.
|
|
111
|
+
*/
|
|
112
|
+
const bare = $derived(
|
|
113
|
+
toast.closable === false &&
|
|
114
|
+
maxSeconds > 0 &&
|
|
115
|
+
!toast.action &&
|
|
116
|
+
!hasExpandable &&
|
|
117
|
+
!showCountdown &&
|
|
118
|
+
toast.type !== 'error',
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The bar says "you have this long to press". A confirmation with nothing
|
|
123
|
+
* to press, in the snackbar, has no use for it (and WCAG 2.2.1 is about
|
|
124
|
+
* time to act, of which there is none), so it is left out there unless
|
|
125
|
+
* asked for. The stack keeps it. The toast's own setting wins over the Toaster's.
|
|
126
|
+
*/
|
|
127
|
+
const drawTimeLine = $derived(toast.timeLine ?? timeLine ?? !(snackbar && bare));
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* What a screen reader hears is a live region that is in the page
|
|
131
|
+
* empty and gets its words afterwards: one that is inserted already
|
|
132
|
+
* filled is the pattern screen readers are least dependable on. So the
|
|
133
|
+
* words are put in a moment after the toast is mounted (the toast is
|
|
134
|
+
* still coming in then). The server's markup has them at once.
|
|
135
|
+
*/
|
|
136
|
+
let spoken = $state(typeof document === 'undefined');
|
|
137
|
+
const SPEAK_AFTER_MS = 100;
|
|
138
|
+
|
|
139
|
+
/** The time left as words: for the sentence, or for a screen reader to find. */
|
|
140
|
+
const countdownText = $derived(
|
|
141
|
+
paused ? strings.pausedClosesIn(count) : strings.closesIn(count),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Said to the app as well: `data-paused` is the same thing in the markup.
|
|
145
|
+
let reportedPause = false;
|
|
146
|
+
$effect(() => {
|
|
147
|
+
const now = paused;
|
|
148
|
+
if (now === reportedPause) return;
|
|
149
|
+
reportedPause = now;
|
|
150
|
+
onpausechange?.({ id: toast.id, paused: now });
|
|
61
151
|
});
|
|
62
152
|
|
|
63
153
|
const statusIconClass = $derived(
|
|
@@ -88,24 +178,34 @@
|
|
|
88
178
|
timerActive = false;
|
|
89
179
|
}
|
|
90
180
|
|
|
91
|
-
|
|
181
|
+
/** (Re)starts the timer from the full time; a toast that stays has none. */
|
|
182
|
+
function startTimer() {
|
|
183
|
+
stopTimer();
|
|
92
184
|
const max = maxSeconds;
|
|
93
|
-
if (max <= 0)
|
|
94
|
-
return () => {};
|
|
95
|
-
}
|
|
185
|
+
if (max <= 0) return;
|
|
96
186
|
count = max;
|
|
97
187
|
timerActive = true;
|
|
98
188
|
|
|
99
189
|
intervalRef = setInterval(() => {
|
|
100
|
-
|
|
190
|
+
// The state, not the `paused` derived: a tick can land while the
|
|
191
|
+
// toast is animating out, and reading a derived then warns
|
|
192
|
+
// (`derived_inert`).
|
|
193
|
+
if (hovered || held || focusWithin) return;
|
|
101
194
|
count -= 1;
|
|
102
195
|
if (count <= 0) {
|
|
103
196
|
stopTimer();
|
|
104
197
|
toastStore.dismiss(toast.id);
|
|
105
198
|
}
|
|
106
199
|
}, 1000);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
onMount(() => {
|
|
203
|
+
startTimer();
|
|
204
|
+
const speak = setTimeout(() => (spoken = true), SPEAK_AFTER_MS);
|
|
107
205
|
|
|
108
206
|
return () => {
|
|
207
|
+
clearTimeout(speak);
|
|
208
|
+
clearTimeout(settleTimer);
|
|
109
209
|
if (intervalRef !== undefined) {
|
|
110
210
|
clearInterval(intervalRef);
|
|
111
211
|
intervalRef = undefined;
|
|
@@ -113,12 +213,207 @@
|
|
|
113
213
|
};
|
|
114
214
|
});
|
|
115
215
|
|
|
216
|
+
// The same id pushed again replaces the toast in place: new words, the full time again.
|
|
217
|
+
let shownToast: ToastItem | undefined;
|
|
218
|
+
$effect(() => {
|
|
219
|
+
const current = toast;
|
|
220
|
+
if (shownToast === undefined) {
|
|
221
|
+
shownToast = current;
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (current === shownToast) return;
|
|
225
|
+
shownToast = current;
|
|
226
|
+
untrack(() => {
|
|
227
|
+
isExpanded = false;
|
|
228
|
+
startTimer();
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
116
232
|
function handleDismiss() {
|
|
117
233
|
toastStore.dismiss(toast.id);
|
|
118
234
|
}
|
|
119
235
|
|
|
236
|
+
/** The toast closes even if the handler throws, unless the action opted out. */
|
|
237
|
+
function handleAction() {
|
|
238
|
+
const action = toast.action;
|
|
239
|
+
if (!action) return;
|
|
240
|
+
try {
|
|
241
|
+
action.onclick();
|
|
242
|
+
} finally {
|
|
243
|
+
if (action.dismissOnClick !== false) {
|
|
244
|
+
toastStore.dismiss(toast.id);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
let expandButton = $state<HTMLButtonElement>();
|
|
250
|
+
let dismissButton = $state<HTMLButtonElement>();
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Okay closes the details and goes with them. Focus moves to the button
|
|
254
|
+
* that opens them again, or it would fall to the page and a keyboard
|
|
255
|
+
* user would be at the top of it.
|
|
256
|
+
*/
|
|
120
257
|
function handleOkay() {
|
|
121
258
|
isExpanded = false;
|
|
259
|
+
expandButton?.focus();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** The stop button goes with the sentence it is in: focus stays in the toast. */
|
|
263
|
+
function handleStop(event: MouseEvent) {
|
|
264
|
+
const held = event.currentTarget === document.activeElement;
|
|
265
|
+
stopTimer();
|
|
266
|
+
if (held) dismissButton?.focus();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* A mouse or a stylus holds the timer once it has moved over the toast,
|
|
271
|
+
* not when the toast arrives under it: a browser sends `pointerenter` to
|
|
272
|
+
* an element that appears under a pointer at rest, and a toast shown
|
|
273
|
+
* where the last click was would otherwise be held until the mouse is
|
|
274
|
+
* moved away. A finger holds it while it is on the toast.
|
|
275
|
+
*/
|
|
276
|
+
function handlePointerLeave(event: PointerEvent) {
|
|
277
|
+
if (event.pointerType !== "touch") hovered = false;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function handlePointerMove(event: PointerEvent) {
|
|
281
|
+
if (dragging) {
|
|
282
|
+
drag(event);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (tracked) maybeStartDrag(event);
|
|
286
|
+
if (event.pointerType === "touch" || hovered) return;
|
|
287
|
+
// A move of no distance is not the pointer moving (a browser repeating
|
|
288
|
+
// its position after the layout changed); one with no such field is.
|
|
289
|
+
const moved =
|
|
290
|
+
typeof event.movementX !== "number" ||
|
|
291
|
+
Number.isNaN(event.movementX) ||
|
|
292
|
+
event.movementX !== 0 ||
|
|
293
|
+
event.movementY !== 0;
|
|
294
|
+
if (moved) hovered = true;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Swiping away: a touch or a pen drags the toast sideways, or down in the
|
|
298
|
+
// snackbar (it is docked at the bottom); past 40% of its width or a flick it
|
|
299
|
+
// goes, otherwise it settles back. Reuses the sheets' drag constants.
|
|
300
|
+
let root = $state<HTMLDivElement>();
|
|
301
|
+
let dragX = $state(0);
|
|
302
|
+
let dragY = $state(0);
|
|
303
|
+
let dragging = $state(false);
|
|
304
|
+
let settling = $state(false);
|
|
305
|
+
let tracked = false;
|
|
306
|
+
let pointerId = -1;
|
|
307
|
+
let startX = 0;
|
|
308
|
+
let startY = 0;
|
|
309
|
+
let axis: "x" | "y" | null = null;
|
|
310
|
+
let swallowClick = false;
|
|
311
|
+
let settleTimer: ReturnType<typeof setTimeout> | undefined;
|
|
312
|
+
const trackX = createVelocityTracker();
|
|
313
|
+
const trackY = createVelocityTracker();
|
|
314
|
+
|
|
315
|
+
function handlePointerDown(event: PointerEvent) {
|
|
316
|
+
if (event.pointerType === "touch") held = true;
|
|
317
|
+
if (!swipeToDismiss || bare || event.pointerType === "mouse" || !event.isPrimary) return;
|
|
318
|
+
// A button keeps its own press.
|
|
319
|
+
if ((event.target as Element | null)?.closest?.("button, a[href], input, select, textarea")) return;
|
|
320
|
+
clearTimeout(settleTimer);
|
|
321
|
+
settling = false;
|
|
322
|
+
tracked = true;
|
|
323
|
+
pointerId = event.pointerId;
|
|
324
|
+
startX = event.clientX;
|
|
325
|
+
startY = event.clientY;
|
|
326
|
+
axis = null;
|
|
327
|
+
const time = timeOf(event);
|
|
328
|
+
trackX.reset(event.clientX, time);
|
|
329
|
+
trackY.reset(event.clientY, time);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function drag(event: PointerEvent) {
|
|
333
|
+
if (event.pointerId !== pointerId) return;
|
|
334
|
+
const dx = event.clientX - startX;
|
|
335
|
+
const dy = event.clientY - startY;
|
|
336
|
+
const time = timeOf(event);
|
|
337
|
+
trackX.add(event.clientX, time);
|
|
338
|
+
trackY.add(event.clientY, time);
|
|
339
|
+
dragX = axis === "x" ? dx : 0;
|
|
340
|
+
dragY = axis === "y" ? Math.max(0, dy) : 0;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Called for every move of a pointer that is down on the toast, before it is a drag. */
|
|
344
|
+
function maybeStartDrag(event: PointerEvent) {
|
|
345
|
+
if (!tracked || event.pointerId !== pointerId) return;
|
|
346
|
+
const dx = event.clientX - startX;
|
|
347
|
+
const dy = event.clientY - startY;
|
|
348
|
+
const time = timeOf(event);
|
|
349
|
+
trackX.add(event.clientX, time);
|
|
350
|
+
trackY.add(event.clientY, time);
|
|
351
|
+
if (Math.hypot(dx, dy) < DRAG_SLOP) return;
|
|
352
|
+
const sideways = Math.abs(dx) > Math.abs(dy);
|
|
353
|
+
// Up is the way a page scrolls, and in the stack there is no edge to go down to.
|
|
354
|
+
if (!sideways && (dy < 0 || !snackbar)) {
|
|
355
|
+
tracked = false;
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
axis = sideways ? "x" : "y";
|
|
359
|
+
dragging = true;
|
|
360
|
+
try {
|
|
361
|
+
root?.setPointerCapture(event.pointerId);
|
|
362
|
+
} catch {
|
|
363
|
+
// A pointer that is gone cannot be captured; the drag simply ends.
|
|
364
|
+
}
|
|
365
|
+
drag(event);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function finishDrag(event: PointerEvent, cancelled: boolean) {
|
|
369
|
+
if (!tracked || event.pointerId !== pointerId) return;
|
|
370
|
+
tracked = false;
|
|
371
|
+
if (!dragging) return;
|
|
372
|
+
dragging = false;
|
|
373
|
+
swallowClick = true;
|
|
374
|
+
setTimeout(() => (swallowClick = false), 0);
|
|
375
|
+
const width = root?.offsetWidth || 1;
|
|
376
|
+
const height = root?.offsetHeight || 1;
|
|
377
|
+
const sideways = axis === "x";
|
|
378
|
+
const distance = sideways ? dragX : dragY;
|
|
379
|
+
const velocity = sideways ? trackX.velocity() : trackY.velocity();
|
|
380
|
+
const flicked = Math.abs(velocity) > FLICK_VELOCITY && Math.sign(velocity) === Math.sign(distance);
|
|
381
|
+
const far = Math.abs(distance) > (sideways ? width : height) * TOAST_SWIPE_DISMISS_FRACTION;
|
|
382
|
+
if (!cancelled && (far || flicked)) {
|
|
383
|
+
if (prefersReducedMotion()) {
|
|
384
|
+
toastStore.dismiss(toast.id);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
// Out the way it was going, then gone.
|
|
388
|
+
settling = true;
|
|
389
|
+
if (sideways) dragX = Math.sign(distance || 1) * (width + 32);
|
|
390
|
+
else dragY = height + 32;
|
|
391
|
+
settleTimer = setTimeout(() => toastStore.dismiss(toast.id), 200);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
// Back where it was.
|
|
395
|
+
settling = !prefersReducedMotion();
|
|
396
|
+
dragX = 0;
|
|
397
|
+
dragY = 0;
|
|
398
|
+
settleTimer = setTimeout(() => (settling = false), 220);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function handleClickCapture(event: MouseEvent) {
|
|
402
|
+
if (!swallowClick) return;
|
|
403
|
+
event.preventDefault();
|
|
404
|
+
event.stopPropagation();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* The finger lifts, or the browser takes the touch for a scroll. The
|
|
409
|
+
* timer runs again, with time left to do something about the toast: it
|
|
410
|
+
* was held because someone was reading it.
|
|
411
|
+
*/
|
|
412
|
+
function handlePointerRelease(event: PointerEvent) {
|
|
413
|
+
finishDrag(event, event.type === "pointercancel");
|
|
414
|
+
if (event.pointerType !== "touch" || !held) return;
|
|
415
|
+
held = false;
|
|
416
|
+
if (timerActive) count = Math.max(count, Math.min(TOAST_SECONDS_AFTER_HOLD, maxSeconds));
|
|
122
417
|
}
|
|
123
418
|
|
|
124
419
|
function handleFocusOut(event: FocusEvent) {
|
|
@@ -128,56 +423,157 @@
|
|
|
128
423
|
}
|
|
129
424
|
}
|
|
130
425
|
|
|
131
|
-
|
|
132
|
-
const
|
|
426
|
+
/** No movement where motion is unwanted: the toast appears and goes at once. */
|
|
427
|
+
const reducedMotion = prefersReducedMotion();
|
|
428
|
+
const motion = (duration: number) => (reducedMotion ? 0 : duration);
|
|
429
|
+
|
|
430
|
+
const toastEnter = { y: 18, duration: motion(260), opacity: 0, easing: cubicOut };
|
|
431
|
+
const toastLeave = { y: 14, duration: motion(300), opacity: 0, easing: cubicOut };
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* The snackbar comes out from behind the edge it is docked on and goes
|
|
435
|
+
* back into it: it slides down by its own height and the gap, on the
|
|
436
|
+
* standard curve, with no overshoot. The region clips it at that edge.
|
|
437
|
+
*/
|
|
438
|
+
function dock(node: HTMLElement, leaving: boolean) {
|
|
439
|
+
const distance = node.offsetHeight + 24;
|
|
440
|
+
return {
|
|
441
|
+
duration: motion(leaving ? 200 : 280),
|
|
442
|
+
easing: easeStandard,
|
|
443
|
+
css: (t: number, u: number) => `transform: translateY(${u * distance}px); opacity: ${Math.min(1, t * 3)}`,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function enter(node: HTMLElement) {
|
|
448
|
+
return snackbar ? dock(node, false) : fly(node, toastEnter);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function leave(node: HTMLElement) {
|
|
452
|
+
// Dropped for a newer toast that is coming in where it is: gone at once.
|
|
453
|
+
if (toastsReplaced.delete(toast.id)) return { duration: 0 };
|
|
454
|
+
return snackbar ? dock(node, true) : fly(node, toastLeave);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/** One line of text is centred on the buttons beside it; text that wraps stays at the top. */
|
|
458
|
+
let textBox = $state<HTMLDivElement>();
|
|
459
|
+
let oneLine = $state(false);
|
|
460
|
+
$effect(() => {
|
|
461
|
+
const box = textBox;
|
|
462
|
+
void headerTitle;
|
|
463
|
+
void secondLine;
|
|
464
|
+
if (!box || typeof getComputedStyle !== 'function') return;
|
|
465
|
+
const measure = () => {
|
|
466
|
+
const line = parseFloat(getComputedStyle(box).lineHeight) || 24;
|
|
467
|
+
oneLine = box.scrollHeight <= line * 1.5;
|
|
468
|
+
};
|
|
469
|
+
measure();
|
|
470
|
+
if (typeof ResizeObserver !== 'function') return;
|
|
471
|
+
const observer = new ResizeObserver(measure);
|
|
472
|
+
observer.observe(box);
|
|
473
|
+
return () => observer.disconnect();
|
|
474
|
+
});
|
|
133
475
|
</script>
|
|
134
476
|
|
|
477
|
+
<!-- Radius: the snackbar's 4px of padding round a control of 8px make 12px (outer = inner + padding). -->
|
|
135
478
|
<div
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
479
|
+
bind:this={root}
|
|
480
|
+
class={cn(
|
|
481
|
+
"relative w-full min-w-[min(18rem,100%)] shrink-0 overflow-hidden material-thick",
|
|
482
|
+
snackbar ? "rounded-container" : "rounded-overlay",
|
|
483
|
+
// No control in it: a tap goes through to the page.
|
|
484
|
+
bare ? "pointer-events-none" : "pointer-events-auto",
|
|
485
|
+
className,
|
|
486
|
+
)}
|
|
487
|
+
in:enter
|
|
488
|
+
out:leave
|
|
489
|
+
style:transform={dragX !== 0 || dragY !== 0 ? `translate3d(${dragX}px, ${dragY}px, 0)` : undefined}
|
|
490
|
+
style:opacity={dragging || settling ? Math.max(0.2, 1 - Math.hypot(dragX, dragY) / (root?.offsetWidth || 300)) : undefined}
|
|
491
|
+
style:transition={settling ? "transform 200ms var(--ease-standard), opacity 200ms var(--ease-standard)" : undefined}
|
|
492
|
+
style:touch-action={swipeToDismiss && !bare ? (snackbar ? "none" : "pan-y") : undefined}
|
|
493
|
+
onpointerleave={handlePointerLeave}
|
|
494
|
+
onpointerdown={handlePointerDown}
|
|
495
|
+
onpointermove={handlePointerMove}
|
|
496
|
+
onpointerup={handlePointerRelease}
|
|
497
|
+
onpointercancel={handlePointerRelease}
|
|
498
|
+
onclickcapture={handleClickCapture}
|
|
140
499
|
onmouseleave={() => (hovered = false)}
|
|
141
500
|
onfocusin={() => (focusWithin = true)}
|
|
142
501
|
onfocusout={handleFocusOut}
|
|
143
502
|
role="group"
|
|
144
503
|
aria-label={headerTitle}
|
|
504
|
+
data-toast-id={toast.id}
|
|
145
505
|
data-paused={paused}
|
|
146
506
|
>
|
|
147
|
-
|
|
148
|
-
|
|
507
|
+
<!-- Padding and gaps in px: at 200% text on a phone, room that grew with
|
|
508
|
+
the text left the text itself a column too narrow for one long word. -->
|
|
509
|
+
<div class={snackbar ? "p-[4px] ps-[12px]" : "p-[16px]"}>
|
|
510
|
+
<!-- The title keeps 8rem at least, icon included. When the toast has
|
|
511
|
+
less beside its buttons (320px with enlarged text), the buttons go to a
|
|
512
|
+
line of their own under it, at the end. -->
|
|
513
|
+
<div class="flex flex-wrap {oneLine ? 'items-center' : 'items-start'} gap-x-[12px] gap-y-[4px] {snackbar ? 'min-h-[44px]' : ''}">
|
|
149
514
|
<!-- Live region holds only title + message, so the countdown and buttons are never re-announced. -->
|
|
150
515
|
<div
|
|
151
|
-
class="flex min-w-0 flex-
|
|
516
|
+
class="flex min-w-0 flex-[1_1_8rem] items-start gap-[12px]"
|
|
152
517
|
role={toast.type === 'error' ? 'alert' : 'status'}
|
|
153
518
|
aria-atomic="true"
|
|
154
519
|
>
|
|
155
|
-
|
|
520
|
+
<!-- 20px at every text size, as the padding and the gap beside it
|
|
521
|
+
are px: an icon that grew with the text took the room the text
|
|
522
|
+
needed. Lowered so it stays at the middle of the first line. -->
|
|
523
|
+
<div class="shrink-0 mt-[calc((1.5rem_-_20px)_/_2)]">
|
|
156
524
|
{#if toast.type === 'success'}
|
|
157
|
-
<CheckCircle class="size-
|
|
525
|
+
<CheckCircle class="size-[20px] {statusIconClass}" aria-hidden="true" />
|
|
158
526
|
{:else if toast.type === 'error'}
|
|
159
|
-
<AlertCircle class="size-
|
|
527
|
+
<AlertCircle class="size-[20px] {statusIconClass}" aria-hidden="true" />
|
|
160
528
|
{:else if toast.type === 'warning'}
|
|
161
|
-
<AlertTriangle class="size-
|
|
529
|
+
<AlertTriangle class="size-[20px] {statusIconClass}" aria-hidden="true" />
|
|
162
530
|
{:else}
|
|
163
|
-
<Info class="size-
|
|
531
|
+
<Info class="size-[20px] {statusIconClass}" aria-hidden="true" />
|
|
164
532
|
{/if}
|
|
165
533
|
</div>
|
|
166
|
-
<
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
534
|
+
<div class="min-w-0 flex-1" bind:this={textBox}>
|
|
535
|
+
<!-- A heading when the app gave the toast a title (or nothing
|
|
536
|
+
at all); a message on its own is a sentence, not a heading. -->
|
|
537
|
+
{#if title || !message}
|
|
538
|
+
<h4 class="text-base font-semibold text-headline [overflow-wrap:anywhere]">
|
|
539
|
+
{spoken ? headerTitle : "\u00a0"}
|
|
540
|
+
</h4>
|
|
541
|
+
{:else}
|
|
542
|
+
<p class="text-base font-medium text-headline [overflow-wrap:anywhere]" data-toast-message>
|
|
543
|
+
{spoken ? headerTitle : "\u00a0"}
|
|
544
|
+
</p>
|
|
545
|
+
{/if}
|
|
546
|
+
{#if secondLine}
|
|
547
|
+
<p class="mt-1 text-sm text-description [overflow-wrap:anywhere]" data-toast-message>
|
|
548
|
+
{spoken ? secondLine : "\u00a0"}
|
|
549
|
+
</p>
|
|
170
550
|
{/if}
|
|
171
|
-
|
|
551
|
+
{#if toast.action && spoken}
|
|
552
|
+
<!-- The button sits outside the live region, so this is how a screen reader user hears that there is one. -->
|
|
553
|
+
<span class="sr-only">{strings.actionAvailable(toast.action.label)}</span>
|
|
554
|
+
{/if}
|
|
555
|
+
</div>
|
|
172
556
|
</div>
|
|
173
|
-
|
|
557
|
+
{#if !bare}
|
|
558
|
+
<div class="ms-auto flex shrink-0 items-center gap-1">
|
|
559
|
+
{#if snackbar && toast.action}
|
|
560
|
+
<Button
|
|
561
|
+
variant="text"
|
|
562
|
+
size="sm"
|
|
563
|
+
text={toast.action.label}
|
|
564
|
+
type="button"
|
|
565
|
+
data-toast-action
|
|
566
|
+
onclick={handleAction}
|
|
567
|
+
/>
|
|
568
|
+
{/if}
|
|
174
569
|
{#if hasExpandable}
|
|
175
570
|
<button
|
|
176
571
|
type="button"
|
|
177
|
-
class="focus-ring cursor-pointer rounded-control p-1 text-description transition-colors hover:bg-surface-overlay-hover hover:text-headline focus:outline-none"
|
|
572
|
+
class="focus-ring cursor-pointer inline-flex items-center justify-center rounded-control p-1 pointer-coarse:min-h-[44px] pointer-coarse:min-w-[44px] text-description transition-colors hover:bg-surface-overlay-hover hover:text-headline active:bg-surface-active focus:outline-none"
|
|
178
573
|
aria-expanded={isExpanded}
|
|
179
|
-
aria-controls=
|
|
180
|
-
aria-label={isExpanded ?
|
|
574
|
+
aria-controls={isExpanded ? `toaster-expand-${toast.id}` : undefined}
|
|
575
|
+
aria-label={isExpanded ? strings.collapse : strings.expand}
|
|
576
|
+
bind:this={expandButton}
|
|
181
577
|
onclick={() => (isExpanded = !isExpanded)}
|
|
182
578
|
>
|
|
183
579
|
<ChevronDown
|
|
@@ -190,26 +586,42 @@
|
|
|
190
586
|
{/if}
|
|
191
587
|
<button
|
|
192
588
|
type="button"
|
|
193
|
-
class="focus-ring cursor-pointer rounded-control p-1 text-description transition-colors hover:bg-surface-overlay-hover hover:text-headline focus:outline-none"
|
|
589
|
+
class="focus-ring cursor-pointer inline-flex items-center justify-center rounded-control p-1 pointer-coarse:min-h-[44px] pointer-coarse:min-w-[44px] text-description transition-colors hover:bg-surface-overlay-hover hover:text-headline active:bg-surface-active focus:outline-none"
|
|
194
590
|
onclick={handleDismiss}
|
|
195
|
-
aria-label=
|
|
591
|
+
aria-label={strings.dismiss}
|
|
592
|
+
bind:this={dismissButton}
|
|
196
593
|
>
|
|
197
594
|
<X class="size-5" aria-hidden="true" />
|
|
198
595
|
</button>
|
|
199
596
|
</div>
|
|
597
|
+
{/if}
|
|
200
598
|
</div>
|
|
201
599
|
|
|
600
|
+
{#if toast.action && !snackbar}
|
|
601
|
+
<!-- Indented to the title: past the 20px status icon and its 12px gap. -->
|
|
602
|
+
<div class="mt-3 pl-[32px]">
|
|
603
|
+
<Button
|
|
604
|
+
variant="secondary"
|
|
605
|
+
size="sm"
|
|
606
|
+
text={toast.action.label}
|
|
607
|
+
type="button"
|
|
608
|
+
data-toast-action
|
|
609
|
+
onclick={handleAction}
|
|
610
|
+
/>
|
|
611
|
+
</div>
|
|
612
|
+
{/if}
|
|
613
|
+
|
|
202
614
|
{#if hasExpandable && isExpanded}
|
|
203
615
|
<div
|
|
204
616
|
id="toaster-expand-{toast.id}"
|
|
205
617
|
class="mt-3"
|
|
206
|
-
transition:slide={{ duration: 200 }}
|
|
618
|
+
transition:slide={{ duration: motion(200) }}
|
|
207
619
|
>
|
|
208
620
|
<p class="text-sm text-description">{expandableText}</p>
|
|
209
621
|
<Button
|
|
210
622
|
variant="primary"
|
|
211
623
|
size="sm"
|
|
212
|
-
text=
|
|
624
|
+
text={strings.okay}
|
|
213
625
|
type="button"
|
|
214
626
|
class="mt-3"
|
|
215
627
|
onclick={handleOkay}
|
|
@@ -218,29 +630,35 @@
|
|
|
218
630
|
{/if}
|
|
219
631
|
</div>
|
|
220
632
|
|
|
221
|
-
{#if timerActive}
|
|
222
|
-
<div class="px-
|
|
633
|
+
{#if timerActive && showCountdown}
|
|
634
|
+
<div class="px-[16px] pb-3" transition:fade={{ duration: motion(180) }}>
|
|
223
635
|
<p class="text-xs text-description" data-toast-countdown>
|
|
224
|
-
{
|
|
636
|
+
{countdownText}
|
|
225
637
|
<button
|
|
226
638
|
type="button"
|
|
227
|
-
class="focus-ring cursor-pointer rounded-control text-link underline-offset-2 hover:underline focus:outline-none"
|
|
228
|
-
onclick={
|
|
639
|
+
class="focus-ring cursor-pointer rounded-control text-link underline-offset-2 hover:underline active:underline focus:outline-none pointer-coarse:inline-flex pointer-coarse:items-center pointer-coarse:min-h-[44px]"
|
|
640
|
+
onclick={handleStop}
|
|
229
641
|
>
|
|
230
|
-
|
|
642
|
+
{strings.stop}
|
|
231
643
|
</button>
|
|
232
644
|
</p>
|
|
233
645
|
</div>
|
|
646
|
+
{:else if timerActive}
|
|
647
|
+
<!-- The bar below shows the time left to the eye. For a screen reader
|
|
648
|
+
it is here as words: outside the live region, so it is found when the
|
|
649
|
+
toast is read and is not spoken again every second. -->
|
|
650
|
+
<p class="sr-only" data-toast-countdown>{countdownText}</p>
|
|
234
651
|
{/if}
|
|
235
652
|
|
|
236
|
-
{#if timerActive}
|
|
653
|
+
{#if timerActive && drawTimeLine}
|
|
237
654
|
<div
|
|
238
|
-
class="pointer-events-none absolute bottom-0 left-0 right-0 h-1 bg-border/70"
|
|
239
|
-
|
|
655
|
+
class="pointer-events-none absolute bottom-0 left-0 right-0 h-1 bg-border/70 forced-colors:outline-1 forced-colors:-outline-offset-1 forced-colors:outline-[color:CanvasText]"
|
|
656
|
+
data-toast-timeline
|
|
657
|
+
transition:fade={{ duration: motion(180) }}
|
|
240
658
|
aria-hidden="true"
|
|
241
659
|
>
|
|
242
660
|
<div
|
|
243
|
-
class="h-full {progressFillClass} transition-all duration-1000 ease-linear"
|
|
661
|
+
class="h-full {progressFillClass} forced-colors:bg-[color:Highlight]! transition-all duration-1000 ease-linear"
|
|
244
662
|
style="width: {(count / maxSeconds) * 100}%"
|
|
245
663
|
></div>
|
|
246
664
|
</div>
|