zabi-components 7.0.2 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1924 -0
- package/README.md +287 -11
- package/THEME.md +149 -332
- package/THEMING.md +1329 -163
- package/dist/atoms/ActionPanel.svelte +24 -9
- package/dist/atoms/ActionPanel.svelte.d.ts +2 -0
- package/dist/atoms/Avatar.svelte +104 -0
- package/dist/atoms/Avatar.svelte.d.ts +34 -0
- package/dist/atoms/Badge.svelte +124 -58
- package/dist/atoms/Badge.svelte.d.ts +17 -5
- package/dist/atoms/Button.svelte +212 -62
- package/dist/atoms/Button.svelte.d.ts +33 -2
- package/dist/atoms/Card.svelte +31 -28
- package/dist/atoms/Card.svelte.d.ts +5 -2
- package/dist/atoms/CardContent.svelte +10 -2
- package/dist/atoms/CardContent.svelte.d.ts +2 -0
- package/dist/atoms/CardFooter.svelte +10 -2
- package/dist/atoms/CardFooter.svelte.d.ts +2 -0
- package/dist/atoms/CardHeader.svelte +26 -6
- package/dist/atoms/CardHeader.svelte.d.ts +2 -0
- package/dist/atoms/Checkbox.svelte +52 -21
- package/dist/atoms/Checkbox.svelte.d.ts +6 -2
- package/dist/atoms/CodeBlock.svelte +35 -9
- package/dist/atoms/CodeBlock.svelte.d.ts +13 -3
- package/dist/atoms/ColorPicker.svelte +272 -52
- package/dist/atoms/ColorPicker.svelte.d.ts +6 -1
- package/dist/atoms/Container.svelte +17 -10
- package/dist/atoms/DateField.svelte +107 -0
- package/dist/atoms/DateField.svelte.d.ts +53 -0
- package/dist/atoms/Divider.svelte +4 -4
- package/dist/atoms/FeatureCard.svelte +4 -3
- package/dist/atoms/FieldMessages.svelte +69 -0
- package/dist/atoms/FieldMessages.svelte.d.ts +23 -0
- package/dist/atoms/FloatingActionButton.svelte +174 -0
- package/dist/atoms/FloatingActionButton.svelte.d.ts +51 -0
- package/dist/atoms/Heading.svelte +93 -32
- package/dist/atoms/Heading.svelte.d.ts +24 -4
- package/dist/atoms/IconButton.svelte +242 -53
- package/dist/atoms/IconButton.svelte.d.ts +49 -4
- package/dist/atoms/Input.svelte +267 -74
- package/dist/atoms/Input.svelte.d.ts +32 -2
- package/dist/atoms/List.svelte +13 -2
- package/dist/atoms/List.svelte.d.ts +2 -0
- package/dist/atoms/ListItem.svelte +44 -11
- package/dist/atoms/ListItem.svelte.d.ts +5 -0
- package/dist/atoms/ListItemLeading.svelte +4 -2
- package/dist/atoms/ListItemLeading.svelte.d.ts +1 -1
- package/dist/atoms/OptimizedImage.svelte +11 -2
- package/dist/atoms/OptimizedImage.svelte.d.ts +2 -0
- package/dist/atoms/Progress.svelte +31 -9
- package/dist/atoms/Progress.svelte.d.ts +6 -0
- package/dist/atoms/Radio.svelte +23 -3
- package/dist/atoms/Radio.svelte.d.ts +6 -2
- package/dist/atoms/Rating.svelte +494 -0
- package/dist/atoms/Rating.svelte.d.ts +56 -0
- package/dist/atoms/Select.svelte +475 -135
- package/dist/atoms/Select.svelte.d.ts +52 -2
- package/dist/atoms/SelectionControl.svelte +99 -18
- package/dist/atoms/SelectionControl.svelte.d.ts +6 -3
- package/dist/atoms/Skeleton.svelte +5 -3
- package/dist/atoms/Slider.svelte +309 -0
- package/dist/atoms/Slider.svelte.d.ts +40 -0
- package/dist/atoms/Spinner.svelte +57 -0
- package/dist/atoms/Spinner.svelte.d.ts +19 -0
- package/dist/atoms/Table.svelte +152 -6
- package/dist/atoms/Table.svelte.d.ts +15 -3
- package/dist/atoms/TemporalField.svelte +140 -0
- package/dist/atoms/TemporalField.svelte.d.ts +32 -0
- package/dist/atoms/Text.svelte +56 -15
- package/dist/atoms/Text.svelte.d.ts +13 -4
- package/dist/atoms/Textarea.svelte +57 -45
- package/dist/atoms/Textarea.svelte.d.ts +14 -2
- package/dist/atoms/ThemeToggle.svelte +238 -56
- package/dist/atoms/ThemeToggle.svelte.d.ts +28 -3
- package/dist/atoms/TimeField.svelte +108 -0
- package/dist/atoms/TimeField.svelte.d.ts +54 -0
- package/dist/atoms/Toast.svelte +25 -10
- package/dist/atoms/Toast.svelte.d.ts +2 -0
- package/dist/atoms/Toggle.svelte +60 -19
- package/dist/atoms/Toggle.svelte.d.ts +18 -2
- package/dist/atoms/Tooltip.svelte +583 -40
- package/dist/atoms/Tooltip.svelte.d.ts +26 -0
- package/dist/atoms/ZabiStringsProvider.svelte +45 -0
- package/dist/atoms/ZabiStringsProvider.svelte.d.ts +27 -0
- package/dist/atoms/index.d.ts +17 -0
- package/dist/atoms/index.js +12 -0
- package/dist/atoms/selection-control.styles.d.ts +36 -5
- package/dist/atoms/selection-control.styles.js +40 -9
- package/dist/components/atoms/index.d.ts +17 -0
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/atoms/selection-control.styles.d.ts +36 -5
- package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
- package/dist/components/index.d.ts +83 -3
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/molecules/index.d.ts +46 -0
- package/dist/components/molecules/index.d.ts.map +1 -1
- package/dist/components/molecules/toast-store.d.ts +53 -7
- package/dist/components/molecules/toast-store.d.ts.map +1 -1
- package/dist/components/organisms/index.d.ts +6 -0
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/components/types/variants.d.ts +15 -1
- package/dist/components/types/variants.d.ts.map +1 -1
- package/dist/components/util/app-shell.d.ts +28 -0
- package/dist/components/util/app-shell.d.ts.map +1 -0
- package/dist/components/util/avatar.d.ts +36 -0
- package/dist/components/util/avatar.d.ts.map +1 -0
- package/dist/components/util/bottom-sheet.d.ts +30 -0
- package/dist/components/util/bottom-sheet.d.ts.map +1 -0
- package/dist/components/util/bottom-tab-bar.d.ts +35 -0
- package/dist/components/util/bottom-tab-bar.d.ts.map +1 -0
- package/dist/components/util/calendar.d.ts +56 -0
- package/dist/components/util/calendar.d.ts.map +1 -0
- package/dist/components/util/cn.d.ts +8 -0
- package/dist/components/util/cn.d.ts.map +1 -0
- package/dist/components/util/collapsible.d.ts +58 -0
- package/dist/components/util/collapsible.d.ts.map +1 -0
- package/dist/components/util/confirm-dialog.d.ts +12 -0
- package/dist/components/util/confirm-dialog.d.ts.map +1 -0
- package/dist/components/util/date.d.ts +96 -0
- package/dist/components/util/date.d.ts.map +1 -0
- package/dist/components/util/drawer.d.ts +13 -0
- package/dist/components/util/drawer.d.ts.map +1 -0
- package/dist/components/util/dropdown.d.ts +47 -0
- package/dist/components/util/dropdown.d.ts.map +1 -0
- package/dist/components/util/field.d.ts +39 -0
- package/dist/components/util/field.d.ts.map +1 -0
- package/dist/components/util/fit-in-viewport.d.ts +201 -0
- package/dist/components/util/fit-in-viewport.d.ts.map +1 -0
- package/dist/components/util/focus-utils.d.ts +75 -2
- package/dist/components/util/focus-utils.d.ts.map +1 -1
- package/dist/components/util/hydration.d.ts +45 -0
- package/dist/components/util/hydration.d.ts.map +1 -0
- package/dist/components/util/keyboard-inset.d.ts +18 -0
- package/dist/components/util/keyboard-inset.d.ts.map +1 -0
- package/dist/components/util/media-grid.d.ts +51 -0
- package/dist/components/util/media-grid.d.ts.map +1 -0
- package/dist/components/util/media.d.ts +6 -0
- package/dist/components/util/media.d.ts.map +1 -0
- package/dist/components/util/overlay-chrome.d.ts +53 -0
- package/dist/components/util/overlay-chrome.d.ts.map +1 -0
- package/dist/components/util/overlay.d.ts +72 -0
- package/dist/components/util/overlay.d.ts.map +1 -0
- package/dist/components/util/photo.d.ts +215 -0
- package/dist/components/util/photo.d.ts.map +1 -0
- package/dist/components/util/portal.d.ts +23 -0
- package/dist/components/util/portal.d.ts.map +1 -0
- package/dist/components/util/pull-to-refresh.d.ts +27 -0
- package/dist/components/util/pull-to-refresh.d.ts.map +1 -0
- package/dist/components/util/radio-keys.d.ts +25 -0
- package/dist/components/util/radio-keys.d.ts.map +1 -0
- package/dist/components/util/rating.d.ts +23 -0
- package/dist/components/util/rating.d.ts.map +1 -0
- package/dist/components/util/ready-made-strings.d.ts +77 -0
- package/dist/components/util/ready-made-strings.d.ts.map +1 -0
- package/dist/components/util/scroll-padding.d.ts +20 -0
- package/dist/components/util/scroll-padding.d.ts.map +1 -0
- package/dist/components/util/segmented-control.d.ts +16 -0
- package/dist/components/util/segmented-control.d.ts.map +1 -0
- package/dist/components/util/select.d.ts +41 -0
- package/dist/components/util/select.d.ts.map +1 -0
- package/dist/components/util/sheet-drag.d.ts +60 -0
- package/dist/components/util/sheet-drag.d.ts.map +1 -0
- package/dist/components/util/sidebar.d.ts +63 -0
- package/dist/components/util/sidebar.d.ts.map +1 -0
- package/dist/components/util/sortable-list.d.ts +65 -0
- package/dist/components/util/sortable-list.d.ts.map +1 -0
- package/dist/components/util/ssr-safe.d.ts +4 -4
- package/dist/components/util/ssr-safe.d.ts.map +1 -1
- package/dist/components/util/stepper.d.ts +40 -0
- package/dist/components/util/stepper.d.ts.map +1 -0
- package/dist/components/util/swipeable-list-item.d.ts +36 -0
- package/dist/components/util/swipeable-list-item.d.ts.map +1 -0
- package/dist/components/util/theme-mode.d.ts +80 -0
- package/dist/components/util/theme-mode.d.ts.map +1 -0
- package/dist/components/util/toaster.d.ts +93 -0
- package/dist/components/util/toaster.d.ts.map +1 -0
- package/dist/components/util/tooltip.d.ts +10 -0
- package/dist/components/util/tooltip.d.ts.map +1 -0
- package/dist/components/util/top-navbar.d.ts +11 -0
- package/dist/components/util/top-navbar.d.ts.map +1 -0
- package/dist/components/util/touch-target.d.ts +31 -0
- package/dist/components/util/touch-target.d.ts.map +1 -0
- package/dist/components/util/zabi-strings.d.ts +133 -0
- package/dist/components/util/zabi-strings.d.ts.map +1 -0
- package/dist/create-theme/cli.js +169 -0
- package/dist/create-theme/index.d.ts +176 -0
- package/dist/create-theme/index.js +817 -0
- package/dist/create-theme/lib/ramp-math.js +128 -0
- package/dist/create-theme/lib/resolve.js +141 -0
- package/dist/create-theme/theme-data.js +1116 -0
- package/dist/index.d.ts +83 -3
- package/dist/index.js +60 -8
- package/dist/molecules/Alert.svelte +73 -66
- package/dist/molecules/Alert.svelte.d.ts +9 -1
- package/dist/molecules/AppBar.svelte +415 -0
- package/dist/molecules/AppBar.svelte.d.ts +69 -0
- package/dist/molecules/AvatarGroup.svelte +116 -0
- package/dist/molecules/AvatarGroup.svelte.d.ts +40 -0
- package/dist/molecules/BottomSheet.svelte +540 -0
- package/dist/molecules/BottomSheet.svelte.d.ts +93 -0
- package/dist/molecules/BottomTabBar.svelte +297 -0
- package/dist/molecules/BottomTabBar.svelte.d.ts +64 -0
- package/dist/molecules/Calendar.svelte +597 -0
- package/dist/molecules/Calendar.svelte.d.ts +72 -0
- package/dist/molecules/Collapsible.svelte +243 -0
- package/dist/molecules/Collapsible.svelte.d.ts +44 -0
- package/dist/molecules/CollapsibleGroup.svelte +126 -0
- package/dist/molecules/CollapsibleGroup.svelte.d.ts +14 -0
- package/dist/molecules/ComponentDemo.svelte +25 -5
- package/dist/molecules/ComponentDemo.svelte.d.ts +5 -0
- package/dist/molecules/ConfirmDialog.svelte +253 -0
- package/dist/molecules/ConfirmDialog.svelte.d.ts +55 -0
- package/dist/molecules/ContactForm.svelte +46 -19
- package/dist/molecules/ContactForm.svelte.d.ts +10 -0
- package/dist/molecules/Drawer.svelte +394 -0
- package/dist/molecules/Drawer.svelte.d.ts +55 -0
- package/dist/molecules/Dropdown.svelte +540 -50
- package/dist/molecules/Dropdown.svelte.d.ts +49 -6
- package/dist/molecules/DropdownItem.svelte +122 -0
- package/dist/molecules/DropdownItem.svelte.d.ts +28 -0
- package/dist/molecules/EmptyState.svelte +56 -10
- package/dist/molecules/EmptyState.svelte.d.ts +8 -0
- package/dist/molecules/Form.svelte +25 -14
- package/dist/molecules/Form.svelte.d.ts +13 -5
- package/dist/molecules/FormField.svelte +11 -2
- package/dist/molecules/FormField.svelte.d.ts +2 -0
- package/dist/molecules/Header.svelte +13 -3
- package/dist/molecules/Header.svelte.d.ts +2 -0
- package/dist/molecules/ImageUpload.svelte +422 -70
- package/dist/molecules/ImageUpload.svelte.d.ts +61 -5
- package/dist/molecules/MediaGrid.svelte +527 -0
- package/dist/molecules/MediaGrid.svelte.d.ts +77 -0
- package/dist/molecules/Modal.svelte +340 -45
- package/dist/molecules/Modal.svelte.d.ts +59 -7
- package/dist/molecules/NavigationMenu.svelte +15 -3
- package/dist/molecules/NavigationMenu.svelte.d.ts +4 -0
- package/dist/molecules/NavigationMenuContent.svelte +91 -36
- package/dist/molecules/NavigationMenuContent.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuItem.svelte +10 -3
- package/dist/molecules/NavigationMenuItem.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuLink.svelte +10 -2
- package/dist/molecules/NavigationMenuLink.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuList.svelte +19 -4
- package/dist/molecules/NavigationMenuList.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuTrigger.svelte +16 -3
- package/dist/molecules/NavigationMenuTrigger.svelte.d.ts +2 -0
- package/dist/molecules/Page.svelte +38 -2
- package/dist/molecules/Page.svelte.d.ts +13 -0
- package/dist/molecules/PhotoGrid.svelte +433 -0
- package/dist/molecules/PhotoGrid.svelte.d.ts +62 -0
- package/dist/molecules/PhotoViewer.svelte +1116 -0
- package/dist/molecules/PhotoViewer.svelte.d.ts +56 -0
- package/dist/molecules/PropsTable.svelte +26 -11
- package/dist/molecules/PropsTable.svelte.d.ts +5 -0
- package/dist/molecules/PullToRefresh.svelte +263 -0
- package/dist/molecules/PullToRefresh.svelte.d.ts +52 -0
- package/dist/molecules/RadioGroup.svelte +57 -15
- package/dist/molecules/RadioGroup.svelte.d.ts +16 -3
- package/dist/molecules/Section.svelte +13 -5
- package/dist/molecules/Section.svelte.d.ts +3 -1
- package/dist/molecules/SegmentedControl.svelte +323 -0
- package/dist/molecules/SegmentedControl.svelte.d.ts +34 -0
- package/dist/molecules/SidebarBrandHeader.svelte +24 -5
- package/dist/molecules/SidebarBrandHeader.svelte.d.ts +5 -0
- package/dist/molecules/SidebarFooter.svelte +39 -8
- package/dist/molecules/SidebarFooter.svelte.d.ts +5 -0
- package/dist/molecules/SidebarNavSection.svelte +12 -4
- package/dist/molecules/SidebarNavSection.svelte.d.ts +2 -0
- package/dist/molecules/SlideUp.svelte +253 -30
- package/dist/molecules/SlideUp.svelte.d.ts +31 -4
- package/dist/molecules/SortableList.svelte +632 -0
- package/dist/molecules/SortableList.svelte.d.ts +69 -0
- package/dist/molecules/Stepper.svelte +548 -0
- package/dist/molecules/Stepper.svelte.d.ts +39 -0
- package/dist/molecules/StickyActionBar.svelte +191 -0
- package/dist/molecules/StickyActionBar.svelte.d.ts +65 -0
- package/dist/molecules/SwipeableListItem.svelte +334 -0
- package/dist/molecules/SwipeableListItem.svelte.d.ts +54 -0
- package/dist/molecules/Tabs.svelte +254 -24
- package/dist/molecules/Tabs.svelte.d.ts +15 -4
- package/dist/molecules/Toaster.svelte +352 -6
- package/dist/molecules/Toaster.svelte.d.ts +95 -2
- package/dist/molecules/ToasterToast.svelte +279 -62
- package/dist/molecules/ToasterToast.svelte.d.ts +11 -0
- package/dist/molecules/UnsavedChangesBar.svelte +212 -0
- package/dist/molecules/UnsavedChangesBar.svelte.d.ts +43 -0
- package/dist/molecules/index.d.ts +46 -0
- package/dist/molecules/index.js +27 -0
- package/dist/molecules/toast-store.d.ts +53 -7
- package/dist/molecules/toast-store.js +21 -0
- package/dist/organisms/AppShell.svelte +196 -0
- package/dist/organisms/AppShell.svelte.d.ts +61 -0
- package/dist/organisms/SidebarAccountPanel.svelte +111 -12
- package/dist/organisms/SidebarAccountPanel.svelte.d.ts +21 -0
- package/dist/organisms/SidebarNavigation.svelte +193 -69
- package/dist/organisms/SidebarNavigation.svelte.d.ts +31 -1
- package/dist/organisms/SidebarPanel.svelte +45 -16
- package/dist/organisms/SidebarPanel.svelte.d.ts +2 -0
- package/dist/organisms/SidebarShell.svelte +263 -0
- package/dist/organisms/SidebarShell.svelte.d.ts +106 -0
- package/dist/organisms/TopNavbar.svelte +329 -33
- package/dist/organisms/TopNavbar.svelte.d.ts +28 -0
- package/dist/organisms/index.d.ts +6 -0
- package/dist/organisms/index.js +4 -0
- package/dist/routes/lib/variant-utils.d.ts +26 -0
- package/dist/routes/lib/{variant-utils.ts → variant-utils.js} +15 -46
- package/dist/types/components.d.ts +4 -0
- package/dist/types/components.d.ts.map +1 -1
- package/dist/types/events.js +1 -0
- package/dist/types/index.d.ts +605 -25
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +3 -0
- package/dist/types/variants.ts +17 -1
- package/dist/util/app-shell.d.ts +27 -0
- package/dist/util/app-shell.js +72 -0
- package/dist/util/avatar.d.ts +35 -0
- package/dist/util/avatar.js +52 -0
- package/dist/util/bottom-sheet.d.ts +29 -0
- package/dist/util/bottom-sheet.js +43 -0
- package/dist/util/bottom-tab-bar.d.ts +34 -0
- package/dist/util/bottom-tab-bar.js +54 -0
- package/dist/util/calendar.d.ts +55 -0
- package/dist/util/calendar.js +74 -0
- package/dist/util/cn.d.ts +7 -0
- package/dist/util/cn.js +48 -0
- package/dist/util/collapsible.d.ts +57 -0
- package/dist/util/collapsible.js +23 -0
- package/dist/util/confirm-dialog.d.ts +11 -0
- package/dist/util/confirm-dialog.js +2 -0
- package/dist/util/date.d.ts +95 -0
- package/dist/util/date.js +237 -0
- package/dist/util/drawer.d.ts +12 -0
- package/dist/util/drawer.js +7 -0
- package/dist/util/dropdown.d.ts +46 -0
- package/dist/util/dropdown.js +21 -0
- package/dist/util/field.d.ts +38 -0
- package/dist/util/field.js +38 -0
- package/dist/util/fit-in-viewport.d.ts +200 -0
- package/dist/util/fit-in-viewport.js +399 -0
- package/dist/util/focus-utils.d.ts +75 -2
- package/dist/util/focus-utils.js +182 -9
- package/dist/util/hydration.d.ts +44 -0
- package/dist/util/hydration.js +58 -0
- package/dist/util/keyboard-inset.d.ts +17 -0
- package/dist/util/keyboard-inset.js +53 -0
- package/dist/util/media-grid.d.ts +50 -0
- package/dist/util/media-grid.js +81 -0
- package/dist/util/media.d.ts +5 -0
- package/dist/util/media.js +10 -0
- package/dist/util/overlay-chrome.d.ts +52 -0
- package/dist/util/overlay-chrome.js +56 -0
- package/dist/util/overlay.d.ts +71 -0
- package/dist/util/overlay.js +240 -0
- package/dist/util/photo.d.ts +214 -0
- package/dist/util/photo.js +206 -0
- package/dist/util/portal.d.ts +22 -0
- package/dist/util/portal.js +50 -0
- package/dist/util/pull-to-refresh.d.ts +26 -0
- package/dist/util/pull-to-refresh.js +30 -0
- package/dist/util/radio-keys.d.ts +24 -0
- package/dist/util/radio-keys.js +49 -0
- package/dist/util/rating.d.ts +22 -0
- package/dist/util/rating.js +26 -0
- package/dist/util/ready-made-strings.d.ts +76 -0
- package/dist/util/ready-made-strings.js +67 -0
- package/dist/util/scroll-padding.d.ts +19 -0
- package/dist/util/scroll-padding.js +44 -0
- package/dist/util/segmented-control.d.ts +15 -0
- package/dist/util/segmented-control.js +1 -0
- package/dist/util/select.d.ts +40 -0
- package/dist/util/select.js +63 -0
- package/dist/util/sheet-drag.d.ts +59 -0
- package/dist/util/sheet-drag.js +241 -0
- package/dist/util/sidebar.d.ts +62 -0
- package/dist/util/sidebar.js +38 -0
- package/dist/util/sortable-list.d.ts +64 -0
- package/dist/util/sortable-list.js +53 -0
- package/dist/util/ssr-safe.d.ts +4 -4
- package/dist/util/stepper.d.ts +39 -0
- package/dist/util/stepper.js +38 -0
- package/dist/util/swipeable-list-item.d.ts +35 -0
- package/dist/util/swipeable-list-item.js +32 -0
- package/dist/util/theme-mode.d.ts +79 -0
- package/dist/util/theme-mode.js +121 -0
- package/dist/util/toaster.d.ts +92 -0
- package/dist/util/toaster.js +75 -0
- package/dist/util/tooltip.d.ts +9 -0
- package/dist/util/tooltip.js +18 -0
- package/dist/util/top-navbar.d.ts +10 -0
- package/dist/util/top-navbar.js +5 -0
- package/dist/util/touch-target.d.ts +30 -0
- package/dist/util/touch-target.js +30 -0
- package/dist/util/zabi-strings.d.ts +132 -0
- package/dist/util/zabi-strings.js +105 -0
- package/dist/zabi-components-colors.css +5 -2
- package/dist/zabi-components-theme-dark-only.css +574 -225
- package/dist/zabi-components-theme-dark.css +574 -225
- package/dist/zabi-components-theme-only.css +1680 -317
- package/dist/zabi-components-theme.css +1680 -317
- package/dist/zabi-components.css +4473 -1158
- package/docs/lucide-icons.md +41 -16
- package/docs/theme-imports.md +81 -11
- package/package.json +51 -29
- package/dist/lib/layout-width-tokens.d.ts +0 -17
- package/dist/lib/layout-width-tokens.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Button.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Button.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Input.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Input.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/List.d.ts +0 -3
- package/dist/lib/showcase/component-docs/List.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Modal.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Modal.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Radio.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Radio.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts +0 -3
- package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Skeleton.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Skeleton.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/_shared.d.ts +0 -12
- package/dist/lib/showcase/component-docs/_shared.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/index.d.ts +0 -4
- package/dist/lib/showcase/component-docs/index.d.ts.map +0 -1
- package/dist/lib/showcase/components-catalog.d.ts +0 -3
- package/dist/lib/showcase/components-catalog.d.ts.map +0 -1
- package/dist/lib/showcase/components-showcase-constants.d.ts +0 -89
- package/dist/lib/showcase/components-showcase-constants.d.ts.map +0 -1
- package/dist/lib/showcase/docs-sidebar-helpers.d.ts +0 -15
- package/dist/lib/showcase/docs-sidebar-helpers.d.ts.map +0 -1
- package/dist/routes/lib/focus-utils.d.ts +0 -12
- package/dist/routes/lib/focus-utils.ts +0 -110
- package/dist/routes/lib/ssr-safe.ts +0 -58
- package/scripts/fix-lucide-svelte-icon-dts.js +0 -35
|
@@ -1,47 +1,151 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { zabiCommonStrings } from "../util/zabi-strings.js";
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
5
|
import {
|
|
3
|
-
trapFocus,
|
|
4
6
|
saveFocus,
|
|
5
7
|
returnFocus,
|
|
6
8
|
focusFirstElement,
|
|
9
|
+
joinOverlayStack,
|
|
10
|
+
recoverStrayFocus,
|
|
7
11
|
} from '../util/focus-utils.js';
|
|
12
|
+
import {
|
|
13
|
+
followKeyboard,
|
|
14
|
+
lockBodyScroll,
|
|
15
|
+
registerOverlayFooter,
|
|
16
|
+
registerOverlayHeader,
|
|
17
|
+
trapTabKey,
|
|
18
|
+
watchScrollerNeedsFocus,
|
|
19
|
+
} from '../util/overlay.js';
|
|
20
|
+
import { TOUCH_HIT_AREA } from '../util/touch-target.js';
|
|
8
21
|
import { generateId } from "../util/ssr-safe.js";
|
|
22
|
+
import { portal as portalTo } from "../util/portal.js";
|
|
9
23
|
import Card from '../atoms/Card.svelte';
|
|
10
24
|
import CardHeader from '../atoms/CardHeader.svelte';
|
|
11
25
|
import CardContent from '../atoms/CardContent.svelte';
|
|
12
26
|
import CardFooter from '../atoms/CardFooter.svelte';
|
|
13
27
|
|
|
28
|
+
import { cn } from "../util/cn.js";
|
|
29
|
+
import {
|
|
30
|
+
OVERLAY_CHROME,
|
|
31
|
+
OVERLAY_CLOSE_GLYPH,
|
|
32
|
+
OVERLAY_TITLE_2XL,
|
|
33
|
+
OVERLAY_TITLE_HYPHENS,
|
|
34
|
+
OVERLAY_TITLE_WRAP,
|
|
35
|
+
textIsEnlarged,
|
|
36
|
+
} from "../util/overlay-chrome.js";
|
|
14
37
|
type Size = 'sm' | 'md' | 'lg';
|
|
38
|
+
type CloseReason = 'escape' | 'backdrop' | 'close-button';
|
|
15
39
|
|
|
16
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Anything else is an attribute of the dialog panel (`aria-describedby`,
|
|
42
|
+
* `aria-busy`, `data-*`, `id`), which is where it is spread.
|
|
43
|
+
*/
|
|
44
|
+
type Props = Omit<
|
|
45
|
+
HTMLAttributes<HTMLDivElement>,
|
|
46
|
+
'class' | 'title' | 'role' | 'onclick' | 'onkeydown' | 'onclose' | 'children'
|
|
47
|
+
> & {
|
|
17
48
|
isOpen?: boolean;
|
|
18
49
|
title?: string;
|
|
19
50
|
description?: string;
|
|
20
51
|
size?: Size;
|
|
52
|
+
/**
|
|
53
|
+
* `alertdialog` for a dialog that interrupts to ask for a response,
|
|
54
|
+
* such as a confirmation. Focus handling is the same for both.
|
|
55
|
+
*/
|
|
56
|
+
role?: 'dialog' | 'alertdialog';
|
|
57
|
+
/** Render the close button even when there is no `title`. */
|
|
58
|
+
showClose?: boolean;
|
|
59
|
+
/** Accessible name of the close button. */
|
|
60
|
+
closeLabel?: string;
|
|
61
|
+
/**
|
|
62
|
+
* CSS selector, looked up inside the panel, of the control that takes
|
|
63
|
+
* focus when the modal opens (a search field, or the safe choice in a
|
|
64
|
+
* confirmation). Without it, or with no match, the first control does.
|
|
65
|
+
*/
|
|
66
|
+
initialFocus?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Render the overlay in `document.body` instead of in place, so an
|
|
69
|
+
* ancestor with a transform, filter or clipped overflow cannot trap it.
|
|
70
|
+
* The theme class belongs on `<html>` or `<body>`; set lower, it does
|
|
71
|
+
* not reach a portalled modal.
|
|
72
|
+
*/
|
|
73
|
+
portal?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* When false, Escape, a backdrop click and the close button do not
|
|
76
|
+
* close the modal (a pending confirm, say). Focus stays trapped, and
|
|
77
|
+
* setting `isOpen` yourself still closes it.
|
|
78
|
+
*/
|
|
79
|
+
dismissible?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Fills the screen instead of floating over it: for a long form on a
|
|
82
|
+
* phone. `true` at every width; `"mobile"` below 768px only, with the
|
|
83
|
+
* usual dialog from there up. The title and the close button stay at
|
|
84
|
+
* the top, the footer at the bottom, both inside the safe areas, and
|
|
85
|
+
* the content scrolls between them. Everything else is the same
|
|
86
|
+
* dialog: focus, Escape, `dismissible`, `portal`.
|
|
87
|
+
*/
|
|
88
|
+
fullScreen?: boolean | 'mobile';
|
|
89
|
+
/** Fired when the modal closes itself, with what the user did. */
|
|
90
|
+
onclose?: (detail: { reason: CloseReason }) => void;
|
|
91
|
+
/**
|
|
92
|
+
* @deprecated for close reporting: use `onclose`. Still called with the
|
|
93
|
+
* event on Escape, a backdrop click and the close button.
|
|
94
|
+
*/
|
|
21
95
|
onclick?: (event: Event) => void;
|
|
22
96
|
onkeydown?: (event: Event) => void;
|
|
23
|
-
/** On the `role="dialog"`
|
|
97
|
+
/** On the `role="dialog"` panel (testing, analytics). */
|
|
24
98
|
"data-testid"?: string;
|
|
25
|
-
|
|
99
|
+
/** Extra classes for the dialog panel. */
|
|
100
|
+
class?: string;
|
|
101
|
+
children?: Snippet;
|
|
102
|
+
footer?: Snippet;
|
|
103
|
+
};
|
|
26
104
|
|
|
27
105
|
let {
|
|
28
|
-
isOpen = $bindable(
|
|
106
|
+
isOpen = $bindable<Exclude<Props["isOpen"], undefined>>(),
|
|
29
107
|
title = '',
|
|
30
108
|
description = '',
|
|
31
109
|
size = 'md',
|
|
110
|
+
role = 'dialog',
|
|
111
|
+
showClose = true,
|
|
112
|
+
closeLabel: closeLabelGiven,
|
|
113
|
+
initialFocus,
|
|
114
|
+
portal = false,
|
|
115
|
+
dismissible = true,
|
|
116
|
+
fullScreen = false,
|
|
117
|
+
class: className = "",
|
|
118
|
+
onclose,
|
|
32
119
|
onclick,
|
|
33
120
|
onkeydown,
|
|
34
121
|
"data-testid": dataTestId = undefined,
|
|
35
122
|
children,
|
|
36
123
|
footer,
|
|
37
124
|
...restProps
|
|
38
|
-
}: Props
|
|
125
|
+
}: Props = $props();
|
|
126
|
+
|
|
127
|
+
/** Words many components share: a `ZabiStringsProvider` above this one may give them; else English. */
|
|
128
|
+
const common = zabiCommonStrings();
|
|
129
|
+
const closeLabel = $derived(closeLabelGiven ?? common().close);
|
|
130
|
+
|
|
131
|
+
// No fallback on a bindable prop: Svelte refuses `bind:…={undefined}` on
|
|
132
|
+
// one that has a fallback (`props_invalid_value`), and a page that throws
|
|
133
|
+
// while it hydrates never becomes interactive. The default is applied
|
|
134
|
+
// here instead: at once, for the server and the first render, and again
|
|
135
|
+
// whenever a parent hands back `undefined`.
|
|
136
|
+
const applyDefaults = () => {
|
|
137
|
+
if (isOpen === undefined) isOpen = false;
|
|
138
|
+
};
|
|
139
|
+
applyDefaults();
|
|
140
|
+
$effect.pre(applyDefaults);
|
|
39
141
|
|
|
40
142
|
const modalTitleId = generateId('modal-title');
|
|
41
143
|
const modalDescriptionId = generateId('modal-description');
|
|
42
144
|
|
|
43
145
|
let modalContainer = $state<HTMLDivElement>();
|
|
44
|
-
let
|
|
146
|
+
let focusActive = false;
|
|
147
|
+
/** Position among the open overlays; lifts a modal opened later above the earlier ones. */
|
|
148
|
+
let depth = $state(0);
|
|
45
149
|
|
|
46
150
|
const sizeClasses = $derived(
|
|
47
151
|
{
|
|
@@ -51,101 +155,292 @@
|
|
|
51
155
|
}[size] || 'w-full md:w-[28rem]',
|
|
52
156
|
);
|
|
53
157
|
|
|
54
|
-
|
|
158
|
+
/**
|
|
159
|
+
* What `fullScreen` adds to each part: `all` at every width, `mobile`
|
|
160
|
+
* below `md` only. Both are written out, because a class name put
|
|
161
|
+
* together at run time is one Tailwind never sees.
|
|
162
|
+
*
|
|
163
|
+
* The panel is as tall as the visible screen (`dvh`: the browser's own
|
|
164
|
+
* bars are not counted) and the content scrolls inside it, so the header
|
|
165
|
+
* and the footer stay where they are. The content keeps 120px at least:
|
|
166
|
+
* when the header and the footer leave less (a long title in very large
|
|
167
|
+
* text on a small screen), the panel scrolls as a whole instead, and the
|
|
168
|
+
* footer is reached by scrolling rather than not at all. The gutters are
|
|
169
|
+
* in px: at 320px with enlarged text, a gutter that grew with the text
|
|
170
|
+
* would leave a form no room. Each is at least the safe-area inset on its
|
|
171
|
+
* side.
|
|
172
|
+
*/
|
|
173
|
+
const FULL_SCREEN = {
|
|
174
|
+
backdrop: { all: 'md:p-0', mobile: '' },
|
|
175
|
+
panel: {
|
|
176
|
+
all: 'h-dvh max-h-none rounded-none border-0 md:w-full md:rounded-none',
|
|
177
|
+
mobile: 'max-md:h-dvh max-md:max-h-none max-md:rounded-none max-md:border-0',
|
|
178
|
+
},
|
|
179
|
+
card: {
|
|
180
|
+
all: 'flex min-h-0 flex-1 flex-col rounded-none md:rounded-none',
|
|
181
|
+
mobile: 'max-md:flex max-md:min-h-0 max-md:flex-1 max-md:flex-col max-md:rounded-none',
|
|
182
|
+
},
|
|
183
|
+
header: {
|
|
184
|
+
all: 'shrink-0 border-b border-border pt-[max(24px,calc(env(safe-area-inset-top,0px)_+_8px))] pr-[max(24px,env(safe-area-inset-right,0px))] pl-[max(24px,env(safe-area-inset-left,0px))]',
|
|
185
|
+
mobile: 'max-md:shrink-0 max-md:border-b max-md:border-border max-md:pt-[max(24px,calc(env(safe-area-inset-top,0px)_+_8px))] max-md:pr-[max(24px,env(safe-area-inset-right,0px))] max-md:pl-[max(24px,env(safe-area-inset-left,0px))]',
|
|
186
|
+
},
|
|
187
|
+
content: {
|
|
188
|
+
all: 'flex min-h-[120px] flex-col',
|
|
189
|
+
mobile: 'max-md:flex max-md:min-h-[120px] max-md:flex-col',
|
|
190
|
+
},
|
|
191
|
+
/** From `md` up, `mobile` restates the padding of the usual dialog. */
|
|
192
|
+
scroller: {
|
|
193
|
+
all: 'min-h-0 flex-1 overflow-y-auto overscroll-contain pr-[max(24px,env(safe-area-inset-right,0px))] pl-[max(24px,env(safe-area-inset-left,0px))]',
|
|
194
|
+
mobile: 'max-md:min-h-0 max-md:flex-1 max-md:overflow-y-auto max-md:overscroll-contain max-md:pr-[max(24px,env(safe-area-inset-right,0px))] max-md:pl-[max(24px,env(safe-area-inset-left,0px))] md:px-[24px]',
|
|
195
|
+
},
|
|
196
|
+
scrollerBelowHeader: { all: 'pt-[16px]', mobile: 'max-md:pt-[16px]' },
|
|
197
|
+
scrollerAtTop: {
|
|
198
|
+
all: 'pt-[max(24px,calc(env(safe-area-inset-top,0px)_+_8px))]',
|
|
199
|
+
mobile: 'max-md:pt-[max(24px,calc(env(safe-area-inset-top,0px)_+_8px))] md:pt-[24px]',
|
|
200
|
+
},
|
|
201
|
+
scrollerAboveFooter: { all: 'pb-[16px]', mobile: 'max-md:pb-[16px]' },
|
|
202
|
+
scrollerAtBottom: {
|
|
203
|
+
all: 'pb-[max(24px,calc(env(safe-area-inset-bottom,0px)_+_8px))]',
|
|
204
|
+
mobile: 'max-md:pb-[max(24px,calc(env(safe-area-inset-bottom,0px)_+_8px))] md:pb-[24px]',
|
|
205
|
+
},
|
|
206
|
+
footer: {
|
|
207
|
+
all: 'shrink-0 flex-wrap border-t border-border pr-[max(24px,env(safe-area-inset-right,0px))] pb-[max(24px,calc(env(safe-area-inset-bottom,0px)_+_8px))] pl-[max(24px,env(safe-area-inset-left,0px))] group-data-keyboard-open/overlay:pb-[24px]',
|
|
208
|
+
mobile: 'max-md:shrink-0 max-md:flex-wrap max-md:border-t max-md:border-border max-md:pr-[max(24px,env(safe-area-inset-right,0px))] max-md:pb-[max(24px,calc(env(safe-area-inset-bottom,0px)_+_8px))] max-md:pl-[max(24px,env(safe-area-inset-left,0px))] max-md:group-data-keyboard-open/overlay:pb-[24px]',
|
|
209
|
+
},
|
|
210
|
+
} as const;
|
|
211
|
+
|
|
212
|
+
const fullScreenMode = $derived(
|
|
213
|
+
fullScreen === 'mobile' ? 'mobile' : fullScreen ? 'all' : null,
|
|
214
|
+
);
|
|
215
|
+
const full = (part: keyof typeof FULL_SCREEN) =>
|
|
216
|
+
fullScreenMode ? FULL_SCREEN[part][fullScreenMode] : '';
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* The scrolling content of a full-screen modal. With nothing in it that
|
|
220
|
+
* takes focus (a long text), the keyboard could not scroll it, so it is a
|
|
221
|
+
* Tab stop itself for as long as that is true.
|
|
222
|
+
*/
|
|
223
|
+
let scroller = $state<HTMLDivElement>();
|
|
224
|
+
let scrollerNeedsFocus = $state(false);
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Read as the overlay opens: with the text enlarged, a long word in the
|
|
228
|
+
* title is hyphenated before it is cut (util/overlay-chrome.ts).
|
|
229
|
+
*/
|
|
230
|
+
let enlargedText = $state(false);
|
|
231
|
+
$effect(() => {
|
|
232
|
+
if (isOpen) enlargedText = textIsEnlarged();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
$effect(() => {
|
|
236
|
+
const box = scroller;
|
|
237
|
+
if (!isOpen || !box) {
|
|
238
|
+
scrollerNeedsFocus = false;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
return watchScrollerNeedsFocus(box, (needsFocus) => (scrollerNeedsFocus = needsFocus));
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
function closeModal(reason: CloseReason, event?: Event) {
|
|
245
|
+
if (!dismissible) return;
|
|
55
246
|
isOpen = false;
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
247
|
+
if (focusActive) {
|
|
248
|
+
focusActive = false;
|
|
249
|
+
returnFocus();
|
|
59
250
|
}
|
|
60
|
-
returnFocus();
|
|
61
251
|
if (onclick && event) {
|
|
62
252
|
onclick(event);
|
|
63
253
|
}
|
|
254
|
+
onclose?.({ reason });
|
|
64
255
|
}
|
|
65
256
|
|
|
66
257
|
$effect(() => {
|
|
67
258
|
const container = modalContainer;
|
|
68
259
|
if (isOpen && container) {
|
|
69
260
|
saveFocus();
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
261
|
+
focusActive = true;
|
|
262
|
+
const unlockScroll = lockBodyScroll();
|
|
263
|
+
const overlay = joinOverlayStack(container);
|
|
264
|
+
depth = overlay.depth;
|
|
265
|
+
const t = setTimeout(() => {
|
|
266
|
+
// Something inside has taken focus already (a field that
|
|
267
|
+
// focuses itself as it mounts): leave it there. `initialFocus`
|
|
268
|
+
// is the consumer saying where focus goes, so it still decides.
|
|
269
|
+
const active = document.activeElement;
|
|
270
|
+
const inside = !!active && active !== container && container.contains(active);
|
|
271
|
+
if (inside && !(initialFocus && container.querySelector(initialFocus))) return;
|
|
272
|
+
focusFirstElement(container, initialFocus);
|
|
73
273
|
}, 0);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
274
|
+
// Focus can leave the dialog without a Tab (a confirm that starts
|
|
275
|
+
// loading disables the focused button); the topmost dialog takes
|
|
276
|
+
// Tab and Escape back from `<body>`.
|
|
277
|
+
const stopRecovery = recoverStrayFocus(container, {
|
|
278
|
+
onEscape: (event) => closeModal('escape', event),
|
|
279
|
+
});
|
|
280
|
+
// Where the on-screen keyboard covers the page, the overlay
|
|
281
|
+
// keeps to what is left above it.
|
|
282
|
+
const stopKeyboard = container.parentElement
|
|
283
|
+
? followKeyboard(container.parentElement)
|
|
284
|
+
: undefined;
|
|
285
|
+
// The footer, for the toast stack to stay off it. The panel holds
|
|
286
|
+
// the Card, and the Card the footer: nothing in the content matches.
|
|
287
|
+
const pinned = footer
|
|
288
|
+
? container.querySelector<HTMLElement>(':scope > div > footer')
|
|
289
|
+
: null;
|
|
290
|
+
const forgetFooter = pinned ? registerOverlayFooter(pinned) : undefined;
|
|
291
|
+
// And the header, which holds the close button: toasts start below it.
|
|
292
|
+
const top = container.querySelector<HTMLElement>(':scope > div > header');
|
|
293
|
+
const forgetHeader = top ? registerOverlayHeader(top) : undefined;
|
|
294
|
+
return () => {
|
|
295
|
+
clearTimeout(t);
|
|
296
|
+
stopRecovery();
|
|
297
|
+
stopKeyboard?.();
|
|
298
|
+
forgetFooter?.();
|
|
299
|
+
forgetHeader?.();
|
|
300
|
+
overlay.leave();
|
|
301
|
+
unlockScroll();
|
|
302
|
+
if (focusActive) {
|
|
303
|
+
focusActive = false;
|
|
304
|
+
returnFocus();
|
|
305
|
+
}
|
|
306
|
+
};
|
|
78
307
|
}
|
|
79
308
|
});
|
|
80
309
|
|
|
81
310
|
function handleBackdropClick(event: Event) {
|
|
82
311
|
if (event.target === event.currentTarget) {
|
|
83
|
-
closeModal(event);
|
|
312
|
+
closeModal('backdrop', event);
|
|
84
313
|
}
|
|
85
314
|
}
|
|
86
315
|
|
|
87
316
|
function handleKeydown(event: Event) {
|
|
88
317
|
const keyboardEvent = event as KeyboardEvent;
|
|
89
|
-
|
|
90
|
-
|
|
318
|
+
// `defaultPrevented` means a nested dialog already handled the key:
|
|
319
|
+
// it closed itself, or it is not dismissible and must keep its parent open.
|
|
320
|
+
if (keyboardEvent.key === 'Escape' && !keyboardEvent.defaultPrevented) {
|
|
321
|
+
keyboardEvent.preventDefault();
|
|
322
|
+
closeModal('escape', event);
|
|
91
323
|
}
|
|
92
324
|
onkeydown?.(event);
|
|
93
325
|
}
|
|
94
326
|
</script>
|
|
95
327
|
|
|
96
328
|
{#if isOpen}
|
|
329
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
97
330
|
<div
|
|
98
|
-
class=
|
|
331
|
+
class={cn(
|
|
332
|
+
"group/overlay fixed inset-0 z-modal flex items-end justify-center bg-overlay p-0 md:items-center md:p-4",
|
|
333
|
+
dismissible ? 'cursor-pointer' : 'cursor-default',
|
|
334
|
+
full('backdrop'),
|
|
335
|
+
)}
|
|
336
|
+
style:z-index={depth > 0 ? `calc(var(--z-modal) + ${depth})` : undefined}
|
|
337
|
+
data-overlay-depth={depth}
|
|
338
|
+
use:portalTo={portal}
|
|
99
339
|
onclick={handleBackdropClick}
|
|
100
340
|
onkeydown={handleKeydown}
|
|
101
|
-
role="
|
|
102
|
-
aria-modal="true"
|
|
103
|
-
aria-labelledby={title ? modalTitleId : undefined}
|
|
104
|
-
aria-describedby={description ? modalDescriptionId : undefined}
|
|
105
|
-
tabindex="-1"
|
|
106
|
-
data-testid={dataTestId}
|
|
107
|
-
{...restProps}
|
|
341
|
+
role="presentation"
|
|
108
342
|
>
|
|
109
343
|
<div
|
|
110
344
|
bind:this={modalContainer}
|
|
111
|
-
class=
|
|
345
|
+
class={cn(
|
|
346
|
+
// With the keyboard up the root is what is left of the screen: never taller than that.
|
|
347
|
+
"group-data-keyboard-open/overlay:max-h-full",
|
|
348
|
+
"flex max-h-[90dvh] min-w-[320px] cursor-default flex-col overflow-y-auto rounded-t-overlay border border-border-overlay bg-surface-overlay p-0 shadow-lg animate-[slideUp_0.3s_ease-out] motion-reduce:animate-none md:animate-none md:rounded-overlay",
|
|
349
|
+
sizeClasses,
|
|
350
|
+
full('panel'),
|
|
351
|
+
className,
|
|
352
|
+
)}
|
|
353
|
+
{role}
|
|
354
|
+
aria-modal="true"
|
|
355
|
+
aria-labelledby={title ? modalTitleId : undefined}
|
|
356
|
+
aria-describedby={description ? modalDescriptionId : undefined}
|
|
357
|
+
tabindex="-1"
|
|
358
|
+
data-testid={dataTestId}
|
|
359
|
+
data-full-screen={fullScreenMode === 'all' ? 'true' : (fullScreenMode ?? undefined)}
|
|
360
|
+
onkeydown={(event) => trapTabKey(modalContainer, event)}
|
|
361
|
+
{...restProps}
|
|
112
362
|
>
|
|
113
|
-
|
|
114
|
-
|
|
363
|
+
<!-- The panel owns padding: Card's own p-6 would stack with the header's px-6/pt-6 and indent the title 24px past the body. -->
|
|
364
|
+
<!-- And the corners: the Card sits 1px inside the panel (its
|
|
365
|
+
border), so its radius is the panel's less that pixel, not a
|
|
366
|
+
Card's own 12px. Below `md` the panel is a sheet with square
|
|
367
|
+
bottom corners, and so is the Card. -->
|
|
368
|
+
<Card
|
|
369
|
+
variant="flat"
|
|
370
|
+
fullWidth={false}
|
|
371
|
+
className={cn(
|
|
372
|
+
"bg-transparent! p-0! rounded-b-none rounded-t-[calc(var(--radius-overlay)-1px)] md:rounded-[calc(var(--radius-overlay)-1px)]",
|
|
373
|
+
full('card'),
|
|
374
|
+
)}
|
|
375
|
+
>
|
|
376
|
+
{#if title || description || showClose}
|
|
377
|
+
<!-- Chrome: laid out in px, with a title that stops at 1.3
|
|
378
|
+
times its size (util/overlay-chrome.ts). The padding of the
|
|
379
|
+
content and the footer is px with it, so they stay in line. -->
|
|
115
380
|
<CardHeader
|
|
116
381
|
{description}
|
|
117
382
|
descriptionId={description ? modalDescriptionId : undefined}
|
|
118
|
-
className="px-6 pt-6 pb-4"
|
|
383
|
+
className={cn("px-6 pt-6 pb-4", OVERLAY_CHROME, full('header'))}
|
|
119
384
|
>
|
|
120
|
-
|
|
121
|
-
|
|
385
|
+
<!-- 12px between a long title and the button: the title wraps, the button keeps its size. -->
|
|
386
|
+
<div class="flex items-center gap-[12px] {title ? 'justify-between' : 'justify-end'}">
|
|
387
|
+
{#if title}
|
|
122
388
|
<h2
|
|
123
389
|
id={modalTitleId}
|
|
124
|
-
class="
|
|
390
|
+
class="min-w-0 {OVERLAY_TITLE_2XL} font-normal tracking-normal text-headline {OVERLAY_TITLE_WRAP} {enlargedText ? OVERLAY_TITLE_HYPHENS : ''}"
|
|
125
391
|
>
|
|
126
392
|
{title}
|
|
127
393
|
</h2>
|
|
394
|
+
{/if}
|
|
395
|
+
{#if showClose}
|
|
396
|
+
<!-- `aria-disabled`, not `disabled` or removed: the button may hold focus when `dismissible` turns false, and a disabled or missing element would drop focus out of the trap. -->
|
|
128
397
|
<button
|
|
129
398
|
type="button"
|
|
130
|
-
onclick={closeModal}
|
|
131
|
-
class="
|
|
132
|
-
|
|
399
|
+
onclick={(event) => closeModal('close-button', event)}
|
|
400
|
+
class="focus-ring pointer-coarse:relative flex size-8 shrink-0 items-center justify-center rounded-control {OVERLAY_CLOSE_GLYPH} text-description transition-colors {TOUCH_HIT_AREA} {dismissible
|
|
401
|
+
? 'cursor-pointer hover:bg-surface-overlay-hover hover:text-headline active:bg-surface-active'
|
|
402
|
+
: 'cursor-not-allowed opacity-50'}"
|
|
403
|
+
aria-label={closeLabel}
|
|
404
|
+
aria-disabled={dismissible ? undefined : "true"}
|
|
133
405
|
>
|
|
134
406
|
×
|
|
135
407
|
</button>
|
|
136
|
-
|
|
137
|
-
|
|
408
|
+
{/if}
|
|
409
|
+
</div>
|
|
138
410
|
</CardHeader>
|
|
139
411
|
{/if}
|
|
140
412
|
|
|
141
|
-
{#if children}
|
|
142
|
-
|
|
413
|
+
{#if children && fullScreenMode}
|
|
414
|
+
<!-- The padding is on the box that scrolls, so its scrollbar is at the edge of the screen. -->
|
|
415
|
+
<CardContent className={cn("flex-1", full('content'))}>
|
|
416
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
417
|
+
<div
|
|
418
|
+
bind:this={scroller}
|
|
419
|
+
data-modal-content
|
|
420
|
+
class={cn(
|
|
421
|
+
full('scroller'),
|
|
422
|
+
full(title || description || showClose ? 'scrollerBelowHeader' : 'scrollerAtTop'),
|
|
423
|
+
full(footer ? 'scrollerAboveFooter' : 'scrollerAtBottom'),
|
|
424
|
+
scrollerNeedsFocus &&
|
|
425
|
+
"focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-focus-ring",
|
|
426
|
+
)}
|
|
427
|
+
tabindex={scrollerNeedsFocus ? 0 : undefined}
|
|
428
|
+
role={scrollerNeedsFocus && title ? "group" : undefined}
|
|
429
|
+
aria-labelledby={scrollerNeedsFocus && title ? modalTitleId : undefined}
|
|
430
|
+
>
|
|
431
|
+
{@render children?.()}
|
|
432
|
+
</div>
|
|
433
|
+
</CardContent>
|
|
434
|
+
{:else if children}
|
|
435
|
+
<CardContent
|
|
436
|
+
className="flex-1 px-[24px] {title || description || showClose ? '' : 'pt-[24px]'} {footer ? '' : 'pb-[24px]'}"
|
|
437
|
+
>
|
|
143
438
|
{@render children?.()}
|
|
144
439
|
</CardContent>
|
|
145
440
|
{/if}
|
|
146
441
|
|
|
147
442
|
{#if footer}
|
|
148
|
-
<CardFooter className="flex justify-end gap-
|
|
443
|
+
<CardFooter className={cn("flex justify-end gap-[12px] p-[24px] pt-[16px]", full('footer'))}>
|
|
149
444
|
{@render footer?.()}
|
|
150
445
|
</CardFooter>
|
|
151
446
|
{/if}
|
|
@@ -1,18 +1,70 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
1
3
|
type Size = 'sm' | 'md' | 'lg';
|
|
2
|
-
|
|
4
|
+
type CloseReason = 'escape' | 'backdrop' | 'close-button';
|
|
5
|
+
/**
|
|
6
|
+
* Anything else is an attribute of the dialog panel (`aria-describedby`,
|
|
7
|
+
* `aria-busy`, `data-*`, `id`), which is where it is spread.
|
|
8
|
+
*/
|
|
9
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'class' | 'title' | 'role' | 'onclick' | 'onkeydown' | 'onclose' | 'children'> & {
|
|
3
10
|
isOpen?: boolean;
|
|
4
11
|
title?: string;
|
|
5
12
|
description?: string;
|
|
6
13
|
size?: Size;
|
|
14
|
+
/**
|
|
15
|
+
* `alertdialog` for a dialog that interrupts to ask for a response,
|
|
16
|
+
* such as a confirmation. Focus handling is the same for both.
|
|
17
|
+
*/
|
|
18
|
+
role?: 'dialog' | 'alertdialog';
|
|
19
|
+
/** Render the close button even when there is no `title`. */
|
|
20
|
+
showClose?: boolean;
|
|
21
|
+
/** Accessible name of the close button. */
|
|
22
|
+
closeLabel?: string;
|
|
23
|
+
/**
|
|
24
|
+
* CSS selector, looked up inside the panel, of the control that takes
|
|
25
|
+
* focus when the modal opens (a search field, or the safe choice in a
|
|
26
|
+
* confirmation). Without it, or with no match, the first control does.
|
|
27
|
+
*/
|
|
28
|
+
initialFocus?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Render the overlay in `document.body` instead of in place, so an
|
|
31
|
+
* ancestor with a transform, filter or clipped overflow cannot trap it.
|
|
32
|
+
* The theme class belongs on `<html>` or `<body>`; set lower, it does
|
|
33
|
+
* not reach a portalled modal.
|
|
34
|
+
*/
|
|
35
|
+
portal?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* When false, Escape, a backdrop click and the close button do not
|
|
38
|
+
* close the modal (a pending confirm, say). Focus stays trapped, and
|
|
39
|
+
* setting `isOpen` yourself still closes it.
|
|
40
|
+
*/
|
|
41
|
+
dismissible?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Fills the screen instead of floating over it: for a long form on a
|
|
44
|
+
* phone. `true` at every width; `"mobile"` below 768px only, with the
|
|
45
|
+
* usual dialog from there up. The title and the close button stay at
|
|
46
|
+
* the top, the footer at the bottom, both inside the safe areas, and
|
|
47
|
+
* the content scrolls between them. Everything else is the same
|
|
48
|
+
* dialog: focus, Escape, `dismissible`, `portal`.
|
|
49
|
+
*/
|
|
50
|
+
fullScreen?: boolean | 'mobile';
|
|
51
|
+
/** Fired when the modal closes itself, with what the user did. */
|
|
52
|
+
onclose?: (detail: {
|
|
53
|
+
reason: CloseReason;
|
|
54
|
+
}) => void;
|
|
55
|
+
/**
|
|
56
|
+
* @deprecated for close reporting: use `onclose`. Still called with the
|
|
57
|
+
* event on Escape, a backdrop click and the close button.
|
|
58
|
+
*/
|
|
7
59
|
onclick?: (event: Event) => void;
|
|
8
60
|
onkeydown?: (event: Event) => void;
|
|
9
|
-
/** On the `role="dialog"`
|
|
61
|
+
/** On the `role="dialog"` panel (testing, analytics). */
|
|
10
62
|
"data-testid"?: string;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
children?:
|
|
14
|
-
footer?:
|
|
63
|
+
/** Extra classes for the dialog panel. */
|
|
64
|
+
class?: string;
|
|
65
|
+
children?: Snippet;
|
|
66
|
+
footer?: Snippet;
|
|
15
67
|
};
|
|
16
|
-
declare const Modal: import("svelte").Component
|
|
68
|
+
declare const Modal: import("svelte").Component<Props, {}, "isOpen">;
|
|
17
69
|
type Modal = ReturnType<typeof Modal>;
|
|
18
70
|
export default Modal;
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
import NavigationMenuLink from "./NavigationMenuLink.svelte";
|
|
10
10
|
|
|
11
11
|
import { generateId } from "../util/ssr-safe.js";
|
|
12
|
+
|
|
13
|
+
import { cn } from "../util/cn.js";
|
|
12
14
|
import {
|
|
13
15
|
NAVIGATION_MENU_CONTEXT_KEY,
|
|
14
16
|
navigationMenuStableInstanceId,
|
|
@@ -33,24 +35,34 @@
|
|
|
33
35
|
|
|
34
36
|
interface Props {
|
|
35
37
|
viewport?: boolean | "mobile";
|
|
38
|
+
class?: string;
|
|
39
|
+
/** @deprecated use `class`. */
|
|
36
40
|
className?: string;
|
|
37
41
|
items?: NavigationMenuItemData[];
|
|
38
42
|
children?: Snippet;
|
|
39
43
|
listClassName?: string;
|
|
40
44
|
/** Panel id / `aria-controls` prefix; pass for SSR or multiple menus. If omitted with non-empty `items`, a deterministic hash is used. */
|
|
41
45
|
menuId?: string;
|
|
46
|
+
/** Accessible name for the `<nav>` landmark. */
|
|
47
|
+
ariaLabel?: string;
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
let {
|
|
45
51
|
viewport = true,
|
|
46
|
-
|
|
52
|
+
class: classAttr = "",
|
|
53
|
+
className: legacyClass = "",
|
|
47
54
|
items = [],
|
|
48
55
|
children,
|
|
49
56
|
listClassName = "",
|
|
50
57
|
menuId = "",
|
|
58
|
+
ariaLabel = "Main navigation",
|
|
51
59
|
...restProps
|
|
52
60
|
}: Props = $props();
|
|
53
61
|
|
|
62
|
+
/** `class` is the public prop; `className` is a deprecated alias.
|
|
63
|
+
* Both are merged here so existing call sites keep working. */
|
|
64
|
+
const className = $derived(cn(`${classAttr} ${legacyClass}`));
|
|
65
|
+
|
|
54
66
|
let activeItem = $state<string | null>(null);
|
|
55
67
|
let isMobile = $state(false);
|
|
56
68
|
let containerElement = $state<HTMLElement | null>(null);
|
|
@@ -104,8 +116,8 @@
|
|
|
104
116
|
|
|
105
117
|
<nav
|
|
106
118
|
bind:this={containerElement}
|
|
107
|
-
class="relative
|
|
108
|
-
aria-label=
|
|
119
|
+
class={cn("relative", className)}
|
|
120
|
+
aria-label={ariaLabel}
|
|
109
121
|
{...restProps}
|
|
110
122
|
>
|
|
111
123
|
{#if items.length > 0}
|
|
@@ -15,12 +15,16 @@ export interface NavigationMenuItemData {
|
|
|
15
15
|
}
|
|
16
16
|
interface Props {
|
|
17
17
|
viewport?: boolean | "mobile";
|
|
18
|
+
class?: string;
|
|
19
|
+
/** @deprecated use `class`. */
|
|
18
20
|
className?: string;
|
|
19
21
|
items?: NavigationMenuItemData[];
|
|
20
22
|
children?: Snippet;
|
|
21
23
|
listClassName?: string;
|
|
22
24
|
/** Panel id / `aria-controls` prefix; pass for SSR or multiple menus. If omitted with non-empty `items`, a deterministic hash is used. */
|
|
23
25
|
menuId?: string;
|
|
26
|
+
/** Accessible name for the `<nav>` landmark. */
|
|
27
|
+
ariaLabel?: string;
|
|
24
28
|
}
|
|
25
29
|
declare const NavigationMenu: import("svelte").Component<Props, {}, "">;
|
|
26
30
|
type NavigationMenu = ReturnType<typeof NavigationMenu>;
|