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,99 @@
|
|
|
1
|
-
|
|
1
|
+
import { type ToastDuration, type ToasterStrings, type ToastPauseChange } from "../util/toaster.js";
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
/**
|
|
4
|
+
* The stack of toasts, fixed to the bottom of the screen: in the corner
|
|
5
|
+
* from 640px up, across the width with 16px on each side below that.
|
|
6
|
+
*
|
|
7
|
+
* It keeps clear of what is at the bottom of a phone screen. The home
|
|
8
|
+
* indicator and a notch at the side are read from the safe-area insets
|
|
9
|
+
* (the page has to ask for them with `viewport-fit=cover`). Inside an
|
|
10
|
+
* `AppShell` the stack sits above the shell's tab bar, which the shell
|
|
11
|
+
* reports in `--app-shell-bottom-inset`. For anything else fixed to the
|
|
12
|
+
* bottom (a `BottomTabBar` on its own, a `StickyActionBar`, a
|
|
13
|
+
* `FloatingActionButton`), say how tall it is with
|
|
14
|
+
* `--toaster-bottom-offset` on the Toaster or any ancestor:
|
|
15
|
+
*
|
|
16
|
+
* ```svelte
|
|
17
|
+
* <Toaster style="--toaster-bottom-offset: 65px" />
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* A `FloatingActionButton` is 56px with 16px under it: `72px` puts the
|
|
21
|
+
* stack above it.
|
|
22
|
+
*
|
|
23
|
+
* That offset is for what is on the page. Over a modal overlay (Modal,
|
|
24
|
+
* BottomSheet, SlideUp, Drawer) the stack minds the overlay instead: it
|
|
25
|
+
* never lies on the header, which holds the close button, nor on a
|
|
26
|
+
* footer pinned to the bottom, and the offset is not added on top. It
|
|
27
|
+
* sits above the panel when there is more room there, and otherwise
|
|
28
|
+
* between the header and the footer. Where the on-screen keyboard covers
|
|
29
|
+
* the page, the stack sits above the keyboard. It is never taller than
|
|
30
|
+
* the room it has: with more toasts than fit, it scrolls. With an
|
|
31
|
+
* overlay open, Tab goes from its last control into the toasts and back,
|
|
32
|
+
* and Escape in a toast returns focus to where it came from.
|
|
33
|
+
*
|
|
34
|
+
* A toast shows what it was pushed with: its `title`, its `message`
|
|
35
|
+
* under it, or the message alone.
|
|
36
|
+
*
|
|
37
|
+
* How long it stays is said in words, per toast (`pushToast({ duration:
|
|
38
|
+
* "short" })`) or for all of them here (`defaultDuration`): `"short"` is
|
|
39
|
+
* 3 seconds, for a few words that need no reading time ("Saved");
|
|
40
|
+
* anything a person has to read is `"medium"` (7 seconds) or `"long"`
|
|
41
|
+
* (14); what they have to act on, and an error, should stay:
|
|
42
|
+
* `"persistent"`. A number is milliseconds. A toast given no duration
|
|
43
|
+
* gets medium up to 120 characters of text, long up to 240, and stays
|
|
44
|
+
* beyond that; an error and a toast with an action stay until they are
|
|
45
|
+
* dismissed.
|
|
46
|
+
*
|
|
47
|
+
* Its timer stops while a mouse is over it, a finger is on it or focus
|
|
48
|
+
* is inside it, which is what lets a timed toast be read in the reader's
|
|
49
|
+
* own time (WCAG 2.2.1). A finger that lifts leaves it at least three
|
|
50
|
+
* seconds, so holding a short toast starts it again.
|
|
51
|
+
* `data-paused` on the toast says so, and `onpausechange` is called.
|
|
52
|
+
* Every word the toaster says by itself is in `strings`:
|
|
53
|
+
*
|
|
54
|
+
* ```svelte
|
|
55
|
+
* <Toaster
|
|
56
|
+
* strings={{
|
|
57
|
+
* regionLabel: "Aviseringar",
|
|
58
|
+
* dismiss: "Stäng aviseringen",
|
|
59
|
+
* closesIn: (seconds) => `Stängs om ${seconds} sekunder.`,
|
|
60
|
+
* }}
|
|
61
|
+
* />
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "role" | "aria-label"> & {
|
|
65
|
+
/**
|
|
66
|
+
* The words the toaster says by itself, for an app in another
|
|
67
|
+
* language: the region's name, the buttons' names, the sentence about
|
|
68
|
+
* the time left. What is left out keeps its English default.
|
|
69
|
+
*/
|
|
70
|
+
strings?: Partial<ToasterStrings>;
|
|
71
|
+
/** Accessible name of the region. Wins over `strings.regionLabel`. */
|
|
72
|
+
"aria-label"?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Shows the time a toast has left as a sentence under it, with a
|
|
75
|
+
* button that stops the timer. Without it the bar along the bottom of
|
|
76
|
+
* the toast shows the time, and the sentence is there for a screen
|
|
77
|
+
* reader only.
|
|
78
|
+
*/
|
|
79
|
+
showCountdown?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* How long a toast stays when it was pushed without a `duration`, in
|
|
82
|
+
* place of `"medium"`: `"short"`, `"medium"`, `"long"`,
|
|
83
|
+
* `"persistent"` or milliseconds. An error and a toast with an action
|
|
84
|
+
* still stay until dismissed, a text of 121 to 240 characters still
|
|
85
|
+
* gets at least `"long"`, and a longer one still stays. A `duration`
|
|
86
|
+
* on the toast wins.
|
|
87
|
+
*/
|
|
88
|
+
defaultDuration?: ToastDuration;
|
|
89
|
+
/**
|
|
90
|
+
* Called when a pointer, a finger or focus starts or stops holding a
|
|
91
|
+
* toast's timer, with the toast's id. The same state is `data-paused`
|
|
92
|
+
* on the toast.
|
|
93
|
+
*/
|
|
94
|
+
onpausechange?: (detail: ToastPauseChange) => void;
|
|
2
95
|
class?: string;
|
|
3
|
-
}
|
|
96
|
+
};
|
|
4
97
|
declare const Toaster: import("svelte").Component<Props, {}, "">;
|
|
5
98
|
type Toaster = ReturnType<typeof Toaster>;
|
|
6
99
|
export default Toaster;
|
|
@@ -2,62 +2,121 @@
|
|
|
2
2
|
import { onMount } 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 {
|
|
15
|
+
DEFAULT_TOASTER_STRINGS,
|
|
16
|
+
resolveToastDuration,
|
|
17
|
+
TOAST_SECONDS_AFTER_HOLD,
|
|
18
|
+
type ToastDuration,
|
|
19
|
+
type ToasterStrings,
|
|
20
|
+
type ToastPauseChange,
|
|
21
|
+
} from "../util/toaster.js";
|
|
16
22
|
|
|
17
23
|
interface Props {
|
|
18
24
|
/** Extra classes for the host element. */
|
|
19
25
|
class?: string;
|
|
20
26
|
toast: ToastItem;
|
|
27
|
+
/** Every word the toast says by itself; the Toaster passes its own. */
|
|
28
|
+
strings?: ToasterStrings;
|
|
29
|
+
/** Shows the time left as a sentence, with a button that stops the timer. */
|
|
30
|
+
showCountdown?: boolean;
|
|
31
|
+
/** What a toast pushed without a duration gets, in place of medium; the Toaster passes its own. */
|
|
32
|
+
defaultDuration?: ToastDuration;
|
|
33
|
+
/** Called when a pointer, a finger or focus starts or stops holding the timer. */
|
|
34
|
+
onpausechange?: (detail: ToastPauseChange) => void;
|
|
21
35
|
}
|
|
22
36
|
|
|
23
|
-
let {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const maxSeconds = $derived(autoDismissSeconds(toast.duration));
|
|
37
|
+
let {
|
|
38
|
+
class: className = "",
|
|
39
|
+
toast,
|
|
40
|
+
strings = DEFAULT_TOASTER_STRINGS,
|
|
41
|
+
showCountdown = false,
|
|
42
|
+
defaultDuration,
|
|
43
|
+
onpausechange,
|
|
44
|
+
}: Props = $props();
|
|
33
45
|
|
|
34
46
|
let count = $state(0);
|
|
35
47
|
let timerActive = $state(false);
|
|
36
48
|
let isExpanded = $state(false);
|
|
37
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* What holds the timer (WCAG 2.2.1): a mouse or a stylus over the toast,
|
|
51
|
+
* a finger on it, keyboard focus inside it.
|
|
52
|
+
*/
|
|
38
53
|
let hovered = $state(false);
|
|
54
|
+
let held = $state(false);
|
|
39
55
|
let focusWithin = $state(false);
|
|
40
|
-
const paused = $derived(hovered || focusWithin);
|
|
56
|
+
const paused = $derived(hovered || held || focusWithin);
|
|
41
57
|
|
|
42
58
|
let intervalRef: ReturnType<typeof setInterval> | undefined;
|
|
43
59
|
|
|
44
|
-
const
|
|
60
|
+
const title = $derived(toast.title?.trim() ?? "");
|
|
61
|
+
const message = $derived(toast.message.trim());
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* What a toast pushed with neither a title nor a message says: a word
|
|
65
|
+
* for its type. A toast with a message says the message.
|
|
66
|
+
*/
|
|
67
|
+
const fallbackTitle = $derived(
|
|
68
|
+
toast.type === 'success'
|
|
69
|
+
? strings.successTitle
|
|
70
|
+
: toast.type === 'error'
|
|
71
|
+
? strings.errorTitle
|
|
72
|
+
: toast.type === 'warning'
|
|
73
|
+
? strings.warningTitle
|
|
74
|
+
: strings.infoTitle,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
/** The first line: the title, or the message when there is no title. */
|
|
78
|
+
const headerTitle = $derived(title || message || fallbackTitle);
|
|
79
|
+
/** The second line: the message, under a title that says something else. */
|
|
80
|
+
const secondLine = $derived(title && message && message !== title ? message : "");
|
|
45
81
|
|
|
82
|
+
/**
|
|
83
|
+
* The part that opens and closes is `detail`, when the toast was given
|
|
84
|
+
* one: the long version. The message is never folded away.
|
|
85
|
+
*/
|
|
86
|
+
const expandableText = $derived((toast.detail ?? "").trim());
|
|
46
87
|
const hasExpandable = $derived(expandableText.length > 0);
|
|
47
88
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Seconds until the toast closes by itself; `0` is never. What the app
|
|
91
|
+
* said for this toast wins. Otherwise it goes by what the toast is and by
|
|
92
|
+
* how much there is to read in it: the two lines that are shown, not the
|
|
93
|
+
* detail behind its button.
|
|
94
|
+
*/
|
|
95
|
+
const maxSeconds = $derived.by(() => {
|
|
96
|
+
const ms = resolveToastDuration(
|
|
97
|
+
{
|
|
98
|
+
duration: toast.duration,
|
|
99
|
+
type: toast.type,
|
|
100
|
+
hasAction: !!toast.action,
|
|
101
|
+
textLength: headerTitle.length + secondLine.length,
|
|
102
|
+
},
|
|
103
|
+
defaultDuration,
|
|
104
|
+
);
|
|
105
|
+
return ms <= 0 ? 0 : Math.max(1, Math.ceil(ms / 1000));
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
/** The time left as words: for the sentence, or for a screen reader to find. */
|
|
109
|
+
const countdownText = $derived(
|
|
110
|
+
paused ? strings.pausedClosesIn(count) : strings.closesIn(count),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// Said to the app as well: `data-paused` is the same thing in the markup.
|
|
114
|
+
let reportedPause = false;
|
|
115
|
+
$effect(() => {
|
|
116
|
+
const now = paused;
|
|
117
|
+
if (now === reportedPause) return;
|
|
118
|
+
reportedPause = now;
|
|
119
|
+
onpausechange?.({ id: toast.id, paused: now });
|
|
61
120
|
});
|
|
62
121
|
|
|
63
122
|
const statusIconClass = $derived(
|
|
@@ -97,7 +156,10 @@
|
|
|
97
156
|
timerActive = true;
|
|
98
157
|
|
|
99
158
|
intervalRef = setInterval(() => {
|
|
100
|
-
|
|
159
|
+
// The state, not the `paused` derived: a tick can land while the
|
|
160
|
+
// toast is animating out, and reading a derived then warns
|
|
161
|
+
// (`derived_inert`).
|
|
162
|
+
if (hovered || held || focusWithin) return;
|
|
101
163
|
count -= 1;
|
|
102
164
|
if (count <= 0) {
|
|
103
165
|
stopTimer();
|
|
@@ -117,8 +179,74 @@
|
|
|
117
179
|
toastStore.dismiss(toast.id);
|
|
118
180
|
}
|
|
119
181
|
|
|
182
|
+
/** The toast closes even if the handler throws, unless the action opted out. */
|
|
183
|
+
function handleAction() {
|
|
184
|
+
const action = toast.action;
|
|
185
|
+
if (!action) return;
|
|
186
|
+
try {
|
|
187
|
+
action.onclick();
|
|
188
|
+
} finally {
|
|
189
|
+
if (action.dismissOnClick !== false) {
|
|
190
|
+
toastStore.dismiss(toast.id);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let expandButton = $state<HTMLButtonElement>();
|
|
196
|
+
let dismissButton = $state<HTMLButtonElement>();
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Okay closes the details and goes with them. Focus moves to the button
|
|
200
|
+
* that opens them again, or it would fall to the page and a keyboard
|
|
201
|
+
* user would be at the top of it.
|
|
202
|
+
*/
|
|
120
203
|
function handleOkay() {
|
|
121
204
|
isExpanded = false;
|
|
205
|
+
expandButton?.focus();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** The stop button goes with the sentence it is in: focus stays in the toast. */
|
|
209
|
+
function handleStop(event: MouseEvent) {
|
|
210
|
+
const held = event.currentTarget === document.activeElement;
|
|
211
|
+
stopTimer();
|
|
212
|
+
if (held) dismissButton?.focus();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* The kind of pointer last seen on the toast. After a tap a browser makes
|
|
217
|
+
* up mouse events for it, `mouseenter` among them and never a
|
|
218
|
+
* `mouseleave`: taken for a mouse, a tap held the timer for good.
|
|
219
|
+
*/
|
|
220
|
+
let lastPointer: string | undefined;
|
|
221
|
+
|
|
222
|
+
function handlePointerEnter(event: PointerEvent) {
|
|
223
|
+
lastPointer = event.pointerType;
|
|
224
|
+
// A mouse, or a stylus in range. A finger is over the toast only while it is on it.
|
|
225
|
+
if (event.pointerType !== "touch") hovered = true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function handlePointerLeave(event: PointerEvent) {
|
|
229
|
+
if (event.pointerType !== "touch") hovered = false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function handleMouseEnter() {
|
|
233
|
+
if (lastPointer !== "touch") hovered = true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function handlePointerDown(event: PointerEvent) {
|
|
237
|
+
lastPointer = event.pointerType;
|
|
238
|
+
if (event.pointerType === "touch") held = true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* The finger lifts, or the browser takes the touch for a scroll. The
|
|
243
|
+
* timer runs again, with time left to do something about the toast: it
|
|
244
|
+
* was held because someone was reading it.
|
|
245
|
+
*/
|
|
246
|
+
function handlePointerRelease(event: PointerEvent) {
|
|
247
|
+
if (event.pointerType !== "touch" || !held) return;
|
|
248
|
+
held = false;
|
|
249
|
+
if (timerActive) count = Math.max(count, Math.min(TOAST_SECONDS_AFTER_HOLD, maxSeconds));
|
|
122
250
|
}
|
|
123
251
|
|
|
124
252
|
function handleFocusOut(event: FocusEvent) {
|
|
@@ -128,56 +256,94 @@
|
|
|
128
256
|
}
|
|
129
257
|
}
|
|
130
258
|
|
|
131
|
-
|
|
132
|
-
const
|
|
259
|
+
/** No movement where motion is unwanted: the toast appears and goes at once. */
|
|
260
|
+
const reducedMotion =
|
|
261
|
+
typeof window !== 'undefined' &&
|
|
262
|
+
typeof window.matchMedia === 'function' &&
|
|
263
|
+
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
264
|
+
const motion = (duration: number) => (reducedMotion ? 0 : duration);
|
|
265
|
+
|
|
266
|
+
const toastEnter = { y: 18, duration: motion(260), opacity: 0, easing: cubicOut };
|
|
267
|
+
const toastLeave = { y: 14, duration: motion(300), opacity: 0, easing: cubicOut };
|
|
133
268
|
</script>
|
|
134
269
|
|
|
135
270
|
<div
|
|
136
|
-
class={cn("pointer-events-auto relative w-full min-w-[18rem] overflow-hidden rounded-overlay border border-border-overlay bg-surface-overlay shadow-lg", className)}
|
|
271
|
+
class={cn("pointer-events-auto relative w-full min-w-[min(18rem,100%)] shrink-0 overflow-hidden rounded-overlay border border-border-overlay bg-surface-overlay shadow-lg", className)}
|
|
137
272
|
in:fly={toastEnter}
|
|
138
273
|
out:fly={toastLeave}
|
|
139
|
-
|
|
274
|
+
onpointerenter={handlePointerEnter}
|
|
275
|
+
onpointerleave={handlePointerLeave}
|
|
276
|
+
onpointerdown={handlePointerDown}
|
|
277
|
+
onpointerup={handlePointerRelease}
|
|
278
|
+
onpointercancel={handlePointerRelease}
|
|
279
|
+
onmouseenter={handleMouseEnter}
|
|
140
280
|
onmouseleave={() => (hovered = false)}
|
|
141
281
|
onfocusin={() => (focusWithin = true)}
|
|
142
282
|
onfocusout={handleFocusOut}
|
|
143
283
|
role="group"
|
|
144
284
|
aria-label={headerTitle}
|
|
285
|
+
data-toast-id={toast.id}
|
|
145
286
|
data-paused={paused}
|
|
146
287
|
>
|
|
147
|
-
|
|
148
|
-
|
|
288
|
+
<!-- Padding and gaps in px: at 200% text on a phone, room that grew with
|
|
289
|
+
the text left the text itself a column too narrow for one long word. -->
|
|
290
|
+
<div class="p-[16px]">
|
|
291
|
+
<!-- The title keeps 8rem at least, icon included. When the toast has
|
|
292
|
+
less beside its buttons (320px with enlarged text), the buttons go to a
|
|
293
|
+
line of their own under it, at the end. -->
|
|
294
|
+
<div class="flex flex-wrap items-start gap-x-[12px] gap-y-[4px]">
|
|
149
295
|
<!-- Live region holds only title + message, so the countdown and buttons are never re-announced. -->
|
|
150
296
|
<div
|
|
151
|
-
class="flex min-w-0 flex-
|
|
297
|
+
class="flex min-w-0 flex-[1_1_8rem] items-start gap-[12px]"
|
|
152
298
|
role={toast.type === 'error' ? 'alert' : 'status'}
|
|
153
299
|
aria-atomic="true"
|
|
154
300
|
>
|
|
155
|
-
|
|
301
|
+
<!-- 20px at every text size, as the padding and the gap beside it
|
|
302
|
+
are px: an icon that grew with the text took the room the text
|
|
303
|
+
needed. Lowered so it stays at the middle of the first line. -->
|
|
304
|
+
<div class="shrink-0 mt-[calc((1.5rem_-_20px)_/_2)]">
|
|
156
305
|
{#if toast.type === 'success'}
|
|
157
|
-
<CheckCircle class="size-
|
|
306
|
+
<CheckCircle class="size-[20px] {statusIconClass}" aria-hidden="true" />
|
|
158
307
|
{:else if toast.type === 'error'}
|
|
159
|
-
<AlertCircle class="size-
|
|
308
|
+
<AlertCircle class="size-[20px] {statusIconClass}" aria-hidden="true" />
|
|
160
309
|
{:else if toast.type === 'warning'}
|
|
161
|
-
<AlertTriangle class="size-
|
|
310
|
+
<AlertTriangle class="size-[20px] {statusIconClass}" aria-hidden="true" />
|
|
162
311
|
{:else}
|
|
163
|
-
<Info class="size-
|
|
312
|
+
<Info class="size-[20px] {statusIconClass}" aria-hidden="true" />
|
|
164
313
|
{/if}
|
|
165
314
|
</div>
|
|
166
|
-
<
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
315
|
+
<div class="min-w-0 flex-1">
|
|
316
|
+
<!-- A heading when the app gave the toast a title (or nothing
|
|
317
|
+
at all); a message on its own is a sentence, not a heading. -->
|
|
318
|
+
{#if title || !message}
|
|
319
|
+
<h4 class="text-base font-semibold text-headline [overflow-wrap:anywhere]">
|
|
320
|
+
{headerTitle}
|
|
321
|
+
</h4>
|
|
322
|
+
{:else}
|
|
323
|
+
<p class="text-base font-medium text-headline [overflow-wrap:anywhere]" data-toast-message>
|
|
324
|
+
{headerTitle}
|
|
325
|
+
</p>
|
|
326
|
+
{/if}
|
|
327
|
+
{#if secondLine}
|
|
328
|
+
<p class="mt-1 text-sm text-description [overflow-wrap:anywhere]" data-toast-message>
|
|
329
|
+
{secondLine}
|
|
330
|
+
</p>
|
|
331
|
+
{/if}
|
|
332
|
+
{#if toast.action}
|
|
333
|
+
<!-- The button sits outside the live region, so this is how a screen reader user hears that there is one. -->
|
|
334
|
+
<span class="sr-only">{strings.actionAvailable(toast.action.label)}</span>
|
|
170
335
|
{/if}
|
|
171
|
-
</h4>
|
|
172
336
|
</div>
|
|
173
|
-
|
|
337
|
+
</div>
|
|
338
|
+
<div class="ms-auto flex shrink-0 items-center gap-1">
|
|
174
339
|
{#if hasExpandable}
|
|
175
340
|
<button
|
|
176
341
|
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"
|
|
342
|
+
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
343
|
aria-expanded={isExpanded}
|
|
179
|
-
aria-controls=
|
|
180
|
-
aria-label={isExpanded ?
|
|
344
|
+
aria-controls={isExpanded ? `toaster-expand-${toast.id}` : undefined}
|
|
345
|
+
aria-label={isExpanded ? strings.collapse : strings.expand}
|
|
346
|
+
bind:this={expandButton}
|
|
181
347
|
onclick={() => (isExpanded = !isExpanded)}
|
|
182
348
|
>
|
|
183
349
|
<ChevronDown
|
|
@@ -190,26 +356,41 @@
|
|
|
190
356
|
{/if}
|
|
191
357
|
<button
|
|
192
358
|
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"
|
|
359
|
+
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
360
|
onclick={handleDismiss}
|
|
195
|
-
aria-label=
|
|
361
|
+
aria-label={strings.dismiss}
|
|
362
|
+
bind:this={dismissButton}
|
|
196
363
|
>
|
|
197
364
|
<X class="size-5" aria-hidden="true" />
|
|
198
365
|
</button>
|
|
199
366
|
</div>
|
|
200
367
|
</div>
|
|
201
368
|
|
|
369
|
+
{#if toast.action}
|
|
370
|
+
<!-- Indented to the title: past the 20px status icon and its 12px gap. -->
|
|
371
|
+
<div class="mt-3 pl-[32px]">
|
|
372
|
+
<Button
|
|
373
|
+
variant="secondary"
|
|
374
|
+
size="sm"
|
|
375
|
+
text={toast.action.label}
|
|
376
|
+
type="button"
|
|
377
|
+
data-toast-action
|
|
378
|
+
onclick={handleAction}
|
|
379
|
+
/>
|
|
380
|
+
</div>
|
|
381
|
+
{/if}
|
|
382
|
+
|
|
202
383
|
{#if hasExpandable && isExpanded}
|
|
203
384
|
<div
|
|
204
385
|
id="toaster-expand-{toast.id}"
|
|
205
386
|
class="mt-3"
|
|
206
|
-
transition:slide={{ duration: 200 }}
|
|
387
|
+
transition:slide={{ duration: motion(200) }}
|
|
207
388
|
>
|
|
208
389
|
<p class="text-sm text-description">{expandableText}</p>
|
|
209
390
|
<Button
|
|
210
391
|
variant="primary"
|
|
211
392
|
size="sm"
|
|
212
|
-
text=
|
|
393
|
+
text={strings.okay}
|
|
213
394
|
type="button"
|
|
214
395
|
class="mt-3"
|
|
215
396
|
onclick={handleOkay}
|
|
@@ -218,25 +399,30 @@
|
|
|
218
399
|
{/if}
|
|
219
400
|
</div>
|
|
220
401
|
|
|
221
|
-
{#if timerActive}
|
|
222
|
-
<div class="px-
|
|
402
|
+
{#if timerActive && showCountdown}
|
|
403
|
+
<div class="px-[16px] pb-3" transition:fade={{ duration: motion(180) }}>
|
|
223
404
|
<p class="text-xs text-description" data-toast-countdown>
|
|
224
|
-
{
|
|
405
|
+
{countdownText}
|
|
225
406
|
<button
|
|
226
407
|
type="button"
|
|
227
|
-
class="focus-ring cursor-pointer rounded-control text-link underline-offset-2 hover:underline focus:outline-none"
|
|
228
|
-
onclick={
|
|
408
|
+
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]"
|
|
409
|
+
onclick={handleStop}
|
|
229
410
|
>
|
|
230
|
-
|
|
411
|
+
{strings.stop}
|
|
231
412
|
</button>
|
|
232
413
|
</p>
|
|
233
414
|
</div>
|
|
415
|
+
{:else if timerActive}
|
|
416
|
+
<!-- The bar below shows the time left to the eye. For a screen reader
|
|
417
|
+
it is here as words: outside the live region, so it is found when the
|
|
418
|
+
toast is read and is not spoken again every second. -->
|
|
419
|
+
<p class="sr-only" data-toast-countdown>{countdownText}</p>
|
|
234
420
|
{/if}
|
|
235
421
|
|
|
236
422
|
{#if timerActive}
|
|
237
423
|
<div
|
|
238
424
|
class="pointer-events-none absolute bottom-0 left-0 right-0 h-1 bg-border/70"
|
|
239
|
-
transition:fade={{ duration: 180 }}
|
|
425
|
+
transition:fade={{ duration: motion(180) }}
|
|
240
426
|
aria-hidden="true"
|
|
241
427
|
>
|
|
242
428
|
<div
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { type ToastItem } from './toast-store.js';
|
|
2
|
+
import { type ToastDuration, type ToasterStrings, type ToastPauseChange } from "../util/toaster.js";
|
|
2
3
|
interface Props {
|
|
3
4
|
/** Extra classes for the host element. */
|
|
4
5
|
class?: string;
|
|
5
6
|
toast: ToastItem;
|
|
7
|
+
/** Every word the toast says by itself; the Toaster passes its own. */
|
|
8
|
+
strings?: ToasterStrings;
|
|
9
|
+
/** Shows the time left as a sentence, with a button that stops the timer. */
|
|
10
|
+
showCountdown?: boolean;
|
|
11
|
+
/** What a toast pushed without a duration gets, in place of medium; the Toaster passes its own. */
|
|
12
|
+
defaultDuration?: ToastDuration;
|
|
13
|
+
/** Called when a pointer, a finger or focus starts or stops holding the timer. */
|
|
14
|
+
onpausechange?: (detail: ToastPauseChange) => void;
|
|
6
15
|
}
|
|
7
16
|
declare const ToasterToast: import("svelte").Component<Props, {}, "">;
|
|
8
17
|
type ToasterToast = ReturnType<typeof ToasterToast>;
|