zabi-components 8.0.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2935 -0
- package/README.md +243 -10
- package/THEME.md +162 -332
- package/THEMING.md +1419 -259
- package/dist/atoms/ActionPanel.svelte +15 -5
- package/dist/atoms/Avatar.svelte +120 -0
- package/dist/atoms/Avatar.svelte.d.ts +46 -0
- package/dist/atoms/Badge.svelte +71 -13
- package/dist/atoms/Badge.svelte.d.ts +8 -2
- package/dist/atoms/Block.svelte +74 -0
- package/dist/atoms/Block.svelte.d.ts +29 -0
- package/dist/atoms/Button.svelte +217 -30
- package/dist/atoms/Button.svelte.d.ts +33 -4
- package/dist/atoms/Card.svelte +182 -26
- package/dist/atoms/Card.svelte.d.ts +55 -3
- package/dist/atoms/CardHeader.svelte +16 -4
- package/dist/atoms/Checkbox.svelte +81 -21
- package/dist/atoms/Checkbox.svelte.d.ts +31 -2
- package/dist/atoms/Chip.svelte +351 -0
- package/dist/atoms/Chip.svelte.d.ts +57 -0
- package/dist/atoms/CodeBlock.svelte +20 -2
- package/dist/atoms/CodeBlock.svelte.d.ts +4 -0
- package/dist/atoms/CodeField.svelte +385 -0
- package/dist/atoms/CodeField.svelte.d.ts +61 -0
- package/dist/atoms/ColorPicker.svelte +260 -49
- package/dist/atoms/ColorPicker.svelte.d.ts +3 -0
- package/dist/atoms/Container.svelte +16 -8
- package/dist/atoms/DateField.svelte +145 -0
- package/dist/atoms/DateField.svelte.d.ts +81 -0
- package/dist/atoms/FieldMessages.svelte +69 -0
- package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
- package/dist/atoms/FloatingActionButton.svelte +176 -0
- package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
- package/dist/atoms/Heading.svelte +63 -5
- package/dist/atoms/Heading.svelte.d.ts +14 -0
- package/dist/atoms/IconButton.svelte +294 -26
- package/dist/atoms/IconButton.svelte.d.ts +62 -4
- package/dist/atoms/Input.svelte +259 -53
- package/dist/atoms/Input.svelte.d.ts +33 -2
- package/dist/atoms/List.svelte +5 -1
- package/dist/atoms/ListItem.svelte +70 -13
- package/dist/atoms/ListItem.svelte.d.ts +11 -0
- package/dist/atoms/PickerField.svelte +239 -0
- package/dist/atoms/PickerField.svelte.d.ts +67 -0
- package/dist/atoms/Progress.svelte +99 -19
- package/dist/atoms/Progress.svelte.d.ts +15 -1
- package/dist/atoms/Radio.svelte +51 -3
- package/dist/atoms/Radio.svelte.d.ts +30 -2
- package/dist/atoms/Rating.svelte +620 -0
- package/dist/atoms/Rating.svelte.d.ts +77 -0
- package/dist/atoms/Select.svelte +438 -98
- package/dist/atoms/Select.svelte.d.ts +61 -3
- package/dist/atoms/SelectionControl.svelte +193 -23
- package/dist/atoms/SelectionControl.svelte.d.ts +29 -3
- package/dist/atoms/Skeleton.svelte +114 -18
- package/dist/atoms/Skeleton.svelte.d.ts +30 -3
- package/dist/atoms/SkeletonGroup.svelte +43 -0
- package/dist/atoms/SkeletonGroup.svelte.d.ts +20 -0
- package/dist/atoms/Slider.svelte +344 -0
- package/dist/atoms/Slider.svelte.d.ts +40 -0
- package/dist/atoms/Sparkline.svelte +109 -0
- package/dist/atoms/Sparkline.svelte.d.ts +27 -0
- package/dist/atoms/SpeechBubble.svelte +187 -0
- package/dist/atoms/SpeechBubble.svelte.d.ts +33 -0
- package/dist/atoms/Spinner.svelte +57 -0
- package/dist/atoms/Spinner.svelte.d.ts +19 -0
- package/dist/atoms/Stat.svelte +72 -0
- package/dist/atoms/Stat.svelte.d.ts +19 -0
- package/dist/atoms/Table.svelte +151 -6
- package/dist/atoms/Table.svelte.d.ts +15 -3
- package/dist/atoms/TemporalField.svelte +472 -0
- package/dist/atoms/TemporalField.svelte.d.ts +48 -0
- package/dist/atoms/Text.svelte +23 -11
- package/dist/atoms/Text.svelte.d.ts +6 -3
- package/dist/atoms/Textarea.svelte +150 -44
- package/dist/atoms/Textarea.svelte.d.ts +35 -2
- package/dist/atoms/ThemeToggle.svelte +223 -44
- package/dist/atoms/ThemeToggle.svelte.d.ts +26 -3
- package/dist/atoms/TimeColumn.svelte +150 -0
- package/dist/atoms/TimeColumn.svelte.d.ts +25 -0
- package/dist/atoms/TimeField.svelte +151 -0
- package/dist/atoms/TimeField.svelte.d.ts +85 -0
- package/dist/atoms/Toast.svelte +22 -8
- package/dist/atoms/Toast.svelte.d.ts +2 -0
- package/dist/atoms/Toggle.svelte +98 -14
- package/dist/atoms/Toggle.svelte.d.ts +26 -2
- package/dist/atoms/Tooltip.svelte +528 -24
- package/dist/atoms/Tooltip.svelte.d.ts +18 -0
- package/dist/atoms/ZabiStringsProvider.svelte +45 -0
- package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
- package/dist/atoms/index.d.ts +38 -0
- package/dist/atoms/index.js +29 -0
- package/dist/atoms/selection-control.styles.d.ts +47 -5
- package/dist/atoms/selection-control.styles.js +52 -10
- package/dist/components/atoms/index.d.ts +38 -0
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/atoms/selection-control.styles.d.ts +47 -5
- package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
- package/dist/components/index.d.ts +104 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/map.d.ts +11 -0
- package/dist/components/map.d.ts.map +1 -0
- package/dist/components/molecules/index.d.ts +51 -0
- package/dist/components/molecules/index.d.ts.map +1 -1
- package/dist/components/molecules/toast-store.d.ts +83 -7
- package/dist/components/molecules/toast-store.d.ts.map +1 -1
- package/dist/components/organisms/index.d.ts +10 -0
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/components/types/variants.d.ts +24 -1
- package/dist/components/types/variants.d.ts.map +1 -1
- package/dist/components/util/app-shell.d.ts +79 -0
- package/dist/components/util/app-shell.d.ts.map +1 -0
- package/dist/components/util/avatar.d.ts +39 -0
- package/dist/components/util/avatar.d.ts.map +1 -0
- package/dist/components/util/bottom-sheet.d.ts +30 -0
- package/dist/components/util/bottom-sheet.d.ts.map +1 -0
- package/dist/components/util/bottom-tab-bar.d.ts +41 -0
- package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
- package/dist/components/util/calendar.d.ts +56 -0
- package/dist/components/util/calendar.d.ts.map +1 -0
- package/dist/components/util/chart.d.ts +140 -0
- package/dist/components/util/chart.d.ts.map +1 -0
- package/dist/components/util/chip.d.ts +66 -0
- package/dist/components/util/chip.d.ts.map +1 -0
- package/dist/components/util/cn.d.ts.map +1 -1
- package/dist/components/util/code-field.d.ts +63 -0
- package/dist/components/util/code-field.d.ts.map +1 -0
- package/dist/components/util/collapsible.d.ts +64 -0
- package/dist/components/util/collapsible.d.ts.map +1 -0
- package/dist/components/util/confirm-dialog.d.ts +12 -0
- package/dist/components/util/confirm-dialog.d.ts.map +1 -0
- package/dist/components/util/date.d.ts +96 -0
- package/dist/components/util/date.d.ts.map +1 -0
- package/dist/components/util/display-voice.d.ts +10 -0
- package/dist/components/util/display-voice.d.ts.map +1 -0
- package/dist/components/util/drawer.d.ts +13 -0
- package/dist/components/util/drawer.d.ts.map +1 -0
- package/dist/components/util/dropdown.d.ts +47 -0
- package/dist/components/util/dropdown.d.ts.map +1 -0
- package/dist/components/util/field-control.d.ts +30 -0
- package/dist/components/util/field-control.d.ts.map +1 -0
- package/dist/components/util/field.d.ts +45 -0
- package/dist/components/util/field.d.ts.map +1 -0
- package/dist/components/util/fit-in-viewport.d.ts +201 -0
- package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
- package/dist/components/util/focus-utils.d.ts +77 -2
- package/dist/components/util/focus-utils.d.ts.map +1 -1
- package/dist/components/util/hydration.d.ts +45 -0
- package/dist/components/util/hydration.d.ts.map +1 -0
- package/dist/components/util/keyboard-inset.d.ts +18 -0
- package/dist/components/util/keyboard-inset.d.ts.map +1 -0
- package/dist/components/util/map-view.d.ts +144 -0
- package/dist/components/util/map-view.d.ts.map +1 -0
- package/dist/components/util/media-grid.d.ts +51 -0
- package/dist/components/util/media-grid.d.ts.map +1 -0
- package/dist/components/util/media.d.ts +6 -0
- package/dist/components/util/media.d.ts.map +1 -0
- package/dist/components/util/overlay-chrome.d.ts +53 -0
- package/dist/components/util/overlay-chrome.d.ts.map +1 -0
- package/dist/components/util/overlay.d.ts +72 -0
- package/dist/components/util/overlay.d.ts.map +1 -0
- package/dist/components/util/photo.d.ts +234 -0
- package/dist/components/util/photo.d.ts.map +1 -0
- package/dist/components/util/portal.d.ts +23 -0
- package/dist/components/util/portal.d.ts.map +1 -0
- package/dist/components/util/progress.d.ts +19 -0
- package/dist/components/util/progress.d.ts.map +1 -0
- package/dist/components/util/pull-to-refresh.d.ts +27 -0
- package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
- package/dist/components/util/radio-keys.d.ts +25 -0
- package/dist/components/util/radio-keys.d.ts.map +1 -0
- package/dist/components/util/rating.d.ts +23 -0
- package/dist/components/util/rating.d.ts.map +1 -0
- package/dist/components/util/ready-made-strings.d.ts +77 -0
- package/dist/components/util/ready-made-strings.d.ts.map +1 -0
- package/dist/components/util/scroll-edge.d.ts +33 -0
- package/dist/components/util/scroll-edge.d.ts.map +1 -0
- package/dist/components/util/scroll-padding.d.ts +20 -0
- package/dist/components/util/scroll-padding.d.ts.map +1 -0
- package/dist/components/util/segmented-control.d.ts +16 -0
- package/dist/components/util/segmented-control.d.ts.map +1 -0
- package/dist/components/util/select.d.ts +41 -0
- package/dist/components/util/select.d.ts.map +1 -0
- package/dist/components/util/sheet-drag.d.ts +84 -0
- package/dist/components/util/sheet-drag.d.ts.map +1 -0
- package/dist/components/util/sidebar.d.ts +63 -0
- package/dist/components/util/sidebar.d.ts.map +1 -0
- package/dist/components/util/skeleton.d.ts +15 -0
- package/dist/components/util/skeleton.d.ts.map +1 -0
- package/dist/components/util/sliding-indicator.d.ts +25 -0
- package/dist/components/util/sliding-indicator.d.ts.map +1 -0
- package/dist/components/util/sortable-list.d.ts +65 -0
- package/dist/components/util/sortable-list.d.ts.map +1 -0
- package/dist/components/util/stepper.d.ts +40 -0
- package/dist/components/util/stepper.d.ts.map +1 -0
- package/dist/components/util/swipeable-list-item.d.ts +36 -0
- package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
- package/dist/components/util/temporal-field.d.ts +89 -0
- package/dist/components/util/temporal-field.d.ts.map +1 -0
- package/dist/components/util/theme-mode.d.ts +80 -0
- package/dist/components/util/theme-mode.d.ts.map +1 -0
- package/dist/components/util/toaster.d.ts +133 -0
- package/dist/components/util/toaster.d.ts.map +1 -0
- package/dist/components/util/tone-surface.d.ts +34 -0
- package/dist/components/util/tone-surface.d.ts.map +1 -0
- package/dist/components/util/tooltip.d.ts +10 -0
- package/dist/components/util/tooltip.d.ts.map +1 -0
- package/dist/components/util/top-navbar.d.ts +35 -0
- package/dist/components/util/top-navbar.d.ts.map +1 -0
- package/dist/components/util/touch-target.d.ts +31 -0
- package/dist/components/util/touch-target.d.ts.map +1 -0
- package/dist/components/util/zabi-strings.d.ts +142 -0
- package/dist/components/util/zabi-strings.d.ts.map +1 -0
- package/dist/create-theme/cli.js +173 -0
- package/dist/create-theme/index.d.ts +190 -0
- package/dist/create-theme/index.js +1034 -0
- package/dist/create-theme/lib/ramp-math.js +128 -0
- package/dist/create-theme/lib/resolve.js +238 -0
- package/dist/create-theme/theme-data.js +2644 -0
- package/dist/index.d.ts +104 -2
- package/dist/index.js +75 -2
- package/dist/map.d.ts +10 -0
- package/dist/map.js +9 -0
- package/dist/molecules/Alert.svelte +63 -16
- package/dist/molecules/Alert.svelte.d.ts +14 -1
- package/dist/molecules/AppBar.svelte +674 -0
- package/dist/molecules/AppBar.svelte.d.ts +131 -0
- package/dist/molecules/AvatarGroup.svelte +116 -0
- package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
- package/dist/molecules/BarChart.svelte +381 -0
- package/dist/molecules/BarChart.svelte.d.ts +45 -0
- package/dist/molecules/BottomSheet.svelte +608 -0
- package/dist/molecules/BottomSheet.svelte.d.ts +109 -0
- package/dist/molecules/BottomTabBar.svelte +403 -0
- package/dist/molecules/BottomTabBar.svelte.d.ts +98 -0
- package/dist/molecules/Calendar.svelte +597 -0
- package/dist/molecules/Calendar.svelte.d.ts +72 -0
- package/dist/molecules/ChipGroup.svelte +325 -0
- package/dist/molecules/ChipGroup.svelte.d.ts +41 -0
- package/dist/molecules/Collapsible.svelte +347 -0
- package/dist/molecules/Collapsible.svelte.d.ts +61 -0
- package/dist/molecules/CollapsibleGroup.svelte +136 -0
- package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
- package/dist/molecules/ComponentDemo.svelte +16 -4
- package/dist/molecules/ComponentDemo.svelte.d.ts +3 -0
- package/dist/molecules/ConfirmDialog.svelte +257 -0
- package/dist/molecules/ConfirmDialog.svelte.d.ts +58 -0
- package/dist/molecules/ContactForm.svelte +34 -17
- package/dist/molecules/ContactForm.svelte.d.ts +8 -0
- package/dist/molecules/Drawer.svelte +415 -0
- package/dist/molecules/Drawer.svelte.d.ts +55 -0
- package/dist/molecules/Dropdown.svelte +517 -48
- package/dist/molecules/Dropdown.svelte.d.ts +47 -6
- package/dist/molecules/DropdownItem.svelte +122 -0
- package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
- package/dist/molecules/EmptyState.svelte +55 -10
- package/dist/molecules/EmptyState.svelte.d.ts +8 -0
- package/dist/molecules/FormField.svelte +9 -1
- package/dist/molecules/FormField.svelte.d.ts +2 -0
- package/dist/molecules/Header.svelte +1 -1
- package/dist/molecules/ImageUpload.svelte +492 -69
- package/dist/molecules/ImageUpload.svelte.d.ts +82 -2
- package/dist/molecules/LineChart.svelte +459 -0
- package/dist/molecules/LineChart.svelte.d.ts +53 -0
- package/dist/molecules/MapView.svelte +565 -0
- package/dist/molecules/MapView.svelte.d.ts +88 -0
- package/dist/molecules/MediaGrid.svelte +527 -0
- package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
- package/dist/molecules/Modal.svelte +293 -72
- package/dist/molecules/Modal.svelte.d.ts +51 -2
- package/dist/molecules/NavigationMenuContent.svelte +58 -1
- package/dist/molecules/NavigationMenuItem.svelte +0 -1
- package/dist/molecules/NavigationMenuLink.svelte +1 -1
- package/dist/molecules/NavigationMenuList.svelte +10 -3
- package/dist/molecules/NavigationMenuTrigger.svelte +2 -2
- package/dist/molecules/Page.svelte +32 -2
- package/dist/molecules/Page.svelte.d.ts +11 -0
- package/dist/molecules/PhotoGrid.svelte +523 -0
- package/dist/molecules/PhotoGrid.svelte.d.ts +73 -0
- package/dist/molecules/PhotoViewer.svelte +1213 -0
- package/dist/molecules/PhotoViewer.svelte.d.ts +74 -0
- package/dist/molecules/PieChart.svelte +421 -0
- package/dist/molecules/PieChart.svelte.d.ts +51 -0
- package/dist/molecules/PropsTable.svelte +22 -10
- package/dist/molecules/PropsTable.svelte.d.ts +3 -0
- package/dist/molecules/PullToRefresh.svelte +263 -0
- package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
- package/dist/molecules/RadioGroup.svelte +72 -18
- package/dist/molecules/RadioGroup.svelte.d.ts +19 -3
- package/dist/molecules/Section.svelte +1 -1
- package/dist/molecules/SegmentedControl.svelte +456 -0
- package/dist/molecules/SegmentedControl.svelte.d.ts +40 -0
- package/dist/molecules/SidebarBrandHeader.svelte +11 -1
- package/dist/molecules/SidebarBrandHeader.svelte.d.ts +3 -0
- package/dist/molecules/SidebarFooter.svelte +30 -7
- package/dist/molecules/SidebarFooter.svelte.d.ts +3 -0
- package/dist/molecules/SidebarNavSection.svelte +1 -1
- package/dist/molecules/SlideUp.svelte +220 -61
- package/dist/molecules/SlideUp.svelte.d.ts +26 -0
- package/dist/molecules/SortableList.svelte +633 -0
- package/dist/molecules/SortableList.svelte.d.ts +69 -0
- package/dist/molecules/Stepper.svelte +562 -0
- package/dist/molecules/Stepper.svelte.d.ts +39 -0
- package/dist/molecules/StickyActionBar.svelte +250 -0
- package/dist/molecules/StickyActionBar.svelte.d.ts +78 -0
- package/dist/molecules/SwipeableListItem.svelte +334 -0
- package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
- package/dist/molecules/Tabs.svelte +310 -9
- package/dist/molecules/Tabs.svelte.d.ts +7 -0
- package/dist/molecules/Toaster.svelte +514 -5
- package/dist/molecules/Toaster.svelte.d.ts +153 -2
- package/dist/molecules/ToasterToast.svelte +495 -77
- package/dist/molecules/ToasterToast.svelte.d.ts +15 -0
- package/dist/molecules/UnsavedChangesBar.svelte +223 -0
- package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
- package/dist/molecules/index.d.ts +51 -0
- package/dist/molecules/index.js +31 -0
- package/dist/molecules/toast-store.d.ts +83 -7
- package/dist/molecules/toast-store.js +25 -1
- package/dist/organisms/AppNavigation.svelte +440 -0
- package/dist/organisms/AppNavigation.svelte.d.ts +72 -0
- package/dist/organisms/AppShell.svelte +696 -0
- package/dist/organisms/AppShell.svelte.d.ts +157 -0
- package/dist/organisms/AppShellHeaderPart.svelte +163 -0
- package/dist/organisms/AppShellHeaderPart.svelte.d.ts +57 -0
- package/dist/organisms/SidebarAccountPanel.svelte +107 -12
- package/dist/organisms/SidebarAccountPanel.svelte.d.ts +19 -0
- package/dist/organisms/SidebarNavigation.svelte +111 -43
- package/dist/organisms/SidebarNavigation.svelte.d.ts +29 -1
- package/dist/organisms/SidebarPanel.svelte +34 -17
- package/dist/organisms/SidebarPanel.svelte.d.ts +1 -0
- package/dist/organisms/SidebarShell.svelte +164 -14
- package/dist/organisms/SidebarShell.svelte.d.ts +56 -2
- package/dist/organisms/TopNavbar.svelte +353 -39
- package/dist/organisms/TopNavbar.svelte.d.ts +47 -0
- package/dist/organisms/TopNavbarSheetContent.svelte +145 -0
- package/dist/organisms/TopNavbarSheetContent.svelte.d.ts +24 -0
- package/dist/organisms/index.d.ts +10 -0
- package/dist/organisms/index.js +7 -0
- package/dist/types/components.d.ts +21 -1
- package/dist/types/components.d.ts.map +1 -1
- package/dist/types/index.d.ts +1093 -26
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/variants.ts +28 -1
- package/dist/util/app-shell.d.ts +78 -0
- package/dist/util/app-shell.js +97 -0
- package/dist/util/avatar.d.ts +38 -0
- package/dist/util/avatar.js +52 -0
- package/dist/util/bottom-sheet.d.ts +29 -0
- package/dist/util/bottom-sheet.js +43 -0
- package/dist/util/bottom-tab-bar.d.ts +40 -0
- package/dist/util/bottom-tab-bar.js +54 -0
- package/dist/util/calendar.d.ts +55 -0
- package/dist/util/calendar.js +74 -0
- package/dist/util/chart.d.ts +139 -0
- package/dist/util/chart.js +305 -0
- package/dist/util/chip.d.ts +65 -0
- package/dist/util/chip.js +58 -0
- package/dist/util/cn.js +28 -2
- package/dist/util/code-field.d.ts +62 -0
- package/dist/util/code-field.js +112 -0
- package/dist/util/collapsible.d.ts +63 -0
- package/dist/util/collapsible.js +23 -0
- package/dist/util/confirm-dialog.d.ts +11 -0
- package/dist/util/confirm-dialog.js +2 -0
- package/dist/util/date.d.ts +95 -0
- package/dist/util/date.js +237 -0
- package/dist/util/display-voice.d.ts +9 -0
- package/dist/util/display-voice.js +9 -0
- package/dist/util/drawer.d.ts +12 -0
- package/dist/util/drawer.js +7 -0
- package/dist/util/dropdown.d.ts +46 -0
- package/dist/util/dropdown.js +21 -0
- package/dist/util/field-control.d.ts +29 -0
- package/dist/util/field-control.js +32 -0
- package/dist/util/field.d.ts +44 -0
- package/dist/util/field.js +38 -0
- package/dist/util/fit-in-viewport.d.ts +200 -0
- package/dist/util/fit-in-viewport.js +399 -0
- package/dist/util/focus-utils.d.ts +77 -2
- package/dist/util/focus-utils.js +186 -9
- package/dist/util/hydration.d.ts +44 -0
- package/dist/util/hydration.js +58 -0
- package/dist/util/keyboard-inset.d.ts +17 -0
- package/dist/util/keyboard-inset.js +53 -0
- package/dist/util/map-view.d.ts +143 -0
- package/dist/util/map-view.js +121 -0
- package/dist/util/media-grid.d.ts +50 -0
- package/dist/util/media-grid.js +81 -0
- package/dist/util/media.d.ts +5 -0
- package/dist/util/media.js +10 -0
- package/dist/util/overlay-chrome.d.ts +52 -0
- package/dist/util/overlay-chrome.js +56 -0
- package/dist/util/overlay.d.ts +71 -0
- package/dist/util/overlay.js +240 -0
- package/dist/util/photo.d.ts +233 -0
- package/dist/util/photo.js +209 -0
- package/dist/util/portal.d.ts +22 -0
- package/dist/util/portal.js +50 -0
- package/dist/util/progress.d.ts +18 -0
- package/dist/util/progress.js +17 -0
- package/dist/util/pull-to-refresh.d.ts +26 -0
- package/dist/util/pull-to-refresh.js +30 -0
- package/dist/util/radio-keys.d.ts +24 -0
- package/dist/util/radio-keys.js +49 -0
- package/dist/util/rating.d.ts +22 -0
- package/dist/util/rating.js +26 -0
- package/dist/util/ready-made-strings.d.ts +76 -0
- package/dist/util/ready-made-strings.js +67 -0
- package/dist/util/scroll-edge.d.ts +32 -0
- package/dist/util/scroll-edge.js +87 -0
- package/dist/util/scroll-padding.d.ts +19 -0
- package/dist/util/scroll-padding.js +44 -0
- package/dist/util/segmented-control.d.ts +15 -0
- package/dist/util/segmented-control.js +1 -0
- package/dist/util/select.d.ts +40 -0
- package/dist/util/select.js +63 -0
- package/dist/util/sheet-drag.d.ts +83 -0
- package/dist/util/sheet-drag.js +311 -0
- package/dist/util/sidebar.d.ts +62 -0
- package/dist/util/sidebar.js +38 -0
- package/dist/util/skeleton.d.ts +14 -0
- package/dist/util/skeleton.js +22 -0
- package/dist/util/sliding-indicator.d.ts +24 -0
- package/dist/util/sliding-indicator.js +44 -0
- package/dist/util/sortable-list.d.ts +64 -0
- package/dist/util/sortable-list.js +53 -0
- package/dist/util/stepper.d.ts +39 -0
- package/dist/util/stepper.js +38 -0
- package/dist/util/swipeable-list-item.d.ts +35 -0
- package/dist/util/swipeable-list-item.js +32 -0
- package/dist/util/temporal-field.d.ts +88 -0
- package/dist/util/temporal-field.js +136 -0
- package/dist/util/theme-mode.d.ts +79 -0
- package/dist/util/theme-mode.js +121 -0
- package/dist/util/toaster.d.ts +132 -0
- package/dist/util/toaster.js +130 -0
- package/dist/util/tone-surface.d.ts +33 -0
- package/dist/util/tone-surface.js +35 -0
- package/dist/util/tooltip.d.ts +9 -0
- package/dist/util/tooltip.js +18 -0
- package/dist/util/top-navbar.d.ts +34 -0
- package/dist/util/top-navbar.js +28 -0
- package/dist/util/touch-target.d.ts +30 -0
- package/dist/util/touch-target.js +30 -0
- package/dist/util/zabi-strings.d.ts +141 -0
- package/dist/util/zabi-strings.js +105 -0
- package/dist/zabi-components-colors.css +11 -2
- package/dist/zabi-components-theme-dark-only.css +870 -258
- package/dist/zabi-components-theme-dark.css +870 -258
- package/dist/zabi-components-theme-only.css +2419 -368
- package/dist/zabi-components-theme.css +2419 -368
- package/dist/zabi-components.css +6032 -711
- package/docs/lucide-icons.md +40 -17
- package/docs/theme-imports.md +81 -11
- package/package.json +45 -8
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, tick, untrack, type Snippet } from "svelte";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
import Check from "@lucide/svelte/icons/check";
|
|
5
|
+
import { cn } from "../util/cn.js";
|
|
6
|
+
import {
|
|
7
|
+
attributionText,
|
|
8
|
+
boundsOf,
|
|
9
|
+
clusterMarkers,
|
|
10
|
+
insetOffset,
|
|
11
|
+
MAP_FIT_MAX_ZOOM,
|
|
12
|
+
MAP_MARKER_TAP,
|
|
13
|
+
MAP_VIEW_STRINGS,
|
|
14
|
+
pointsSignature,
|
|
15
|
+
type MapInset,
|
|
16
|
+
type MapLibreLike,
|
|
17
|
+
type MapLibreMapLike,
|
|
18
|
+
type MapLngLat,
|
|
19
|
+
type MapMarker,
|
|
20
|
+
type MapPixel,
|
|
21
|
+
type MapPoint,
|
|
22
|
+
type MapPointKey,
|
|
23
|
+
type MapViewPosition,
|
|
24
|
+
type MapViewStatus,
|
|
25
|
+
type MapViewStrings,
|
|
26
|
+
} from "../util/map-view.js";
|
|
27
|
+
import { mergeStrings } from "../util/ready-made-strings.js";
|
|
28
|
+
import { isThemeDark } from "../util/theme-mode.js";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A map with markers from a list, on MapLibre GL JS. It fills the box it
|
|
32
|
+
* is given and has no control of its own: a finger pans and pinches, and
|
|
33
|
+
* everything else is the page's.
|
|
34
|
+
*
|
|
35
|
+
* ```svelte
|
|
36
|
+
* <MapView {points} styleLight={light} styleDark={dark} bind:status onpointpress={open} />
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* It comes from its own entry, `zabi-components/map`, and loads the map
|
|
40
|
+
* library (`maplibre-gl`, the app's own dependency) only when it is
|
|
41
|
+
* mounted in a browser: nothing else in the package brings any of it.
|
|
42
|
+
* Ask `mapViewSupported()` before offering a map at all.
|
|
43
|
+
*
|
|
44
|
+
* The markers are the map for a keyboard and a screen reader: each is a
|
|
45
|
+
* named button, in the order of `points`. Markers whose tap areas overlap
|
|
46
|
+
* are drawn as one with their number. Every move the component makes is a
|
|
47
|
+
* jump; the finger's own pan and pinch are the only motion.
|
|
48
|
+
*
|
|
49
|
+
* It shows no text of its own and no error: read `status` and put your
|
|
50
|
+
* own skeleton or empty state in its place.
|
|
51
|
+
*
|
|
52
|
+
* Other attributes (`id`, `data-*`, ...) land on the host `<div>`.
|
|
53
|
+
*/
|
|
54
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "aria-label"> & {
|
|
55
|
+
/** The markers, in the order they take in the tab order. */
|
|
56
|
+
points: MapPoint[];
|
|
57
|
+
/** The style drawn in the light theme: a URL or a style object. Nothing about the tile host is fixed here. */
|
|
58
|
+
styleLight: string | object;
|
|
59
|
+
/** The style drawn while the page is dark (`data-theme` on `<html>`, which may follow the system). Without it, `styleLight`. */
|
|
60
|
+
styleDark?: string | object;
|
|
61
|
+
/** Called with the point when its marker is pressed. */
|
|
62
|
+
onpointpress?: (point: MapPoint, index: number) => void;
|
|
63
|
+
/** Called with the points a count marker holds when it is pressed. */
|
|
64
|
+
onclusterpress?: (points: MapPoint[]) => void;
|
|
65
|
+
/** Names a count marker from its points. Without it, `strings.cluster` with the count. */
|
|
66
|
+
clusterLabel?: (points: MapPoint[]) => string;
|
|
67
|
+
/** The closest the fit to the markers zooms in. */
|
|
68
|
+
fitMaxZoom?: number;
|
|
69
|
+
/**
|
|
70
|
+
* The middle of the map; supports `bind:center`. Given with `zoom`
|
|
71
|
+
* when the map is made, it opens there and is not fitted to the
|
|
72
|
+
* markers; set later, the map jumps there.
|
|
73
|
+
*/
|
|
74
|
+
center?: MapLngLat;
|
|
75
|
+
/** The zoom; supports `bind:zoom`. */
|
|
76
|
+
zoom?: number;
|
|
77
|
+
/** Called when a move has ended, by the finger or by the component. */
|
|
78
|
+
onmove?: (position: MapViewPosition) => void;
|
|
79
|
+
/**
|
|
80
|
+
* The viewer's own position, from the page: a dot is drawn there and
|
|
81
|
+
* the map jumps to it when it is set or changes. The component never
|
|
82
|
+
* asks the browser for it.
|
|
83
|
+
*/
|
|
84
|
+
viewer?: MapLngLat | null;
|
|
85
|
+
/** Where the map stands; supports `bind:status`. */
|
|
86
|
+
status?: MapViewStatus;
|
|
87
|
+
onstatus?: (status: MapViewStatus) => void;
|
|
88
|
+
/**
|
|
89
|
+
* The credit that is always shown, bottom left. Without it, the words
|
|
90
|
+
* of the attribution the style's sources carry, as plain text.
|
|
91
|
+
*/
|
|
92
|
+
attribution?: string | Snippet;
|
|
93
|
+
/** Loads the map library. Default: `() => import("maplibre-gl")`. */
|
|
94
|
+
load?: () => Promise<unknown>;
|
|
95
|
+
/** Overrides for the built-in strings. */
|
|
96
|
+
strings?: Partial<MapViewStrings>;
|
|
97
|
+
/** Extra classes for the host element. */
|
|
98
|
+
class?: string;
|
|
99
|
+
/** Accessible name of the map. Without it, `strings.label`. */
|
|
100
|
+
"aria-label"?: string;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
let {
|
|
104
|
+
points,
|
|
105
|
+
styleLight,
|
|
106
|
+
styleDark,
|
|
107
|
+
onpointpress,
|
|
108
|
+
onclusterpress,
|
|
109
|
+
clusterLabel,
|
|
110
|
+
fitMaxZoom = MAP_FIT_MAX_ZOOM,
|
|
111
|
+
center = $bindable<Props["center"]>(),
|
|
112
|
+
zoom = $bindable<Props["zoom"]>(),
|
|
113
|
+
onmove,
|
|
114
|
+
viewer = null,
|
|
115
|
+
status = $bindable<Exclude<Props["status"], undefined>>(),
|
|
116
|
+
onstatus,
|
|
117
|
+
attribution,
|
|
118
|
+
load,
|
|
119
|
+
strings,
|
|
120
|
+
class: className = "",
|
|
121
|
+
"aria-label": ariaLabel,
|
|
122
|
+
...restProps
|
|
123
|
+
}: Props = $props();
|
|
124
|
+
|
|
125
|
+
// No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
|
|
126
|
+
// one that has a fallback (`props_invalid_value`), and a page that throws
|
|
127
|
+
// while it hydrates never becomes interactive. The default is applied
|
|
128
|
+
// here instead: at once, for the server and the first render, and again
|
|
129
|
+
// whenever a parent hands back `undefined`.
|
|
130
|
+
const applyDefaults = () => {
|
|
131
|
+
if (status === undefined) status = "loading";
|
|
132
|
+
};
|
|
133
|
+
applyDefaults();
|
|
134
|
+
$effect.pre(applyDefaults);
|
|
135
|
+
|
|
136
|
+
const text = $derived(mergeStrings(MAP_VIEW_STRINGS, strings));
|
|
137
|
+
|
|
138
|
+
let host = $state<HTMLDivElement>();
|
|
139
|
+
let surface = $state<HTMLDivElement>();
|
|
140
|
+
let instance: MapLibreMapLike | undefined;
|
|
141
|
+
/** The map has drawn once: it can be fitted and moved. */
|
|
142
|
+
let loaded = $state(false);
|
|
143
|
+
let dark = $state(false);
|
|
144
|
+
let markers = $state.raw<MapMarker<MapPoint>[]>([]);
|
|
145
|
+
let viewerPixel = $state.raw<MapPixel | null>(null);
|
|
146
|
+
let styleCredit = $state("");
|
|
147
|
+
|
|
148
|
+
const style = $derived(dark && styleDark ? styleDark : styleLight);
|
|
149
|
+
let appliedStyle: string | object | undefined;
|
|
150
|
+
|
|
151
|
+
function setStatus(next: MapViewStatus) {
|
|
152
|
+
if (status === next) return;
|
|
153
|
+
status = next;
|
|
154
|
+
onstatus?.(next);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// --- Tiles ---------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* "Tiles failed" is told when none has arrived, not at the first one that
|
|
161
|
+
* fails: the page puts its own message in the map's place, and a map with
|
|
162
|
+
* one hole in it is still a map. A tile that arrives later takes it back.
|
|
163
|
+
*/
|
|
164
|
+
let tilesArrived = 0;
|
|
165
|
+
let tileErrors = 0;
|
|
166
|
+
|
|
167
|
+
function handleError(event: { tile?: unknown; sourceId?: unknown }) {
|
|
168
|
+
if (event?.tile || event?.sourceId) {
|
|
169
|
+
tileErrors += 1;
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// Not a tile: before the first draw it is the style that did not come.
|
|
173
|
+
if (!loaded && tilesArrived === 0) setStatus("tiles-error");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function handleData(event: { dataType?: string; tile?: unknown }) {
|
|
177
|
+
if (event?.dataType !== "source" || !event.tile) return;
|
|
178
|
+
tilesArrived += 1;
|
|
179
|
+
if (loaded && status === "tiles-error") setStatus("ready");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function handleIdle() {
|
|
183
|
+
if (tileErrors > 0 && tilesArrived === 0) setStatus("tiles-error");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// --- Markers -------------------------------------------------------------
|
|
187
|
+
|
|
188
|
+
/** Where each point is on the screen now, and which of them share a marker. */
|
|
189
|
+
function reproject() {
|
|
190
|
+
const map = instance;
|
|
191
|
+
if (!map) return;
|
|
192
|
+
const pixels = points.map((point) => map.project([point.lng, point.lat]));
|
|
193
|
+
const before = focusedKey;
|
|
194
|
+
markers = clusterMarkers(points, pixels);
|
|
195
|
+
viewerPixel = viewer ? map.project([viewer.lng, viewer.lat]) : null;
|
|
196
|
+
if (before !== null && !markers.some((marker) => marker.key === before)) followFocus(before);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The marker that has the focus keeps its element while others join it.
|
|
201
|
+
* When it joins a marker earlier in the list its element goes; the
|
|
202
|
+
* focus follows its point to the marker that now holds it.
|
|
203
|
+
*/
|
|
204
|
+
let focusedKey: MapPointKey | null = null;
|
|
205
|
+
|
|
206
|
+
function followFocus(id: MapPointKey) {
|
|
207
|
+
const holder = markers.find((marker) => marker.points.some((point) => point.id === id));
|
|
208
|
+
focusedKey = null;
|
|
209
|
+
if (!holder) return;
|
|
210
|
+
void tick().then(() => {
|
|
211
|
+
const active = document.activeElement;
|
|
212
|
+
if (active && active !== document.body) return;
|
|
213
|
+
markerElement(holder.key)?.focus({ preventScroll: true });
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function markerElement(key: MapPointKey): HTMLElement | undefined {
|
|
218
|
+
return Array.from(host?.querySelectorAll<HTMLElement>("[data-map-marker]") ?? []).find(
|
|
219
|
+
(element) => element.dataset.pointId === String(key),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** A marker reached with Tab may be outside what is shown: the map jumps so that it is in view. */
|
|
224
|
+
function handleMarkerFocus(marker: MapMarker<MapPoint>) {
|
|
225
|
+
focusedKey = marker.key;
|
|
226
|
+
const map = instance;
|
|
227
|
+
if (!map || !host) return;
|
|
228
|
+
const inside =
|
|
229
|
+
marker.x >= 0 && marker.y >= 0 && marker.x <= host.clientWidth && marker.y <= host.clientHeight;
|
|
230
|
+
if (!inside) map.jumpTo({ center: map.unproject([marker.x, marker.y]) });
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function handleMarkerBlur(event: FocusEvent) {
|
|
234
|
+
// A focus that went somewhere is let go; one that was dropped (the element was taken out) is followed.
|
|
235
|
+
if (event.relatedTarget) focusedKey = null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function press(marker: MapMarker<MapPoint>) {
|
|
239
|
+
if (marker.points.length > 1) {
|
|
240
|
+
onclusterpress?.(marker.points);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const point = marker.points[0];
|
|
244
|
+
onpointpress?.(point, points.indexOf(point));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function nameOf(marker: MapMarker<MapPoint>): string {
|
|
248
|
+
if (marker.points.length === 1) return marker.points[0].label;
|
|
249
|
+
return clusterLabel ? clusterLabel(marker.points) : text.cluster(marker.points.length);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// --- Moving ----------------------------------------------------------------
|
|
253
|
+
|
|
254
|
+
/** Fits the map to the markers, no closer than `fitMaxZoom`. A jump. */
|
|
255
|
+
export function fit() {
|
|
256
|
+
const map = instance;
|
|
257
|
+
const box = boundsOf(points);
|
|
258
|
+
if (!map || !box) return;
|
|
259
|
+
if (box[0][0] === box[1][0] && box[0][1] === box[1][1]) {
|
|
260
|
+
map.jumpTo({ center: box[0], zoom: fitMaxZoom });
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
// Room for half a marker at each edge, so none is cut by it.
|
|
264
|
+
const camera = map.cameraForBounds(box, { padding: MAP_MARKER_TAP, maxZoom: fitMaxZoom });
|
|
265
|
+
if (camera) map.jumpTo(camera as { center?: MapLngLat; zoom?: number });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Puts a place in the middle of what `inset` leaves clear: a sheet over
|
|
270
|
+
* the lower part of the map is `{ bottom: its height }`. A jump.
|
|
271
|
+
*/
|
|
272
|
+
export function showPlace(place: MapLngLat, options: { inset?: MapInset; zoom?: number } = {}) {
|
|
273
|
+
const map = instance;
|
|
274
|
+
if (!map) return;
|
|
275
|
+
map.jumpTo({ center: [place.lng, place.lat], zoom: options.zoom });
|
|
276
|
+
const offset = insetOffset(options.inset);
|
|
277
|
+
if (offset.x === 0 && offset.y === 0) return;
|
|
278
|
+
const middle = map.project([place.lng, place.lat]);
|
|
279
|
+
map.jumpTo({ center: map.unproject([middle.x - offset.x, middle.y - offset.y]) });
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** The same for one of `points`, by its id. Nothing happens for an id that is not there. */
|
|
283
|
+
export function showPoint(id: MapPointKey, options: { inset?: MapInset; zoom?: number } = {}) {
|
|
284
|
+
const point = points.find((candidate) => candidate.id === id);
|
|
285
|
+
if (point) showPlace(point, options);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** What the map last told the page, so that the page handing it back moves nothing. */
|
|
289
|
+
let reported: MapViewPosition | null = null;
|
|
290
|
+
|
|
291
|
+
function reportPosition() {
|
|
292
|
+
const map = instance;
|
|
293
|
+
if (!map) return;
|
|
294
|
+
const at = map.getCenter();
|
|
295
|
+
const position = { center: { lng: at.lng, lat: at.lat }, zoom: map.getZoom() };
|
|
296
|
+
reported = position;
|
|
297
|
+
center = position.center;
|
|
298
|
+
zoom = position.zoom;
|
|
299
|
+
onmove?.(position);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
$effect(() => {
|
|
303
|
+
const to = center;
|
|
304
|
+
const level = zoom;
|
|
305
|
+
if (!loaded) return;
|
|
306
|
+
untrack(() => {
|
|
307
|
+
const map = instance;
|
|
308
|
+
if (!map || (!to && level === undefined)) return;
|
|
309
|
+
const same =
|
|
310
|
+
reported !== null &&
|
|
311
|
+
(!to || (to.lng === reported.center.lng && to.lat === reported.center.lat)) &&
|
|
312
|
+
(level === undefined || level === reported.zoom);
|
|
313
|
+
if (!same) map.jumpTo({ center: to ? [to.lng, to.lat] : undefined, zoom: level });
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
/** The list the map was last fitted to. */
|
|
318
|
+
let fitted: string | null = null;
|
|
319
|
+
|
|
320
|
+
$effect(() => {
|
|
321
|
+
const signature = pointsSignature(points);
|
|
322
|
+
if (!loaded) return;
|
|
323
|
+
untrack(() => {
|
|
324
|
+
if (signature !== fitted) {
|
|
325
|
+
fitted = signature;
|
|
326
|
+
fit();
|
|
327
|
+
}
|
|
328
|
+
// A name or a check may have changed without anything moving.
|
|
329
|
+
reproject();
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
let viewerShown = "";
|
|
334
|
+
|
|
335
|
+
$effect(() => {
|
|
336
|
+
const at = viewer;
|
|
337
|
+
if (!loaded) return;
|
|
338
|
+
untrack(() => {
|
|
339
|
+
const signature = at ? `${at.lng}:${at.lat}` : "";
|
|
340
|
+
if (signature !== viewerShown) {
|
|
341
|
+
viewerShown = signature;
|
|
342
|
+
if (at) instance?.jumpTo({ center: [at.lng, at.lat] });
|
|
343
|
+
}
|
|
344
|
+
reproject();
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// --- Theme and credit ------------------------------------------------------
|
|
349
|
+
|
|
350
|
+
$effect(() => {
|
|
351
|
+
const next = style;
|
|
352
|
+
untrack(() => {
|
|
353
|
+
if (!instance || next === appliedStyle) return;
|
|
354
|
+
appliedStyle = next;
|
|
355
|
+
// The position and the markers stay: the markers are this component's own elements.
|
|
356
|
+
instance.setStyle(next);
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
function readCredit() {
|
|
361
|
+
const map = instance;
|
|
362
|
+
if (!map) return;
|
|
363
|
+
const sources = map.getStyle()?.sources ?? {};
|
|
364
|
+
const found: string[] = [];
|
|
365
|
+
for (const [id, source] of Object.entries(sources)) {
|
|
366
|
+
const words = attributionText(map.getSource?.(id)?.attribution ?? source.attribution ?? "");
|
|
367
|
+
if (words && !found.includes(words)) found.push(words);
|
|
368
|
+
}
|
|
369
|
+
styleCredit = found.join(" · ");
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// --- The map itself ----------------------------------------------------------
|
|
373
|
+
|
|
374
|
+
onMount(() => {
|
|
375
|
+
let gone = false;
|
|
376
|
+
dark = isThemeDark();
|
|
377
|
+
const readTheme = () => (dark = isThemeDark());
|
|
378
|
+
const themeObserver = new MutationObserver(readTheme);
|
|
379
|
+
themeObserver.observe(document.documentElement, {
|
|
380
|
+
attributes: true,
|
|
381
|
+
attributeFilter: ["data-theme", "class"],
|
|
382
|
+
});
|
|
383
|
+
const system = window.matchMedia?.("(prefers-color-scheme: dark)");
|
|
384
|
+
system?.addEventListener?.("change", readTheme);
|
|
385
|
+
|
|
386
|
+
void (async () => {
|
|
387
|
+
let library: MapLibreLike | undefined;
|
|
388
|
+
try {
|
|
389
|
+
const module = (await (load ? load() : import("maplibre-gl"))) as
|
|
390
|
+
| (Partial<MapLibreLike> & { default?: MapLibreLike })
|
|
391
|
+
| undefined;
|
|
392
|
+
library = module?.Map ? (module as MapLibreLike) : module?.default;
|
|
393
|
+
if (!library?.Map) throw new Error("no Map in the map library");
|
|
394
|
+
} catch {
|
|
395
|
+
if (!gone) setStatus("library-error");
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (gone || !surface) return;
|
|
399
|
+
// Opened where the page says, if it says both; else fitted to the markers once it has drawn.
|
|
400
|
+
const restored = !!center && zoom !== undefined;
|
|
401
|
+
try {
|
|
402
|
+
appliedStyle = style;
|
|
403
|
+
instance = new library.Map({
|
|
404
|
+
container: surface,
|
|
405
|
+
style,
|
|
406
|
+
// No control on the surface: the credit is drawn below, always open.
|
|
407
|
+
attributionControl: false,
|
|
408
|
+
maplibreLogo: false,
|
|
409
|
+
// The finger's pan and pinch only: no turning, no tilting, no zoom that eases on a double tap.
|
|
410
|
+
dragRotate: false,
|
|
411
|
+
pitchWithRotate: false,
|
|
412
|
+
touchPitch: false,
|
|
413
|
+
doubleClickZoom: false,
|
|
414
|
+
boxZoom: false,
|
|
415
|
+
keyboard: false,
|
|
416
|
+
...(restored && center ? { center: [center.lng, center.lat], zoom } : {}),
|
|
417
|
+
});
|
|
418
|
+
} catch {
|
|
419
|
+
setStatus("library-error");
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const map = instance;
|
|
423
|
+
map.touchZoomRotate?.disableRotation();
|
|
424
|
+
// The markers are the map for a keyboard: the canvas is not a stop of its own.
|
|
425
|
+
map.getCanvas().tabIndex = -1;
|
|
426
|
+
map.on("error", handleError);
|
|
427
|
+
map.on("data", handleData);
|
|
428
|
+
map.on("idle", handleIdle);
|
|
429
|
+
map.on("styledata", readCredit);
|
|
430
|
+
map.on("sourcedata", readCredit);
|
|
431
|
+
map.on("move", reproject);
|
|
432
|
+
map.on("resize", reproject);
|
|
433
|
+
map.on("moveend", reportPosition);
|
|
434
|
+
map.on("load", () => {
|
|
435
|
+
if (gone) return;
|
|
436
|
+
if (restored) fitted = pointsSignature(points);
|
|
437
|
+
loaded = true;
|
|
438
|
+
if (tilesArrived > 0 || tileErrors === 0) setStatus("ready");
|
|
439
|
+
readCredit();
|
|
440
|
+
reproject();
|
|
441
|
+
});
|
|
442
|
+
})();
|
|
443
|
+
|
|
444
|
+
return () => {
|
|
445
|
+
gone = true;
|
|
446
|
+
themeObserver.disconnect();
|
|
447
|
+
system?.removeEventListener?.("change", readTheme);
|
|
448
|
+
instance?.remove();
|
|
449
|
+
instance = undefined;
|
|
450
|
+
};
|
|
451
|
+
});
|
|
452
|
+
</script>
|
|
453
|
+
|
|
454
|
+
<!-- `touch-action: none` and `data-sheet-no-drag`: a finger on the map pans the
|
|
455
|
+
map, and neither scrolls the page nor drags a sheet the map is in. -->
|
|
456
|
+
<div
|
|
457
|
+
bind:this={host}
|
|
458
|
+
class={cn("relative size-full min-h-0 touch-none overflow-hidden", className)}
|
|
459
|
+
role="group"
|
|
460
|
+
aria-label={ariaLabel ?? text.label}
|
|
461
|
+
data-map-view
|
|
462
|
+
data-status={status}
|
|
463
|
+
data-sheet-no-drag
|
|
464
|
+
{...restProps}
|
|
465
|
+
>
|
|
466
|
+
<!-- The library's own element is inside one more box: its stylesheet, where
|
|
467
|
+
a page has it, makes the element `relative`, and it must still fill. -->
|
|
468
|
+
<div class="absolute inset-0" aria-hidden="true">
|
|
469
|
+
<div bind:this={surface} class="map-surface relative size-full"></div>
|
|
470
|
+
</div>
|
|
471
|
+
|
|
472
|
+
<!-- In px throughout: a marker does not grow with the text size. In the
|
|
473
|
+
order of the list, which is the tab order. -->
|
|
474
|
+
<div class="pointer-events-none absolute inset-0" data-map-markers>
|
|
475
|
+
{#each markers as marker (marker.key)}
|
|
476
|
+
{@const count = marker.points.length}
|
|
477
|
+
{@const point = marker.points[0]}
|
|
478
|
+
<button
|
|
479
|
+
type="button"
|
|
480
|
+
class="focus-ring pointer-events-auto absolute top-0 left-0 flex size-[44px] cursor-pointer items-center justify-center rounded-full bg-transparent"
|
|
481
|
+
style:transform="translate({Math.round(marker.x - MAP_MARKER_TAP / 2)}px, {Math.round(
|
|
482
|
+
marker.y - MAP_MARKER_TAP / 2,
|
|
483
|
+
)}px)"
|
|
484
|
+
aria-label={nameOf(marker)}
|
|
485
|
+
data-map-marker={count > 1 ? "cluster" : point.checked ? "checked" : "point"}
|
|
486
|
+
data-point-id={marker.key}
|
|
487
|
+
onfocus={() => handleMarkerFocus(marker)}
|
|
488
|
+
onblur={handleMarkerBlur}
|
|
489
|
+
onclick={() => press(marker)}
|
|
490
|
+
>
|
|
491
|
+
<!-- Each mark has a ring in the surface colour around it, so
|
|
492
|
+
its edge shows on any tile in either style. -->
|
|
493
|
+
{#if count > 1}
|
|
494
|
+
<span
|
|
495
|
+
class="flex h-[28px] min-w-[28px] items-center justify-center rounded-full border-2 border-action-primary bg-surface-overlay px-[6px] text-[13px] leading-none font-semibold text-headline tabular-nums ring-2 ring-(--color-surface-overlay)"
|
|
496
|
+
>
|
|
497
|
+
{count}
|
|
498
|
+
</span>
|
|
499
|
+
{:else if point.checked}
|
|
500
|
+
<span
|
|
501
|
+
class="flex size-[24px] items-center justify-center rounded-full bg-action-primary text-action-primary ring-2 ring-(--color-surface-overlay)"
|
|
502
|
+
>
|
|
503
|
+
<Check size={14} strokeWidth={3} aria-hidden="true" />
|
|
504
|
+
</span>
|
|
505
|
+
{:else}
|
|
506
|
+
<span
|
|
507
|
+
class="size-[20px] rounded-full border-[3px] border-action-primary bg-surface-overlay ring-2 ring-(--color-surface-overlay)"
|
|
508
|
+
></span>
|
|
509
|
+
{/if}
|
|
510
|
+
</button>
|
|
511
|
+
{/each}
|
|
512
|
+
{#if viewerPixel}
|
|
513
|
+
<span
|
|
514
|
+
class="absolute top-0 left-0 size-[16px] rounded-full border-[3px] border-(--color-surface-overlay) bg-info"
|
|
515
|
+
style:transform="translate({Math.round(viewerPixel.x - 8)}px, {Math.round(viewerPixel.y - 8)}px)"
|
|
516
|
+
role="img"
|
|
517
|
+
aria-label={text.viewer}
|
|
518
|
+
data-map-viewer
|
|
519
|
+
></span>
|
|
520
|
+
{/if}
|
|
521
|
+
</div>
|
|
522
|
+
|
|
523
|
+
<!-- Always open, never behind a button: the licence of the map data asks for it. -->
|
|
524
|
+
{#if attribution || styleCredit}
|
|
525
|
+
<p
|
|
526
|
+
class="pointer-events-auto absolute start-[4px] bottom-[4px] m-0 [max-inline-size:calc(100%-8px)] rounded-control bg-surface-overlay px-[6px] py-[2px] text-xs text-description"
|
|
527
|
+
data-map-attribution
|
|
528
|
+
>
|
|
529
|
+
{#if typeof attribution === "function"}
|
|
530
|
+
{@render attribution()}
|
|
531
|
+
{:else}
|
|
532
|
+
{attribution || styleCredit}
|
|
533
|
+
{/if}
|
|
534
|
+
</p>
|
|
535
|
+
{/if}
|
|
536
|
+
</div>
|
|
537
|
+
|
|
538
|
+
<style>
|
|
539
|
+
/* The few rules of the library's stylesheet the map needs, so that the page
|
|
540
|
+
does not have to load the rest of it: the canvas laid over the box, and
|
|
541
|
+
nothing of the browser's own panning under a finger. */
|
|
542
|
+
.map-surface {
|
|
543
|
+
overflow: hidden;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.map-surface :global(.maplibregl-canvas-container),
|
|
547
|
+
.map-surface :global(.maplibregl-canvas) {
|
|
548
|
+
touch-action: none;
|
|
549
|
+
-webkit-user-select: none;
|
|
550
|
+
-moz-user-select: none;
|
|
551
|
+
user-select: none;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.map-surface :global(.maplibregl-canvas) {
|
|
555
|
+
position: absolute;
|
|
556
|
+
top: 0;
|
|
557
|
+
left: 0;
|
|
558
|
+
outline: none;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/* No control is asked for; the holders the library still makes take no room. */
|
|
562
|
+
.map-surface :global(.maplibregl-control-container) {
|
|
563
|
+
display: none;
|
|
564
|
+
}
|
|
565
|
+
</style>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { type Snippet } from "svelte";
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
import { type MapInset, type MapLngLat, type MapPoint, type MapPointKey, type MapViewPosition, type MapViewStatus, type MapViewStrings } from "../util/map-view.js";
|
|
4
|
+
/**
|
|
5
|
+
* A map with markers from a list, on MapLibre GL JS. It fills the box it
|
|
6
|
+
* is given and has no control of its own: a finger pans and pinches, and
|
|
7
|
+
* everything else is the page's.
|
|
8
|
+
*
|
|
9
|
+
* ```svelte
|
|
10
|
+
* <MapView {points} styleLight={light} styleDark={dark} bind:status onpointpress={open} />
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* It comes from its own entry, `zabi-components/map`, and loads the map
|
|
14
|
+
* library (`maplibre-gl`, the app's own dependency) only when it is
|
|
15
|
+
* mounted in a browser: nothing else in the package brings any of it.
|
|
16
|
+
* Ask `mapViewSupported()` before offering a map at all.
|
|
17
|
+
*
|
|
18
|
+
* The markers are the map for a keyboard and a screen reader: each is a
|
|
19
|
+
* named button, in the order of `points`. Markers whose tap areas overlap
|
|
20
|
+
* are drawn as one with their number. Every move the component makes is a
|
|
21
|
+
* jump; the finger's own pan and pinch are the only motion.
|
|
22
|
+
*
|
|
23
|
+
* It shows no text of its own and no error: read `status` and put your
|
|
24
|
+
* own skeleton or empty state in its place.
|
|
25
|
+
*
|
|
26
|
+
* Other attributes (`id`, `data-*`, ...) land on the host `<div>`.
|
|
27
|
+
*/
|
|
28
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "aria-label"> & {
|
|
29
|
+
/** The markers, in the order they take in the tab order. */
|
|
30
|
+
points: MapPoint[];
|
|
31
|
+
/** The style drawn in the light theme: a URL or a style object. Nothing about the tile host is fixed here. */
|
|
32
|
+
styleLight: string | object;
|
|
33
|
+
/** The style drawn while the page is dark (`data-theme` on `<html>`, which may follow the system). Without it, `styleLight`. */
|
|
34
|
+
styleDark?: string | object;
|
|
35
|
+
/** Called with the point when its marker is pressed. */
|
|
36
|
+
onpointpress?: (point: MapPoint, index: number) => void;
|
|
37
|
+
/** Called with the points a count marker holds when it is pressed. */
|
|
38
|
+
onclusterpress?: (points: MapPoint[]) => void;
|
|
39
|
+
/** Names a count marker from its points. Without it, `strings.cluster` with the count. */
|
|
40
|
+
clusterLabel?: (points: MapPoint[]) => string;
|
|
41
|
+
/** The closest the fit to the markers zooms in. */
|
|
42
|
+
fitMaxZoom?: number;
|
|
43
|
+
/**
|
|
44
|
+
* The middle of the map; supports `bind:center`. Given with `zoom`
|
|
45
|
+
* when the map is made, it opens there and is not fitted to the
|
|
46
|
+
* markers; set later, the map jumps there.
|
|
47
|
+
*/
|
|
48
|
+
center?: MapLngLat;
|
|
49
|
+
/** The zoom; supports `bind:zoom`. */
|
|
50
|
+
zoom?: number;
|
|
51
|
+
/** Called when a move has ended, by the finger or by the component. */
|
|
52
|
+
onmove?: (position: MapViewPosition) => void;
|
|
53
|
+
/**
|
|
54
|
+
* The viewer's own position, from the page: a dot is drawn there and
|
|
55
|
+
* the map jumps to it when it is set or changes. The component never
|
|
56
|
+
* asks the browser for it.
|
|
57
|
+
*/
|
|
58
|
+
viewer?: MapLngLat | null;
|
|
59
|
+
/** Where the map stands; supports `bind:status`. */
|
|
60
|
+
status?: MapViewStatus;
|
|
61
|
+
onstatus?: (status: MapViewStatus) => void;
|
|
62
|
+
/**
|
|
63
|
+
* The credit that is always shown, bottom left. Without it, the words
|
|
64
|
+
* of the attribution the style's sources carry, as plain text.
|
|
65
|
+
*/
|
|
66
|
+
attribution?: string | Snippet;
|
|
67
|
+
/** Loads the map library. Default: `() => import("maplibre-gl")`. */
|
|
68
|
+
load?: () => Promise<unknown>;
|
|
69
|
+
/** Overrides for the built-in strings. */
|
|
70
|
+
strings?: Partial<MapViewStrings>;
|
|
71
|
+
/** Extra classes for the host element. */
|
|
72
|
+
class?: string;
|
|
73
|
+
/** Accessible name of the map. Without it, `strings.label`. */
|
|
74
|
+
"aria-label"?: string;
|
|
75
|
+
};
|
|
76
|
+
declare const MapView: import("svelte").Component<Props, {
|
|
77
|
+
fit: () => void;
|
|
78
|
+
showPlace: (place: MapLngLat, options?: {
|
|
79
|
+
inset?: MapInset;
|
|
80
|
+
zoom?: number;
|
|
81
|
+
}) => void;
|
|
82
|
+
showPoint: (id: MapPointKey, options?: {
|
|
83
|
+
inset?: MapInset;
|
|
84
|
+
zoom?: number;
|
|
85
|
+
}) => void;
|
|
86
|
+
}, "zoom" | "center" | "status">;
|
|
87
|
+
type MapView = ReturnType<typeof MapView>;
|
|
88
|
+
export default MapView;
|