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,696 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
import { untrack } from "svelte";
|
|
5
|
+
import {
|
|
6
|
+
APP_SHELL_RAIL_MIN_WIDTH,
|
|
7
|
+
APP_SHELL_SIDEBAR_MIN_WIDTH,
|
|
8
|
+
markAppShell,
|
|
9
|
+
publishAppShellInsets,
|
|
10
|
+
type AppShellNavigationContext,
|
|
11
|
+
type AppShellNavigationMode,
|
|
12
|
+
type AppShellNavigationPlacement,
|
|
13
|
+
} from "../util/app-shell.js";
|
|
14
|
+
import { cn } from "../util/cn.js";
|
|
15
|
+
import { watchScrollEdge } from "../util/scroll-edge.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The layout of a phone app: a bar at the top, content that scrolls, a
|
|
19
|
+
* tab bar at the bottom, and none of it under the notch or the home
|
|
20
|
+
* indicator.
|
|
21
|
+
*
|
|
22
|
+
* The shell is exactly as tall as the visible screen (`100dvh`, so the
|
|
23
|
+
* browser's own bars are accounted for) and only the middle scrolls. The
|
|
24
|
+
* header stays at the top of that scrolling area. The footer lies over
|
|
25
|
+
* the bottom of it, so content passes beneath both bars: they are the
|
|
26
|
+
* page colour at rest and turn to glass once content is under them.
|
|
27
|
+
*
|
|
28
|
+
* ```svelte
|
|
29
|
+
* <AppShell>
|
|
30
|
+
* {#snippet header()}
|
|
31
|
+
* <AppBar title="Quizrundan" collapseOnScroll />
|
|
32
|
+
* {/snippet}
|
|
33
|
+
* <div class="p-4">…</div>
|
|
34
|
+
* {#snippet footer()}
|
|
35
|
+
* <BottomTabBar {items} active={page.url.pathname} />
|
|
36
|
+
* {/snippet}
|
|
37
|
+
* </AppShell>
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* Two custom properties on the host say how much of the screen the bars
|
|
41
|
+
* take, safe areas included, so anything inside the shell can stay clear
|
|
42
|
+
* of them:
|
|
43
|
+
*
|
|
44
|
+
* - `--app-shell-top-inset`: the height of the header (with an `AppBar`
|
|
45
|
+
* `largeTitle`, the height it condenses to);
|
|
46
|
+
* - `--app-shell-bottom-inset`: the height of the footer.
|
|
47
|
+
*
|
|
48
|
+
* Without a bar the value is the safe-area inset alone. With a footer the
|
|
49
|
+
* content takes the bottom inset as padding and the scroller as
|
|
50
|
+
* `scroll-padding-bottom`, so the end of the content and a focused field
|
|
51
|
+
* clear the footer.
|
|
52
|
+
*
|
|
53
|
+
* A third, on the scroller, is for what sits in the content and has to
|
|
54
|
+
* stay above the footer (`StickyActionBar` and `UnsavedChangesBar` read it):
|
|
55
|
+
*
|
|
56
|
+
* - `--app-shell-footer-overlay`: the bottom inset when a footer lies over
|
|
57
|
+
* the scroller, `0px` when there is no footer.
|
|
58
|
+
*
|
|
59
|
+
* The host also says where content is: `data-scrolled-top="true"` once it
|
|
60
|
+
* has scrolled under the header, `data-scrolled-bottom="true"` while it
|
|
61
|
+
* passes under the footer (both `"false"` on the server and before the
|
|
62
|
+
* first measurement). The bars read the same state.
|
|
63
|
+
*
|
|
64
|
+
* To let the consumer choose what stays, compose the header from
|
|
65
|
+
* `AppShellHeaderPart`s: each says `sticky` (the default) or `sticky={false}`
|
|
66
|
+
* and scrolls away with the content, so a bar can leave while the filter
|
|
67
|
+
* row stays, or a large title can go while the bar and the tabs stay. The
|
|
68
|
+
* shell measures the parts to stack the ones that stick, reports only
|
|
69
|
+
* what is stuck as `--app-shell-top-inset` and `scroll-padding-top`, and
|
|
70
|
+
* puts the glass and the hairline under the lowest one. Without parts the
|
|
71
|
+
* header is one block that sticks, as before.
|
|
72
|
+
*
|
|
73
|
+
* With `canvas` the shell is painted with the page colour and the brand
|
|
74
|
+
* wash (`bg-canvas`), and the header bar rests transparent so the wash
|
|
75
|
+
* shows through it.
|
|
76
|
+
*
|
|
77
|
+
* The host is the containing block for anything positioned inside it, and such an element
|
|
78
|
+
* does not scroll with the content, so a floating button above the tab bar
|
|
79
|
+
* is `absolute` with `bottom: calc(var(--app-shell-bottom-inset) + 1rem)`.
|
|
80
|
+
*
|
|
81
|
+
* While the shell is mounted the same two properties are also set on
|
|
82
|
+
* `<html>`, so an overlay that is moved to `document.body` (a sheet, a
|
|
83
|
+
* toast) can read them too. With more than one shell on the page, the one
|
|
84
|
+
* mounted last is the one mirrored there.
|
|
85
|
+
*
|
|
86
|
+
* With `navigation` the shell holds one navigation that is drawn as tabs
|
|
87
|
+
* over the bottom of the screen on a phone, a rail on a tablet and a
|
|
88
|
+
* sidebar on a desktop, and the app writes one layout, not three
|
|
89
|
+
* (`AppNavigation` is the ready-made content). The snippet is rendered
|
|
90
|
+
* once and only CSS moves it, so the server's markup is right at every
|
|
91
|
+
* width and nothing jumps when scripts load. As tabs it lies over the
|
|
92
|
+
* bottom like `footer` (the footer then sits directly above it); as a
|
|
93
|
+
* rail or sidebar it is a full-height column at the inline start, and the
|
|
94
|
+
* header, content and footer take the rest. The host says which with
|
|
95
|
+
* `data-navigation-placement`, and the bottom inset counts the tabs, so a
|
|
96
|
+
* sticky action bar or a floating button clears them too.
|
|
97
|
+
*
|
|
98
|
+
* ```svelte
|
|
99
|
+
* <AppShell>
|
|
100
|
+
* {#snippet header()}<AppBar title="Quizrundan" largeTitle />{/snippet}
|
|
101
|
+
* {#snippet navigation()}<AppNavigation {items} active={page.url.pathname} />{/snippet}
|
|
102
|
+
* <div class="p-4">…</div>
|
|
103
|
+
* </AppShell>
|
|
104
|
+
* ```
|
|
105
|
+
*
|
|
106
|
+
* One more custom property: `--app-shell-start-inset`, the width of the
|
|
107
|
+
* rail or sidebar (`0px` as tabs), also mirrored onto `<html>`.
|
|
108
|
+
*
|
|
109
|
+
* With `flushTop` and no `header` the content starts at the top edge of
|
|
110
|
+
* the screen, under the status bar, so a block of colour at the top of it
|
|
111
|
+
* can run there. The content then handles the safe area itself: pad by
|
|
112
|
+
* `--app-shell-top-inset`, or let an `AppBar position="static"` in the
|
|
113
|
+
* block do it.
|
|
114
|
+
*/
|
|
115
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, "class" | "style"> & {
|
|
116
|
+
/**
|
|
117
|
+
* The top bar, an `AppBar`. It handles the safe area above it. Or
|
|
118
|
+
* several `AppShellHeaderPart`s, each of which sticks or scrolls away.
|
|
119
|
+
*/
|
|
120
|
+
header?: Snippet;
|
|
121
|
+
/** The scrolling content, rendered in the element `contentElement` names. */
|
|
122
|
+
children?: Snippet;
|
|
123
|
+
/**
|
|
124
|
+
* The element around the content. `main` when the shell is the page,
|
|
125
|
+
* which is what it is for. A page has one `<main>`: use `div` for a
|
|
126
|
+
* shell that sits inside another one, such as a preview in a page of
|
|
127
|
+
* its own.
|
|
128
|
+
*/
|
|
129
|
+
contentElement?: "main" | "div";
|
|
130
|
+
/**
|
|
131
|
+
* The bottom bar, a `BottomTabBar`. It lies over the bottom of the
|
|
132
|
+
* scrolling area and handles the safe area below it.
|
|
133
|
+
*/
|
|
134
|
+
footer?: Snippet;
|
|
135
|
+
/**
|
|
136
|
+
* Paints the shell with the page colour and the brand wash (`bg-canvas`)
|
|
137
|
+
* instead of the plain page colour, and lets the wash show through the
|
|
138
|
+
* header bar at rest.
|
|
139
|
+
*/
|
|
140
|
+
canvas?: boolean;
|
|
141
|
+
/**
|
|
142
|
+
* One navigation, drawn as tabs, a rail or a sidebar by
|
|
143
|
+
* `navigationPlacement`. Rendered once, first in the DOM: the Tab order
|
|
144
|
+
* is navigation, header, content. `placement` is the forced value, or
|
|
145
|
+
* in `auto` what the viewport says once mounted (`tabs` on the server
|
|
146
|
+
* and before mount); use it only to render different markup per
|
|
147
|
+
* placement, since the layout itself is CSS and right without it.
|
|
148
|
+
*/
|
|
149
|
+
navigation?: Snippet<[AppShellNavigationContext]>;
|
|
150
|
+
/**
|
|
151
|
+
* Where the `navigation` goes. `auto` follows the viewport in CSS:
|
|
152
|
+
* tabs below 48rem (768px), a rail from 48rem, a sidebar from 64rem
|
|
153
|
+
* (1024px). The others apply at every width.
|
|
154
|
+
*/
|
|
155
|
+
navigationPlacement?: AppShellNavigationMode;
|
|
156
|
+
/**
|
|
157
|
+
* Without a `header`, leaves out the padding the content has for the
|
|
158
|
+
* status bar and the notch, so it starts at the top of the screen and
|
|
159
|
+
* handles the safe area itself. `--app-shell-top-inset` keeps its
|
|
160
|
+
* value. With a `header` it does nothing: the header covers the safe
|
|
161
|
+
* area.
|
|
162
|
+
*/
|
|
163
|
+
flushTop?: boolean;
|
|
164
|
+
class?: string;
|
|
165
|
+
/** Added after the two custom properties the shell sets. */
|
|
166
|
+
style?: string;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
let {
|
|
170
|
+
header,
|
|
171
|
+
children,
|
|
172
|
+
contentElement = "main",
|
|
173
|
+
footer,
|
|
174
|
+
canvas = false,
|
|
175
|
+
navigation,
|
|
176
|
+
navigationPlacement = "auto",
|
|
177
|
+
flushTop = false,
|
|
178
|
+
class: className = "",
|
|
179
|
+
style = "",
|
|
180
|
+
...restProps
|
|
181
|
+
}: Props = $props();
|
|
182
|
+
|
|
183
|
+
let headerRegion: HTMLDivElement | undefined = $state();
|
|
184
|
+
let footerRegion: HTMLDivElement | undefined = $state();
|
|
185
|
+
/** Measured heights in px; null on the server and until the first measurement. */
|
|
186
|
+
let headerHeight = $state<number | null>(null);
|
|
187
|
+
let footerHeight = $state<number | null>(null);
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* What the bars measure before they have been measured: the default
|
|
191
|
+
* heights of AppBar and BottomTabBar. The real size replaces it once the
|
|
192
|
+
* shell runs, which matters when the text is enlarged or a label wraps.
|
|
193
|
+
*/
|
|
194
|
+
// In px: AppBar (56) and BottomTabBar (64) keep their height when the text is enlarged.
|
|
195
|
+
const HEADER_ESTIMATE = "calc(56px + env(safe-area-inset-top, 0px))";
|
|
196
|
+
const FOOTER_ESTIMATE = "calc(64px + env(safe-area-inset-bottom, 0px))";
|
|
197
|
+
|
|
198
|
+
function track(region: HTMLElement | undefined, set: (height: number | null) => void) {
|
|
199
|
+
if (!region) {
|
|
200
|
+
set(null);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const measure = () => {
|
|
204
|
+
const height = region.getBoundingClientRect().height;
|
|
205
|
+
// No layout (a hidden shell, a test DOM): keep the estimate.
|
|
206
|
+
set(height > 0 ? height : null);
|
|
207
|
+
};
|
|
208
|
+
measure();
|
|
209
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
210
|
+
const observer = new ResizeObserver(measure);
|
|
211
|
+
observer.observe(region);
|
|
212
|
+
return () => observer.disconnect();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let navRegion: HTMLDivElement | undefined = $state();
|
|
216
|
+
/** Measured size of the navigation region in px; null on the server and until the first measurement. */
|
|
217
|
+
let navBox = $state<{ width: number; height: number } | null>(null);
|
|
218
|
+
/** What the viewport says in `auto`, after mount. `tabs` before, as on the server. */
|
|
219
|
+
let viewportPlacement = $state<AppShellNavigationPlacement>("tabs");
|
|
220
|
+
const placement = $derived<AppShellNavigationPlacement>(
|
|
221
|
+
navigationPlacement === "auto" ? viewportPlacement : navigationPlacement,
|
|
222
|
+
);
|
|
223
|
+
/** The navigation lies over the bottom of the scroller, like a footer. */
|
|
224
|
+
const navigationOverlays = $derived(!!navigation && placement === "tabs");
|
|
225
|
+
|
|
226
|
+
// The same two breakpoints as the stylesheet below, for the `placement` the snippet is given.
|
|
227
|
+
$effect(() => {
|
|
228
|
+
if (!navigation || navigationPlacement !== "auto") return;
|
|
229
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
230
|
+
const rail = window.matchMedia(`(min-width: ${APP_SHELL_RAIL_MIN_WIDTH})`);
|
|
231
|
+
const sidebar = window.matchMedia(`(min-width: ${APP_SHELL_SIDEBAR_MIN_WIDTH})`);
|
|
232
|
+
const read = () => {
|
|
233
|
+
viewportPlacement = sidebar.matches ? "sidebar" : rail.matches ? "rail" : "tabs";
|
|
234
|
+
};
|
|
235
|
+
read();
|
|
236
|
+
rail.addEventListener?.("change", read);
|
|
237
|
+
sidebar.addEventListener?.("change", read);
|
|
238
|
+
return () => {
|
|
239
|
+
rail.removeEventListener?.("change", read);
|
|
240
|
+
sidebar.removeEventListener?.("change", read);
|
|
241
|
+
};
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
$effect(() => {
|
|
245
|
+
const region = navRegion;
|
|
246
|
+
if (!region) {
|
|
247
|
+
navBox = null;
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const measure = () => {
|
|
251
|
+
const { width, height } = region.getBoundingClientRect();
|
|
252
|
+
navBox = width > 0 && height > 0 ? { width, height } : null;
|
|
253
|
+
};
|
|
254
|
+
measure();
|
|
255
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
256
|
+
const observer = new ResizeObserver(measure);
|
|
257
|
+
observer.observe(region);
|
|
258
|
+
return () => observer.disconnect();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
let scroller: HTMLDivElement | undefined = $state();
|
|
262
|
+
/** Content is under the header / the footer. False until the browser has measured. */
|
|
263
|
+
let scrolledTop = $state(false);
|
|
264
|
+
let scrolledBottom = $state(false);
|
|
265
|
+
/** How far the header wrapper sticks above the top of the scroller: a large title's row (px). */
|
|
266
|
+
let headerOverscroll = $state(0);
|
|
267
|
+
/**
|
|
268
|
+
* What the `AppShellHeaderPart`s measure to: the px that cover content while
|
|
269
|
+
* the stuck parts are stuck (status bar strip included) and how far the
|
|
270
|
+
* content scrolls before it passes under them. Null without parts, on the
|
|
271
|
+
* server and until measured.
|
|
272
|
+
*/
|
|
273
|
+
let partsLayout = $state<{ inset: number; threshold: number } | null>(null);
|
|
274
|
+
let strip: HTMLDivElement | undefined = $state();
|
|
275
|
+
|
|
276
|
+
markAppShell({
|
|
277
|
+
get scrolledTop() {
|
|
278
|
+
return scrolledTop;
|
|
279
|
+
},
|
|
280
|
+
get scrolledBottom() {
|
|
281
|
+
return scrolledBottom;
|
|
282
|
+
},
|
|
283
|
+
get footerOverlays() {
|
|
284
|
+
return !!footer || navigationOverlays;
|
|
285
|
+
},
|
|
286
|
+
get footerHeight() {
|
|
287
|
+
return (footer ? (footerHeight ?? 0) : 0) + (navigationOverlays ? (navBox?.height ?? 0) : 0);
|
|
288
|
+
},
|
|
289
|
+
get navigationPlacement() {
|
|
290
|
+
return navigation ? placement : "tabs";
|
|
291
|
+
},
|
|
292
|
+
setHeaderOverscroll(px) {
|
|
293
|
+
headerOverscroll = px > 0 ? px : 0;
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
$effect(() => {
|
|
298
|
+
if (!scroller) return;
|
|
299
|
+
// Under the header means past the large title's row, when there is one.
|
|
300
|
+
return watchScrollEdge(
|
|
301
|
+
scroller,
|
|
302
|
+
(edge) => {
|
|
303
|
+
scrolledTop = edge.top;
|
|
304
|
+
scrolledBottom = edge.bottom;
|
|
305
|
+
},
|
|
306
|
+
partsLayout ? partsLayout.threshold : headerOverscroll,
|
|
307
|
+
);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
/** Stuck parts may cover this much of the scroller; the lower ones then scroll (WCAG 2.4.11, 200% text). */
|
|
311
|
+
const PARTS_MAX_COVER = 0.4;
|
|
312
|
+
|
|
313
|
+
$effect(() => {
|
|
314
|
+
const region = headerRegion;
|
|
315
|
+
const area = scroller;
|
|
316
|
+
if (!region || !area) {
|
|
317
|
+
partsLayout = null;
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const partsOf = () =>
|
|
321
|
+
Array.from(region.querySelectorAll<HTMLElement>(":scope > [data-app-shell-part]"));
|
|
322
|
+
const heightOf = (part: HTMLElement) => part.getBoundingClientRect().height;
|
|
323
|
+
|
|
324
|
+
const layout = () => {
|
|
325
|
+
const parts = partsOf();
|
|
326
|
+
const env = strip ? strip.getBoundingClientRect().height : 0;
|
|
327
|
+
const heights = parts.map(heightOf);
|
|
328
|
+
// No parts, or no layout (a hidden shell, a test DOM): the header measure applies.
|
|
329
|
+
if (parts.length === 0 || heights.every((height) => height === 0)) {
|
|
330
|
+
for (const part of parts) part.removeAttribute("data-released");
|
|
331
|
+
partsLayout = null;
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const overscrolls = parts.map((part) => Number(part.dataset.overscroll) || 0);
|
|
335
|
+
let natural = 0;
|
|
336
|
+
const items = parts.map((part, index) => {
|
|
337
|
+
const top = natural;
|
|
338
|
+
natural += heights[index];
|
|
339
|
+
return {
|
|
340
|
+
part,
|
|
341
|
+
natural: top,
|
|
342
|
+
height: heights[index],
|
|
343
|
+
overscroll: overscrolls[index],
|
|
344
|
+
sticky: part.dataset.sticky === "true",
|
|
345
|
+
// Pads the status bar itself: an AppBar, or the part at the very top.
|
|
346
|
+
atTop: index === 0 || part.querySelector("[data-appbar]") !== null,
|
|
347
|
+
};
|
|
348
|
+
});
|
|
349
|
+
const stuck = items.filter((item) => item.sticky);
|
|
350
|
+
// The stack, top down, for the first `count` stuck parts.
|
|
351
|
+
const stack = (count: number) => {
|
|
352
|
+
let cover = 0;
|
|
353
|
+
const placed = stuck.slice(0, count).map((item, index) => {
|
|
354
|
+
if (index === 0) cover = item.atTop ? 0 : env;
|
|
355
|
+
const top = cover - item.overscroll;
|
|
356
|
+
cover = top + item.height;
|
|
357
|
+
return { item, top };
|
|
358
|
+
});
|
|
359
|
+
return { placed, cover: placed.length ? cover : env };
|
|
360
|
+
};
|
|
361
|
+
let count = stuck.length;
|
|
362
|
+
const limit = area.clientHeight * PARTS_MAX_COVER;
|
|
363
|
+
// Keep at least the first: a stack of one is the author's choice.
|
|
364
|
+
while (count > 1 && area.clientHeight > 0 && stack(count).cover > limit) count -= 1;
|
|
365
|
+
const { placed, cover } = stack(count);
|
|
366
|
+
const released = new Set(stuck.slice(count).map((item) => item.part));
|
|
367
|
+
for (const item of items) {
|
|
368
|
+
if (released.has(item.part)) item.part.setAttribute("data-released", "");
|
|
369
|
+
else item.part.removeAttribute("data-released");
|
|
370
|
+
}
|
|
371
|
+
for (const { item, top } of placed) {
|
|
372
|
+
item.part.style.setProperty("--app-shell-part-top", `${top}px`);
|
|
373
|
+
}
|
|
374
|
+
const last = placed[placed.length - 1];
|
|
375
|
+
// Content is under the stuck parts once the lowest has reached its place.
|
|
376
|
+
const threshold = last ? Math.max(0, last.item.natural - last.top) : 0;
|
|
377
|
+
const current = untrack(() => partsLayout);
|
|
378
|
+
if (!current || current.inset !== cover || current.threshold !== threshold) {
|
|
379
|
+
partsLayout = { inset: cover, threshold };
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const resize = typeof ResizeObserver === "undefined" ? undefined : new ResizeObserver(layout);
|
|
384
|
+
const watch = () => {
|
|
385
|
+
if (!resize) return;
|
|
386
|
+
resize.disconnect();
|
|
387
|
+
resize.observe(area);
|
|
388
|
+
if (strip) resize.observe(strip);
|
|
389
|
+
for (const part of partsOf()) resize.observe(part);
|
|
390
|
+
};
|
|
391
|
+
// Parts come and go, and `sticky` and a large title's height change.
|
|
392
|
+
const changes =
|
|
393
|
+
typeof MutationObserver === "undefined"
|
|
394
|
+
? undefined
|
|
395
|
+
: new MutationObserver(() => {
|
|
396
|
+
watch();
|
|
397
|
+
layout();
|
|
398
|
+
});
|
|
399
|
+
changes?.observe(region, {
|
|
400
|
+
childList: true,
|
|
401
|
+
subtree: true,
|
|
402
|
+
attributes: true,
|
|
403
|
+
attributeFilter: ["data-sticky", "data-overscroll"],
|
|
404
|
+
});
|
|
405
|
+
watch();
|
|
406
|
+
untrack(layout);
|
|
407
|
+
return () => {
|
|
408
|
+
resize?.disconnect();
|
|
409
|
+
changes?.disconnect();
|
|
410
|
+
partsLayout = null;
|
|
411
|
+
};
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
$effect(() => track(headerRegion, (height) => (headerHeight = height)));
|
|
415
|
+
$effect(() => track(footerRegion, (height) => (footerHeight = height)));
|
|
416
|
+
|
|
417
|
+
const topInset = $derived(
|
|
418
|
+
!header
|
|
419
|
+
? "env(safe-area-inset-top, 0px)"
|
|
420
|
+
: partsLayout
|
|
421
|
+
? `${partsLayout.inset}px`
|
|
422
|
+
: headerHeight === null
|
|
423
|
+
? HEADER_ESTIMATE
|
|
424
|
+
: // What covers content while scrolled: the header less the large row that scrolls away.
|
|
425
|
+
`${Math.max(0, headerHeight - headerOverscroll)}px`,
|
|
426
|
+
);
|
|
427
|
+
/** The footer's height: measured, or the estimate. */
|
|
428
|
+
const footerPart = $derived(
|
|
429
|
+
footer ? (footerHeight === null ? FOOTER_ESTIMATE : `${footerHeight}px`) : null,
|
|
430
|
+
);
|
|
431
|
+
/** The tabs' height when the navigation lies over the bottom: measured, or the estimate. */
|
|
432
|
+
const navigationPart = $derived(
|
|
433
|
+
navigationOverlays ? (navBox === null ? FOOTER_ESTIMATE : `${navBox.height}px`) : null,
|
|
434
|
+
);
|
|
435
|
+
/**
|
|
436
|
+
* What lies over the bottom of the content, resolved for the placement the
|
|
437
|
+
* script knows. The host itself says it in CSS (see `hostStyle`), which is
|
|
438
|
+
* right before any script runs; this value is for `<html>`, where the
|
|
439
|
+
* stylesheet's own properties do not reach.
|
|
440
|
+
*/
|
|
441
|
+
const bottomInset = $derived(
|
|
442
|
+
footerPart && navigationPart
|
|
443
|
+
? `calc(${footerPart} + ${navigationPart})`
|
|
444
|
+
: (footerPart ?? navigationPart ?? "env(safe-area-inset-bottom, 0px)"),
|
|
445
|
+
);
|
|
446
|
+
const startInset = $derived(
|
|
447
|
+
!navigation
|
|
448
|
+
? undefined
|
|
449
|
+
: placement === "tabs"
|
|
450
|
+
? "0px"
|
|
451
|
+
: navBox
|
|
452
|
+
? `${navBox.width}px`
|
|
453
|
+
: placement === "rail"
|
|
454
|
+
? "80px"
|
|
455
|
+
: "256px",
|
|
456
|
+
);
|
|
457
|
+
/**
|
|
458
|
+
* With a navigation the bottom inset and the start inset are CSS
|
|
459
|
+
* expressions over the placement, so they are right on the server; the
|
|
460
|
+
* measured sizes only replace the estimates in them.
|
|
461
|
+
*/
|
|
462
|
+
const hostStyle = $derived(
|
|
463
|
+
navigation
|
|
464
|
+
? `--app-shell-top-inset: ${topInset}; --app-shell-bottom-inset: calc(var(--shell-overlay) + ${footer ? "0px" : "var(--shell-safe-bottom)"}); --app-shell-start-inset: var(--shell-start); --shell-footer: ${footerPart ?? "0px"};${navBox ? ` --shell-nav-h: ${navBox.height}px; --shell-nav-w: ${navBox.width}px;` : ""} ${style}`
|
|
465
|
+
: `--app-shell-top-inset: ${topInset}; --app-shell-bottom-inset: ${bottomInset}; ${style}`,
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
// On `<html>` as well, for what is rendered outside the shell.
|
|
469
|
+
let published: ReturnType<typeof publishAppShellInsets> | undefined;
|
|
470
|
+
$effect(() => {
|
|
471
|
+
published = untrack(() => publishAppShellInsets(topInset, bottomInset, startInset));
|
|
472
|
+
return () => {
|
|
473
|
+
published?.leave();
|
|
474
|
+
published = undefined;
|
|
475
|
+
};
|
|
476
|
+
});
|
|
477
|
+
$effect(() => {
|
|
478
|
+
published?.update(topInset, bottomInset, startInset);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
const contentClasses = $derived(
|
|
482
|
+
navigation
|
|
483
|
+
? cn(
|
|
484
|
+
// A rail or sidebar clears the inline-start safe area itself: not twice.
|
|
485
|
+
"min-w-0 flex-1 pl-(--shell-content-pl) pr-[env(safe-area-inset-right)]",
|
|
486
|
+
!header && !flushTop && "pt-[env(safe-area-inset-top)]",
|
|
487
|
+
// Whatever lies over the bottom (footer, tabs), or the safe area alone.
|
|
488
|
+
"pb-(--app-shell-bottom-inset)",
|
|
489
|
+
)
|
|
490
|
+
: cn(
|
|
491
|
+
"min-w-0 flex-1 pl-[env(safe-area-inset-left)] pr-[env(safe-area-inset-right)]",
|
|
492
|
+
!header && !flushTop && "pt-[env(safe-area-inset-top)]",
|
|
493
|
+
// With a footer over the scroller the content ends above it.
|
|
494
|
+
footer ? "pb-(--app-shell-bottom-inset)" : "pb-[env(safe-area-inset-bottom)]",
|
|
495
|
+
),
|
|
496
|
+
);
|
|
497
|
+
/** Whether the content ends above something that lies over the scroller. */
|
|
498
|
+
const padsBottom = $derived(!!footer || !!navigation);
|
|
499
|
+
</script>
|
|
500
|
+
|
|
501
|
+
{#snippet body()}
|
|
502
|
+
<!--
|
|
503
|
+
The header is inside the scrolling area and sticks to its top. It keeps
|
|
504
|
+
its place in the flow, so the content starts below it with nothing to
|
|
505
|
+
measure, and it can slide away (AppBar's collapseOnScroll) without the
|
|
506
|
+
content moving. scroll-padding keeps a focused field from landing under it.
|
|
507
|
+
-->
|
|
508
|
+
<div
|
|
509
|
+
bind:this={scroller}
|
|
510
|
+
data-app-shell-scroller
|
|
511
|
+
class={cn(
|
|
512
|
+
"flex min-h-0 flex-1 flex-col overflow-x-hidden overflow-y-auto overscroll-y-contain scroll-pt-(--app-shell-top-inset)",
|
|
513
|
+
// Shared with StickyActionBar's reservation: the larger one is set inline.
|
|
514
|
+
padsBottom && "scroll-pb-(--app-shell-bottom-inset)",
|
|
515
|
+
)}
|
|
516
|
+
style="--app-shell-footer-overlay: {navigation
|
|
517
|
+
? 'var(--shell-overlay)'
|
|
518
|
+
: footer
|
|
519
|
+
? 'var(--app-shell-bottom-inset)'
|
|
520
|
+
: '0px'}"
|
|
521
|
+
>
|
|
522
|
+
{#if header}
|
|
523
|
+
<!-- With header parts: the status bar's strip. Clear at rest, the glass of the bars once
|
|
524
|
+
content is under the stuck parts, so content never shows through the status bar when
|
|
525
|
+
the first part has scrolled away. It takes no room. -->
|
|
526
|
+
<div
|
|
527
|
+
bind:this={strip}
|
|
528
|
+
data-app-shell-strip
|
|
529
|
+
aria-hidden="true"
|
|
530
|
+
data-scrolled-under={String(scrolledTop)}
|
|
531
|
+
class="material-bar app-shell-strip [--color-bar:transparent]"
|
|
532
|
+
></div>
|
|
533
|
+
<!-- An empty strip is left when the bar slides away; it must not take taps. -->
|
|
534
|
+
<div
|
|
535
|
+
bind:this={headerRegion}
|
|
536
|
+
data-app-shell-header
|
|
537
|
+
style:top={headerOverscroll > 0 ? `-${headerOverscroll}px` : undefined}
|
|
538
|
+
class={cn(
|
|
539
|
+
"pointer-events-none sticky top-0 z-sticky shrink-0 *:pointer-events-auto",
|
|
540
|
+
// The wash shows through a bar at rest (glass still takes over once scrolled).
|
|
541
|
+
canvas && "[--color-bar:transparent]",
|
|
542
|
+
)}
|
|
543
|
+
>
|
|
544
|
+
{@render header()}
|
|
545
|
+
</div>
|
|
546
|
+
{/if}
|
|
547
|
+
<!-- Two branches, not `<svelte:element>`: hydration takes a dynamic
|
|
548
|
+
element out and puts it back, which blurs a control inside it that the
|
|
549
|
+
user had already tabbed to, and the whole page is inside this one. -->
|
|
550
|
+
{#if contentElement === "div"}
|
|
551
|
+
<div data-app-shell-content class={contentClasses}>
|
|
552
|
+
{@render children?.()}
|
|
553
|
+
</div>
|
|
554
|
+
{:else}
|
|
555
|
+
<main data-app-shell-content class={contentClasses}>
|
|
556
|
+
{@render children?.()}
|
|
557
|
+
</main>
|
|
558
|
+
{/if}
|
|
559
|
+
</div>
|
|
560
|
+
{#if footer}
|
|
561
|
+
<!-- Over the bottom of the scroller, not a row of its own: content passes beneath it.
|
|
562
|
+
Above the tabs when the navigation is placed there. -->
|
|
563
|
+
<div
|
|
564
|
+
bind:this={footerRegion}
|
|
565
|
+
data-app-shell-footer
|
|
566
|
+
class={cn(
|
|
567
|
+
"absolute inset-x-0",
|
|
568
|
+
navigation ? "bottom-(--shell-nav-bottom)" : "bottom-0",
|
|
569
|
+
"z-sticky",
|
|
570
|
+
)}
|
|
571
|
+
>
|
|
572
|
+
{@render footer()}
|
|
573
|
+
</div>
|
|
574
|
+
{/if}
|
|
575
|
+
{/snippet}
|
|
576
|
+
|
|
577
|
+
<div
|
|
578
|
+
data-app-shell
|
|
579
|
+
class={cn(
|
|
580
|
+
navigation
|
|
581
|
+
? "relative flex h-dvh w-full overflow-hidden text-body"
|
|
582
|
+
: "relative flex h-dvh w-full flex-col overflow-hidden text-body",
|
|
583
|
+
canvas ? "bg-canvas" : "bg-background",
|
|
584
|
+
className,
|
|
585
|
+
)}
|
|
586
|
+
class:app-shell--nav={!!navigation}
|
|
587
|
+
data-navigation-placement={navigation ? navigationPlacement : undefined}
|
|
588
|
+
data-scrolled-top={String(scrolledTop)}
|
|
589
|
+
data-scrolled-bottom={String(scrolledBottom)}
|
|
590
|
+
style={hostStyle}
|
|
591
|
+
{...restProps}
|
|
592
|
+
>
|
|
593
|
+
{#if navigation}
|
|
594
|
+
<!-- First in the DOM: Tab reaches the navigation, then the header, then the content.
|
|
595
|
+
Over the bottom as tabs, a column at the start as a rail or a sidebar: the stylesheet
|
|
596
|
+
below moves it, the snippet is rendered once. -->
|
|
597
|
+
<div bind:this={navRegion} data-app-shell-navigation class="shell-nav z-sticky">
|
|
598
|
+
{@render navigation({ placement })}
|
|
599
|
+
</div>
|
|
600
|
+
<div data-app-shell-column class="relative flex min-h-0 min-w-0 flex-1 flex-col">
|
|
601
|
+
{@render body()}
|
|
602
|
+
</div>
|
|
603
|
+
{:else}
|
|
604
|
+
{@render body()}
|
|
605
|
+
{/if}
|
|
606
|
+
</div>
|
|
607
|
+
|
|
608
|
+
<style>
|
|
609
|
+
/*
|
|
610
|
+
* Global on purpose: a scoped class would be added to every element of the
|
|
611
|
+
* shell, and a shell without `navigation` renders what it always did.
|
|
612
|
+
*
|
|
613
|
+
* The navigation's placement is two things: the host's data attribute
|
|
614
|
+
* (forced) or the viewport (`auto`, below 48rem tabs, from 48rem a rail,
|
|
615
|
+
* from 64rem a sidebar: the constants in util/app-shell.ts). Both only set
|
|
616
|
+
* the same two flags; every length the shell uses is derived from them, so
|
|
617
|
+
* the rules are written once. `--is-side` is 1 for a rail or a sidebar,
|
|
618
|
+
* `--is-wide` is 1 for a sidebar. Server and client render the same
|
|
619
|
+
* markup; only these rules decide.
|
|
620
|
+
*/
|
|
621
|
+
:global(.app-shell--nav) {
|
|
622
|
+
--is-side: 0;
|
|
623
|
+
--is-wide: 0;
|
|
624
|
+
/* A flex item of the host as a column, over the bottom as tabs. */
|
|
625
|
+
--nav-position: absolute;
|
|
626
|
+
/* What the navigation takes over the bottom: its height as tabs, nothing as a side column. */
|
|
627
|
+
--shell-nav-bottom: calc(
|
|
628
|
+
(1 - var(--is-side)) * var(--shell-nav-h, calc(64px + env(safe-area-inset-bottom, 0px)))
|
|
629
|
+
);
|
|
630
|
+
/* The width of the side column: the estimates until the shell has measured it. */
|
|
631
|
+
--shell-start: calc(var(--is-side) * var(--shell-nav-w, calc(80px + var(--is-wide) * 176px)));
|
|
632
|
+
/* Without a footer, a side column leaves the safe area under the content. Tabs carry their own. */
|
|
633
|
+
--shell-safe-bottom: calc(var(--is-side) * env(safe-area-inset-bottom, 0px));
|
|
634
|
+
/* The side column clears the inline-start safe area itself. */
|
|
635
|
+
--shell-content-pl: calc((1 - var(--is-side)) * env(safe-area-inset-left, 0px));
|
|
636
|
+
/* Everything that lies over the bottom of the content: footer plus tabs. */
|
|
637
|
+
--shell-overlay: calc(var(--shell-footer, 0px) + var(--shell-nav-bottom));
|
|
638
|
+
}
|
|
639
|
+
:global(.app-shell--nav[data-navigation-placement="rail"]),
|
|
640
|
+
:global(.app-shell--nav[data-navigation-placement="sidebar"]) {
|
|
641
|
+
--is-side: 1;
|
|
642
|
+
--nav-position: static;
|
|
643
|
+
}
|
|
644
|
+
:global(.app-shell--nav[data-navigation-placement="sidebar"]) {
|
|
645
|
+
--is-wide: 1;
|
|
646
|
+
}
|
|
647
|
+
@media (min-width: 48rem) {
|
|
648
|
+
:global(.app-shell--nav[data-navigation-placement="auto"]) {
|
|
649
|
+
--is-side: 1;
|
|
650
|
+
--nav-position: static;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
@media (min-width: 64rem) {
|
|
654
|
+
:global(.app-shell--nav[data-navigation-placement="auto"]) {
|
|
655
|
+
--is-wide: 1;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/*
|
|
660
|
+
* Header parts. A header whose children are `AppShellHeaderPart`s is not
|
|
661
|
+
* one sticky block: the wrapper drops out of the layout so that each part
|
|
662
|
+
* is a flex item of the scroller and sticks (or not) by itself. The
|
|
663
|
+
* decision is made in CSS, so the server's markup is right.
|
|
664
|
+
*/
|
|
665
|
+
:global([data-app-shell-header]:has(> [data-app-shell-part])) {
|
|
666
|
+
display: contents;
|
|
667
|
+
}
|
|
668
|
+
:global(.app-shell-strip) {
|
|
669
|
+
display: none;
|
|
670
|
+
}
|
|
671
|
+
/* The strip is only for parts: the height of the status bar, taking no room, over the stuck parts' place. */
|
|
672
|
+
:global(.app-shell-strip:has(+ [data-app-shell-header] > [data-app-shell-part])) {
|
|
673
|
+
display: block;
|
|
674
|
+
position: sticky;
|
|
675
|
+
top: 0;
|
|
676
|
+
z-index: var(--z-sticky);
|
|
677
|
+
flex-shrink: 0;
|
|
678
|
+
height: env(safe-area-inset-top, 0px);
|
|
679
|
+
margin-bottom: calc(-1 * env(safe-area-inset-top, 0px));
|
|
680
|
+
pointer-events: none;
|
|
681
|
+
}
|
|
682
|
+
/* The surface is the bars' (`material-bar`): glass without the hairline, which belongs to the lowest part. */
|
|
683
|
+
:global(.app-shell-strip::before) {
|
|
684
|
+
border-width: 0;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
:global(.shell-nav) {
|
|
688
|
+
position: var(--nav-position);
|
|
689
|
+
inset-inline: 0;
|
|
690
|
+
bottom: 0;
|
|
691
|
+
display: flex;
|
|
692
|
+
flex-direction: column;
|
|
693
|
+
flex: none;
|
|
694
|
+
min-height: 0;
|
|
695
|
+
}
|
|
696
|
+
</style>
|