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
|
@@ -1,36 +1,241 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { DEFAULT_IMAGE_UPLOAD_TEXTS, zabiStringsFor } from "../util/zabi-strings.js";
|
|
3
|
+
import { onDestroy, tick, untrack, type Snippet } from "svelte";
|
|
4
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
5
|
import Button from "../atoms/Button.svelte";
|
|
4
|
-
import
|
|
6
|
+
import Image from "@lucide/svelte/icons/image";
|
|
7
|
+
import { cn } from "../util/cn.js";
|
|
8
|
+
import { isVideoPath, isVideoUrl } from "../util/media.js";
|
|
9
|
+
import { generateId } from "../util/ssr-safe.js";
|
|
5
10
|
|
|
6
|
-
|
|
11
|
+
export type ImageUploadFileDetail = {
|
|
12
|
+
/** Selected file, or `null` when the image is removed. */
|
|
13
|
+
file: File | null;
|
|
14
|
+
/** Object URL used for the preview, or `null`. */
|
|
15
|
+
url: string | null;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type ImageUploadPreviewType = "image" | "video";
|
|
19
|
+
|
|
20
|
+
export type ImageUploadActionsPlacement = "overlay" | "strip";
|
|
21
|
+
|
|
22
|
+
export type ImageUploadPreviewDetail = {
|
|
23
|
+
/** The current `value`. */
|
|
24
|
+
url: string;
|
|
25
|
+
/** What the built-in preview would have rendered. */
|
|
26
|
+
type: ImageUploadPreviewType;
|
|
27
|
+
alt: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, "onchange" | "onclick" | "id"> & {
|
|
31
|
+
/** Extra classes for the host element. */
|
|
32
|
+
class?: string;
|
|
33
|
+
/** Id of the control the label points at; generated when omitted. */
|
|
34
|
+
id?: string;
|
|
35
|
+
/** Visible label, associated with the dropzone. */
|
|
36
|
+
label?: string;
|
|
37
|
+
/** Preview URL; `bind:value` to read it. */
|
|
7
38
|
value?: string | null;
|
|
8
39
|
disabled?: boolean;
|
|
9
40
|
accept?: string;
|
|
10
41
|
placeholder?: string;
|
|
42
|
+
/** Second line of the dropzone. */
|
|
43
|
+
browseText?: string;
|
|
44
|
+
changeText?: string;
|
|
45
|
+
removeText?: string;
|
|
46
|
+
/** Accessible name of the Change action; defaults to `changeText` plus `label`. */
|
|
47
|
+
changeLabel?: string;
|
|
48
|
+
/** Accessible name of the Remove action; defaults to `removeText` plus `label`. */
|
|
49
|
+
removeLabel?: string;
|
|
50
|
+
/** Text alternative for the preview. */
|
|
51
|
+
alt?: string;
|
|
52
|
+
/** Forces the preview kind when the URL has no extension to go by. */
|
|
53
|
+
previewType?: ImageUploadPreviewType;
|
|
54
|
+
/** Replaces the built-in `<img>` / `<video>` preview. */
|
|
55
|
+
preview?: Snippet<[ImageUploadPreviewDetail]>;
|
|
56
|
+
/**
|
|
57
|
+
* Where Change and Remove sit: over the whole preview, or in a strip
|
|
58
|
+
* along its top edge. Defaults to the strip for a video, whose controls
|
|
59
|
+
* an overlay would cover, and to the overlay otherwise.
|
|
60
|
+
*/
|
|
61
|
+
actionsPlacement?: ImageUploadActionsPlacement;
|
|
62
|
+
/** Announced to screen readers when a file or URL is set. */
|
|
63
|
+
selectedText?: string;
|
|
64
|
+
/** Announced to screen readers when the value is cleared. */
|
|
65
|
+
removedText?: string;
|
|
11
66
|
errorMessage?: string;
|
|
67
|
+
errorTitle?: string;
|
|
68
|
+
/** Closing line of the error block; `false` or `""` leaves it out. */
|
|
69
|
+
errorRecovery?: string | false;
|
|
70
|
+
/** Native `change` event from the hidden file input. */
|
|
12
71
|
onchange?: (event: Event) => void;
|
|
72
|
+
/** Fires before the file chooser opens; `event.preventDefault()` keeps it closed. */
|
|
13
73
|
onclick?: (event: Event) => void;
|
|
14
|
-
|
|
74
|
+
/** Replaces the native file chooser, for a media library or another source. */
|
|
75
|
+
onbrowse?: (event: Event) => void;
|
|
76
|
+
/** Selected file and preview URL; `{ file: null, url: null }` on remove. */
|
|
77
|
+
onfileselect?: (detail: ImageUploadFileDetail) => void;
|
|
78
|
+
/** A dropped file that `accept` does not allow. */
|
|
79
|
+
onfilereject?: (file: File) => void;
|
|
80
|
+
};
|
|
15
81
|
|
|
16
82
|
let {
|
|
17
|
-
|
|
83
|
+
class: className = "",
|
|
84
|
+
id: idProp,
|
|
85
|
+
label = "",
|
|
86
|
+
value = $bindable<Exclude<Props["value"], undefined>>(),
|
|
18
87
|
disabled = false,
|
|
19
88
|
accept = "image/*",
|
|
20
|
-
placeholder
|
|
89
|
+
placeholder: placeholderGiven,
|
|
90
|
+
browseText: browseTextGiven,
|
|
91
|
+
changeText: changeTextGiven,
|
|
92
|
+
removeText: removeTextGiven,
|
|
93
|
+
changeLabel,
|
|
94
|
+
removeLabel,
|
|
95
|
+
alt = "",
|
|
96
|
+
previewType,
|
|
97
|
+
preview,
|
|
98
|
+
actionsPlacement,
|
|
99
|
+
selectedText: selectedTextGiven,
|
|
100
|
+
removedText: removedTextGiven,
|
|
21
101
|
errorMessage = "",
|
|
22
|
-
|
|
102
|
+
errorTitle: errorTitleGiven,
|
|
103
|
+
errorRecovery: errorRecoveryGiven,
|
|
104
|
+
onchange,
|
|
105
|
+
onclick,
|
|
106
|
+
onbrowse,
|
|
107
|
+
onfileselect,
|
|
108
|
+
onfilereject,
|
|
109
|
+
children: _children,
|
|
23
110
|
...restProps
|
|
24
|
-
} = $props
|
|
111
|
+
}: Props = $props();
|
|
25
112
|
|
|
113
|
+
/** Each text: the prop, else the app-wide word from a `ZabiStringsProvider` above this one, else English. */
|
|
114
|
+
const provided = zabiStringsFor("imageUpload");
|
|
115
|
+
const placeholder = $derived(placeholderGiven ?? provided()?.placeholder ?? DEFAULT_IMAGE_UPLOAD_TEXTS.placeholder);
|
|
116
|
+
const browseText = $derived(browseTextGiven ?? provided()?.browseText ?? DEFAULT_IMAGE_UPLOAD_TEXTS.browseText);
|
|
117
|
+
const changeText = $derived(changeTextGiven ?? provided()?.changeText ?? DEFAULT_IMAGE_UPLOAD_TEXTS.changeText);
|
|
118
|
+
const removeText = $derived(removeTextGiven ?? provided()?.removeText ?? DEFAULT_IMAGE_UPLOAD_TEXTS.removeText);
|
|
119
|
+
const selectedText = $derived(selectedTextGiven ?? provided()?.selectedText ?? DEFAULT_IMAGE_UPLOAD_TEXTS.selectedText);
|
|
120
|
+
const removedText = $derived(removedTextGiven ?? provided()?.removedText ?? DEFAULT_IMAGE_UPLOAD_TEXTS.removedText);
|
|
121
|
+
const errorTitle = $derived(errorTitleGiven ?? provided()?.errorTitle ?? DEFAULT_IMAGE_UPLOAD_TEXTS.errorTitle);
|
|
122
|
+
const errorRecovery = $derived(errorRecoveryGiven ?? provided()?.errorRecovery ?? DEFAULT_IMAGE_UPLOAD_TEXTS.errorRecovery);
|
|
123
|
+
|
|
124
|
+
// No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
|
|
125
|
+
// one that has a fallback (`props_invalid_value`), and a page that throws
|
|
126
|
+
// while it hydrates never becomes interactive. The default is applied
|
|
127
|
+
// here instead: at once, for the server and the first render, and again
|
|
128
|
+
// whenever a parent hands back `undefined`.
|
|
129
|
+
const applyDefaults = () => {
|
|
130
|
+
if (value === undefined) value = null;
|
|
131
|
+
};
|
|
132
|
+
applyDefaults();
|
|
133
|
+
$effect.pre(applyDefaults);
|
|
134
|
+
|
|
135
|
+
const fallbackId = generateId("image-upload");
|
|
136
|
+
const controlId = $derived(idProp ?? fallbackId);
|
|
137
|
+
const labelId = $derived(`${controlId}-label`);
|
|
138
|
+
const hintId = $derived(`${controlId}-hint`);
|
|
139
|
+
const errorId = $derived(`${controlId}-error`);
|
|
140
|
+
|
|
141
|
+
let host = $state<HTMLDivElement>();
|
|
26
142
|
let fileInput = $state<HTMLInputElement>();
|
|
27
143
|
let currentObjectUrl = $state<string | null>(null);
|
|
144
|
+
// An object URL has no extension, so the kind is kept from the file itself.
|
|
145
|
+
let currentObjectType = $state<ImageUploadPreviewType | null>(null);
|
|
146
|
+
// dragenter/dragleave also fire for children; count them instead of toggling.
|
|
147
|
+
let dragDepth = $state(0);
|
|
148
|
+
|
|
149
|
+
const isDragOver = $derived(dragDepth > 0);
|
|
150
|
+
|
|
151
|
+
let statusMessage = $state("");
|
|
152
|
+
let previousValue: string | null | undefined;
|
|
153
|
+
// Set by Change, Remove and the dropzone: the next swap is this control's doing.
|
|
154
|
+
let focusReturnArmed = false;
|
|
155
|
+
|
|
156
|
+
const resolvedPreviewType = $derived.by<ImageUploadPreviewType>(() => {
|
|
157
|
+
if (previewType) return previewType;
|
|
158
|
+
if (!value) return "image";
|
|
159
|
+
if (value === currentObjectUrl && currentObjectType) return currentObjectType;
|
|
160
|
+
return isVideoUrl(value) ? "video" : "image";
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const actionsInStrip = $derived(
|
|
164
|
+
(actionsPlacement ?? (resolvedPreviewType === "video" ? "strip" : "overlay")) ===
|
|
165
|
+
"strip",
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
function announce(message: string) {
|
|
169
|
+
// An unchanged live region is not read again; a trailing no-break
|
|
170
|
+
// space makes a repeat a change without altering what is heard.
|
|
171
|
+
statusMessage = message === statusMessage ? `${message}\u00a0` : message;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The dropzone and the actions are different nodes, so filling or clearing
|
|
176
|
+
* the value unmounts whichever had focus and the browser drops focus on
|
|
177
|
+
* `<body>`. Hand it to the control that now carries the id, but only when
|
|
178
|
+
* focus was in here or the swap came from one of this component's own
|
|
179
|
+
* controls, and never when it has since settled somewhere else.
|
|
180
|
+
*/
|
|
181
|
+
$effect.pre(() => {
|
|
182
|
+
const current = value;
|
|
183
|
+
untrack(() => {
|
|
184
|
+
if (previousValue === undefined) {
|
|
185
|
+
previousValue = current ?? null;
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if ((current ?? null) === previousValue) return;
|
|
189
|
+
|
|
190
|
+
const swapped = Boolean(current) !== Boolean(previousValue);
|
|
191
|
+
previousValue = current ?? null;
|
|
192
|
+
announce(current ? selectedText : removedText);
|
|
193
|
+
if (!swapped) return;
|
|
194
|
+
|
|
195
|
+
const hadFocus = Boolean(host?.contains(document.activeElement));
|
|
196
|
+
const armed = focusReturnArmed;
|
|
197
|
+
focusReturnArmed = false;
|
|
198
|
+
if (!hadFocus && !armed) return;
|
|
199
|
+
|
|
200
|
+
void tick().then(() => {
|
|
201
|
+
const active = document.activeElement;
|
|
202
|
+
if (active && active !== document.body) return;
|
|
203
|
+
document.getElementById(controlId)?.focus();
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const changeName = $derived(
|
|
209
|
+
changeLabel ?? (label ? `${changeText} ${label}` : undefined),
|
|
210
|
+
);
|
|
211
|
+
const removeName = $derived(
|
|
212
|
+
removeLabel ?? (label ? `${removeText} ${label}` : undefined),
|
|
213
|
+
);
|
|
28
214
|
|
|
29
215
|
function revokeCurrentObjectUrl() {
|
|
30
216
|
if (currentObjectUrl && typeof URL !== "undefined" && URL.revokeObjectURL) {
|
|
31
217
|
URL.revokeObjectURL(currentObjectUrl);
|
|
32
218
|
currentObjectUrl = null;
|
|
219
|
+
currentObjectType = null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Shared by the file chooser and drag and drop. */
|
|
224
|
+
function selectFile(file: File): string | null {
|
|
225
|
+
let url: string | null = null;
|
|
226
|
+
|
|
227
|
+
if (typeof URL !== "undefined" && URL.createObjectURL) {
|
|
228
|
+
revokeCurrentObjectUrl();
|
|
229
|
+
url = URL.createObjectURL(file);
|
|
230
|
+
value = url;
|
|
231
|
+
currentObjectUrl = url;
|
|
232
|
+
currentObjectType =
|
|
233
|
+
file.type.startsWith("video/") || (!file.type && isVideoPath(file.name))
|
|
234
|
+
? "video"
|
|
235
|
+
: "image";
|
|
33
236
|
}
|
|
237
|
+
|
|
238
|
+
return url;
|
|
34
239
|
}
|
|
35
240
|
|
|
36
241
|
function handleFileSelect(event: Event) {
|
|
@@ -40,90 +245,224 @@
|
|
|
40
245
|
if (!input.files || input.files.length === 0) return;
|
|
41
246
|
|
|
42
247
|
const file = input.files[0];
|
|
248
|
+
const url = selectFile(file);
|
|
43
249
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const url = URL.createObjectURL(file);
|
|
47
|
-
value = url;
|
|
48
|
-
currentObjectUrl = url;
|
|
49
|
-
}
|
|
250
|
+
onchange?.(event);
|
|
251
|
+
onfileselect?.({ file, url });
|
|
50
252
|
}
|
|
51
253
|
|
|
52
254
|
function removeImage() {
|
|
53
255
|
if (disabled) return;
|
|
54
256
|
|
|
257
|
+
focusReturnArmed = true;
|
|
55
258
|
revokeCurrentObjectUrl();
|
|
56
259
|
value = null;
|
|
260
|
+
if (fileInput) fileInput.value = "";
|
|
261
|
+
onfileselect?.({ file: null, url: null });
|
|
57
262
|
}
|
|
58
263
|
|
|
59
|
-
function triggerFileSelect() {
|
|
264
|
+
function triggerFileSelect(event: Event) {
|
|
60
265
|
if (disabled) return;
|
|
266
|
+
onclick?.(event);
|
|
267
|
+
if (event.defaultPrevented) return;
|
|
268
|
+
focusReturnArmed = true;
|
|
269
|
+
if (onbrowse) {
|
|
270
|
+
onbrowse(event);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
61
273
|
fileInput?.click();
|
|
62
274
|
}
|
|
63
275
|
|
|
276
|
+
/** Same rules the browser applies to the `accept` attribute. */
|
|
277
|
+
function matchesAccept(file: File) {
|
|
278
|
+
const tokens = accept
|
|
279
|
+
.split(",")
|
|
280
|
+
.map((token) => token.trim().toLowerCase())
|
|
281
|
+
.filter(Boolean);
|
|
282
|
+
if (tokens.length === 0) return true;
|
|
283
|
+
|
|
284
|
+
const type = file.type.toLowerCase();
|
|
285
|
+
const name = file.name.toLowerCase();
|
|
286
|
+
return tokens.some((token) => {
|
|
287
|
+
if (token.startsWith(".")) return name.endsWith(token);
|
|
288
|
+
if (token.endsWith("/*")) return type.startsWith(token.slice(0, -1));
|
|
289
|
+
return type === token;
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function carriesFiles(event: DragEvent) {
|
|
294
|
+
return Array.from(event.dataTransfer?.types ?? []).includes("Files");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// A disabled upload still cancels a file drag: left alone, the browser
|
|
298
|
+
// opens the dropped file in the tab and the surrounding form is lost.
|
|
299
|
+
function handleDragEnter(event: DragEvent) {
|
|
300
|
+
if (!carriesFiles(event)) return;
|
|
301
|
+
event.preventDefault();
|
|
302
|
+
if (!disabled) dragDepth += 1;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function handleDragOver(event: DragEvent) {
|
|
306
|
+
if (!carriesFiles(event)) return;
|
|
307
|
+
// Without this the browser refuses the drop and opens the file instead.
|
|
308
|
+
event.preventDefault();
|
|
309
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = disabled ? "none" : "copy";
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function handleDragLeave() {
|
|
313
|
+
if (dragDepth > 0) dragDepth -= 1;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function handleDrop(event: DragEvent) {
|
|
317
|
+
dragDepth = 0;
|
|
318
|
+
if (!carriesFiles(event)) return;
|
|
319
|
+
event.preventDefault();
|
|
320
|
+
if (disabled) return;
|
|
321
|
+
|
|
322
|
+
const file = event.dataTransfer?.files?.[0];
|
|
323
|
+
if (!file) return;
|
|
324
|
+
if (!matchesAccept(file)) {
|
|
325
|
+
onfilereject?.(file);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const url = selectFile(file);
|
|
330
|
+
// The input did not take part; clear it so the same file can be picked again.
|
|
331
|
+
if (fileInput) fileInput.value = "";
|
|
332
|
+
onfileselect?.({ file, url });
|
|
333
|
+
}
|
|
334
|
+
|
|
64
335
|
onDestroy(() => {
|
|
65
336
|
revokeCurrentObjectUrl();
|
|
66
337
|
});
|
|
67
338
|
</script>
|
|
68
339
|
|
|
69
|
-
<div class="space-y-3">
|
|
70
|
-
{#if
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
340
|
+
<div bind:this={host} class={cn("space-y-3", className)} {...restProps}>
|
|
341
|
+
{#if label}
|
|
342
|
+
<label
|
|
343
|
+
id={labelId}
|
|
344
|
+
for={controlId}
|
|
345
|
+
class={cn("block text-sm font-medium text-label", disabled && "opacity-60")}
|
|
346
|
+
>
|
|
347
|
+
{label}
|
|
348
|
+
</label>
|
|
349
|
+
{/if}
|
|
350
|
+
|
|
351
|
+
<!-- Wraps both states so a drop is handled the same over either. -->
|
|
352
|
+
<div
|
|
353
|
+
role="presentation"
|
|
354
|
+
class={disabled ? "cursor-not-allowed" : undefined}
|
|
355
|
+
ondragenter={handleDragEnter}
|
|
356
|
+
ondragover={handleDragOver}
|
|
357
|
+
ondragleave={handleDragLeave}
|
|
358
|
+
ondrop={handleDrop}
|
|
359
|
+
data-drag-over={isDragOver ? "" : undefined}
|
|
360
|
+
>
|
|
361
|
+
{#if value}
|
|
77
362
|
<div
|
|
78
|
-
class="
|
|
363
|
+
class="relative group"
|
|
364
|
+
role={label ? "group" : undefined}
|
|
365
|
+
aria-labelledby={label ? labelId : undefined}
|
|
79
366
|
>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
tabindex={disabled ? -1 : 0}
|
|
108
|
-
onkeydown={(e) =>
|
|
109
|
-
e.key === "Enter" && !disabled && triggerFileSelect()}
|
|
110
|
-
aria-disabled={disabled}
|
|
111
|
-
>
|
|
112
|
-
<div class="space-y-3">
|
|
367
|
+
{#if preview}
|
|
368
|
+
{@render preview({ url: value, type: resolvedPreviewType, alt })}
|
|
369
|
+
{:else if resolvedPreviewType === "video"}
|
|
370
|
+
<!-- Never autoplays, so there is no motion to reduce; muted
|
|
371
|
+
until the viewer unmutes it from the native controls. -->
|
|
372
|
+
<video
|
|
373
|
+
src={value}
|
|
374
|
+
controls
|
|
375
|
+
muted
|
|
376
|
+
playsinline
|
|
377
|
+
preload="metadata"
|
|
378
|
+
aria-label={alt || undefined}
|
|
379
|
+
class="w-full h-32 object-cover rounded-container border-0"
|
|
380
|
+
></video>
|
|
381
|
+
{:else}
|
|
382
|
+
<img
|
|
383
|
+
src={value}
|
|
384
|
+
{alt}
|
|
385
|
+
class="w-full h-32 object-cover rounded-container border-0"
|
|
386
|
+
/>
|
|
387
|
+
{/if}
|
|
388
|
+
<!-- Hover cannot reveal the actions on a touch screen, so there
|
|
389
|
+
they stay visible as a strip that leaves the preview in
|
|
390
|
+
view. The layer itself never takes the pointer: only the
|
|
391
|
+
plate under the buttons does, so a video beneath stays
|
|
392
|
+
operable. The plate is opaque so the labels keep their
|
|
393
|
+
contrast whatever the image behind them. -->
|
|
113
394
|
<div
|
|
114
|
-
class=
|
|
395
|
+
class={cn(
|
|
396
|
+
"absolute inset-0 p-1 border border-border-overlay bg-surface-overlay/60 backdrop-blur-md opacity-0 group-hover:opacity-100 group-has-[:focus-visible]:opacity-100 transition-opacity motion-reduce:transition-none rounded-container flex items-center justify-center pointer-events-none",
|
|
397
|
+
"pointer-coarse:opacity-100 [@media(hover:none)]:opacity-100",
|
|
398
|
+
actionsInStrip
|
|
399
|
+
? "bottom-auto"
|
|
400
|
+
: "pointer-coarse:bottom-auto [@media(hover:none)]:bottom-auto",
|
|
401
|
+
isDragOver && "opacity-100 border-action-primary",
|
|
402
|
+
)}
|
|
403
|
+
data-testid="image-upload-actions"
|
|
115
404
|
>
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
405
|
+
<div
|
|
406
|
+
class="pointer-events-auto flex min-w-0 flex-wrap justify-center gap-2 rounded-control bg-surface-overlay p-1"
|
|
407
|
+
>
|
|
408
|
+
<Button
|
|
409
|
+
id={controlId}
|
|
410
|
+
variant="secondary"
|
|
411
|
+
size="sm"
|
|
412
|
+
class="min-w-0"
|
|
413
|
+
onclick={triggerFileSelect}
|
|
414
|
+
aria-label={changeName}
|
|
415
|
+
aria-describedby={errorMessage ? errorId : undefined}
|
|
416
|
+
{disabled}
|
|
417
|
+
>
|
|
418
|
+
<span class="truncate">{changeText}</span>
|
|
419
|
+
</Button>
|
|
420
|
+
<Button
|
|
421
|
+
variant="danger"
|
|
422
|
+
size="sm"
|
|
423
|
+
class="min-w-0"
|
|
424
|
+
onclick={removeImage}
|
|
425
|
+
aria-label={removeName}
|
|
426
|
+
{disabled}
|
|
427
|
+
>
|
|
428
|
+
<span class="truncate">{removeText}</span>
|
|
429
|
+
</Button>
|
|
430
|
+
</div>
|
|
123
431
|
</div>
|
|
124
432
|
</div>
|
|
125
|
-
|
|
126
|
-
|
|
433
|
+
{:else}
|
|
434
|
+
<button
|
|
435
|
+
type="button"
|
|
436
|
+
id={controlId}
|
|
437
|
+
class={cn(
|
|
438
|
+
"focus-ring block w-full border-2 border-dashed border-input-border rounded-container p-6 text-center enabled:hover:border-action-primary enabled:active:bg-surface-active transition-colors",
|
|
439
|
+
// Disabled form controls can swallow pointer events; let a
|
|
440
|
+
// drag land on the wrapper, which cancels it.
|
|
441
|
+
disabled ? "pointer-events-none opacity-50" : "cursor-pointer",
|
|
442
|
+
isDragOver && "border-action-primary bg-action-primary-subtle",
|
|
443
|
+
)}
|
|
444
|
+
onclick={triggerFileSelect}
|
|
445
|
+
{disabled}
|
|
446
|
+
aria-describedby={[label ? hintId : "", errorMessage ? errorId : ""]
|
|
447
|
+
.filter(Boolean)
|
|
448
|
+
.join(" ") || undefined}
|
|
449
|
+
>
|
|
450
|
+
<span class="block space-y-3">
|
|
451
|
+
<span
|
|
452
|
+
class="w-12 h-12 mx-auto bg-action-secondary rounded-control flex items-center justify-center"
|
|
453
|
+
>
|
|
454
|
+
<Image size={24} class="text-description" aria-hidden="true" />
|
|
455
|
+
</span>
|
|
456
|
+
<span id={hintId} class="block">
|
|
457
|
+
<span class="block font-medium text-headline">{placeholder}</span>
|
|
458
|
+
<span class="block text-sm text-description">
|
|
459
|
+
{browseText}
|
|
460
|
+
</span>
|
|
461
|
+
</span>
|
|
462
|
+
</span>
|
|
463
|
+
</button>
|
|
464
|
+
{/if}
|
|
465
|
+
</div>
|
|
127
466
|
|
|
128
467
|
<input
|
|
129
468
|
bind:this={fileInput}
|
|
@@ -132,13 +471,26 @@
|
|
|
132
471
|
onchange={handleFileSelect}
|
|
133
472
|
{disabled}
|
|
134
473
|
class="hidden"
|
|
474
|
+
data-testid="image-upload-input"
|
|
135
475
|
/>
|
|
136
476
|
|
|
477
|
+
<div class="sr-only" role="status" aria-live="polite" aria-atomic="true">
|
|
478
|
+
{statusMessage}
|
|
479
|
+
</div>
|
|
480
|
+
|
|
137
481
|
{#if errorMessage}
|
|
138
|
-
|
|
139
|
-
|
|
482
|
+
<!-- Same tinted fill + tinted border Alert uses, rather than a bare
|
|
483
|
+
step-600 outline on a transparent ground. -->
|
|
484
|
+
<div
|
|
485
|
+
id={errorId}
|
|
486
|
+
class="rounded-container border bg-error-subtle border-error-border text-error-text px-3 py-2 text-sm"
|
|
487
|
+
role="alert"
|
|
488
|
+
>
|
|
489
|
+
<p class="font-medium">{errorTitle}</p>
|
|
140
490
|
<p>{errorMessage}</p>
|
|
141
|
-
|
|
491
|
+
{#if errorRecovery}
|
|
492
|
+
<p class="mt-1">{errorRecovery}</p>
|
|
493
|
+
{/if}
|
|
142
494
|
</div>
|
|
143
495
|
{/if}
|
|
144
496
|
</div>
|
|
@@ -1,15 +1,71 @@
|
|
|
1
|
-
|
|
1
|
+
import { type Snippet } from "svelte";
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
export type ImageUploadFileDetail = {
|
|
4
|
+
/** Selected file, or `null` when the image is removed. */
|
|
5
|
+
file: File | null;
|
|
6
|
+
/** Object URL used for the preview, or `null`. */
|
|
7
|
+
url: string | null;
|
|
8
|
+
};
|
|
9
|
+
export type ImageUploadPreviewType = "image" | "video";
|
|
10
|
+
export type ImageUploadActionsPlacement = "overlay" | "strip";
|
|
11
|
+
export type ImageUploadPreviewDetail = {
|
|
12
|
+
/** The current `value`. */
|
|
13
|
+
url: string;
|
|
14
|
+
/** What the built-in preview would have rendered. */
|
|
15
|
+
type: ImageUploadPreviewType;
|
|
16
|
+
alt: string;
|
|
17
|
+
};
|
|
18
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, "onchange" | "onclick" | "id"> & {
|
|
19
|
+
/** Extra classes for the host element. */
|
|
20
|
+
class?: string;
|
|
21
|
+
/** Id of the control the label points at; generated when omitted. */
|
|
22
|
+
id?: string;
|
|
23
|
+
/** Visible label, associated with the dropzone. */
|
|
24
|
+
label?: string;
|
|
25
|
+
/** Preview URL; `bind:value` to read it. */
|
|
2
26
|
value?: string | null;
|
|
3
27
|
disabled?: boolean;
|
|
4
28
|
accept?: string;
|
|
5
29
|
placeholder?: string;
|
|
30
|
+
/** Second line of the dropzone. */
|
|
31
|
+
browseText?: string;
|
|
32
|
+
changeText?: string;
|
|
33
|
+
removeText?: string;
|
|
34
|
+
/** Accessible name of the Change action; defaults to `changeText` plus `label`. */
|
|
35
|
+
changeLabel?: string;
|
|
36
|
+
/** Accessible name of the Remove action; defaults to `removeText` plus `label`. */
|
|
37
|
+
removeLabel?: string;
|
|
38
|
+
/** Text alternative for the preview. */
|
|
39
|
+
alt?: string;
|
|
40
|
+
/** Forces the preview kind when the URL has no extension to go by. */
|
|
41
|
+
previewType?: ImageUploadPreviewType;
|
|
42
|
+
/** Replaces the built-in `<img>` / `<video>` preview. */
|
|
43
|
+
preview?: Snippet<[ImageUploadPreviewDetail]>;
|
|
44
|
+
/**
|
|
45
|
+
* Where Change and Remove sit: over the whole preview, or in a strip
|
|
46
|
+
* along its top edge. Defaults to the strip for a video, whose controls
|
|
47
|
+
* an overlay would cover, and to the overlay otherwise.
|
|
48
|
+
*/
|
|
49
|
+
actionsPlacement?: ImageUploadActionsPlacement;
|
|
50
|
+
/** Announced to screen readers when a file or URL is set. */
|
|
51
|
+
selectedText?: string;
|
|
52
|
+
/** Announced to screen readers when the value is cleared. */
|
|
53
|
+
removedText?: string;
|
|
6
54
|
errorMessage?: string;
|
|
55
|
+
errorTitle?: string;
|
|
56
|
+
/** Closing line of the error block; `false` or `""` leaves it out. */
|
|
57
|
+
errorRecovery?: string | false;
|
|
58
|
+
/** Native `change` event from the hidden file input. */
|
|
7
59
|
onchange?: (event: Event) => void;
|
|
60
|
+
/** Fires before the file chooser opens; `event.preventDefault()` keeps it closed. */
|
|
8
61
|
onclick?: (event: Event) => void;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
62
|
+
/** Replaces the native file chooser, for a media library or another source. */
|
|
63
|
+
onbrowse?: (event: Event) => void;
|
|
64
|
+
/** Selected file and preview URL; `{ file: null, url: null }` on remove. */
|
|
65
|
+
onfileselect?: (detail: ImageUploadFileDetail) => void;
|
|
66
|
+
/** A dropped file that `accept` does not allow. */
|
|
67
|
+
onfilereject?: (file: File) => void;
|
|
12
68
|
};
|
|
13
|
-
declare const ImageUpload: import("svelte").Component
|
|
69
|
+
declare const ImageUpload: import("svelte").Component<Props, {}, "value">;
|
|
14
70
|
type ImageUpload = ReturnType<typeof ImageUpload>;
|
|
15
71
|
export default ImageUpload;
|