zabi-components 7.0.2 → 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 +287 -11
- package/THEME.md +149 -332
- package/THEMING.md +1329 -163
- package/dist/atoms/ActionPanel.svelte +24 -9
- package/dist/atoms/ActionPanel.svelte.d.ts +2 -0
- package/dist/atoms/Avatar.svelte +104 -0
- package/dist/atoms/Avatar.svelte.d.ts +34 -0
- package/dist/atoms/Badge.svelte +124 -58
- package/dist/atoms/Badge.svelte.d.ts +17 -5
- package/dist/atoms/Button.svelte +212 -62
- package/dist/atoms/Button.svelte.d.ts +33 -2
- package/dist/atoms/Card.svelte +31 -28
- package/dist/atoms/Card.svelte.d.ts +5 -2
- package/dist/atoms/CardContent.svelte +10 -2
- package/dist/atoms/CardContent.svelte.d.ts +2 -0
- package/dist/atoms/CardFooter.svelte +10 -2
- package/dist/atoms/CardFooter.svelte.d.ts +2 -0
- package/dist/atoms/CardHeader.svelte +26 -6
- package/dist/atoms/CardHeader.svelte.d.ts +2 -0
- package/dist/atoms/Checkbox.svelte +52 -21
- package/dist/atoms/Checkbox.svelte.d.ts +6 -2
- package/dist/atoms/CodeBlock.svelte +35 -9
- package/dist/atoms/CodeBlock.svelte.d.ts +13 -3
- package/dist/atoms/ColorPicker.svelte +272 -52
- package/dist/atoms/ColorPicker.svelte.d.ts +6 -1
- package/dist/atoms/Container.svelte +17 -10
- package/dist/atoms/DateField.svelte +107 -0
- package/dist/atoms/DateField.svelte.d.ts +53 -0
- package/dist/atoms/Divider.svelte +4 -4
- package/dist/atoms/FeatureCard.svelte +4 -3
- 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 +93 -32
- package/dist/atoms/Heading.svelte.d.ts +24 -4
- package/dist/atoms/IconButton.svelte +242 -53
- package/dist/atoms/IconButton.svelte.d.ts +49 -4
- package/dist/atoms/Input.svelte +267 -74
- package/dist/atoms/Input.svelte.d.ts +32 -2
- package/dist/atoms/List.svelte +13 -2
- package/dist/atoms/List.svelte.d.ts +2 -0
- package/dist/atoms/ListItem.svelte +44 -11
- package/dist/atoms/ListItem.svelte.d.ts +5 -0
- package/dist/atoms/ListItemLeading.svelte +4 -2
- package/dist/atoms/ListItemLeading.svelte.d.ts +1 -1
- package/dist/atoms/OptimizedImage.svelte +11 -2
- package/dist/atoms/OptimizedImage.svelte.d.ts +2 -0
- package/dist/atoms/Progress.svelte +31 -9
- package/dist/atoms/Progress.svelte.d.ts +6 -0
- package/dist/atoms/Radio.svelte +23 -3
- package/dist/atoms/Radio.svelte.d.ts +6 -2
- package/dist/atoms/Rating.svelte +494 -0
- package/dist/atoms/Rating.svelte.d.ts +56 -0
- package/dist/atoms/Select.svelte +475 -135
- package/dist/atoms/Select.svelte.d.ts +52 -2
- package/dist/atoms/SelectionControl.svelte +99 -18
- package/dist/atoms/SelectionControl.svelte.d.ts +6 -3
- package/dist/atoms/Skeleton.svelte +5 -3
- 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 +152 -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 +56 -15
- package/dist/atoms/Text.svelte.d.ts +13 -4
- package/dist/atoms/Textarea.svelte +57 -45
- package/dist/atoms/Textarea.svelte.d.ts +14 -2
- package/dist/atoms/ThemeToggle.svelte +238 -56
- package/dist/atoms/ThemeToggle.svelte.d.ts +28 -3
- package/dist/atoms/TimeField.svelte +108 -0
- package/dist/atoms/TimeField.svelte.d.ts +54 -0
- package/dist/atoms/Toast.svelte +25 -10
- package/dist/atoms/Toast.svelte.d.ts +2 -0
- package/dist/atoms/Toggle.svelte +60 -19
- package/dist/atoms/Toggle.svelte.d.ts +18 -2
- package/dist/atoms/Tooltip.svelte +583 -40
- package/dist/atoms/Tooltip.svelte.d.ts +26 -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 +83 -3
- 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 +6 -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/cn.d.ts +8 -0
- package/dist/components/util/cn.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/ssr-safe.d.ts +4 -4
- package/dist/components/util/ssr-safe.d.ts.map +1 -1
- 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 +83 -3
- package/dist/index.js +60 -8
- package/dist/molecules/Alert.svelte +73 -66
- package/dist/molecules/Alert.svelte.d.ts +9 -1
- 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 +25 -5
- package/dist/molecules/ComponentDemo.svelte.d.ts +5 -0
- package/dist/molecules/ConfirmDialog.svelte +253 -0
- package/dist/molecules/ConfirmDialog.svelte.d.ts +55 -0
- package/dist/molecules/ContactForm.svelte +46 -19
- package/dist/molecules/ContactForm.svelte.d.ts +10 -0
- package/dist/molecules/Drawer.svelte +394 -0
- package/dist/molecules/Drawer.svelte.d.ts +55 -0
- package/dist/molecules/Dropdown.svelte +540 -50
- package/dist/molecules/Dropdown.svelte.d.ts +49 -6
- package/dist/molecules/DropdownItem.svelte +122 -0
- package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
- package/dist/molecules/EmptyState.svelte +56 -10
- package/dist/molecules/EmptyState.svelte.d.ts +8 -0
- package/dist/molecules/Form.svelte +25 -14
- package/dist/molecules/Form.svelte.d.ts +13 -5
- package/dist/molecules/FormField.svelte +11 -2
- package/dist/molecules/FormField.svelte.d.ts +2 -0
- package/dist/molecules/Header.svelte +13 -3
- package/dist/molecules/Header.svelte.d.ts +2 -0
- package/dist/molecules/ImageUpload.svelte +422 -70
- package/dist/molecules/ImageUpload.svelte.d.ts +61 -5
- package/dist/molecules/MediaGrid.svelte +527 -0
- package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
- package/dist/molecules/Modal.svelte +340 -45
- package/dist/molecules/Modal.svelte.d.ts +59 -7
- package/dist/molecules/NavigationMenu.svelte +15 -3
- package/dist/molecules/NavigationMenu.svelte.d.ts +4 -0
- package/dist/molecules/NavigationMenuContent.svelte +91 -36
- package/dist/molecules/NavigationMenuContent.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuItem.svelte +10 -3
- package/dist/molecules/NavigationMenuItem.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuLink.svelte +10 -2
- package/dist/molecules/NavigationMenuLink.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuList.svelte +19 -4
- package/dist/molecules/NavigationMenuList.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuTrigger.svelte +16 -3
- package/dist/molecules/NavigationMenuTrigger.svelte.d.ts +2 -0
- package/dist/molecules/Page.svelte +38 -2
- package/dist/molecules/Page.svelte.d.ts +13 -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 +26 -11
- package/dist/molecules/PropsTable.svelte.d.ts +5 -0
- package/dist/molecules/PullToRefresh.svelte +263 -0
- package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
- package/dist/molecules/RadioGroup.svelte +57 -15
- package/dist/molecules/RadioGroup.svelte.d.ts +16 -3
- package/dist/molecules/Section.svelte +13 -5
- package/dist/molecules/Section.svelte.d.ts +3 -1
- package/dist/molecules/SegmentedControl.svelte +323 -0
- package/dist/molecules/SegmentedControl.svelte.d.ts +34 -0
- package/dist/molecules/SidebarBrandHeader.svelte +24 -5
- package/dist/molecules/SidebarBrandHeader.svelte.d.ts +5 -0
- package/dist/molecules/SidebarFooter.svelte +39 -8
- package/dist/molecules/SidebarFooter.svelte.d.ts +5 -0
- package/dist/molecules/SidebarNavSection.svelte +12 -4
- package/dist/molecules/SidebarNavSection.svelte.d.ts +2 -0
- package/dist/molecules/SlideUp.svelte +253 -30
- package/dist/molecules/SlideUp.svelte.d.ts +31 -4
- 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 +254 -24
- package/dist/molecules/Tabs.svelte.d.ts +15 -4
- package/dist/molecules/Toaster.svelte +352 -6
- package/dist/molecules/Toaster.svelte.d.ts +95 -2
- package/dist/molecules/ToasterToast.svelte +279 -62
- package/dist/molecules/ToasterToast.svelte.d.ts +11 -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 +111 -12
- package/dist/organisms/SidebarAccountPanel.svelte.d.ts +21 -0
- package/dist/organisms/SidebarNavigation.svelte +193 -69
- package/dist/organisms/SidebarNavigation.svelte.d.ts +31 -1
- package/dist/organisms/SidebarPanel.svelte +45 -16
- package/dist/organisms/SidebarPanel.svelte.d.ts +2 -0
- package/dist/organisms/SidebarShell.svelte +263 -0
- package/dist/organisms/SidebarShell.svelte.d.ts +106 -0
- package/dist/organisms/TopNavbar.svelte +329 -33
- package/dist/organisms/TopNavbar.svelte.d.ts +28 -0
- package/dist/organisms/index.d.ts +6 -0
- package/dist/organisms/index.js +4 -0
- package/dist/routes/lib/variant-utils.d.ts +26 -0
- package/dist/routes/lib/{variant-utils.ts → variant-utils.js} +15 -46
- package/dist/types/components.d.ts +4 -0
- package/dist/types/components.d.ts.map +1 -1
- package/dist/types/events.js +1 -0
- package/dist/types/index.d.ts +605 -25
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +3 -0
- 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/cn.d.ts +7 -0
- package/dist/util/cn.js +48 -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/ssr-safe.d.ts +4 -4
- 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 +574 -225
- package/dist/zabi-components-theme-dark.css +574 -225
- package/dist/zabi-components-theme-only.css +1680 -317
- package/dist/zabi-components-theme.css +1680 -317
- package/dist/zabi-components.css +4473 -1158
- package/docs/lucide-icons.md +41 -16
- package/docs/theme-imports.md +81 -11
- package/package.json +51 -29
- package/dist/lib/layout-width-tokens.d.ts +0 -17
- package/dist/lib/layout-width-tokens.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Button.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Button.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Input.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Input.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/List.d.ts +0 -3
- package/dist/lib/showcase/component-docs/List.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Modal.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Modal.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Radio.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Radio.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts +0 -3
- package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Skeleton.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Skeleton.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/_shared.d.ts +0 -12
- package/dist/lib/showcase/component-docs/_shared.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/index.d.ts +0 -4
- package/dist/lib/showcase/component-docs/index.d.ts.map +0 -1
- package/dist/lib/showcase/components-catalog.d.ts +0 -3
- package/dist/lib/showcase/components-catalog.d.ts.map +0 -1
- package/dist/lib/showcase/components-showcase-constants.d.ts +0 -89
- package/dist/lib/showcase/components-showcase-constants.d.ts.map +0 -1
- package/dist/lib/showcase/docs-sidebar-helpers.d.ts +0 -15
- package/dist/lib/showcase/docs-sidebar-helpers.d.ts.map +0 -1
- package/dist/routes/lib/focus-utils.d.ts +0 -12
- package/dist/routes/lib/focus-utils.ts +0 -110
- package/dist/routes/lib/ssr-safe.ts +0 -58
- package/scripts/fix-lucide-svelte-icon-dts.js +0 -35
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
import { findScrollParent } from "../util/app-shell.js";
|
|
5
|
+
import { cn } from "../util/cn.js";
|
|
6
|
+
import { watchKeyboardInset } from "../util/keyboard-inset.js";
|
|
7
|
+
import { reserveScrollPaddingBottom } from "../util/scroll-padding.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Keeps a form's main button on screen: at the bottom of whatever
|
|
11
|
+
* scrolls, and above the on-screen keyboard when that is up.
|
|
12
|
+
*
|
|
13
|
+
* Put it after the form's last field. It is sticky, not fixed, so it
|
|
14
|
+
* keeps its place in the flow and the last field can always be scrolled
|
|
15
|
+
* clear of it. A form shorter than the screen ends where its fields end,
|
|
16
|
+
* and so would the bar: make the form a column as tall as the screen
|
|
17
|
+
* (`flex min-h-full flex-col`) and the bar takes the bottom of it.
|
|
18
|
+
*
|
|
19
|
+
* ```svelte
|
|
20
|
+
* <form class="flex min-h-full flex-col" onsubmit={save}>
|
|
21
|
+
* <div class="space-y-4 p-4">
|
|
22
|
+
* <Input label="Name" bind:value={name} />
|
|
23
|
+
* …
|
|
24
|
+
* </div>
|
|
25
|
+
* <StickyActionBar>
|
|
26
|
+
* <Button type="submit" size="lg" fullWidth>Save visit</Button>
|
|
27
|
+
* </StickyActionBar>
|
|
28
|
+
* </form>
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* Where the keyboard covers the bottom of the page instead of shrinking
|
|
32
|
+
* it (iOS Safari, Chrome on Android), the bar rises above it. The covered
|
|
33
|
+
* part is read from `window.visualViewport`, and the bar is raised by as
|
|
34
|
+
* much of its own scrolling box as the keyboard covers: the keyboard's
|
|
35
|
+
* full height in a box that ends at the bottom of the screen, less in
|
|
36
|
+
* one that ends above a tab bar or inside a sheet. It never leaves its
|
|
37
|
+
* box. It takes a bottom margin of the same size, so the content gains
|
|
38
|
+
* that much room to scroll, which a short form needs to bring its last
|
|
39
|
+
* field above the keyboard. Where the page shrinks by itself, nothing is
|
|
40
|
+
* done.
|
|
41
|
+
*
|
|
42
|
+
* While it is mounted the bar also sets `scroll-padding-bottom` on its
|
|
43
|
+
* scrolling ancestor (or the page) to its own height plus that rise, so a
|
|
44
|
+
* field that takes focus is brought into view above the bar, not under
|
|
45
|
+
* it. Use one bar per scrolling box: two would both stick to the same
|
|
46
|
+
* edge, one on top of the other.
|
|
47
|
+
*
|
|
48
|
+
* In an `AppShell`, a screen with a form is a task of its own: leave the
|
|
49
|
+
* tab bar out (no `footer`) and let this bar be the bottom of the screen.
|
|
50
|
+
* With a tab bar it sits directly above it.
|
|
51
|
+
*
|
|
52
|
+
* `UnsavedChangesBar` is the other bar at the bottom of a form. That one
|
|
53
|
+
* appears only while there is something to save, announces itself, and
|
|
54
|
+
* brings its own Save and Discard. This one is always there, holds the
|
|
55
|
+
* buttons you give it, and follows the keyboard.
|
|
56
|
+
*/
|
|
57
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "style"> & {
|
|
58
|
+
/**
|
|
59
|
+
* Accessible name. With it the bar is a group, so a screen reader
|
|
60
|
+
* says what the buttons belong to; without it, it is a plain box.
|
|
61
|
+
*/
|
|
62
|
+
label?: string;
|
|
63
|
+
class?: string;
|
|
64
|
+
/** Added after the offset and margin the bar sets while the keyboard is up. */
|
|
65
|
+
style?: string;
|
|
66
|
+
/** The main action, and at most one or two beside it. */
|
|
67
|
+
children?: Snippet;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
let { label, class: className = "", style = "", children, ...restProps }: Props = $props();
|
|
71
|
+
|
|
72
|
+
let host: HTMLDivElement | undefined = $state();
|
|
73
|
+
/** Height of the keyboard over the page, in px. 0 on the server and where the page shrinks by itself. */
|
|
74
|
+
let keyboardInset = $state(0);
|
|
75
|
+
/**
|
|
76
|
+
* How far the bar is raised from the bottom of its scrolling box, in px.
|
|
77
|
+
* Not the keyboard's height: only the part of this box the keyboard covers.
|
|
78
|
+
*/
|
|
79
|
+
let lift = $state(0);
|
|
80
|
+
|
|
81
|
+
$effect(() =>
|
|
82
|
+
watchKeyboardInset((inset) => {
|
|
83
|
+
keyboardInset = inset;
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The keyboard covers the screen from the bottom of the visual viewport
|
|
89
|
+
* down. The bar has to rise by as much of its own scrolling box as lies
|
|
90
|
+
* under that line: all of the keyboard's height when the box ends at the
|
|
91
|
+
* bottom of the screen, less when it ends higher (above a tab bar, in a
|
|
92
|
+
* sheet), nothing when it ends above the keyboard. It never leaves the
|
|
93
|
+
* box: a box wholly under the keyboard keeps its bar at its own top.
|
|
94
|
+
*/
|
|
95
|
+
$effect(() => {
|
|
96
|
+
const bar = host;
|
|
97
|
+
if (!bar || keyboardInset === 0) {
|
|
98
|
+
lift = 0;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const container = findScrollParent(bar);
|
|
102
|
+
const measure = () => {
|
|
103
|
+
const viewport = window.visualViewport;
|
|
104
|
+
if (!viewport) return;
|
|
105
|
+
const keyboardTop = viewport.offsetTop + viewport.height;
|
|
106
|
+
// Where the bar sticks: the box's content edge, inside its
|
|
107
|
+
// border and its own bottom padding.
|
|
108
|
+
const padding = container
|
|
109
|
+
? parseFloat(getComputedStyle(container).paddingBottom) || 0
|
|
110
|
+
: 0;
|
|
111
|
+
const boxBottom = container
|
|
112
|
+
? container.getBoundingClientRect().top +
|
|
113
|
+
container.clientTop +
|
|
114
|
+
container.clientHeight -
|
|
115
|
+
padding
|
|
116
|
+
: document.documentElement.clientHeight;
|
|
117
|
+
const boxHeight =
|
|
118
|
+
(container ? container.clientHeight : document.documentElement.clientHeight) -
|
|
119
|
+
padding;
|
|
120
|
+
const room = Math.max(0, boxHeight - bar.getBoundingClientRect().height);
|
|
121
|
+
lift = Math.round(Math.min(Math.max(boxBottom - keyboardTop, 0), room));
|
|
122
|
+
};
|
|
123
|
+
measure();
|
|
124
|
+
// The box moves on the screen when the page scrolls or is resized.
|
|
125
|
+
// Captured, so a scroll of any ancestor is heard.
|
|
126
|
+
window.addEventListener("scroll", measure, { capture: true, passive: true });
|
|
127
|
+
window.addEventListener("resize", measure);
|
|
128
|
+
window.visualViewport?.addEventListener("resize", measure);
|
|
129
|
+
window.visualViewport?.addEventListener("scroll", measure);
|
|
130
|
+
// An overlay moves itself up over the keyboard, and its content with
|
|
131
|
+
// it: the box then ends above the keyboard, and the bar is not raised
|
|
132
|
+
// a second time. That move is heard here, whichever came first.
|
|
133
|
+
const resized =
|
|
134
|
+
container && typeof ResizeObserver === "function"
|
|
135
|
+
? new ResizeObserver(measure)
|
|
136
|
+
: undefined;
|
|
137
|
+
if (container) resized?.observe(container);
|
|
138
|
+
return () => {
|
|
139
|
+
resized?.disconnect();
|
|
140
|
+
window.removeEventListener("scroll", measure, true);
|
|
141
|
+
window.removeEventListener("resize", measure);
|
|
142
|
+
window.visualViewport?.removeEventListener("resize", measure);
|
|
143
|
+
window.visualViewport?.removeEventListener("scroll", measure);
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The browser scrolls a focused field into view, to the edge of the
|
|
149
|
+
* scroll container: under this bar, since it lies over that edge.
|
|
150
|
+
*/
|
|
151
|
+
$effect(() => {
|
|
152
|
+
const bar = host;
|
|
153
|
+
if (!bar) return;
|
|
154
|
+
const raised = lift;
|
|
155
|
+
const container = findScrollParent(bar) ?? document.documentElement;
|
|
156
|
+
const reservation = reserveScrollPaddingBottom(container);
|
|
157
|
+
const reserve = () => {
|
|
158
|
+
reservation.set(Math.ceil(bar.getBoundingClientRect().height) + raised);
|
|
159
|
+
};
|
|
160
|
+
reserve();
|
|
161
|
+
const observer =
|
|
162
|
+
typeof ResizeObserver === "function" ? new ResizeObserver(reserve) : undefined;
|
|
163
|
+
observer?.observe(bar);
|
|
164
|
+
return () => {
|
|
165
|
+
observer?.disconnect();
|
|
166
|
+
reservation.release();
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const keyboardOpen = $derived(keyboardInset > 0);
|
|
171
|
+
</script>
|
|
172
|
+
|
|
173
|
+
<!-- Nothing here takes focus by itself, and the bar never moves focus. -->
|
|
174
|
+
<div
|
|
175
|
+
bind:this={host}
|
|
176
|
+
role={label ? "group" : undefined}
|
|
177
|
+
aria-label={label}
|
|
178
|
+
data-keyboard-open={keyboardOpen ? "true" : undefined}
|
|
179
|
+
class={cn(
|
|
180
|
+
// `mt-auto`: in a column taller than its fields, the bar goes to the bottom.
|
|
181
|
+
"sticky bottom-0 z-sticky mt-auto flex flex-wrap items-center justify-end gap-[8px] border-t border-border-weak bg-surface-elevated pt-3",
|
|
182
|
+
"pl-[max(1rem,env(safe-area-inset-left))] pr-[max(1rem,env(safe-area-inset-right))]",
|
|
183
|
+
// The home indicator is under the keyboard while that is up.
|
|
184
|
+
keyboardOpen ? "pb-3" : "pb-[max(0.75rem,env(safe-area-inset-bottom))]",
|
|
185
|
+
className,
|
|
186
|
+
)}
|
|
187
|
+
style="{lift > 0 ? `bottom: ${lift}px; margin-bottom: ${lift}px;` : ''} {style}"
|
|
188
|
+
{...restProps}
|
|
189
|
+
>
|
|
190
|
+
{@render children?.()}
|
|
191
|
+
</div>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
/**
|
|
4
|
+
* Keeps a form's main button on screen: at the bottom of whatever
|
|
5
|
+
* scrolls, and above the on-screen keyboard when that is up.
|
|
6
|
+
*
|
|
7
|
+
* Put it after the form's last field. It is sticky, not fixed, so it
|
|
8
|
+
* keeps its place in the flow and the last field can always be scrolled
|
|
9
|
+
* clear of it. A form shorter than the screen ends where its fields end,
|
|
10
|
+
* and so would the bar: make the form a column as tall as the screen
|
|
11
|
+
* (`flex min-h-full flex-col`) and the bar takes the bottom of it.
|
|
12
|
+
*
|
|
13
|
+
* ```svelte
|
|
14
|
+
* <form class="flex min-h-full flex-col" onsubmit={save}>
|
|
15
|
+
* <div class="space-y-4 p-4">
|
|
16
|
+
* <Input label="Name" bind:value={name} />
|
|
17
|
+
* …
|
|
18
|
+
* </div>
|
|
19
|
+
* <StickyActionBar>
|
|
20
|
+
* <Button type="submit" size="lg" fullWidth>Save visit</Button>
|
|
21
|
+
* </StickyActionBar>
|
|
22
|
+
* </form>
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* Where the keyboard covers the bottom of the page instead of shrinking
|
|
26
|
+
* it (iOS Safari, Chrome on Android), the bar rises above it. The covered
|
|
27
|
+
* part is read from `window.visualViewport`, and the bar is raised by as
|
|
28
|
+
* much of its own scrolling box as the keyboard covers: the keyboard's
|
|
29
|
+
* full height in a box that ends at the bottom of the screen, less in
|
|
30
|
+
* one that ends above a tab bar or inside a sheet. It never leaves its
|
|
31
|
+
* box. It takes a bottom margin of the same size, so the content gains
|
|
32
|
+
* that much room to scroll, which a short form needs to bring its last
|
|
33
|
+
* field above the keyboard. Where the page shrinks by itself, nothing is
|
|
34
|
+
* done.
|
|
35
|
+
*
|
|
36
|
+
* While it is mounted the bar also sets `scroll-padding-bottom` on its
|
|
37
|
+
* scrolling ancestor (or the page) to its own height plus that rise, so a
|
|
38
|
+
* field that takes focus is brought into view above the bar, not under
|
|
39
|
+
* it. Use one bar per scrolling box: two would both stick to the same
|
|
40
|
+
* edge, one on top of the other.
|
|
41
|
+
*
|
|
42
|
+
* In an `AppShell`, a screen with a form is a task of its own: leave the
|
|
43
|
+
* tab bar out (no `footer`) and let this bar be the bottom of the screen.
|
|
44
|
+
* With a tab bar it sits directly above it.
|
|
45
|
+
*
|
|
46
|
+
* `UnsavedChangesBar` is the other bar at the bottom of a form. That one
|
|
47
|
+
* appears only while there is something to save, announces itself, and
|
|
48
|
+
* brings its own Save and Discard. This one is always there, holds the
|
|
49
|
+
* buttons you give it, and follows the keyboard.
|
|
50
|
+
*/
|
|
51
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "style"> & {
|
|
52
|
+
/**
|
|
53
|
+
* Accessible name. With it the bar is a group, so a screen reader
|
|
54
|
+
* says what the buttons belong to; without it, it is a plain box.
|
|
55
|
+
*/
|
|
56
|
+
label?: string;
|
|
57
|
+
class?: string;
|
|
58
|
+
/** Added after the offset and margin the bar sets while the keyboard is up. */
|
|
59
|
+
style?: string;
|
|
60
|
+
/** The main action, and at most one or two beside it. */
|
|
61
|
+
children?: Snippet;
|
|
62
|
+
};
|
|
63
|
+
declare const StickyActionBar: import("svelte").Component<Props, {}, "">;
|
|
64
|
+
type StickyActionBar = ReturnType<typeof StickyActionBar>;
|
|
65
|
+
export default StickyActionBar;
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { mergeStrings } from "../util/ready-made-strings.js";
|
|
3
|
+
import { zabiStringsFor } from "../util/zabi-strings.js";
|
|
4
|
+
import { tick, type Snippet } from "svelte";
|
|
5
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
6
|
+
import Ellipsis from "@lucide/svelte/icons/ellipsis";
|
|
7
|
+
import { cn } from "../util/cn.js";
|
|
8
|
+
import { isInsideToastRegion } from "../util/focus-utils.js";
|
|
9
|
+
import {
|
|
10
|
+
createVelocityTracker,
|
|
11
|
+
DRAG_SLOP,
|
|
12
|
+
FLICK_VELOCITY,
|
|
13
|
+
prefersReducedMotion,
|
|
14
|
+
timeOf,
|
|
15
|
+
} from "../util/sheet-drag.js";
|
|
16
|
+
import { generateId } from "../util/ssr-safe.js";
|
|
17
|
+
import {
|
|
18
|
+
DEFAULT_SWIPEABLE_LIST_ITEM_STRINGS,
|
|
19
|
+
registerOpenRow,
|
|
20
|
+
settleSwipe,
|
|
21
|
+
type SwipeableListItemAction,
|
|
22
|
+
type SwipeableListItemStrings,
|
|
23
|
+
} from "../util/swipeable-list-item.js";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A row with one or two actions behind its inline end, such as Delete on
|
|
27
|
+
* a draft. A finger swipes the row towards the inline start to show them;
|
|
28
|
+
* a swipe only shows them, and an action runs when its button is pressed.
|
|
29
|
+
*
|
|
30
|
+
* The swipe is a shortcut and never the only way. The row has a "more"
|
|
31
|
+
* button at its end that opens the same actions for a mouse, a keyboard,
|
|
32
|
+
* a switch or a screen reader, and they are ordinary buttons once open.
|
|
33
|
+
* Escape or a press elsewhere closes the row. In a list (`ul`, `ol`,
|
|
34
|
+
* `List`) one row is open at a time.
|
|
35
|
+
*
|
|
36
|
+
* It is the row's content and a group of buttons, not a list item: put
|
|
37
|
+
* it inside your own `li` or `ListItem`.
|
|
38
|
+
*
|
|
39
|
+
* ```svelte
|
|
40
|
+
* <ul>
|
|
41
|
+
* {#each drafts as draft (draft.id)}
|
|
42
|
+
* <li>
|
|
43
|
+
* <SwipeableListItem
|
|
44
|
+
* actions={[{ id: "delete", label: "Delete", tone: "danger", onselect: () => remove(draft) }]}
|
|
45
|
+
* >
|
|
46
|
+
* {draft.title}
|
|
47
|
+
* </SwipeableListItem>
|
|
48
|
+
* </li>
|
|
49
|
+
* {/each}
|
|
50
|
+
* </ul>
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "children"> & {
|
|
54
|
+
/** One or two actions, shown at the inline end. */
|
|
55
|
+
actions: SwipeableListItemAction[];
|
|
56
|
+
/** Whether the actions are shown. Bindable. */
|
|
57
|
+
open?: boolean;
|
|
58
|
+
/** Runs when the row opens or closes, by whatever means. */
|
|
59
|
+
onopenchange?: (open: boolean) => void;
|
|
60
|
+
/**
|
|
61
|
+
* The button at the end of the row that opens the actions without a
|
|
62
|
+
* swipe. Without it the swipe is the only way this component offers,
|
|
63
|
+
* and the app has to give another: the same actions on the page the
|
|
64
|
+
* row leads to, or in a menu (WCAG 2.5.1, 2.5.7).
|
|
65
|
+
*/
|
|
66
|
+
showMoreButton?: boolean;
|
|
67
|
+
/** The words the row says by itself, for another language. */
|
|
68
|
+
strings?: Partial<SwipeableListItemStrings>;
|
|
69
|
+
class?: string;
|
|
70
|
+
/** The row's content. */
|
|
71
|
+
children?: Snippet;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
let {
|
|
75
|
+
actions,
|
|
76
|
+
open = $bindable<Exclude<Props["open"], undefined>>(),
|
|
77
|
+
onopenchange,
|
|
78
|
+
showMoreButton = true,
|
|
79
|
+
strings,
|
|
80
|
+
class: className = "",
|
|
81
|
+
children,
|
|
82
|
+
...restProps
|
|
83
|
+
}: Props = $props();
|
|
84
|
+
|
|
85
|
+
// No fallback on a bindable prop (see Collapsible): the default is applied here.
|
|
86
|
+
const applyDefaults = () => {
|
|
87
|
+
if (open === undefined) open = false;
|
|
88
|
+
};
|
|
89
|
+
applyDefaults();
|
|
90
|
+
$effect.pre(applyDefaults);
|
|
91
|
+
|
|
92
|
+
/** The app-wide words for this component, from a `ZabiStringsProvider` above it, if there is one. */
|
|
93
|
+
const provided = zabiStringsFor("swipeableListItem");
|
|
94
|
+
const text = $derived(mergeStrings(DEFAULT_SWIPEABLE_LIST_ITEM_STRINGS, provided(), strings));
|
|
95
|
+
const groupId = generateId("swipeable-actions");
|
|
96
|
+
|
|
97
|
+
let root = $state<HTMLDivElement>();
|
|
98
|
+
let track = $state<HTMLDivElement>();
|
|
99
|
+
let group = $state<HTMLDivElement>();
|
|
100
|
+
let moreButton = $state<HTMLButtonElement>();
|
|
101
|
+
|
|
102
|
+
/** While a finger drags the row: how many px of the actions show. */
|
|
103
|
+
let dragged = $state<number | null>(null);
|
|
104
|
+
|
|
105
|
+
function setOpen(next: boolean) {
|
|
106
|
+
if (open === next) return;
|
|
107
|
+
open = next;
|
|
108
|
+
onopenchange?.(next);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Closes the row and puts focus back on the button that opens it, when focus was in the actions. */
|
|
112
|
+
function close({ returnFocus = false } = {}) {
|
|
113
|
+
const held = !!group?.contains(document.activeElement);
|
|
114
|
+
setOpen(false);
|
|
115
|
+
if (returnFocus || held) moreButton?.focus();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function toggleFromButton() {
|
|
119
|
+
if (open) {
|
|
120
|
+
close();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
setOpen(true);
|
|
124
|
+
// Opened on purpose, by a press: on to the first action.
|
|
125
|
+
await tick();
|
|
126
|
+
group?.querySelector<HTMLElement>("button")?.focus();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function select(action: SwipeableListItemAction) {
|
|
130
|
+
try {
|
|
131
|
+
action.onselect();
|
|
132
|
+
} finally {
|
|
133
|
+
close({ returnFocus: true });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
138
|
+
if (event.key !== "Escape" || !open || event.defaultPrevented) return;
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
event.stopPropagation();
|
|
141
|
+
close({ returnFocus: true });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// One open row in a list, and a press anywhere else closes this one.
|
|
145
|
+
$effect(() => {
|
|
146
|
+
const element = root;
|
|
147
|
+
if (!open || !element) return;
|
|
148
|
+
const unregister = registerOpenRow(element, () => setOpen(false));
|
|
149
|
+
const onPointerDown = (event: PointerEvent) => {
|
|
150
|
+
const target = event.target;
|
|
151
|
+
if (target instanceof Node && element.contains(target)) return;
|
|
152
|
+
// A toast lies over the list; a press on it is for the toast.
|
|
153
|
+
if (isInsideToastRegion(target)) return;
|
|
154
|
+
setOpen(false);
|
|
155
|
+
};
|
|
156
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
157
|
+
return () => {
|
|
158
|
+
unregister();
|
|
159
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// --- The swipe. A finger or a stylus; a mouse has the button. The row
|
|
164
|
+
// has `touch-action: pan-y`, so a vertical move is the browser's (it
|
|
165
|
+
// scrolls and cancels the pointer) and only a sideways one comes here.
|
|
166
|
+
const tracker = createVelocityTracker();
|
|
167
|
+
let pointerId: number | null = null;
|
|
168
|
+
let startX = 0;
|
|
169
|
+
let startY = 0;
|
|
170
|
+
let startRevealed = 0;
|
|
171
|
+
let width = 0;
|
|
172
|
+
let swiping = false;
|
|
173
|
+
/** 1 where the inline start is on the left, -1 in a right-to-left page. */
|
|
174
|
+
let direction = 1;
|
|
175
|
+
|
|
176
|
+
const revealedAt = (clientX: number) =>
|
|
177
|
+
Math.max(0, Math.min(width, startRevealed + (startX - clientX) * direction));
|
|
178
|
+
|
|
179
|
+
function handlePointerDown(event: PointerEvent) {
|
|
180
|
+
if (pointerId !== null || event.pointerType === "mouse" || !group || !track) return;
|
|
181
|
+
pointerId = event.pointerId;
|
|
182
|
+
startX = event.clientX;
|
|
183
|
+
startY = event.clientY;
|
|
184
|
+
swiping = false;
|
|
185
|
+
width = group.offsetWidth;
|
|
186
|
+
startRevealed = open ? width : 0;
|
|
187
|
+
direction = getComputedStyle(track).direction === "rtl" ? -1 : 1;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function handlePointerMove(event: PointerEvent) {
|
|
191
|
+
if (event.pointerId !== pointerId) return;
|
|
192
|
+
const dx = event.clientX - startX;
|
|
193
|
+
const dy = event.clientY - startY;
|
|
194
|
+
if (!swiping) {
|
|
195
|
+
if (Math.abs(dx) < DRAG_SLOP && Math.abs(dy) < DRAG_SLOP) return;
|
|
196
|
+
// Clearly sideways, or it is not a swipe of the row.
|
|
197
|
+
if (Math.abs(dx) <= Math.abs(dy)) {
|
|
198
|
+
pointerId = null;
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
swiping = true;
|
|
202
|
+
try {
|
|
203
|
+
track?.setPointerCapture(event.pointerId);
|
|
204
|
+
} catch {
|
|
205
|
+
/* a test DOM, or a pointer that is already gone */
|
|
206
|
+
}
|
|
207
|
+
// Measured from where the swipe was recognised, so the row does not jump by the slop.
|
|
208
|
+
startX = event.clientX;
|
|
209
|
+
tracker.reset(0, timeOf(event));
|
|
210
|
+
}
|
|
211
|
+
const revealed = revealedAt(event.clientX);
|
|
212
|
+
tracker.add(revealed, timeOf(event));
|
|
213
|
+
// Where motion is unwanted the row does not follow the finger; the actions appear at the end.
|
|
214
|
+
if (!prefersReducedMotion()) dragged = revealed;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function swallowClick(event: MouseEvent) {
|
|
218
|
+
// The click that ends a swipe is not a press of what is in the row.
|
|
219
|
+
event.preventDefault();
|
|
220
|
+
event.stopPropagation();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function finishPointer(event: PointerEvent, cancelled: boolean) {
|
|
224
|
+
if (event.pointerId !== pointerId) return;
|
|
225
|
+
pointerId = null;
|
|
226
|
+
const wasSwiping = swiping;
|
|
227
|
+
swiping = false;
|
|
228
|
+
dragged = null;
|
|
229
|
+
if (!wasSwiping || cancelled) return;
|
|
230
|
+
const revealed = revealedAt(event.clientX);
|
|
231
|
+
tracker.add(revealed, timeOf(event));
|
|
232
|
+
track?.addEventListener("click", swallowClick, { capture: true, once: true });
|
|
233
|
+
setTimeout(() => track?.removeEventListener("click", swallowClick, true), 0);
|
|
234
|
+
setOpen(settleSwipe(revealed, width, tracker.velocity(), FLICK_VELOCITY));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** The actions are laid out and can be seen: open, or coming out under a finger. */
|
|
238
|
+
const shown = $derived(open || dragged !== null);
|
|
239
|
+
|
|
240
|
+
/** How far the row is moved, as CSS: by the width of the actions when open. */
|
|
241
|
+
const shift = $derived.by(() => {
|
|
242
|
+
if (dragged !== null) return `${dragged}px`;
|
|
243
|
+
return open ? "var(--swipeable-actions-width, 0px)" : "0px";
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// The width of the actions, for the row to move by. Measured, since the labels are the app's.
|
|
247
|
+
$effect(() => {
|
|
248
|
+
const element = group;
|
|
249
|
+
const host = root;
|
|
250
|
+
if (!element || !host) return;
|
|
251
|
+
const measure = () => host.style.setProperty("--swipeable-actions-width", `${element.offsetWidth}px`);
|
|
252
|
+
measure();
|
|
253
|
+
const observer = typeof ResizeObserver === "function" ? new ResizeObserver(measure) : undefined;
|
|
254
|
+
observer?.observe(element);
|
|
255
|
+
return () => observer?.disconnect();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const actionClasses = (tone: SwipeableListItemAction["tone"]) =>
|
|
259
|
+
cn(
|
|
260
|
+
// The ring is drawn inside the button: the row clips what reaches past its ends.
|
|
261
|
+
"flex min-h-11 min-w-[72px] cursor-pointer flex-col items-center justify-center gap-[4px] px-[12px] py-[8px] text-center text-xs font-medium transition-colors motion-reduce:transition-none focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-4 focus-visible:outline-current forced-colors:border forced-colors:border-[ButtonText]",
|
|
262
|
+
tone === "danger"
|
|
263
|
+
? "bg-action-danger text-action-danger-text hover:bg-action-danger-hover active:bg-action-danger-active"
|
|
264
|
+
: "bg-action-secondary text-headline hover:bg-action-secondary-hover active:bg-action-secondary-active",
|
|
265
|
+
);
|
|
266
|
+
</script>
|
|
267
|
+
|
|
268
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
269
|
+
<div
|
|
270
|
+
{...restProps}
|
|
271
|
+
bind:this={root}
|
|
272
|
+
class={cn("relative overflow-x-clip", className)}
|
|
273
|
+
data-swipeable-list-item
|
|
274
|
+
data-open={open ? "true" : "false"}
|
|
275
|
+
onkeydown={handleKeydown}
|
|
276
|
+
>
|
|
277
|
+
<!-- The row and its actions side by side, the actions past the end of the
|
|
278
|
+
row until the two are moved over. `pan-y`: a vertical move scrolls. -->
|
|
279
|
+
<div
|
|
280
|
+
bind:this={track}
|
|
281
|
+
class={cn(
|
|
282
|
+
// Towards the inline start: to the left, or to the right in a right-to-left page.
|
|
283
|
+
"flex touch-pan-y items-stretch [--swipeable-direction:-1] rtl:[--swipeable-direction:1]",
|
|
284
|
+
dragged === null && "transition-transform duration-200 ease-out motion-reduce:transition-none",
|
|
285
|
+
)}
|
|
286
|
+
style:transform="translateX(calc({shift} * var(--swipeable-direction, -1)))"
|
|
287
|
+
onpointerdown={handlePointerDown}
|
|
288
|
+
onpointermove={handlePointerMove}
|
|
289
|
+
onpointerup={(event) => finishPointer(event, false)}
|
|
290
|
+
onpointercancel={(event) => finishPointer(event, true)}
|
|
291
|
+
>
|
|
292
|
+
<div class="flex min-w-0 shrink-0 basis-full items-center gap-[8px]">
|
|
293
|
+
<div class="min-w-0 flex-1">
|
|
294
|
+
{@render children?.()}
|
|
295
|
+
</div>
|
|
296
|
+
{#if showMoreButton}
|
|
297
|
+
<button
|
|
298
|
+
bind:this={moreButton}
|
|
299
|
+
type="button"
|
|
300
|
+
class="flex size-10 shrink-0 cursor-pointer items-center justify-center rounded-control text-description transition-colors hover:bg-surface-hover hover:text-headline active:bg-surface-active focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-focus-ring motion-reduce:transition-none pointer-coarse:size-[44px]"
|
|
301
|
+
aria-label={text.actions}
|
|
302
|
+
aria-expanded={open}
|
|
303
|
+
aria-controls={groupId}
|
|
304
|
+
data-swipeable-more
|
|
305
|
+
onclick={toggleFromButton}
|
|
306
|
+
>
|
|
307
|
+
<Ellipsis size={20} aria-hidden="true" />
|
|
308
|
+
</button>
|
|
309
|
+
{/if}
|
|
310
|
+
</div>
|
|
311
|
+
<!-- In the page while closed, so the button above has something to
|
|
312
|
+
name; out of reach until the row is open. -->
|
|
313
|
+
<div
|
|
314
|
+
bind:this={group}
|
|
315
|
+
id={groupId}
|
|
316
|
+
role="group"
|
|
317
|
+
aria-label={text.actions}
|
|
318
|
+
class={cn("flex shrink-0 items-stretch", !shown && "invisible")}
|
|
319
|
+
style:max-inline-size="max(66%, {actions.length * 72}px)"
|
|
320
|
+
inert={!open}
|
|
321
|
+
data-swipeable-actions
|
|
322
|
+
>
|
|
323
|
+
{#each actions as action (action.id)}
|
|
324
|
+
<button type="button" class={actionClasses(action.tone)} onclick={() => select(action)}>
|
|
325
|
+
{#if action.icon}
|
|
326
|
+
{@const Icon = action.icon}
|
|
327
|
+
<span class="flex" aria-hidden="true"><Icon size={20} /></span>
|
|
328
|
+
{/if}
|
|
329
|
+
<span class="min-w-0 text-balance break-words">{action.label}</span>
|
|
330
|
+
</button>
|
|
331
|
+
{/each}
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type Snippet } from "svelte";
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
import { type SwipeableListItemAction, type SwipeableListItemStrings } from "../util/swipeable-list-item.js";
|
|
4
|
+
/**
|
|
5
|
+
* A row with one or two actions behind its inline end, such as Delete on
|
|
6
|
+
* a draft. A finger swipes the row towards the inline start to show them;
|
|
7
|
+
* a swipe only shows them, and an action runs when its button is pressed.
|
|
8
|
+
*
|
|
9
|
+
* The swipe is a shortcut and never the only way. The row has a "more"
|
|
10
|
+
* button at its end that opens the same actions for a mouse, a keyboard,
|
|
11
|
+
* a switch or a screen reader, and they are ordinary buttons once open.
|
|
12
|
+
* Escape or a press elsewhere closes the row. In a list (`ul`, `ol`,
|
|
13
|
+
* `List`) one row is open at a time.
|
|
14
|
+
*
|
|
15
|
+
* It is the row's content and a group of buttons, not a list item: put
|
|
16
|
+
* it inside your own `li` or `ListItem`.
|
|
17
|
+
*
|
|
18
|
+
* ```svelte
|
|
19
|
+
* <ul>
|
|
20
|
+
* {#each drafts as draft (draft.id)}
|
|
21
|
+
* <li>
|
|
22
|
+
* <SwipeableListItem
|
|
23
|
+
* actions={[{ id: "delete", label: "Delete", tone: "danger", onselect: () => remove(draft) }]}
|
|
24
|
+
* >
|
|
25
|
+
* {draft.title}
|
|
26
|
+
* </SwipeableListItem>
|
|
27
|
+
* </li>
|
|
28
|
+
* {/each}
|
|
29
|
+
* </ul>
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "children"> & {
|
|
33
|
+
/** One or two actions, shown at the inline end. */
|
|
34
|
+
actions: SwipeableListItemAction[];
|
|
35
|
+
/** Whether the actions are shown. Bindable. */
|
|
36
|
+
open?: boolean;
|
|
37
|
+
/** Runs when the row opens or closes, by whatever means. */
|
|
38
|
+
onopenchange?: (open: boolean) => void;
|
|
39
|
+
/**
|
|
40
|
+
* The button at the end of the row that opens the actions without a
|
|
41
|
+
* swipe. Without it the swipe is the only way this component offers,
|
|
42
|
+
* and the app has to give another: the same actions on the page the
|
|
43
|
+
* row leads to, or in a menu (WCAG 2.5.1, 2.5.7).
|
|
44
|
+
*/
|
|
45
|
+
showMoreButton?: boolean;
|
|
46
|
+
/** The words the row says by itself, for another language. */
|
|
47
|
+
strings?: Partial<SwipeableListItemStrings>;
|
|
48
|
+
class?: string;
|
|
49
|
+
/** The row's content. */
|
|
50
|
+
children?: Snippet;
|
|
51
|
+
};
|
|
52
|
+
declare const SwipeableListItem: import("svelte").Component<Props, {}, "open">;
|
|
53
|
+
type SwipeableListItem = ReturnType<typeof SwipeableListItem>;
|
|
54
|
+
export default SwipeableListItem;
|