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,50 @@
|
|
|
1
|
+
/** Types and pure helpers for `MediaGrid`. Kept out of the component so they can be tested without a DOM. */
|
|
2
|
+
export type MediaGridKey = string | number;
|
|
3
|
+
export type MediaGridItemType = "image" | "video";
|
|
4
|
+
/** Payload of `onselect`. */
|
|
5
|
+
export interface MediaGridSelectDetail<T> {
|
|
6
|
+
item: T;
|
|
7
|
+
/** False when the activation cleared the item's selection. */
|
|
8
|
+
selected: boolean;
|
|
9
|
+
/** Every selected key after the change: one or none unless `multiple`. */
|
|
10
|
+
keys: MediaGridKey[];
|
|
11
|
+
}
|
|
12
|
+
/** Every built-in string. Pass a partial object to `strings` to translate. */
|
|
13
|
+
export interface MediaGridStrings {
|
|
14
|
+
/** Accessible name of an item's delete button. */
|
|
15
|
+
deleteLabel: (label: string) => string;
|
|
16
|
+
/** Accessible name of a video item; an image is named by its label alone. */
|
|
17
|
+
videoLabel: (label: string) => string;
|
|
18
|
+
/** Announced, and never shown, while `loading` is set. */
|
|
19
|
+
loading: string;
|
|
20
|
+
emptyTitle: string;
|
|
21
|
+
emptyDescription: string;
|
|
22
|
+
/**
|
|
23
|
+
* How to get around by keyboard. Read once when focus enters the grid,
|
|
24
|
+
* and shown below it while it has keyboard focus. Keep it to one short
|
|
25
|
+
* sentence.
|
|
26
|
+
*/
|
|
27
|
+
keyboardHint: string;
|
|
28
|
+
}
|
|
29
|
+
export declare const MEDIA_GRID_STRINGS: MediaGridStrings;
|
|
30
|
+
export { isVideoUrl } from "./media.js";
|
|
31
|
+
/**
|
|
32
|
+
* Columns in the rendered grid, read from the top edge of each tile in DOM
|
|
33
|
+
* order: the first row ends where the top edge changes. The layout is CSS
|
|
34
|
+
* `auto-fill`, so only the browser knows the answer.
|
|
35
|
+
*/
|
|
36
|
+
export declare function columnCount(tops: readonly number[]): number;
|
|
37
|
+
export type MediaGridMove = "left" | "right" | "up" | "down" | "rowStart" | "rowEnd" | "first" | "last";
|
|
38
|
+
/**
|
|
39
|
+
* Index a navigation key lands on. Nothing wraps. Moving down from the row
|
|
40
|
+
* above a short last row lands on the last item, so the last row can always
|
|
41
|
+
* be reached from any column.
|
|
42
|
+
*/
|
|
43
|
+
export declare function moveIndex(index: number, move: MediaGridMove, columns: number, total: number): number;
|
|
44
|
+
/**
|
|
45
|
+
* The move a key asks for in a grid of tiles, or undefined for a key that is
|
|
46
|
+
* not a navigation key. Left and Right follow what is on screen, which is
|
|
47
|
+
* mirrored in a right-to-left layout; Ctrl with Home or End goes to the first
|
|
48
|
+
* or last tile. Shared by `MediaGrid` and `PhotoGrid`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function moveForKey(event: Pick<KeyboardEvent, "key" | "ctrlKey">, rtl: boolean): MediaGridMove | undefined;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** Types and pure helpers for `MediaGrid`. Kept out of the component so they can be tested without a DOM. */
|
|
2
|
+
export const MEDIA_GRID_STRINGS = {
|
|
3
|
+
deleteLabel: (label) => `Delete ${label}`,
|
|
4
|
+
videoLabel: (label) => `${label}, video`,
|
|
5
|
+
loading: "Loading media",
|
|
6
|
+
emptyTitle: "No media yet",
|
|
7
|
+
emptyDescription: "Images and videos you upload appear here.",
|
|
8
|
+
keyboardHint: "Use the arrow keys to move between items.",
|
|
9
|
+
};
|
|
10
|
+
// The rule is shared with `ImageUpload`; re-exported so this module's exports stay as they were.
|
|
11
|
+
export { isVideoUrl } from "./media.js";
|
|
12
|
+
/**
|
|
13
|
+
* Columns in the rendered grid, read from the top edge of each tile in DOM
|
|
14
|
+
* order: the first row ends where the top edge changes. The layout is CSS
|
|
15
|
+
* `auto-fill`, so only the browser knows the answer.
|
|
16
|
+
*/
|
|
17
|
+
export function columnCount(tops) {
|
|
18
|
+
if (tops.length === 0)
|
|
19
|
+
return 1;
|
|
20
|
+
let count = 1;
|
|
21
|
+
while (count < tops.length && Math.abs(tops[count] - tops[0]) < 1)
|
|
22
|
+
count += 1;
|
|
23
|
+
return count;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Index a navigation key lands on. Nothing wraps. Moving down from the row
|
|
27
|
+
* above a short last row lands on the last item, so the last row can always
|
|
28
|
+
* be reached from any column.
|
|
29
|
+
*/
|
|
30
|
+
export function moveIndex(index, move, columns, total) {
|
|
31
|
+
if (total === 0)
|
|
32
|
+
return -1;
|
|
33
|
+
const last = total - 1;
|
|
34
|
+
const rowStart = index - (index % columns);
|
|
35
|
+
switch (move) {
|
|
36
|
+
case "left":
|
|
37
|
+
return Math.max(0, index - 1);
|
|
38
|
+
case "right":
|
|
39
|
+
return Math.min(last, index + 1);
|
|
40
|
+
case "up":
|
|
41
|
+
return index - columns >= 0 ? index - columns : index;
|
|
42
|
+
case "down": {
|
|
43
|
+
if (index + columns <= last)
|
|
44
|
+
return index + columns;
|
|
45
|
+
// A row exists below, but not under this column.
|
|
46
|
+
return rowStart + columns <= last ? last : index;
|
|
47
|
+
}
|
|
48
|
+
case "rowStart":
|
|
49
|
+
return rowStart;
|
|
50
|
+
case "rowEnd":
|
|
51
|
+
return Math.min(last, rowStart + columns - 1);
|
|
52
|
+
case "first":
|
|
53
|
+
return 0;
|
|
54
|
+
case "last":
|
|
55
|
+
return last;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const KEY_MOVES = {
|
|
59
|
+
ArrowUp: "up",
|
|
60
|
+
ArrowDown: "down",
|
|
61
|
+
Home: "rowStart",
|
|
62
|
+
End: "rowEnd",
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* The move a key asks for in a grid of tiles, or undefined for a key that is
|
|
66
|
+
* not a navigation key. Left and Right follow what is on screen, which is
|
|
67
|
+
* mirrored in a right-to-left layout; Ctrl with Home or End goes to the first
|
|
68
|
+
* or last tile. Shared by `MediaGrid` and `PhotoGrid`.
|
|
69
|
+
*/
|
|
70
|
+
export function moveForKey(event, rtl) {
|
|
71
|
+
if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
|
|
72
|
+
return (event.key === "ArrowLeft") !== rtl ? "left" : "right";
|
|
73
|
+
}
|
|
74
|
+
if (event.ctrlKey && event.key === "Home")
|
|
75
|
+
return "first";
|
|
76
|
+
if (event.ctrlKey && event.key === "End")
|
|
77
|
+
return "last";
|
|
78
|
+
if (!event.ctrlKey)
|
|
79
|
+
return KEY_MOVES[event.key];
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** How the library tells a video from an image by its address. Shared by `ImageUpload` and `MediaGrid`. */
|
|
2
|
+
/** True for a path or file name with a video extension, before any query or hash. */
|
|
3
|
+
export declare function isVideoPath(path: string): boolean;
|
|
4
|
+
/** True for a video data URL, or a URL whose path has a video extension. */
|
|
5
|
+
export declare function isVideoUrl(url: string): boolean;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** How the library tells a video from an image by its address. Shared by `ImageUpload` and `MediaGrid`. */
|
|
2
|
+
const VIDEO_EXTENSION = /\.(mp4|m4v|webm|ogv|mov)$/i;
|
|
3
|
+
/** True for a path or file name with a video extension, before any query or hash. */
|
|
4
|
+
export function isVideoPath(path) {
|
|
5
|
+
return VIDEO_EXTENSION.test(path.split(/[?#]/)[0]);
|
|
6
|
+
}
|
|
7
|
+
/** True for a video data URL, or a URL whose path has a video extension. */
|
|
8
|
+
export function isVideoUrl(url) {
|
|
9
|
+
return url.startsWith("data:video/") || isVideoPath(url);
|
|
10
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The chrome of an overlay at large text: the header of a BottomSheet, Drawer,
|
|
3
|
+
* Modal or SlideUp, and the padding of its footer.
|
|
4
|
+
*
|
|
5
|
+
* Tailwind's spacing is in rem, so at `html { font-size: 200% }` the gutters,
|
|
6
|
+
* the gap above the title, the title and the close button all doubled. On a
|
|
7
|
+
* phone that left a third of a half-height sheet to what the sheet was opened
|
|
8
|
+
* for. This is navigation chrome, as the app bar is (see AppBar): it says
|
|
9
|
+
* where you are and how to leave, and it is not what the user enlarged the
|
|
10
|
+
* text to read. So it is laid out in px, and its title grows to 1.3 times its
|
|
11
|
+
* size and stops. The content, and whatever the app puts in the footer, scale
|
|
12
|
+
* in full. docs/ACCESSIBILITY.md has the reasoning against WCAG 1.4.4.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* On the header box: the spacing scale in px for everything inside it, so
|
|
16
|
+
* `px-6`, `gap-4`, `size-8` and the 44px of `TOUCH_HIT_AREA` are the sizes
|
|
17
|
+
* they are at 100% text. Only for a box that holds chrome alone: an app's
|
|
18
|
+
* Button inside it would stop growing too, which is why a footer spells its
|
|
19
|
+
* padding out in px instead.
|
|
20
|
+
*/
|
|
21
|
+
export declare const OVERLAY_CHROME = "[--spacing:4px]";
|
|
22
|
+
/**
|
|
23
|
+
* How a title wraps: onto as many lines as it needs, between words, and a
|
|
24
|
+
* word wider than the whole line is cut rather than left sticking out.
|
|
25
|
+
*/
|
|
26
|
+
export declare const OVERLAY_TITLE_WRAP = "[overflow-wrap:break-word]";
|
|
27
|
+
/**
|
|
28
|
+
* On the title while the text is enlarged (`textIsEnlarged`): a word that does
|
|
29
|
+
* not fit at the end of a line is hyphenated by the rules of the page's
|
|
30
|
+
* `lang`, where the browser has them, before it is cut.
|
|
31
|
+
*
|
|
32
|
+
* Not at the default text size, and not narrowed with `hyphenate-limit-chars`:
|
|
33
|
+
* `hyphens: auto` hyphenates at the end of every line, not only where a word
|
|
34
|
+
* is too wide, and what a browser offers as a hyphenation point is the
|
|
35
|
+
* platform's ("sett-ings", "histor-y" in Chromium on macOS). A title that
|
|
36
|
+
* wraps between words today would get those. A limit, where it is supported
|
|
37
|
+
* at all (not in Safari), turns a hyphenated break of a shorter word into a
|
|
38
|
+
* plain cut.
|
|
39
|
+
*/
|
|
40
|
+
export declare const OVERLAY_TITLE_HYPHENS = "hyphens-auto";
|
|
41
|
+
/**
|
|
42
|
+
* Whether the reader has enlarged the text: the root font size is above the
|
|
43
|
+
* 16px every browser starts from. Read when an overlay opens. False on the
|
|
44
|
+
* server and until then, which is the layout at the default size.
|
|
45
|
+
*/
|
|
46
|
+
export declare function textIsEnlarged(): boolean;
|
|
47
|
+
/** A title of 20px (`text-xl`): up to 26px, on the same 1.4 line. */
|
|
48
|
+
export declare const OVERLAY_TITLE_XL = "text-[length:min(1.25rem,26px)] leading-[1.4]";
|
|
49
|
+
/** A title of 24px (`text-2xl`): up to 31.2px, on the same 4/3 line. */
|
|
50
|
+
export declare const OVERLAY_TITLE_2XL = "text-[length:min(1.5rem,31.2px)] leading-[calc(4/3)]";
|
|
51
|
+
/** The "×" of a close button: a control, so the size it is at 100%. */
|
|
52
|
+
export declare const OVERLAY_CLOSE_GLYPH = "text-[24px] leading-[32px]";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The chrome of an overlay at large text: the header of a BottomSheet, Drawer,
|
|
3
|
+
* Modal or SlideUp, and the padding of its footer.
|
|
4
|
+
*
|
|
5
|
+
* Tailwind's spacing is in rem, so at `html { font-size: 200% }` the gutters,
|
|
6
|
+
* the gap above the title, the title and the close button all doubled. On a
|
|
7
|
+
* phone that left a third of a half-height sheet to what the sheet was opened
|
|
8
|
+
* for. This is navigation chrome, as the app bar is (see AppBar): it says
|
|
9
|
+
* where you are and how to leave, and it is not what the user enlarged the
|
|
10
|
+
* text to read. So it is laid out in px, and its title grows to 1.3 times its
|
|
11
|
+
* size and stops. The content, and whatever the app puts in the footer, scale
|
|
12
|
+
* in full. docs/ACCESSIBILITY.md has the reasoning against WCAG 1.4.4.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* On the header box: the spacing scale in px for everything inside it, so
|
|
16
|
+
* `px-6`, `gap-4`, `size-8` and the 44px of `TOUCH_HIT_AREA` are the sizes
|
|
17
|
+
* they are at 100% text. Only for a box that holds chrome alone: an app's
|
|
18
|
+
* Button inside it would stop growing too, which is why a footer spells its
|
|
19
|
+
* padding out in px instead.
|
|
20
|
+
*/
|
|
21
|
+
export const OVERLAY_CHROME = "[--spacing:4px]";
|
|
22
|
+
/**
|
|
23
|
+
* How a title wraps: onto as many lines as it needs, between words, and a
|
|
24
|
+
* word wider than the whole line is cut rather than left sticking out.
|
|
25
|
+
*/
|
|
26
|
+
export const OVERLAY_TITLE_WRAP = "[overflow-wrap:break-word]";
|
|
27
|
+
/**
|
|
28
|
+
* On the title while the text is enlarged (`textIsEnlarged`): a word that does
|
|
29
|
+
* not fit at the end of a line is hyphenated by the rules of the page's
|
|
30
|
+
* `lang`, where the browser has them, before it is cut.
|
|
31
|
+
*
|
|
32
|
+
* Not at the default text size, and not narrowed with `hyphenate-limit-chars`:
|
|
33
|
+
* `hyphens: auto` hyphenates at the end of every line, not only where a word
|
|
34
|
+
* is too wide, and what a browser offers as a hyphenation point is the
|
|
35
|
+
* platform's ("sett-ings", "histor-y" in Chromium on macOS). A title that
|
|
36
|
+
* wraps between words today would get those. A limit, where it is supported
|
|
37
|
+
* at all (not in Safari), turns a hyphenated break of a shorter word into a
|
|
38
|
+
* plain cut.
|
|
39
|
+
*/
|
|
40
|
+
export const OVERLAY_TITLE_HYPHENS = "hyphens-auto";
|
|
41
|
+
/**
|
|
42
|
+
* Whether the reader has enlarged the text: the root font size is above the
|
|
43
|
+
* 16px every browser starts from. Read when an overlay opens. False on the
|
|
44
|
+
* server and until then, which is the layout at the default size.
|
|
45
|
+
*/
|
|
46
|
+
export function textIsEnlarged() {
|
|
47
|
+
if (typeof document === "undefined")
|
|
48
|
+
return false;
|
|
49
|
+
return parseFloat(getComputedStyle(document.documentElement).fontSize) > 16;
|
|
50
|
+
}
|
|
51
|
+
/** A title of 20px (`text-xl`): up to 26px, on the same 1.4 line. */
|
|
52
|
+
export const OVERLAY_TITLE_XL = "text-[length:min(1.25rem,26px)] leading-[1.4]";
|
|
53
|
+
/** A title of 24px (`text-2xl`): up to 31.2px, on the same 4/3 line. */
|
|
54
|
+
export const OVERLAY_TITLE_2XL = "text-[length:min(1.5rem,31.2px)] leading-[calc(4/3)]";
|
|
55
|
+
/** The "×" of a close button: a control, so the size it is at 100%. */
|
|
56
|
+
export const OVERLAY_CLOSE_GLYPH = "text-[24px] leading-[32px]";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What Modal, SlideUp and Drawer share beyond `focus-utils.ts`: the scroll
|
|
3
|
+
* lock on `<body>` and the Tab cycle inside the panel. One copy, so the
|
|
4
|
+
* overlays cannot drift apart and keep working when nested in any order.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Stops the page behind an overlay from scrolling. Ref-counted on `<body>`
|
|
8
|
+
* (`data-zabi-scroll-lock`), so nested overlays share one lock and the page
|
|
9
|
+
* scrolls again only when the last one has released it.
|
|
10
|
+
*
|
|
11
|
+
* Call it when the overlay opens, in the browser, and call the returned
|
|
12
|
+
* function when it closes.
|
|
13
|
+
*/
|
|
14
|
+
export declare function lockBodyScroll(): () => void;
|
|
15
|
+
/**
|
|
16
|
+
* Keeps Tab inside `container`: call it from the panel's `keydown` handler.
|
|
17
|
+
* Focusables are re-queried on every Tab, so content added while the overlay
|
|
18
|
+
* is open stays inside the cycle. A Tab that comes from a dialog nested in the
|
|
19
|
+
* container is left to that dialog. The toasts on screen are in the cycle too,
|
|
20
|
+
* after the container's own controls: see `getToastFocusables`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function trapTabKey(container: HTMLElement | undefined, event: KeyboardEvent): void;
|
|
23
|
+
/**
|
|
24
|
+
* Watches a scrolling box in an overlay and reports whether it has to be a
|
|
25
|
+
* Tab stop itself: true when its content is taller than it and holds nothing
|
|
26
|
+
* that can take focus, so the keyboard would otherwise have no way to scroll
|
|
27
|
+
* it. The answer changes with the viewport and with the content, so it is
|
|
28
|
+
* reported again whenever either does.
|
|
29
|
+
*
|
|
30
|
+
* Call it when the overlay opens, in the browser; the returned function stops it.
|
|
31
|
+
*/
|
|
32
|
+
export declare function watchScrollerNeedsFocus(box: HTMLElement, onChange: (needsFocus: boolean) => void): () => void;
|
|
33
|
+
/**
|
|
34
|
+
* Keeps an overlay on the part of the screen the on-screen keyboard leaves.
|
|
35
|
+
*
|
|
36
|
+
* Where the keyboard covers the page instead of shrinking it (iOS Safari,
|
|
37
|
+
* Chrome on Android), a panel at the bottom of a `fixed inset-0` root ends up
|
|
38
|
+
* behind it, footer and all. This moves the root's bottom edge up to the top
|
|
39
|
+
* of the keyboard (and its top edge down, if the browser has panned the
|
|
40
|
+
* screen), so everything laid out against the root follows: a sheet's height,
|
|
41
|
+
* a pinned footer, a drawer's end. `data-keyboard-open` is set on the root
|
|
42
|
+
* meanwhile. The field that has focus is scrolled back into view once the
|
|
43
|
+
* root has its new size. Where the page shrinks by itself, nothing is done.
|
|
44
|
+
*
|
|
45
|
+
* The root is usually the backdrop as well: while it is moved, its dimming is
|
|
46
|
+
* carried on past its edges, so the page does not show undimmed where the
|
|
47
|
+
* keyboard was while that slides away.
|
|
48
|
+
*
|
|
49
|
+
* `root` is the overlay's outermost, fixed element. Call it when the overlay
|
|
50
|
+
* opens, in the browser; the returned function stops it and puts the root back.
|
|
51
|
+
*/
|
|
52
|
+
export declare function followKeyboard(root: HTMLElement): () => void;
|
|
53
|
+
/**
|
|
54
|
+
* Says that `footer` is pinned to the bottom of an open overlay, so what is
|
|
55
|
+
* drawn over the overlays (the toast stack) can stay off it. Call it when the
|
|
56
|
+
* footer is rendered, in the browser; the returned function withdraws it.
|
|
57
|
+
*/
|
|
58
|
+
export declare function registerOverlayFooter(footer: HTMLElement): () => void;
|
|
59
|
+
/**
|
|
60
|
+
* Says that `header` is the top of an open overlay and holds its close
|
|
61
|
+
* button, so the toast stack starts below it and never lies over the one
|
|
62
|
+
* control that always closes the overlay. Call it when the header is
|
|
63
|
+
* rendered, in the browser; the returned function withdraws it.
|
|
64
|
+
*/
|
|
65
|
+
export declare function registerOverlayHeader(header: HTMLElement): () => void;
|
|
66
|
+
/** The footer of the overlay on top, if it has one. */
|
|
67
|
+
export declare function topOverlayFooter(): HTMLElement | null;
|
|
68
|
+
/** The header of the overlay on top, if it has one. */
|
|
69
|
+
export declare function topOverlayHeader(): HTMLElement | null;
|
|
70
|
+
/** Calls `listener` whenever a registered header or footer appears, goes, moves or changes size. */
|
|
71
|
+
export declare function watchOverlayFooters(listener: () => void): () => void;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What Modal, SlideUp and Drawer share beyond `focus-utils.ts`: the scroll
|
|
3
|
+
* lock on `<body>` and the Tab cycle inside the panel. One copy, so the
|
|
4
|
+
* overlays cannot drift apart and keep working when nested in any order.
|
|
5
|
+
*/
|
|
6
|
+
import { cycleTrappedFocus, DIALOG_SELECTOR, getFocusableElements } from "./focus-utils.js";
|
|
7
|
+
import { readKeyboardInset } from "./keyboard-inset.js";
|
|
8
|
+
/**
|
|
9
|
+
* Stops the page behind an overlay from scrolling. Ref-counted on `<body>`
|
|
10
|
+
* (`data-zabi-scroll-lock`), so nested overlays share one lock and the page
|
|
11
|
+
* scrolls again only when the last one has released it.
|
|
12
|
+
*
|
|
13
|
+
* Call it when the overlay opens, in the browser, and call the returned
|
|
14
|
+
* function when it closes.
|
|
15
|
+
*/
|
|
16
|
+
export function lockBodyScroll() {
|
|
17
|
+
const body = document.body;
|
|
18
|
+
const count = Number(body.dataset.zabiScrollLock ?? "0");
|
|
19
|
+
if (count === 0) {
|
|
20
|
+
body.dataset.zabiScrollLockOverflow = body.style.overflow;
|
|
21
|
+
body.style.overflow = "hidden";
|
|
22
|
+
}
|
|
23
|
+
body.dataset.zabiScrollLock = String(count + 1);
|
|
24
|
+
return () => {
|
|
25
|
+
const next = Number(body.dataset.zabiScrollLock ?? "1") - 1;
|
|
26
|
+
if (next <= 0) {
|
|
27
|
+
body.style.overflow = body.dataset.zabiScrollLockOverflow ?? "";
|
|
28
|
+
delete body.dataset.zabiScrollLock;
|
|
29
|
+
delete body.dataset.zabiScrollLockOverflow;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
body.dataset.zabiScrollLock = String(next);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Keeps Tab inside `container`: call it from the panel's `keydown` handler.
|
|
38
|
+
* Focusables are re-queried on every Tab, so content added while the overlay
|
|
39
|
+
* is open stays inside the cycle. A Tab that comes from a dialog nested in the
|
|
40
|
+
* container is left to that dialog. The toasts on screen are in the cycle too,
|
|
41
|
+
* after the container's own controls: see `getToastFocusables`.
|
|
42
|
+
*/
|
|
43
|
+
export function trapTabKey(container, event) {
|
|
44
|
+
if (event.key !== "Tab" || !container)
|
|
45
|
+
return;
|
|
46
|
+
// A nested dialog handles its own Tab cycle.
|
|
47
|
+
const owner = event.target?.closest?.(DIALOG_SELECTOR);
|
|
48
|
+
if (owner && owner !== container)
|
|
49
|
+
return;
|
|
50
|
+
cycleTrappedFocus(container, event);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Watches a scrolling box in an overlay and reports whether it has to be a
|
|
54
|
+
* Tab stop itself: true when its content is taller than it and holds nothing
|
|
55
|
+
* that can take focus, so the keyboard would otherwise have no way to scroll
|
|
56
|
+
* it. The answer changes with the viewport and with the content, so it is
|
|
57
|
+
* reported again whenever either does.
|
|
58
|
+
*
|
|
59
|
+
* Call it when the overlay opens, in the browser; the returned function stops it.
|
|
60
|
+
*/
|
|
61
|
+
export function watchScrollerNeedsFocus(box, onChange) {
|
|
62
|
+
const measure = () => {
|
|
63
|
+
onChange(box.scrollHeight > box.clientHeight && getFocusableElements(box).length === 0);
|
|
64
|
+
};
|
|
65
|
+
measure();
|
|
66
|
+
const resize = typeof ResizeObserver === "function" ? new ResizeObserver(measure) : undefined;
|
|
67
|
+
resize?.observe(box);
|
|
68
|
+
const mutation = new MutationObserver(measure);
|
|
69
|
+
mutation.observe(box, { childList: true, subtree: true, characterData: true });
|
|
70
|
+
return () => {
|
|
71
|
+
resize?.disconnect();
|
|
72
|
+
mutation.disconnect();
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Keeps an overlay on the part of the screen the on-screen keyboard leaves.
|
|
77
|
+
*
|
|
78
|
+
* Where the keyboard covers the page instead of shrinking it (iOS Safari,
|
|
79
|
+
* Chrome on Android), a panel at the bottom of a `fixed inset-0` root ends up
|
|
80
|
+
* behind it, footer and all. This moves the root's bottom edge up to the top
|
|
81
|
+
* of the keyboard (and its top edge down, if the browser has panned the
|
|
82
|
+
* screen), so everything laid out against the root follows: a sheet's height,
|
|
83
|
+
* a pinned footer, a drawer's end. `data-keyboard-open` is set on the root
|
|
84
|
+
* meanwhile. The field that has focus is scrolled back into view once the
|
|
85
|
+
* root has its new size. Where the page shrinks by itself, nothing is done.
|
|
86
|
+
*
|
|
87
|
+
* The root is usually the backdrop as well: while it is moved, its dimming is
|
|
88
|
+
* carried on past its edges, so the page does not show undimmed where the
|
|
89
|
+
* keyboard was while that slides away.
|
|
90
|
+
*
|
|
91
|
+
* `root` is the overlay's outermost, fixed element. Call it when the overlay
|
|
92
|
+
* opens, in the browser; the returned function stops it and puts the root back.
|
|
93
|
+
*/
|
|
94
|
+
export function followKeyboard(root) {
|
|
95
|
+
if (typeof window === "undefined")
|
|
96
|
+
return () => { };
|
|
97
|
+
const viewport = window.visualViewport;
|
|
98
|
+
const restore = () => {
|
|
99
|
+
root.style.bottom = "";
|
|
100
|
+
root.style.top = "";
|
|
101
|
+
root.style.boxShadow = "";
|
|
102
|
+
delete root.dataset.keyboardOpen;
|
|
103
|
+
};
|
|
104
|
+
let last = "";
|
|
105
|
+
const update = () => {
|
|
106
|
+
const inset = readKeyboardInset();
|
|
107
|
+
// How far the browser has panned the screen to show the field. It
|
|
108
|
+
// counts when the pan is complete as well: nothing of the page is
|
|
109
|
+
// under the keyboard then (the inset is 0), and the top of the root
|
|
110
|
+
// is as far above the screen as the keyboard is tall.
|
|
111
|
+
const shorter = !!viewport &&
|
|
112
|
+
Math.abs(viewport.scale - 1) <= 0.01 &&
|
|
113
|
+
document.documentElement.clientHeight - viewport.height > 1;
|
|
114
|
+
const panned = shorter ? Math.max(0, Math.round(viewport.offsetTop)) : 0;
|
|
115
|
+
const key = `${inset}:${panned}`;
|
|
116
|
+
if (key === last)
|
|
117
|
+
return;
|
|
118
|
+
last = key;
|
|
119
|
+
if (inset > 0 || panned > 0) {
|
|
120
|
+
root.style.bottom = inset > 0 ? `${inset}px` : "";
|
|
121
|
+
root.style.top = panned > 0 ? `${panned}px` : "";
|
|
122
|
+
root.dataset.keyboardOpen = "true";
|
|
123
|
+
// The root is the backdrop too. What it no longer covers is under
|
|
124
|
+
// the keyboard, but shows for a moment while the keyboard slides
|
|
125
|
+
// away: the dimming goes on past the root's edges.
|
|
126
|
+
const dim = getComputedStyle(root).backgroundColor;
|
|
127
|
+
root.style.boxShadow =
|
|
128
|
+
dim && dim !== "transparent" && dim !== "rgba(0, 0, 0, 0)"
|
|
129
|
+
? `0 0 0 100vmax ${dim}`
|
|
130
|
+
: "";
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
restore();
|
|
134
|
+
}
|
|
135
|
+
notifyOverlayFooters();
|
|
136
|
+
const active = document.activeElement;
|
|
137
|
+
if (inset > 0 && active instanceof HTMLElement && root.contains(active)) {
|
|
138
|
+
active.scrollIntoView?.({ block: "nearest" });
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
update();
|
|
142
|
+
viewport?.addEventListener("resize", update);
|
|
143
|
+
viewport?.addEventListener("scroll", update);
|
|
144
|
+
window.addEventListener("resize", update);
|
|
145
|
+
return () => {
|
|
146
|
+
viewport?.removeEventListener("resize", update);
|
|
147
|
+
viewport?.removeEventListener("scroll", update);
|
|
148
|
+
window.removeEventListener("resize", update);
|
|
149
|
+
restore();
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const overlayParts = [];
|
|
153
|
+
const footerListeners = new Set();
|
|
154
|
+
function notifyOverlayFooters() {
|
|
155
|
+
for (const listener of footerListeners)
|
|
156
|
+
listener();
|
|
157
|
+
}
|
|
158
|
+
function registerOverlayPart(part, element) {
|
|
159
|
+
const panel = element.closest(DIALOG_SELECTOR);
|
|
160
|
+
let entry = panel ? overlayParts.find((candidate) => candidate.panel === panel) : undefined;
|
|
161
|
+
if (!entry) {
|
|
162
|
+
entry = { panel };
|
|
163
|
+
overlayParts.push(entry);
|
|
164
|
+
}
|
|
165
|
+
const mine = entry;
|
|
166
|
+
mine[part] = element;
|
|
167
|
+
const observer = typeof ResizeObserver === "function" ? new ResizeObserver(notifyOverlayFooters) : undefined;
|
|
168
|
+
observer?.observe(element);
|
|
169
|
+
notifyOverlayFooters();
|
|
170
|
+
// An overlay that slides in is still on its way when its parts are
|
|
171
|
+
// rendered, and a move is not a change of size: say so again once the
|
|
172
|
+
// panel has come to rest.
|
|
173
|
+
let frame = 0;
|
|
174
|
+
const settle = () => {
|
|
175
|
+
frame = 0;
|
|
176
|
+
if (typeof panel?.getAnimations === "function" && panel.getAnimations().length > 0) {
|
|
177
|
+
frame = requestAnimationFrame(settle);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
notifyOverlayFooters();
|
|
181
|
+
};
|
|
182
|
+
if (panel && typeof requestAnimationFrame === "function")
|
|
183
|
+
frame = requestAnimationFrame(settle);
|
|
184
|
+
return () => {
|
|
185
|
+
if (frame)
|
|
186
|
+
cancelAnimationFrame(frame);
|
|
187
|
+
observer?.disconnect();
|
|
188
|
+
if (mine[part] === element)
|
|
189
|
+
delete mine[part];
|
|
190
|
+
if (!mine.header && !mine.footer) {
|
|
191
|
+
const index = overlayParts.indexOf(mine);
|
|
192
|
+
if (index !== -1)
|
|
193
|
+
overlayParts.splice(index, 1);
|
|
194
|
+
}
|
|
195
|
+
notifyOverlayFooters();
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Says that `footer` is pinned to the bottom of an open overlay, so what is
|
|
200
|
+
* drawn over the overlays (the toast stack) can stay off it. Call it when the
|
|
201
|
+
* footer is rendered, in the browser; the returned function withdraws it.
|
|
202
|
+
*/
|
|
203
|
+
export function registerOverlayFooter(footer) {
|
|
204
|
+
return registerOverlayPart("footer", footer);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Says that `header` is the top of an open overlay and holds its close
|
|
208
|
+
* button, so the toast stack starts below it and never lies over the one
|
|
209
|
+
* control that always closes the overlay. Call it when the header is
|
|
210
|
+
* rendered, in the browser; the returned function withdraws it.
|
|
211
|
+
*/
|
|
212
|
+
export function registerOverlayHeader(header) {
|
|
213
|
+
return registerOverlayPart("header", header);
|
|
214
|
+
}
|
|
215
|
+
/** The parts of the overlay opened last that has registered any. */
|
|
216
|
+
function topOverlayParts() {
|
|
217
|
+
for (let index = overlayParts.length - 1; index >= 0; index -= 1) {
|
|
218
|
+
const entry = overlayParts[index];
|
|
219
|
+
if (entry.header?.isConnected || entry.footer?.isConnected)
|
|
220
|
+
return entry;
|
|
221
|
+
}
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
/** The footer of the overlay on top, if it has one. */
|
|
225
|
+
export function topOverlayFooter() {
|
|
226
|
+
const footer = topOverlayParts()?.footer;
|
|
227
|
+
return footer?.isConnected ? footer : null;
|
|
228
|
+
}
|
|
229
|
+
/** The header of the overlay on top, if it has one. */
|
|
230
|
+
export function topOverlayHeader() {
|
|
231
|
+
const header = topOverlayParts()?.header;
|
|
232
|
+
return header?.isConnected ? header : null;
|
|
233
|
+
}
|
|
234
|
+
/** Calls `listener` whenever a registered header or footer appears, goes, moves or changes size. */
|
|
235
|
+
export function watchOverlayFooters(listener) {
|
|
236
|
+
footerListeners.add(listener);
|
|
237
|
+
return () => {
|
|
238
|
+
footerListeners.delete(listener);
|
|
239
|
+
};
|
|
240
|
+
}
|