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,433 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { mergeStrings } from "../util/ready-made-strings.js";
|
|
3
|
+
import { zabiStringsFor } from "../util/zabi-strings.js";
|
|
4
|
+
import { untrack } from "svelte";
|
|
5
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
6
|
+
import Check from "@lucide/svelte/icons/check";
|
|
7
|
+
import ImageOff from "@lucide/svelte/icons/image-off";
|
|
8
|
+
import Maximize2 from "@lucide/svelte/icons/maximize-2";
|
|
9
|
+
import Plus from "@lucide/svelte/icons/plus";
|
|
10
|
+
import Skeleton from "../atoms/Skeleton.svelte";
|
|
11
|
+
import { isDevBuild } from "../util/app-shell.js";
|
|
12
|
+
import { cn } from "../util/cn.js";
|
|
13
|
+
import { columnCount, moveForKey, moveIndex } from "../util/media-grid.js";
|
|
14
|
+
import {
|
|
15
|
+
PHOTO_GRID_STRINGS,
|
|
16
|
+
photoKey,
|
|
17
|
+
visibleCount,
|
|
18
|
+
type Photo,
|
|
19
|
+
type PhotoGridSelectDetail,
|
|
20
|
+
type PhotoGridStrings,
|
|
21
|
+
type PhotoKey,
|
|
22
|
+
} from "../util/photo.js";
|
|
23
|
+
import { generateId } from "../util/ssr-safe.js";
|
|
24
|
+
import { TOUCH_HIT_AREA } from "../util/touch-target.js";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A grid of square photo thumbnails. A press on one tells you which, so
|
|
28
|
+
* you can open it in a `PhotoViewer`:
|
|
29
|
+
*
|
|
30
|
+
* ```svelte
|
|
31
|
+
* <PhotoGrid {photos} max={9} onopen={(at) => { index = at; open = true; }} onadd={pickFiles} />
|
|
32
|
+
* <PhotoViewer {photos} bind:index bind:isOpen={open} />
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* With `selectable` a press selects instead, for choosing a cover photo
|
|
36
|
+
* (`single`) or photos to delete (`multiple`); each tile then has a small
|
|
37
|
+
* button of its own that opens it. In `single` a press on the selected
|
|
38
|
+
* photo changes nothing: the selection is never cleared by pressing it
|
|
39
|
+
* again.
|
|
40
|
+
*
|
|
41
|
+
* The grid is one Tab stop; the arrow keys move between the tiles.
|
|
42
|
+
*
|
|
43
|
+
* Other attributes (`id`, `data-*`, ...) land on the host `<div>`; the
|
|
44
|
+
* two ARIA names go to the list.
|
|
45
|
+
*/
|
|
46
|
+
type Props = Omit<
|
|
47
|
+
HTMLAttributes<HTMLDivElement>,
|
|
48
|
+
// `onselect` is also a native event (text selection); ours replaces it.
|
|
49
|
+
"class" | "aria-label" | "aria-labelledby" | "onselect"
|
|
50
|
+
> & {
|
|
51
|
+
photos: Photo[];
|
|
52
|
+
/**
|
|
53
|
+
* How many columns. Left out, it follows the width of the grid: 3
|
|
54
|
+
* under 480px, 4 from there and 5 from 768px.
|
|
55
|
+
*/
|
|
56
|
+
columns?: number;
|
|
57
|
+
/**
|
|
58
|
+
* Shows no more than this many photos. The last tile then says how
|
|
59
|
+
* many more there are ("+12") and opens the viewer at its photo.
|
|
60
|
+
*/
|
|
61
|
+
max?: number;
|
|
62
|
+
/** Called with the photo's place in `photos` when one is opened. */
|
|
63
|
+
onopen?: (index: number) => void;
|
|
64
|
+
/**
|
|
65
|
+
* Makes a press select: `single` for one photo, `multiple` for any
|
|
66
|
+
* number, with a check mark on each selected tile.
|
|
67
|
+
*/
|
|
68
|
+
selectable?: "single" | "multiple";
|
|
69
|
+
/** Key of the selected photo, or `null`; supports `bind:selected`. Used with `selectable="single"`. */
|
|
70
|
+
selected?: PhotoKey | null;
|
|
71
|
+
/** Keys of the selected photos; supports `bind:selectedKeys`. Used with `selectable="multiple"`. */
|
|
72
|
+
selectedKeys?: PhotoKey[];
|
|
73
|
+
/** Called when a photo is selected or, in `multiple`, its selection is cleared. */
|
|
74
|
+
onselect?: (detail: PhotoGridSelectDetail) => void;
|
|
75
|
+
/**
|
|
76
|
+
* Puts an "Add photo" tile first and is called when it is pressed.
|
|
77
|
+
* Open your own file picker or an ImageUpload from it.
|
|
78
|
+
*/
|
|
79
|
+
onadd?: () => void;
|
|
80
|
+
/** Overrides for the built-in strings. */
|
|
81
|
+
strings?: Partial<PhotoGridStrings>;
|
|
82
|
+
/** Extra classes for the host element. */
|
|
83
|
+
class?: string;
|
|
84
|
+
"aria-label"?: string;
|
|
85
|
+
"aria-labelledby"?: string;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
let {
|
|
89
|
+
photos,
|
|
90
|
+
columns,
|
|
91
|
+
max,
|
|
92
|
+
onopen,
|
|
93
|
+
selectable,
|
|
94
|
+
selected = $bindable<Exclude<Props["selected"], undefined>>(),
|
|
95
|
+
selectedKeys = $bindable<Exclude<Props["selectedKeys"], undefined>>(),
|
|
96
|
+
onselect,
|
|
97
|
+
onadd,
|
|
98
|
+
strings,
|
|
99
|
+
class: className = "",
|
|
100
|
+
"aria-label": ariaLabel,
|
|
101
|
+
"aria-labelledby": ariaLabelledby,
|
|
102
|
+
...restProps
|
|
103
|
+
}: Props = $props();
|
|
104
|
+
|
|
105
|
+
// No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
|
|
106
|
+
// one that has a fallback (`props_invalid_value`), and a page that throws
|
|
107
|
+
// while it hydrates never becomes interactive. The default is applied
|
|
108
|
+
// here instead: at once, for the server and the first render, and again
|
|
109
|
+
// whenever a parent hands back `undefined`.
|
|
110
|
+
const applyDefaults = () => {
|
|
111
|
+
if (selected === undefined) selected = null;
|
|
112
|
+
if (selectedKeys === undefined) selectedKeys = [];
|
|
113
|
+
};
|
|
114
|
+
applyDefaults();
|
|
115
|
+
$effect.pre(applyDefaults);
|
|
116
|
+
|
|
117
|
+
/** The add tile's place among the tile keys. No photo has this key: it is not an address. */
|
|
118
|
+
const ADD_KEY = "\u0000add";
|
|
119
|
+
type TileKey = PhotoKey;
|
|
120
|
+
|
|
121
|
+
/** The app-wide words for this component, from a `ZabiStringsProvider` above it, if there is one. */
|
|
122
|
+
const provided = zabiStringsFor("photoGrid");
|
|
123
|
+
const text = $derived(mergeStrings(PHOTO_GRID_STRINGS, provided(), strings));
|
|
124
|
+
const hintId = generateId("photo-grid-hint");
|
|
125
|
+
|
|
126
|
+
const counts = $derived(visibleCount(photos.length, max));
|
|
127
|
+
const shown = $derived(photos.slice(0, counts.shown));
|
|
128
|
+
const multiple = $derived(selectable === "multiple");
|
|
129
|
+
|
|
130
|
+
/** Every tile in order, by key: the add tile, then the photos shown. */
|
|
131
|
+
const tileKeys = $derived<TileKey[]>([...(onadd ? [ADD_KEY] : []), ...shown.map(photoKey)]);
|
|
132
|
+
|
|
133
|
+
$effect(() => {
|
|
134
|
+
if (!isDevBuild()) return;
|
|
135
|
+
const unnamed = photos.filter((photo) => !photo.alt?.trim()).length;
|
|
136
|
+
if (unnamed > 0) {
|
|
137
|
+
console.warn(
|
|
138
|
+
`[zabi-components] PhotoGrid: ${unnamed} of ${photos.length} photos have no alt text. ` +
|
|
139
|
+
'Each is named "Photo 3 of 12" until it has one.',
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
function nameOf(photo: Photo, index: number): string {
|
|
145
|
+
return photo.alt?.trim() || text.photoName(index + 1, photos.length);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let host: HTMLDivElement | undefined = $state();
|
|
149
|
+
/** Thumbnails that have loaded, and those that failed; a changed address is tried again. */
|
|
150
|
+
let loaded = $state<string[]>([]);
|
|
151
|
+
let failed = $state<string[]>([]);
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* An image that was already there when the page started running (the
|
|
155
|
+
* server sent it, the cache had it) fired its `load` before anything was
|
|
156
|
+
* listening.
|
|
157
|
+
*/
|
|
158
|
+
function settled(image: HTMLImageElement) {
|
|
159
|
+
if (!image.complete) return;
|
|
160
|
+
const address = image.getAttribute("src") ?? "";
|
|
161
|
+
// Untracked: this runs as an attachment, which would otherwise run
|
|
162
|
+
// again for every image each time one of the lists changes.
|
|
163
|
+
untrack(() => {
|
|
164
|
+
if (image.naturalWidth > 0) markLoaded(address);
|
|
165
|
+
else markFailed(address);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function markLoaded(address: string) {
|
|
170
|
+
if (!loaded.includes(address)) loaded = [...loaded, address];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function markFailed(address: string) {
|
|
174
|
+
if (!failed.includes(address)) failed = [...failed, address];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The tile the arrow keys last reached. Only its buttons are in the Tab
|
|
179
|
+
* order (a roving tabindex), so the grid is one stop however many photos
|
|
180
|
+
* it holds.
|
|
181
|
+
*/
|
|
182
|
+
let activeKey = $state<TileKey | null>(null);
|
|
183
|
+
const tabbableKey = $derived.by(() => {
|
|
184
|
+
if (activeKey !== null && tileKeys.includes(activeKey)) return activeKey;
|
|
185
|
+
if (selectable === "single" && selected !== null && tileKeys.includes(selected)) return selected;
|
|
186
|
+
return tileKeys[0] ?? null;
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
/** As in MediaGrid: the hint describes the tile focus arrives on, and only that one. */
|
|
190
|
+
let focusInside = $state(false);
|
|
191
|
+
let entryKey = $state<TileKey | null>(null);
|
|
192
|
+
const describedKey = $derived(focusInside ? entryKey : tabbableKey);
|
|
193
|
+
|
|
194
|
+
function handleTileFocus(key: TileKey) {
|
|
195
|
+
// Runs before the host's own focusin, which sets `focusInside`.
|
|
196
|
+
if (!focusInside) entryKey = key;
|
|
197
|
+
activeKey = key;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function handleHostFocusOut(event: FocusEvent) {
|
|
201
|
+
const next = event.relatedTarget;
|
|
202
|
+
if (!(next instanceof Node) || !host?.contains(next)) focusInside = false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function isSelected(key: PhotoKey): boolean {
|
|
206
|
+
if (!selectable) return false;
|
|
207
|
+
return multiple ? selectedKeys.includes(key) : selected === key;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function toggle(photo: Photo, index: number) {
|
|
211
|
+
const key = photoKey(photo);
|
|
212
|
+
const on = !isSelected(key);
|
|
213
|
+
if (multiple) {
|
|
214
|
+
selectedKeys = on
|
|
215
|
+
? [...selectedKeys, key]
|
|
216
|
+
: selectedKeys.filter((candidate) => candidate !== key);
|
|
217
|
+
onselect?.({ photo, index, selected: on, keys: selectedKeys });
|
|
218
|
+
} else {
|
|
219
|
+
// A single choice is never cleared by pressing it again; only the
|
|
220
|
+
// checkbox-like `multiple` mode toggles off.
|
|
221
|
+
if (!on) return;
|
|
222
|
+
selected = key;
|
|
223
|
+
onselect?.({ photo, index, selected: true, keys: [key] });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** The tile that stands for the photos not shown always opens: it is not one photo to select. */
|
|
228
|
+
function press(photo: Photo, index: number, standsForMore: boolean) {
|
|
229
|
+
if (selectable && !standsForMore) toggle(photo, index);
|
|
230
|
+
else onopen?.(index);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function mainButtons(): HTMLElement[] {
|
|
234
|
+
return host ? Array.from(host.querySelectorAll<HTMLElement>("[data-photo-grid-item]")) : [];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
238
|
+
const tile = (event.target as HTMLElement).closest<HTMLElement>("[data-photo-grid-tile]");
|
|
239
|
+
if (!tile || event.altKey || event.metaKey) return;
|
|
240
|
+
const buttons = mainButtons();
|
|
241
|
+
const index = buttons.findIndex((button) => tile.contains(button));
|
|
242
|
+
if (index === -1) return;
|
|
243
|
+
// The arrows follow what is on screen, which is mirrored in RTL.
|
|
244
|
+
const move = moveForKey(event, getComputedStyle(tile).direction === "rtl");
|
|
245
|
+
if (!move) return;
|
|
246
|
+
// Also keeps the arrow keys from scrolling the page under the grid.
|
|
247
|
+
event.preventDefault();
|
|
248
|
+
const perRow = columnCount(buttons.map((button) => button.getBoundingClientRect().top));
|
|
249
|
+
const target = moveIndex(index, move, perRow, buttons.length);
|
|
250
|
+
if (target !== index) buttons[target]?.focus();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* The tile's radius is the container role's. What sits 4px inside a
|
|
255
|
+
* corner of it (the check mark, the open button) takes that radius less
|
|
256
|
+
* the 4px, so the two curves stay parallel.
|
|
257
|
+
*/
|
|
258
|
+
const cornerMark =
|
|
259
|
+
"absolute flex items-center justify-center rounded-[calc(var(--radius-container)-4px)]";
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The tile's edge is an outline drawn inside it, not a border: a border
|
|
263
|
+
* would move everything in the tile in by its own width, and by another
|
|
264
|
+
* pixel when the tile is selected, and the marks would no longer be 4px
|
|
265
|
+
* from the corner they are rounded for.
|
|
266
|
+
*/
|
|
267
|
+
const tileButton =
|
|
268
|
+
"focus-ring relative block size-full cursor-pointer overflow-hidden rounded-container bg-(--color-surface-2) transition-[outline-color,scale] duration-150 motion-reduce:transition-none";
|
|
269
|
+
</script>
|
|
270
|
+
|
|
271
|
+
<div
|
|
272
|
+
bind:this={host}
|
|
273
|
+
class={cn("group/photos @container relative", className)}
|
|
274
|
+
data-photo-grid
|
|
275
|
+
onfocusin={() => (focusInside = true)}
|
|
276
|
+
onfocusout={handleHostFocusOut}
|
|
277
|
+
{...restProps}
|
|
278
|
+
>
|
|
279
|
+
<!-- The reset removes the markers, and Safari then drops the list role. -->
|
|
280
|
+
<!-- The gap is in px: it is the line between two photos, not text spacing. -->
|
|
281
|
+
<!-- svelte-ignore a11y_no_redundant_roles, a11y_no_noninteractive_element_interactions -->
|
|
282
|
+
<ul
|
|
283
|
+
role="list"
|
|
284
|
+
class={cn(
|
|
285
|
+
"m-0 grid list-none gap-[4px] p-0",
|
|
286
|
+
// By the width of the grid itself, so it is right in a sheet or a
|
|
287
|
+
// sidebar too. In px: the count must not change with the text size.
|
|
288
|
+
columns === undefined && "grid-cols-3 @[480px]:grid-cols-4 @[768px]:grid-cols-5",
|
|
289
|
+
)}
|
|
290
|
+
style:grid-template-columns={columns === undefined
|
|
291
|
+
? undefined
|
|
292
|
+
: `repeat(${Math.max(1, Math.floor(columns))}, minmax(0, 1fr))`}
|
|
293
|
+
aria-label={ariaLabel}
|
|
294
|
+
aria-labelledby={ariaLabelledby}
|
|
295
|
+
onkeydown={handleKeydown}
|
|
296
|
+
>
|
|
297
|
+
{#if onadd}
|
|
298
|
+
<li class="relative aspect-square" data-photo-grid-tile onfocusin={() => handleTileFocus(ADD_KEY)}>
|
|
299
|
+
<!-- A dashed edge the tile depends on to be seen, so in the
|
|
300
|
+
control-border colour; the label is shown, not only read out. -->
|
|
301
|
+
<button
|
|
302
|
+
type="button"
|
|
303
|
+
class="focus-ring flex size-full cursor-pointer flex-col items-center justify-center gap-1 rounded-container border border-dashed border-control-border bg-transparent p-1 text-center text-xs font-medium text-body transition-colors duration-150 hover:bg-surface-hover active:bg-surface-active motion-reduce:transition-none"
|
|
304
|
+
aria-describedby={describedKey === ADD_KEY ? hintId : undefined}
|
|
305
|
+
tabindex={tabbableKey === ADD_KEY ? 0 : -1}
|
|
306
|
+
data-photo-grid-item
|
|
307
|
+
data-photo-grid-add
|
|
308
|
+
onclick={() => onadd?.()}
|
|
309
|
+
>
|
|
310
|
+
<Plus size={24} class="shrink-0" aria-hidden="true" />
|
|
311
|
+
<span class="[overflow-wrap:anywhere]">{text.addPhoto}</span>
|
|
312
|
+
</button>
|
|
313
|
+
</li>
|
|
314
|
+
{/if}
|
|
315
|
+
{#each shown as photo, index (photoKey(photo))}
|
|
316
|
+
{@const key = photoKey(photo)}
|
|
317
|
+
{@const name = nameOf(photo, index)}
|
|
318
|
+
{@const source = photo.thumbSrc ?? photo.src}
|
|
319
|
+
{@const more = counts.hidden > 0 && index === shown.length - 1}
|
|
320
|
+
{@const on = isSelected(key)}
|
|
321
|
+
{@const tabindex = key === tabbableKey ? 0 : -1}
|
|
322
|
+
{@const selects = !!selectable && !more}
|
|
323
|
+
<li
|
|
324
|
+
class="relative aspect-square"
|
|
325
|
+
data-photo-grid-tile
|
|
326
|
+
data-selected={on ? "" : undefined}
|
|
327
|
+
onfocusin={() => handleTileFocus(key)}
|
|
328
|
+
>
|
|
329
|
+
<!-- Selection is a thicker edge and a check mark, not a colour
|
|
330
|
+
alone. `data-photo-key` is how a PhotoViewer finds the tile of
|
|
331
|
+
the photo it shows, to give focus back to it. -->
|
|
332
|
+
<button
|
|
333
|
+
type="button"
|
|
334
|
+
class={cn(
|
|
335
|
+
tileButton,
|
|
336
|
+
on
|
|
337
|
+
? "outline-2 -outline-offset-2 outline-(--color-action-primary)"
|
|
338
|
+
: "outline-1 -outline-offset-1 outline-(--color-border) hover:outline-(--color-border-medium) active:scale-[0.98] motion-reduce:active:scale-100",
|
|
339
|
+
)}
|
|
340
|
+
aria-label={more ? `${name}, ${text.morePhotos(counts.hidden)}` : name}
|
|
341
|
+
aria-pressed={selects ? on : undefined}
|
|
342
|
+
aria-describedby={key === describedKey ? hintId : undefined}
|
|
343
|
+
{tabindex}
|
|
344
|
+
data-photo-grid-item
|
|
345
|
+
data-photo-key={key}
|
|
346
|
+
onclick={() => press(photo, index, more)}
|
|
347
|
+
>
|
|
348
|
+
{#if failed.includes(source)}
|
|
349
|
+
<span
|
|
350
|
+
class="flex size-full items-center justify-center text-description"
|
|
351
|
+
data-photo-grid-fallback
|
|
352
|
+
>
|
|
353
|
+
<ImageOff size={24} aria-hidden="true" />
|
|
354
|
+
</span>
|
|
355
|
+
{:else}
|
|
356
|
+
{#if !loaded.includes(source)}
|
|
357
|
+
<span class="absolute inset-0" aria-hidden="true" data-photo-grid-skeleton>
|
|
358
|
+
<Skeleton variant="block" class="h-full rounded-none" />
|
|
359
|
+
</span>
|
|
360
|
+
{/if}
|
|
361
|
+
<!-- The button carries the name; the image would only repeat it. -->
|
|
362
|
+
<img
|
|
363
|
+
src={source}
|
|
364
|
+
alt=""
|
|
365
|
+
loading="lazy"
|
|
366
|
+
decoding="async"
|
|
367
|
+
draggable="false"
|
|
368
|
+
class={cn(
|
|
369
|
+
"relative size-full object-cover transition-opacity duration-200 motion-reduce:transition-none",
|
|
370
|
+
loaded.includes(source) ? "opacity-100" : "opacity-0",
|
|
371
|
+
)}
|
|
372
|
+
onload={() => markLoaded(source)}
|
|
373
|
+
onerror={() => markFailed(source)}
|
|
374
|
+
{@attach settled}
|
|
375
|
+
/>
|
|
376
|
+
{/if}
|
|
377
|
+
{#if on}
|
|
378
|
+
<span
|
|
379
|
+
class={cn(cornerMark, "start-[4px] top-[4px] size-6 bg-action-primary text-action-primary")}
|
|
380
|
+
data-photo-grid-check
|
|
381
|
+
>
|
|
382
|
+
<Check size={14} aria-hidden="true" />
|
|
383
|
+
</span>
|
|
384
|
+
{/if}
|
|
385
|
+
{#if more}
|
|
386
|
+
<!-- The photo is dimmed and the number sits on an opaque
|
|
387
|
+
plate, so it reads on any photo in either theme. The
|
|
388
|
+
name says it in words. -->
|
|
389
|
+
<span
|
|
390
|
+
class="absolute inset-0 flex items-center justify-center bg-overlay"
|
|
391
|
+
aria-hidden="true"
|
|
392
|
+
data-photo-grid-more
|
|
393
|
+
>
|
|
394
|
+
<span
|
|
395
|
+
class="rounded-control border border-control-border bg-surface-overlay px-2 py-1 text-base font-semibold text-headline"
|
|
396
|
+
>
|
|
397
|
+
+{counts.hidden}
|
|
398
|
+
</span>
|
|
399
|
+
</span>
|
|
400
|
+
{/if}
|
|
401
|
+
</button>
|
|
402
|
+
{#if selects && onopen}
|
|
403
|
+
<!-- Beside the tile's button, not inside it. An opaque
|
|
404
|
+
plate, so it reads over any photo; on a coarse pointer an
|
|
405
|
+
invisible layer widens what takes the tap to 44px. -->
|
|
406
|
+
<button
|
|
407
|
+
type="button"
|
|
408
|
+
class={cn(
|
|
409
|
+
cornerMark,
|
|
410
|
+
TOUCH_HIT_AREA,
|
|
411
|
+
"focus-ring bottom-[4px] end-[4px] size-7 cursor-pointer border border-control-border bg-surface-overlay text-body transition-colors duration-150 hover:text-headline motion-reduce:transition-none",
|
|
412
|
+
)}
|
|
413
|
+
aria-label={text.openPhoto(name)}
|
|
414
|
+
{tabindex}
|
|
415
|
+
data-photo-grid-open
|
|
416
|
+
onclick={() => onopen?.(index)}
|
|
417
|
+
>
|
|
418
|
+
<Maximize2 size={14} aria-hidden="true" />
|
|
419
|
+
</button>
|
|
420
|
+
{/if}
|
|
421
|
+
</li>
|
|
422
|
+
{/each}
|
|
423
|
+
</ul>
|
|
424
|
+
<!-- Hidden until the grid has keyboard focus: a sighted keyboard user
|
|
425
|
+
needs it as much as a screen reader user, and a pointer user does not. -->
|
|
426
|
+
<p
|
|
427
|
+
id={hintId}
|
|
428
|
+
class="mt-2 hidden text-xs text-description group-has-[:focus-visible]/photos:block"
|
|
429
|
+
data-photo-grid-hint
|
|
430
|
+
>
|
|
431
|
+
{text.keyboardHint}
|
|
432
|
+
</p>
|
|
433
|
+
</div>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
2
|
+
import { type Photo, type PhotoGridSelectDetail, type PhotoGridStrings, type PhotoKey } from "../util/photo.js";
|
|
3
|
+
/**
|
|
4
|
+
* A grid of square photo thumbnails. A press on one tells you which, so
|
|
5
|
+
* you can open it in a `PhotoViewer`:
|
|
6
|
+
*
|
|
7
|
+
* ```svelte
|
|
8
|
+
* <PhotoGrid {photos} max={9} onopen={(at) => { index = at; open = true; }} onadd={pickFiles} />
|
|
9
|
+
* <PhotoViewer {photos} bind:index bind:isOpen={open} />
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* With `selectable` a press selects instead, for choosing a cover photo
|
|
13
|
+
* (`single`) or photos to delete (`multiple`); each tile then has a small
|
|
14
|
+
* button of its own that opens it. In `single` a press on the selected
|
|
15
|
+
* photo changes nothing: the selection is never cleared by pressing it
|
|
16
|
+
* again.
|
|
17
|
+
*
|
|
18
|
+
* The grid is one Tab stop; the arrow keys move between the tiles.
|
|
19
|
+
*
|
|
20
|
+
* Other attributes (`id`, `data-*`, ...) land on the host `<div>`; the
|
|
21
|
+
* two ARIA names go to the list.
|
|
22
|
+
*/
|
|
23
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "aria-label" | "aria-labelledby" | "onselect"> & {
|
|
24
|
+
photos: Photo[];
|
|
25
|
+
/**
|
|
26
|
+
* How many columns. Left out, it follows the width of the grid: 3
|
|
27
|
+
* under 480px, 4 from there and 5 from 768px.
|
|
28
|
+
*/
|
|
29
|
+
columns?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Shows no more than this many photos. The last tile then says how
|
|
32
|
+
* many more there are ("+12") and opens the viewer at its photo.
|
|
33
|
+
*/
|
|
34
|
+
max?: number;
|
|
35
|
+
/** Called with the photo's place in `photos` when one is opened. */
|
|
36
|
+
onopen?: (index: number) => void;
|
|
37
|
+
/**
|
|
38
|
+
* Makes a press select: `single` for one photo, `multiple` for any
|
|
39
|
+
* number, with a check mark on each selected tile.
|
|
40
|
+
*/
|
|
41
|
+
selectable?: "single" | "multiple";
|
|
42
|
+
/** Key of the selected photo, or `null`; supports `bind:selected`. Used with `selectable="single"`. */
|
|
43
|
+
selected?: PhotoKey | null;
|
|
44
|
+
/** Keys of the selected photos; supports `bind:selectedKeys`. Used with `selectable="multiple"`. */
|
|
45
|
+
selectedKeys?: PhotoKey[];
|
|
46
|
+
/** Called when a photo is selected or, in `multiple`, its selection is cleared. */
|
|
47
|
+
onselect?: (detail: PhotoGridSelectDetail) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Puts an "Add photo" tile first and is called when it is pressed.
|
|
50
|
+
* Open your own file picker or an ImageUpload from it.
|
|
51
|
+
*/
|
|
52
|
+
onadd?: () => void;
|
|
53
|
+
/** Overrides for the built-in strings. */
|
|
54
|
+
strings?: Partial<PhotoGridStrings>;
|
|
55
|
+
/** Extra classes for the host element. */
|
|
56
|
+
class?: string;
|
|
57
|
+
"aria-label"?: string;
|
|
58
|
+
"aria-labelledby"?: string;
|
|
59
|
+
};
|
|
60
|
+
declare const PhotoGrid: import("svelte").Component<Props, {}, "selected" | "selectedKeys">;
|
|
61
|
+
type PhotoGrid = ReturnType<typeof PhotoGrid>;
|
|
62
|
+
export default PhotoGrid;
|