@noxlovette/material 0.5.1 → 0.6.1
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/bin/material-claude-skill.js +31 -0
- package/claude-skill/material-design/SKILL.md +68 -0
- package/claude-skill/material-design/references/accessibility-checklist.md +29 -0
- package/claude-skill/material-design/references/component-patterns.md +55 -0
- package/claude-skill/material-design/references/motion-guide.md +163 -0
- package/claude-skill/material-design/references/tokens-and-styles.md +68 -0
- package/dist/animation/Shapes.stories.svelte +10 -6
- package/dist/animation/TransitionPatterns.stories.svelte +30 -20
- package/dist/animation/containerTransform.d.ts +4 -2
- package/dist/animation/containerTransform.js +4 -2
- package/dist/animation/enterExit.js +5 -2
- package/dist/animation/skeleton.d.ts +1 -1
- package/dist/animation/skeleton.js +1 -1
- package/dist/components/badge/Badge.mdx +70 -0
- package/dist/components/badge/Badge.stories.svelte +38 -20
- package/dist/components/badge/Badge.svelte +12 -5
- package/dist/components/badge/theme.d.ts +30 -7
- package/dist/components/badge/theme.js +25 -5
- package/dist/components/badge/types.d.ts +18 -4
- package/dist/components/buttons/Button.mdx +85 -0
- package/dist/components/buttons/Button.stories.svelte +62 -27
- package/dist/components/buttons/Button.svelte +29 -25
- package/dist/components/buttons/Button.svelte.d.ts +11 -9
- package/dist/components/buttons/ButtonIcon.mdx +52 -0
- package/dist/components/buttons/ButtonIcon.stories.svelte +72 -48
- package/dist/components/buttons/ButtonIcon.svelte +58 -36
- package/dist/components/buttons/ButtonIcon.svelte.d.ts +9 -7
- package/dist/components/buttons/FAB.mdx +84 -0
- package/dist/components/buttons/FAB.stories.svelte +103 -24
- package/dist/components/buttons/FAB.stories.svelte.d.ts +2 -17
- package/dist/components/buttons/FAB.svelte +353 -29
- package/dist/components/buttons/FAB.svelte.d.ts +11 -6
- package/dist/components/buttons/FABMenu.svelte +27 -8
- package/dist/components/buttons/FABMenu.svelte.d.ts +7 -3
- package/dist/components/buttons/FABMenuItem.svelte +20 -16
- package/dist/components/buttons/FABMenuItem.svelte.d.ts +3 -3
- package/dist/components/buttons/Toggle.stories.svelte +31 -23
- package/dist/components/buttons/Toggle.stories.svelte.d.ts +2 -17
- package/dist/components/buttons/Toggle.svelte +28 -25
- package/dist/components/buttons/Toggle.svelte.d.ts +5 -5
- package/dist/components/buttons/button-group/ButtonGroup.mdx +39 -0
- package/dist/components/buttons/button-group/ButtonGroup.stories.svelte +62 -43
- package/dist/components/buttons/button-group/ButtonGroup.stories.svelte.d.ts +2 -17
- package/dist/components/buttons/button-group/ButtonGroup.svelte +102 -8
- package/dist/components/buttons/button-group/ButtonGroup.svelte.d.ts +6 -7
- package/dist/components/buttons/button-group/theme.d.ts +46 -16
- package/dist/components/buttons/button-group/theme.js +14 -9
- package/dist/components/buttons/button-group/types.d.ts +13 -7
- package/dist/components/buttons/connected/ConnectedButtonGroup.mdx +43 -0
- package/dist/components/buttons/connected/ConnectedButtonGroup.stories.svelte +56 -74
- package/dist/components/buttons/connected/ConnectedButtonGroup.stories.svelte.d.ts +2 -17
- package/dist/components/buttons/connected/ConnectedButtonGroup.svelte +16 -4
- package/dist/components/buttons/connected/ConnectedButtonGroup.svelte.d.ts +4 -4
- package/dist/components/buttons/connected/ConnectedButtonGroupItem.svelte +35 -8
- package/dist/components/buttons/connected/ConnectedButtonGroupItem.svelte.d.ts +2 -3
- package/dist/components/buttons/connected/theme.d.ts +34 -19
- package/dist/components/buttons/connected/theme.js +33 -10
- package/dist/components/buttons/connected/types.d.ts +36 -9
- package/dist/components/buttons/shapeMorph.d.ts +26 -0
- package/dist/components/buttons/shapeMorph.js +142 -0
- package/dist/components/buttons/split-button/SplitButton.mdx +48 -0
- package/dist/components/buttons/split-button/SplitButton.stories.svelte +9 -14
- package/dist/components/buttons/split-button/SplitButton.svelte +66 -49
- package/dist/components/buttons/split-button/SplitButton.svelte.d.ts +6 -11
- package/dist/components/buttons/split-button/theme.d.ts +111 -18
- package/dist/components/buttons/split-button/theme.js +75 -17
- package/dist/components/buttons/split-button/types.d.ts +24 -18
- package/dist/components/buttons/theme.d.ts +423 -231
- package/dist/components/buttons/theme.js +287 -381
- package/dist/components/buttons/types.d.ts +130 -125
- package/dist/components/cards/Card.mdx +44 -0
- package/dist/components/cards/Card.stories.svelte +11 -11
- package/dist/components/cards/theme.js +6 -6
- package/dist/components/chips/Chip.mdx +68 -0
- package/dist/components/chips/Chip.stories.svelte +4 -4
- package/dist/components/chips/theme.d.ts +1 -1
- package/dist/components/chips/theme.js +4 -4
- package/dist/components/containers/action-rail/ActionRail.stories.svelte +1 -0
- package/dist/components/containers/action-rail/theme.d.ts +1 -1
- package/dist/components/containers/action-rail/theme.js +2 -2
- package/dist/components/containers/app/theme.d.ts +1 -1
- package/dist/components/containers/app/theme.js +1 -1
- package/dist/components/containers/bottom-sheet/BottomSheet.mdx +64 -0
- package/dist/components/containers/bottom-sheet/BottomSheet.stories.svelte +108 -12
- package/dist/components/containers/bottom-sheet/BottomSheet.svelte +179 -109
- package/dist/components/containers/bottom-sheet/BottomSheet.svelte.d.ts +10 -5
- package/dist/components/containers/bottom-sheet/index.d.ts +1 -0
- package/dist/components/containers/bottom-sheet/index.js +1 -0
- package/dist/components/containers/bottom-sheet/theme.d.ts +45 -0
- package/dist/components/containers/bottom-sheet/theme.js +42 -0
- package/dist/components/containers/bottom-sheet/types.d.ts +37 -5
- package/dist/components/containers/carousel/Carousel.mdx +80 -0
- package/dist/components/containers/carousel/Carousel.stories.svelte +131 -0
- package/dist/components/containers/carousel/Carousel.stories.svelte.d.ts +4 -0
- package/dist/components/containers/carousel/Carousel.svelte +194 -0
- package/dist/components/containers/carousel/Carousel.svelte.d.ts +25 -0
- package/dist/components/containers/carousel/index.d.ts +3 -0
- package/dist/components/containers/{stack → carousel}/index.js +1 -2
- package/dist/components/containers/carousel/keylines.d.ts +32 -0
- package/dist/components/containers/carousel/keylines.js +88 -0
- package/dist/components/containers/carousel/theme.d.ts +162 -0
- package/dist/components/containers/carousel/theme.js +67 -0
- package/dist/components/containers/carousel/types.d.ts +52 -0
- package/dist/components/containers/context-menu/ContextMenu.stories.svelte +1 -0
- package/dist/components/containers/context-menu/ContextMenu.svelte +2 -2
- package/dist/components/containers/context-menu/ContextMenu.svelte.d.ts +1 -1
- package/dist/components/containers/context-menu/theme.d.ts +1 -1
- package/dist/components/containers/context-menu/theme.js +11 -11
- package/dist/components/containers/dialogue/Dialogue.mdx +65 -0
- package/dist/components/containers/dialogue/Dialogue.stories.svelte +4 -4
- package/dist/components/containers/dialogue/Dialogue.svelte +2 -2
- package/dist/components/containers/dialogue/theme.d.ts +1 -1
- package/dist/components/containers/dialogue/theme.js +4 -4
- package/dist/components/containers/dialogue/types.d.ts +1 -1
- package/dist/components/containers/divider/Divider.stories.svelte +2 -1
- package/dist/components/containers/divider/Divider.svelte +2 -2
- package/dist/components/containers/divider/theme.d.ts +7 -7
- package/dist/components/containers/divider/theme.js +3 -3
- package/dist/components/containers/index.d.ts +1 -1
- package/dist/components/containers/index.js +1 -1
- package/dist/components/containers/link-preview/theme.d.ts +1 -1
- package/dist/components/containers/link-preview/theme.js +3 -3
- package/dist/components/containers/list/List.svelte +18 -0
- package/dist/components/containers/list/List.svelte.d.ts +4 -0
- package/dist/components/containers/list/ListItem.mdx +100 -0
- package/dist/components/containers/list/ListItem.stories.svelte +177 -42
- package/dist/components/containers/list/ListItem.stories.svelte.d.ts +2 -17
- package/dist/components/containers/list/ListItem.svelte +92 -37
- package/dist/components/containers/list/ListItem.svelte.d.ts +1 -1
- package/dist/components/containers/list/context.d.ts +6 -0
- package/dist/components/containers/list/context.js +4 -0
- package/dist/components/containers/list/index.d.ts +1 -0
- package/dist/components/containers/list/index.js +1 -0
- package/dist/components/containers/list/theme.d.ts +487 -12
- package/dist/components/containers/list/theme.js +141 -14
- package/dist/components/containers/list/types.d.ts +39 -5
- package/dist/components/containers/menu/Menu.mdx +52 -0
- package/dist/components/containers/menu/Menu.stories.svelte +14 -14
- package/dist/components/containers/menu/Menu.svelte +2 -2
- package/dist/components/containers/menu/MenuGroup.svelte +1 -1
- package/dist/components/containers/menu/MenuGroup.svelte.d.ts +1 -1
- package/dist/components/containers/menu/MenuSub.svelte +1 -1
- package/dist/components/containers/menu/menu-item/MenuItem.svelte +1 -1
- package/dist/components/containers/menu/theme.d.ts +1 -1
- package/dist/components/containers/menu/theme.js +9 -9
- package/dist/components/containers/pane/DraggablePane.stories.svelte +4 -3
- package/dist/components/containers/pane/DraggablePane.svelte +2 -2
- package/dist/components/containers/pane/Pane.stories.svelte +11 -10
- package/dist/components/containers/pane/Pane.svelte +24 -2
- package/dist/components/containers/pane/PaneGrid.stories.svelte +19 -18
- package/dist/components/containers/pane/PaneGrid.svelte +3 -2
- package/dist/components/containers/pane/PaneGrid.svelte.d.ts +2 -1
- package/dist/components/containers/pane/PaneHandle.svelte +48 -2
- package/dist/components/containers/pane/PaneHandle.svelte.d.ts +4 -0
- package/dist/components/containers/pane/resizeStore.svelte.d.ts +10 -1
- package/dist/components/containers/pane/resizeStore.svelte.js +13 -2
- package/dist/components/containers/pane/theme.d.ts +1 -1
- package/dist/components/containers/pane/theme.js +131 -47
- package/dist/components/containers/pane/types.d.ts +3 -2
- package/dist/components/containers/popover/Popover.mdx +45 -0
- package/dist/components/containers/popover/Popover.stories.svelte +1 -1
- package/dist/components/containers/popover/Popover.svelte +1 -1
- package/dist/components/containers/popover/theme.d.ts +1 -1
- package/dist/components/containers/popover/theme.js +4 -4
- package/dist/components/containers/scroll-area/theme.js +3 -3
- package/dist/components/containers/side-sheet/SideSheet.mdx +71 -0
- package/dist/components/containers/side-sheet/SideSheet.stories.svelte +113 -17
- package/dist/components/containers/side-sheet/SideSheet.svelte +113 -66
- package/dist/components/containers/side-sheet/SideSheet.svelte.d.ts +8 -7
- package/dist/components/containers/side-sheet/index.d.ts +1 -0
- package/dist/components/containers/side-sheet/index.js +1 -0
- package/dist/components/containers/side-sheet/theme.d.ts +108 -0
- package/dist/components/containers/side-sheet/theme.js +72 -0
- package/dist/components/containers/side-sheet/types.d.ts +35 -7
- package/dist/components/date/DateField.stories.svelte +4 -3
- package/dist/components/date/DateField.svelte +11 -2
- package/dist/components/date/DateRangeField.stories.svelte +4 -3
- package/dist/components/date/DateRangeField.svelte +12 -3
- package/dist/components/date/theme.d.ts +8 -8
- package/dist/components/date/theme.js +32 -32
- package/dist/components/forms/checkbox/Checkbox.mdx +35 -0
- package/dist/components/forms/checkbox/Checkbox.stories.svelte +2 -2
- package/dist/components/forms/checkbox/Checkbox.svelte +1 -1
- package/dist/components/forms/checkbox/theme.d.ts +1 -1
- package/dist/components/forms/checkbox/theme.js +4 -4
- package/dist/components/forms/command/Command.stories.svelte +1 -0
- package/dist/components/forms/command/theme.d.ts +1 -1
- package/dist/components/forms/command/theme.js +10 -10
- package/dist/components/forms/pin/theme.d.ts +1 -1
- package/dist/components/forms/pin/theme.js +5 -5
- package/dist/components/forms/radio-group/RadioGroup.svelte +1 -1
- package/dist/components/forms/radio-group/theme.d.ts +1 -1
- package/dist/components/forms/radio-group/theme.js +6 -6
- package/dist/components/forms/search/Search.mdx +38 -0
- package/dist/components/forms/search/Search.stories.svelte +19 -9
- package/dist/components/forms/search/Search.svelte +40 -17
- package/dist/components/forms/search/Search.svelte.d.ts +2 -2
- package/dist/components/forms/search/theme.d.ts +46 -13
- package/dist/components/forms/search/theme.js +14 -9
- package/dist/components/forms/search/types.d.ts +18 -12
- package/dist/components/forms/select/Select.mdx +53 -0
- package/dist/components/forms/select/Select.stories.svelte +4 -4
- package/dist/components/forms/select/Select.svelte +12 -4
- package/dist/components/forms/select/SelectItem.svelte +1 -1
- package/dist/components/forms/select/theme.d.ts +1 -1
- package/dist/components/forms/select/theme.js +14 -14
- package/dist/components/forms/slider/Slider.mdx +85 -0
- package/dist/components/forms/slider/Slider.stories.svelte +7 -7
- package/dist/components/forms/slider/Slider.svelte +46 -28
- package/dist/components/forms/slider/theme.d.ts +15 -1
- package/dist/components/forms/slider/theme.js +29 -18
- package/dist/components/forms/slider/types.d.ts +3 -2
- package/dist/components/forms/switch/Switch.mdx +38 -0
- package/dist/components/forms/switch/Switch.stories.svelte +3 -3
- package/dist/components/forms/switch/theme.d.ts +1 -1
- package/dist/components/forms/switch/theme.js +7 -7
- package/dist/components/forms/textfield/Textfield.mdx +53 -0
- package/dist/components/forms/textfield/Textfield.stories.svelte +6 -6
- package/dist/components/forms/textfield/Textfield.svelte +1 -0
- package/dist/components/forms/textfield/theme.d.ts +1 -1
- package/dist/components/forms/textfield/theme.js +20 -20
- package/dist/components/forms/toggle-group/theme.d.ts +1 -1
- package/dist/components/forms/toggle-group/theme.js +3 -3
- package/dist/components/forms/tooltip/Tooltip.stories.svelte +1 -0
- package/dist/components/forms/tooltip/theme.d.ts +1 -1
- package/dist/components/forms/tooltip/theme.js +4 -4
- package/dist/components/misc/Avatar.stories.svelte +5 -4
- package/dist/components/misc/ThemeSettings.svelte +9 -9
- package/dist/components/misc/ThemeSwitcher.svelte +3 -3
- package/dist/components/misc/theme.js +5 -5
- package/dist/components/nav/appbar/AppBar.mdx +71 -0
- package/dist/components/nav/appbar/AppBar.stories.svelte +93 -3
- package/dist/components/nav/appbar/AppBar.stories.svelte.d.ts +2 -17
- package/dist/components/nav/appbar/AppBar.svelte +90 -37
- package/dist/components/nav/appbar/AppBar.svelte.d.ts +9 -2
- package/dist/components/nav/appbar/theme.d.ts +86 -19
- package/dist/components/nav/appbar/theme.js +160 -24
- package/dist/components/nav/appbar/types.d.ts +46 -9
- package/dist/components/nav/breadcrumb/Breadcrumb.stories.svelte +1 -0
- package/dist/components/nav/breadcrumb/theme.d.ts +1 -1
- package/dist/components/nav/breadcrumb/theme.js +6 -6
- package/dist/components/nav/navbar/Navbar.mdx +38 -0
- package/dist/components/nav/navbar/Navbar.svelte +1 -1
- package/dist/components/nav/navbar/NavbarItem.svelte +12 -9
- package/dist/components/nav/navbar/theme.d.ts +3 -3
- package/dist/components/nav/navbar/theme.js +7 -7
- package/dist/components/nav/rail/Rail.mdx +73 -0
- package/dist/components/nav/rail/Rail.stories.svelte +3 -7
- package/dist/components/nav/rail/Rail.svelte +5 -2
- package/dist/components/nav/rail/RailItem.svelte +12 -9
- package/dist/components/nav/rail/theme.d.ts +1 -1
- package/dist/components/nav/rail/theme.js +14 -14
- package/dist/components/nav/rail/types.d.ts +8 -1
- package/dist/components/nav/tabs/Tabs.mdx +62 -0
- package/dist/components/nav/tabs/Tabs.stories.svelte +1 -1
- package/dist/components/nav/tabs/theme.d.ts +1 -1
- package/dist/components/nav/tabs/theme.js +5 -5
- package/dist/components/pill/Pill.mdx +32 -0
- package/dist/components/pill/Pill.stories.svelte +1 -1
- package/dist/components/pill/theme.d.ts +2 -2
- package/dist/components/pill/theme.js +2 -2
- package/dist/components/progress/LinearProgress.svelte +2 -2
- package/dist/components/progress/Progress.mdx +50 -0
- package/dist/components/progress/Progress.stories.svelte +6 -6
- package/dist/components/snackbar/Snackbar.mdx +52 -0
- package/dist/components/snackbar/Snackbar.stories.svelte +5 -5
- package/dist/components/snackbar/Snackbar.svelte +1 -1
- package/dist/components/snackbar/theme.d.ts +4 -4
- package/dist/components/snackbar/theme.js +5 -5
- package/dist/components/table/Table.stories.svelte +1 -0
- package/dist/components/table/TableHeader.svelte +1 -1
- package/dist/components/table/theme.d.ts +1 -1
- package/dist/components/table/theme.js +4 -4
- package/dist/components/time/TimeField.stories.svelte +3 -2
- package/dist/components/time/TimeField.svelte +2 -2
- package/dist/components/time/TimepickerInput.svelte +1 -1
- package/dist/components/time/theme.d.ts +1 -1
- package/dist/components/time/theme.js +6 -6
- package/dist/components/toolbar/Toolbar.stories.svelte +1 -0
- package/dist/components/toolbar/Toolbar.svelte +1 -1
- package/dist/components/toolbar/Toolbar.svelte.d.ts +1 -1
- package/dist/components/toolbar/theme.d.ts +1 -1
- package/dist/components/toolbar/theme.js +15 -15
- package/dist/components/typography/Typography.mdx +67 -0
- package/dist/components/typography/body/Body.stories.svelte +2 -1
- package/dist/components/typography/body/theme.d.ts +16 -13
- package/dist/components/typography/body/theme.js +20 -5
- package/dist/components/typography/body/types.d.ts +2 -1
- package/dist/components/typography/display/Display.stories.svelte +2 -1
- package/dist/components/typography/display/theme.d.ts +16 -13
- package/dist/components/typography/display/theme.js +20 -5
- package/dist/components/typography/display/types.d.ts +2 -1
- package/dist/components/typography/headline/Headline.stories.svelte +2 -1
- package/dist/components/typography/headline/theme.d.ts +16 -13
- package/dist/components/typography/headline/theme.js +20 -5
- package/dist/components/typography/headline/types.d.ts +2 -1
- package/dist/components/typography/kbd/Kbd.stories.svelte +2 -1
- package/dist/components/typography/kbd/theme.d.ts +5 -5
- package/dist/components/typography/kbd/theme.js +3 -3
- package/dist/components/typography/label/Label.stories.svelte +2 -1
- package/dist/components/typography/label/theme.d.ts +15 -12
- package/dist/components/typography/label/theme.js +20 -5
- package/dist/components/typography/label/types.d.ts +2 -1
- package/dist/components/typography/title/Title.stories.svelte +2 -1
- package/dist/components/typography/title/theme.d.ts +16 -13
- package/dist/components/typography/title/theme.js +20 -5
- package/dist/components/typography/title/types.d.ts +2 -1
- package/dist/index.css +2 -0
- package/dist/styles/component.css +78 -175
- package/dist/styles/elevation.css +9 -0
- package/dist/styles/layers.css +33 -0
- package/dist/styles/spacing.css +29 -0
- package/dist/styles/typescale.css +635 -20
- package/dist/utils/Layer.svelte +1 -1
- package/dist/utils/icon/Icon.svelte +8 -6
- package/dist/utils/icon/theme.d.ts +1 -1
- package/dist/utils/icon/theme.js +1 -1
- package/dist/utils/tv.d.ts +6 -0
- package/dist/utils/tv.js +12 -0
- package/package.json +19 -15
- package/dist/components/containers/stack/HStack.svelte +0 -23
- package/dist/components/containers/stack/HStack.svelte.d.ts +0 -5
- package/dist/components/containers/stack/VStack.svelte +0 -23
- package/dist/components/containers/stack/VStack.svelte.d.ts +0 -5
- package/dist/components/containers/stack/index.d.ts +0 -4
- package/dist/components/containers/stack/theme.d.ts +0 -66
- package/dist/components/containers/stack/theme.js +0 -26
- package/dist/components/containers/stack/types.d.ts +0 -12
- /package/dist/components/containers/{stack → carousel}/types.js +0 -0
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
+
/** Why a modal bottom sheet dismissed itself. */
|
|
3
|
+
export type BottomSheetCloseReason = 'esc' | 'scrim' | 'drag' | 'handle';
|
|
2
4
|
/**
|
|
3
5
|
* Props for the BottomSheet component.
|
|
4
6
|
*/
|
|
5
7
|
export interface BottomSheetProps {
|
|
6
8
|
/**
|
|
7
|
-
* The content to be rendered inside the bottom sheet.
|
|
8
|
-
*
|
|
9
|
+
* The content to be rendered inside the bottom sheet, below the drag handle. It scrolls inside
|
|
10
|
+
* the sheet once the sheet reaches its maximum height. Pad it yourself (M3 content usually sits
|
|
11
|
+
* 16–24dp from the edges); lists and menu items can run edge to edge.
|
|
9
12
|
*/
|
|
10
13
|
children: Snippet;
|
|
14
|
+
/**
|
|
15
|
+
* - `modal`: in front of the app above a scrim, blocking everything else until it's dismissed
|
|
16
|
+
* (drag it down, tap the scrim, press Esc, or activate the drag handle).
|
|
17
|
+
* - `standard`: supplementary content that leaves the rest of the screen usable, such as an
|
|
18
|
+
* audio player. No scrim; it collapses to `peekHeight` instead of dismissing.
|
|
19
|
+
* @default 'modal'
|
|
20
|
+
*/
|
|
21
|
+
variant?: 'modal' | 'standard';
|
|
11
22
|
/**
|
|
12
23
|
* Whether the sheet is shown. Bind it (`bind:open`) so the sheet can play its exit animation
|
|
13
24
|
* before unmounting.
|
|
@@ -15,8 +26,29 @@ export interface BottomSheetProps {
|
|
|
15
26
|
*/
|
|
16
27
|
open?: boolean;
|
|
17
28
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
29
|
+
* Standard sheets only: whether the sheet is expanded to its full height, or collapsed to
|
|
30
|
+
* `peekHeight`. Dragging the handle or activating it changes this; bind it to follow along.
|
|
31
|
+
* @default true
|
|
32
|
+
*/
|
|
33
|
+
expanded?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Standard sheets only: how much of the sheet stays visible when collapsed, in px. Include the
|
|
36
|
+
* 48dp drag handle area.
|
|
37
|
+
* @default 112
|
|
38
|
+
*/
|
|
39
|
+
peekHeight?: number;
|
|
40
|
+
/**
|
|
41
|
+
* The sheet's accessible name, e.g. "Share". Give one whenever the sheet has no visible
|
|
42
|
+
* heading that you point to with `aria-labelledby`.
|
|
43
|
+
*/
|
|
44
|
+
label?: string;
|
|
45
|
+
/** Hides the drag handle. Only for sheets that can't be resized or dismissed by dragging. */
|
|
46
|
+
hideHandle?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Called when a modal sheet dismisses itself.
|
|
49
|
+
* @param reason `esc`, a `scrim` click, a downward `drag`, or activating the drag `handle`.
|
|
20
50
|
*/
|
|
21
|
-
close?: (reason:
|
|
51
|
+
close?: (reason: BottomSheetCloseReason) => void;
|
|
52
|
+
class?: string;
|
|
53
|
+
'aria-labelledby'?: string;
|
|
22
54
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Meta, Title, Subtitle, Canvas, Controls } from '@storybook/addon-docs/blocks';
|
|
2
|
+
import * as CarouselStories from './Carousel.stories.svelte';
|
|
3
|
+
|
|
4
|
+
<Meta of={CarouselStories} />
|
|
5
|
+
|
|
6
|
+
<Title />
|
|
7
|
+
|
|
8
|
+
<Subtitle>
|
|
9
|
+
Carousels show a collection of items that can be scrolled on and off the screen.
|
|
10
|
+
</Subtitle>
|
|
11
|
+
|
|
12
|
+
[M3 spec](https://m3.material.io/components/carousel/specs) ·
|
|
13
|
+
`import { Carousel } from '@noxlovette/material';`
|
|
14
|
+
|
|
15
|
+
<Canvas of={CarouselStories.Playground} />
|
|
16
|
+
|
|
17
|
+
<Controls of={CarouselStories.Playground} />
|
|
18
|
+
|
|
19
|
+
## Choosing a layout
|
|
20
|
+
|
|
21
|
+
| Layout | Items | Use for |
|
|
22
|
+
| -------------- | ----------------------------------------------------------- | ---------------------------------------------------- |
|
|
23
|
+
| `multiBrowse` | Large, medium and small at once; they resize as they scroll | Browsing many items: albums, photos, products |
|
|
24
|
+
| `hero` | One large item and a small one peeking | Spotlighting one item at a time, with a hint of more |
|
|
25
|
+
| `centeredHero` | One large item centred between two small ones | The same, symmetric |
|
|
26
|
+
| `uncontained` | Fixed widths that run off the trailing edge; no snapping | Items that shouldn't resize, e.g. cards with text |
|
|
27
|
+
| `fullScreen` | One edge-to-edge item, scrolling vertically | Immersive media, one item per screen |
|
|
28
|
+
|
|
29
|
+
Give `uncontained` an `itemWidth` function for mixed aspect ratios.
|
|
30
|
+
|
|
31
|
+
<Canvas of={CarouselStories.Hero} />
|
|
32
|
+
|
|
33
|
+
<Canvas of={CarouselStories.UncontainedMultiAspect} />
|
|
34
|
+
|
|
35
|
+
## Sizing
|
|
36
|
+
|
|
37
|
+
The carousel fills its container's width. Set its height with a class; it defaults to `h-56`.
|
|
38
|
+
|
|
39
|
+
- **Multi-browse** takes `itemWidth` as the preferred large width (186 by default). It then fits
|
|
40
|
+
as many whole large items as it can, plus a medium and a small one.
|
|
41
|
+
- **Small items** are 40–56dp. They are 56dp once the carousel is at least 392px wide.
|
|
42
|
+
- Items keep 28dp corners and 8dp gaps. Multi-browse and hero keep 16dp padding on both sides.
|
|
43
|
+
|
|
44
|
+
## Item content
|
|
45
|
+
|
|
46
|
+
The `item` snippet fills each item, so use `object-cover` media. It also gets `progress`, which
|
|
47
|
+
runs from 0 when the item is small to 1 when it's large. Fade titles and other secondary content
|
|
48
|
+
with it, since text belongs on large items only.
|
|
49
|
+
|
|
50
|
+
Item content keeps the large width and stays centred while the item narrows, which gives the
|
|
51
|
+
parallax.
|
|
52
|
+
|
|
53
|
+
```svelte
|
|
54
|
+
<Carousel items={albums} label="Recently played">
|
|
55
|
+
{#snippet item(album, { progress })}
|
|
56
|
+
<img src={album.cover} alt="" class="h-full w-full object-cover" />
|
|
57
|
+
<span style:opacity={progress}>{album.title}</span>
|
|
58
|
+
{/snippet}
|
|
59
|
+
</Carousel>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Interaction and accessibility
|
|
63
|
+
|
|
64
|
+
- `label` names the carousel (`role="region"`, `aria-roledescription="carousel"`). It's required.
|
|
65
|
+
- `onitemclick` makes items buttons and `itemHref` makes them links. `itemLabel` names each item;
|
|
66
|
+
it defaults to "n of total". A tapped item opening into its detail is a
|
|
67
|
+
[container transform](?path=/story/motion-transition-patterns--container-transform) that the app applies.
|
|
68
|
+
- Tab reaches every item, including ones scrolled out of view. Focusing an item scrolls it into
|
|
69
|
+
the large slot. When items aren't interactive, the carousel itself takes focus so arrow keys
|
|
70
|
+
can scroll it.
|
|
71
|
+
- Touch and trackpads scroll natively. A mouse can drag to scroll, and a drag doesn't count as
|
|
72
|
+
a click.
|
|
73
|
+
- Before the carousel is measured, including during SSR, keyline layouts render as an
|
|
74
|
+
uncontained row.
|
|
75
|
+
|
|
76
|
+
<Canvas of={CarouselStories.OutlinedAndDisabled} />
|
|
77
|
+
|
|
78
|
+
## Not yet supported
|
|
79
|
+
|
|
80
|
+
Right-to-left layouts.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import { defineMeta } from '@storybook/addon-svelte-csf';
|
|
3
|
+
import Carousel from './Carousel.svelte';
|
|
4
|
+
|
|
5
|
+
const { Story } = defineMeta({
|
|
6
|
+
title: 'Containers/Carousel',
|
|
7
|
+
component: Carousel,
|
|
8
|
+
argTypes: {
|
|
9
|
+
layout: {
|
|
10
|
+
control: 'select',
|
|
11
|
+
options: ['multiBrowse', 'hero', 'centeredHero', 'uncontained', 'fullScreen']
|
|
12
|
+
},
|
|
13
|
+
itemWidth: { control: 'number' },
|
|
14
|
+
snap: { control: 'boolean' },
|
|
15
|
+
outlined: { control: 'boolean' }
|
|
16
|
+
},
|
|
17
|
+
args: { layout: 'multiBrowse', itemWidth: 186, outlined: false }
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
type Photo = { title: string; from: string; to: string; ratio: number };
|
|
21
|
+
|
|
22
|
+
const tones = ['primary', 'secondary', 'tertiary'];
|
|
23
|
+
const photos: Photo[] = [
|
|
24
|
+
'Coast',
|
|
25
|
+
'Harbour',
|
|
26
|
+
'Dunes',
|
|
27
|
+
'Forest',
|
|
28
|
+
'Glacier',
|
|
29
|
+
'Canyon',
|
|
30
|
+
'Meadow',
|
|
31
|
+
'Reef',
|
|
32
|
+
'Summit',
|
|
33
|
+
'Delta'
|
|
34
|
+
].map((title, i) => ({
|
|
35
|
+
title,
|
|
36
|
+
from: `var(--color-md-sys-color-${tones[i % 3]})`,
|
|
37
|
+
to: `var(--color-md-sys-color-${tones[(i + 1) % 3]}-container)`,
|
|
38
|
+
ratio: [16 / 9, 9 / 16, 1, 3 / 4][i % 4]
|
|
39
|
+
}));
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<script lang="ts">
|
|
43
|
+
let opened = $state('');
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
{#snippet photo(p: Photo, { progress }: { index: number; progress: number })}
|
|
47
|
+
<div
|
|
48
|
+
class="p-spacing-200 flex h-full w-full items-end"
|
|
49
|
+
style="background: linear-gradient(135deg, {p.from}, {p.to})"
|
|
50
|
+
>
|
|
51
|
+
<span
|
|
52
|
+
class="md-sys-typescale-title-medium text-md-sys-color-on-primary"
|
|
53
|
+
style="opacity: {progress}">{p.title}</span
|
|
54
|
+
>
|
|
55
|
+
</div>
|
|
56
|
+
{/snippet}
|
|
57
|
+
|
|
58
|
+
<Story name="Playground">
|
|
59
|
+
{#snippet template(args)}
|
|
60
|
+
<div class="w-full max-w-xl">
|
|
61
|
+
<Carousel
|
|
62
|
+
items={photos}
|
|
63
|
+
item={photo}
|
|
64
|
+
label="Places"
|
|
65
|
+
layout={args.layout}
|
|
66
|
+
itemWidth={args.itemWidth}
|
|
67
|
+
snap={args.snap}
|
|
68
|
+
outlined={args.outlined}
|
|
69
|
+
class={args.layout === 'fullScreen' ? 'h-[32rem]' : undefined}
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
{/snippet}
|
|
73
|
+
</Story>
|
|
74
|
+
|
|
75
|
+
<Story name="Multi-browse" asChild>
|
|
76
|
+
<div class="w-full max-w-xl">
|
|
77
|
+
<Carousel items={photos} item={photo} label="Places" onitemclick={(p) => (opened = p.title)} />
|
|
78
|
+
<p class="md-sys-typescale-body-medium mt-spacing-100 px-spacing-200">
|
|
79
|
+
{opened ? `Opened ${opened}` : 'Tap an item. Drag with the mouse, or Tab through the items.'}
|
|
80
|
+
</p>
|
|
81
|
+
</div>
|
|
82
|
+
</Story>
|
|
83
|
+
|
|
84
|
+
<Story name="Hero" asChild>
|
|
85
|
+
<div class="w-full max-w-xl">
|
|
86
|
+
<Carousel items={photos} item={photo} label="Places" layout="hero" class="h-72" />
|
|
87
|
+
</div>
|
|
88
|
+
</Story>
|
|
89
|
+
|
|
90
|
+
<Story name="Centered hero" asChild>
|
|
91
|
+
<div class="w-full max-w-xl">
|
|
92
|
+
<Carousel items={photos} item={photo} label="Places" layout="centeredHero" class="h-72" />
|
|
93
|
+
</div>
|
|
94
|
+
</Story>
|
|
95
|
+
|
|
96
|
+
<Story name="Uncontained" asChild>
|
|
97
|
+
<div class="w-full max-w-xl">
|
|
98
|
+
<Carousel items={photos} item={photo} label="Places" layout="uncontained" />
|
|
99
|
+
</div>
|
|
100
|
+
</Story>
|
|
101
|
+
|
|
102
|
+
<Story name="Uncontained multi-aspect" asChild>
|
|
103
|
+
<div class="w-full max-w-xl">
|
|
104
|
+
<Carousel
|
|
105
|
+
items={photos}
|
|
106
|
+
item={photo}
|
|
107
|
+
label="Places"
|
|
108
|
+
layout="uncontained"
|
|
109
|
+
itemWidth={(p) => Math.round(208 * p.ratio)}
|
|
110
|
+
/>
|
|
111
|
+
</div>
|
|
112
|
+
</Story>
|
|
113
|
+
|
|
114
|
+
<Story name="Full-screen" asChild>
|
|
115
|
+
<div class="h-[36rem] w-80">
|
|
116
|
+
<Carousel items={photos} item={photo} label="Places" layout="fullScreen" class="h-full" />
|
|
117
|
+
</div>
|
|
118
|
+
</Story>
|
|
119
|
+
|
|
120
|
+
<Story name="Outlined and disabled" asChild>
|
|
121
|
+
<div class="w-full max-w-xl">
|
|
122
|
+
<Carousel
|
|
123
|
+
items={photos}
|
|
124
|
+
item={photo}
|
|
125
|
+
label="Places"
|
|
126
|
+
outlined
|
|
127
|
+
itemHref={() => '#'}
|
|
128
|
+
itemDisabled={(_, i) => i === 1}
|
|
129
|
+
/>
|
|
130
|
+
</div>
|
|
131
|
+
</Story>
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
<script lang="ts" generics="T">
|
|
2
|
+
import { Layer } from '../../../utils/index.js';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { carousel } from './theme.js';
|
|
5
|
+
import { keylines, maxSteps, place, type KeylineLayout } from './keylines.js';
|
|
6
|
+
import type { CarouselProps } from './types.js';
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
items,
|
|
10
|
+
item,
|
|
11
|
+
label,
|
|
12
|
+
layout = 'multiBrowse',
|
|
13
|
+
itemWidth,
|
|
14
|
+
snap = layout !== 'uncontained',
|
|
15
|
+
outlined = false,
|
|
16
|
+
onitemclick,
|
|
17
|
+
itemHref,
|
|
18
|
+
itemLabel = (_, i) => `${i + 1} of ${items.length}`,
|
|
19
|
+
itemDisabled,
|
|
20
|
+
class: className,
|
|
21
|
+
...restProps
|
|
22
|
+
}: CarouselProps<T> = $props();
|
|
23
|
+
|
|
24
|
+
let scroller = $state<HTMLElement>();
|
|
25
|
+
let width = $state(0);
|
|
26
|
+
let scrollLeft = $state(0);
|
|
27
|
+
|
|
28
|
+
const isKeyline = $derived(
|
|
29
|
+
layout === 'multiBrowse' || layout === 'hero' || layout === 'centeredHero'
|
|
30
|
+
);
|
|
31
|
+
// Keyline layouts need the container width; until it's measured (and during SSR) they render
|
|
32
|
+
// as a plain uncontained row.
|
|
33
|
+
const k = $derived(
|
|
34
|
+
isKeyline && width > 0
|
|
35
|
+
? keylines(
|
|
36
|
+
layout as KeylineLayout,
|
|
37
|
+
width - 32,
|
|
38
|
+
typeof itemWidth === 'number' ? itemWidth : undefined
|
|
39
|
+
)
|
|
40
|
+
: null
|
|
41
|
+
);
|
|
42
|
+
/** Scroll position in steps (one step moves an item by one slot). */
|
|
43
|
+
const f = $derived(k ? scrollLeft / k.step : 0);
|
|
44
|
+
const steps = $derived(k ? maxSteps(k, items.length) : 0);
|
|
45
|
+
const placements = $derived(k ? place(k, items.length, f) : []);
|
|
46
|
+
const vertical = $derived(layout === 'fullScreen');
|
|
47
|
+
|
|
48
|
+
const cls = $derived(
|
|
49
|
+
carousel({
|
|
50
|
+
layout: k || !isKeyline ? layout : 'uncontained',
|
|
51
|
+
keyline: !!k,
|
|
52
|
+
snap: snap && !vertical,
|
|
53
|
+
outlined,
|
|
54
|
+
interactive: !!onitemclick || !!itemHref
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const widthOf = (data: T, i: number) =>
|
|
59
|
+
typeof itemWidth === 'function' ? itemWidth(data, i) : (itemWidth ?? (isKeyline ? 186 : 240));
|
|
60
|
+
|
|
61
|
+
function itemStyle(data: T, i: number): string {
|
|
62
|
+
if (vertical) return '';
|
|
63
|
+
if (!k) return `width:${widthOf(data, i)}px`;
|
|
64
|
+
const p = placements[i];
|
|
65
|
+
// Items outside the container stay in the DOM, parked just past either edge, so Tab still
|
|
66
|
+
// reaches them; focusing one scrolls it into a large slot.
|
|
67
|
+
const firstShown = placements.findIndex((q) => q !== null);
|
|
68
|
+
const x = p ? p.x : i < firstShown ? -(k.large + 64) : width;
|
|
69
|
+
return `width:${p ? p.size : k.small}px;transform:translateX(${x}px)`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const progressOf = (i: number) => {
|
|
73
|
+
const p = placements[i];
|
|
74
|
+
if (!k || !p) return k ? 0 : 1;
|
|
75
|
+
return Math.min(1, Math.max(0, (p.size - k.small) / (k.large - k.small)));
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
function onscroll() {
|
|
79
|
+
if (scroller) scrollLeft = scroller.scrollLeft;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const reducedMotion = () => matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
83
|
+
|
|
84
|
+
function onfocusin(e: FocusEvent) {
|
|
85
|
+
if (!k || !scroller) return;
|
|
86
|
+
const el = (e.target as Element).closest<HTMLElement>('[data-index]');
|
|
87
|
+
if (!el) return;
|
|
88
|
+
const target = Math.min(steps, Math.max(0, Number(el.dataset.index) - k.focalIndex));
|
|
89
|
+
if (Math.abs(target - f) < 0.01) return;
|
|
90
|
+
scroller.scrollTo({ left: target * k.step, behavior: reducedMotion() ? 'auto' : 'smooth' });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Mouse drag to scroll: touch and trackpads scroll natively, a mouse wheel only scrolls
|
|
94
|
+
// vertically. Snapping is paused while dragging and resumes (and settles) on release.
|
|
95
|
+
let dragged = false;
|
|
96
|
+
function onpointerdown(e: PointerEvent) {
|
|
97
|
+
if (e.pointerType !== 'mouse' || e.button !== 0 || !scroller) return;
|
|
98
|
+
const el = scroller;
|
|
99
|
+
const start = vertical ? e.clientY : e.clientX;
|
|
100
|
+
const startScroll = vertical ? el.scrollTop : el.scrollLeft;
|
|
101
|
+
dragged = false;
|
|
102
|
+
|
|
103
|
+
const move = (ev: PointerEvent) => {
|
|
104
|
+
const delta = (vertical ? ev.clientY : ev.clientX) - start;
|
|
105
|
+
if (!dragged && Math.abs(delta) < 4) return;
|
|
106
|
+
dragged = true;
|
|
107
|
+
el.style.scrollSnapType = 'none';
|
|
108
|
+
if (vertical) el.scrollTop = startScroll - delta;
|
|
109
|
+
else el.scrollLeft = startScroll - delta;
|
|
110
|
+
};
|
|
111
|
+
const up = () => {
|
|
112
|
+
el.style.scrollSnapType = '';
|
|
113
|
+
window.removeEventListener('pointermove', move);
|
|
114
|
+
window.removeEventListener('pointerup', up);
|
|
115
|
+
};
|
|
116
|
+
window.addEventListener('pointermove', move);
|
|
117
|
+
window.addEventListener('pointerup', up);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function onclickcapture(e: MouseEvent) {
|
|
121
|
+
// A drag that ends over an item isn't a tap on it.
|
|
122
|
+
if (dragged) {
|
|
123
|
+
e.preventDefault();
|
|
124
|
+
e.stopPropagation();
|
|
125
|
+
dragged = false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
</script>
|
|
129
|
+
|
|
130
|
+
{#snippet itemEl(data: T, i: number)}
|
|
131
|
+
{@const disabled = itemDisabled?.(data, i) ?? false}
|
|
132
|
+
{@const href = itemHref?.(data, i)}
|
|
133
|
+
{@const tag = href ? 'a' : onitemclick ? 'button' : 'div'}
|
|
134
|
+
<svelte:element
|
|
135
|
+
this={tag}
|
|
136
|
+
class={cls.item({ disabled })}
|
|
137
|
+
style={itemStyle(data, i)}
|
|
138
|
+
data-index={i}
|
|
139
|
+
href={href && !disabled ? href : undefined}
|
|
140
|
+
type={tag === 'button' ? 'button' : undefined}
|
|
141
|
+
disabled={tag === 'button' ? disabled : undefined}
|
|
142
|
+
aria-disabled={tag !== 'button' && disabled ? true : undefined}
|
|
143
|
+
role={tag === 'div' ? 'group' : undefined}
|
|
144
|
+
aria-roledescription={tag === 'div' ? 'slide' : undefined}
|
|
145
|
+
aria-label={itemLabel(data, i)}
|
|
146
|
+
onclick={onitemclick && !disabled ? () => onitemclick(data, i) : undefined}
|
|
147
|
+
>
|
|
148
|
+
{#if tag !== 'div' && !disabled}
|
|
149
|
+
<Layer />
|
|
150
|
+
{/if}
|
|
151
|
+
<div class={cls.content()} style={k ? `width:${k.large}px` : undefined}>
|
|
152
|
+
{@render item(data, { index: i, progress: progressOf(i) })}
|
|
153
|
+
</div>
|
|
154
|
+
</svelte:element>
|
|
155
|
+
{/snippet}
|
|
156
|
+
|
|
157
|
+
<div
|
|
158
|
+
class={cls.base({ class: clsx(className) })}
|
|
159
|
+
role="region"
|
|
160
|
+
aria-roledescription="carousel"
|
|
161
|
+
aria-label={label}
|
|
162
|
+
{...restProps}
|
|
163
|
+
>
|
|
164
|
+
<!-- The scroller is a scroll container, focusable only when no item is (arrow keys scroll it). -->
|
|
165
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex, a11y_no_static_element_interactions -->
|
|
166
|
+
<div
|
|
167
|
+
bind:this={scroller}
|
|
168
|
+
bind:clientWidth={width}
|
|
169
|
+
class={cls.scroller()}
|
|
170
|
+
tabindex={onitemclick || itemHref ? undefined : 0}
|
|
171
|
+
{onscroll}
|
|
172
|
+
{onfocusin}
|
|
173
|
+
{onpointerdown}
|
|
174
|
+
{onclickcapture}
|
|
175
|
+
ondragstart={(e) => e.preventDefault()}
|
|
176
|
+
>
|
|
177
|
+
{#if k}
|
|
178
|
+
<div class={cls.track()} style="width:{width + steps * k.step}px">
|
|
179
|
+
{#each { length: steps + 1 } as _, s (s)}
|
|
180
|
+
<div class={cls.snapPoint()} style="left:{s * k.step}px"></div>
|
|
181
|
+
{/each}
|
|
182
|
+
<div class={cls.viewport()} style="width:{width}px">
|
|
183
|
+
{#each items as data, i (i)}
|
|
184
|
+
{@render itemEl(data, i)}
|
|
185
|
+
{/each}
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
{:else}
|
|
189
|
+
{#each items as data, i (i)}
|
|
190
|
+
{@render itemEl(data, i)}
|
|
191
|
+
{/each}
|
|
192
|
+
{/if}
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CarouselProps } from './types.js';
|
|
2
|
+
declare function $$render<T>(): {
|
|
3
|
+
props: CarouselProps<T>;
|
|
4
|
+
exports: {};
|
|
5
|
+
bindings: "";
|
|
6
|
+
slots: {};
|
|
7
|
+
events: {};
|
|
8
|
+
};
|
|
9
|
+
declare class __sveltets_Render<T> {
|
|
10
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
11
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
12
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
13
|
+
bindings(): "";
|
|
14
|
+
exports(): {};
|
|
15
|
+
}
|
|
16
|
+
interface $$IsomorphicComponent {
|
|
17
|
+
new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
18
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
19
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
20
|
+
<T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
21
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
22
|
+
}
|
|
23
|
+
declare const Carousel: $$IsomorphicComponent;
|
|
24
|
+
type Carousel<T> = InstanceType<typeof Carousel<T>>;
|
|
25
|
+
export default Carousel;
|
|
@@ -3,5 +3,4 @@
|
|
|
3
3
|
export * from './theme.js';
|
|
4
4
|
export * from './types.js';
|
|
5
5
|
// default exports for components in this folder
|
|
6
|
-
export { default as
|
|
7
|
-
export { default as VStack } from './VStack.svelte';
|
|
6
|
+
export { default as Carousel } from './Carousel.svelte';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare const GAP = 8;
|
|
2
|
+
export declare const SMALL_MIN = 40;
|
|
3
|
+
export declare const SMALL_MAX = 56;
|
|
4
|
+
export type KeylineLayout = 'multiBrowse' | 'hero' | 'centeredHero';
|
|
5
|
+
export type Keylines = {
|
|
6
|
+
/** Large item width: the unmasked width of every item's content. */
|
|
7
|
+
large: number;
|
|
8
|
+
small: number;
|
|
9
|
+
/** Scroll distance that moves an item by one slot. */
|
|
10
|
+
step: number;
|
|
11
|
+
start: number[];
|
|
12
|
+
focal: number[];
|
|
13
|
+
end: number[];
|
|
14
|
+
/** Slot of the first large item in `focal`: where a focused item scrolls to. */
|
|
15
|
+
focalIndex: number;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Fits the slots for `layout` into `available` px (container width minus its 16dp padding).
|
|
19
|
+
* `preferredLarge` is the multi-browse large width to aim for.
|
|
20
|
+
*/
|
|
21
|
+
export declare function keylines(layout: KeylineLayout, available: number, preferredLarge?: number): Keylines;
|
|
22
|
+
export type Placement = {
|
|
23
|
+
x: number;
|
|
24
|
+
size: number;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Where each of `count` items sits at scroll position `f` (in steps). Items outside the container
|
|
28
|
+
* get `null`. `x` is relative to the container's inner (padded) edge.
|
|
29
|
+
*/
|
|
30
|
+
export declare function place(k: Keylines, count: number, f: number): (Placement | null)[];
|
|
31
|
+
/** Scroll positions (in steps) the carousel can come to rest at. */
|
|
32
|
+
export declare const maxSteps: (k: Keylines, count: number) => number;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Keyline layout for the M3 carousel (https://m3.material.io/components/carousel/specs), after
|
|
3
|
+
Material Components' carousel (KeylineState / MultiBrowseCarouselStrategy).
|
|
4
|
+
|
|
5
|
+
A layout is a row of slot sizes that fills the container, e.g. multi-browse [L, L, M, S]. Scrolling
|
|
6
|
+
moves items through the slots one "step" (large width + gap) at a time; an item between two slots
|
|
7
|
+
takes an interpolated size, and items stack edge to edge with the gap between them. An item
|
|
8
|
+
leaving either end shrinks to the small size. The slot row itself also morphs: `start` at the
|
|
9
|
+
beginning of the scroll, `focal` in the middle, `end` at the end, so the last items become large.
|
|
10
|
+
*/
|
|
11
|
+
export const GAP = 8;
|
|
12
|
+
export const SMALL_MIN = 40;
|
|
13
|
+
export const SMALL_MAX = 56;
|
|
14
|
+
const repeat = (n, v) => Array.from({ length: n }, () => v);
|
|
15
|
+
/**
|
|
16
|
+
* Fits the slots for `layout` into `available` px (container width minus its 16dp padding).
|
|
17
|
+
* `preferredLarge` is the multi-browse large width to aim for.
|
|
18
|
+
*/
|
|
19
|
+
export function keylines(layout, available, preferredLarge = 186) {
|
|
20
|
+
const small = available >= 360 ? SMALL_MAX : SMALL_MIN;
|
|
21
|
+
const make = (large, start, focal, end, focalIndex = 0) => ({
|
|
22
|
+
large,
|
|
23
|
+
small,
|
|
24
|
+
step: large + GAP,
|
|
25
|
+
start,
|
|
26
|
+
focal,
|
|
27
|
+
end,
|
|
28
|
+
focalIndex
|
|
29
|
+
});
|
|
30
|
+
if (layout === 'hero') {
|
|
31
|
+
const L = Math.max(small, available - small - GAP);
|
|
32
|
+
return make(L, [L, small], [L, small], [small, L]);
|
|
33
|
+
}
|
|
34
|
+
if (layout === 'centeredHero') {
|
|
35
|
+
const L = Math.max(small, available - 2 * small - 2 * GAP);
|
|
36
|
+
return make(L, [L, small, small], [small, L, small], [small, small, L], 1);
|
|
37
|
+
}
|
|
38
|
+
// Multi-browse: n large + one medium + one small; the medium sits halfway between the two.
|
|
39
|
+
let best = { n: 1, L: 0, cost: Infinity };
|
|
40
|
+
for (let n = 1; n <= 8; n++) {
|
|
41
|
+
const L = (available - (n + 1) * GAP - small * 1.5) / (n + 0.5);
|
|
42
|
+
if (L <= small * 1.5)
|
|
43
|
+
break;
|
|
44
|
+
const cost = Math.abs(L - preferredLarge);
|
|
45
|
+
if (cost < best.cost)
|
|
46
|
+
best = { n, L, cost };
|
|
47
|
+
}
|
|
48
|
+
if (!Number.isFinite(best.cost))
|
|
49
|
+
return keylines('hero', available);
|
|
50
|
+
const { n, L } = best;
|
|
51
|
+
const M = (L + small) / 2;
|
|
52
|
+
const focal = [...repeat(n, L), M, small];
|
|
53
|
+
return make(L, focal, focal, [small, M, ...repeat(n, L)]);
|
|
54
|
+
}
|
|
55
|
+
const lerp = (a, b, t) => a + (b - a) * t;
|
|
56
|
+
const clamp01 = (t) => Math.min(1, Math.max(0, t));
|
|
57
|
+
/**
|
|
58
|
+
* Where each of `count` items sits at scroll position `f` (in steps). Items outside the container
|
|
59
|
+
* get `null`. `x` is relative to the container's inner (padded) edge.
|
|
60
|
+
*/
|
|
61
|
+
export function place(k, count, f) {
|
|
62
|
+
const V = k.focal.length;
|
|
63
|
+
const maxF = Math.max(0, count - V);
|
|
64
|
+
f = Math.min(maxF, Math.max(0, f));
|
|
65
|
+
// How far the slots morph from `start` into `focal`, then from `focal` into `end`.
|
|
66
|
+
const endSpan = Math.min(V - 1 - k.focal.lastIndexOf(k.large), maxF);
|
|
67
|
+
const startSpan = Math.min(k.focal.indexOf(k.large), maxF - endSpan);
|
|
68
|
+
const wStart = startSpan > 0 ? clamp01(f / startSpan) : 1;
|
|
69
|
+
const wEnd = endSpan > 0 ? clamp01((f - (maxF - endSpan)) / endSpan) : 0;
|
|
70
|
+
const slots = k.focal.map((s, i) => lerp(lerp(k.start[i], s, wStart), k.end[i], wEnd));
|
|
71
|
+
const sizeAt = (r) => {
|
|
72
|
+
if (r < 0)
|
|
73
|
+
return lerp(slots[0], k.small, -r);
|
|
74
|
+
const i = Math.floor(r);
|
|
75
|
+
return lerp(slots[i], i + 1 < V ? slots[i + 1] : k.small, r - i);
|
|
76
|
+
};
|
|
77
|
+
const out = Array.from({ length: count }, () => null);
|
|
78
|
+
const first = Math.floor(f);
|
|
79
|
+
let x = (first - f) * (k.small + GAP);
|
|
80
|
+
for (let i = first; i < count && i - f < V; i++) {
|
|
81
|
+
const size = sizeAt(i - f);
|
|
82
|
+
out[i] = { x, size };
|
|
83
|
+
x += size + GAP;
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
/** Scroll positions (in steps) the carousel can come to rest at. */
|
|
88
|
+
export const maxSteps = (k, count) => Math.max(0, count - k.focal.length);
|