@noxlovette/material 0.4.8 → 0.5.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/dist/animation/Shapes.stories.svelte +56 -0
- package/dist/animation/Shapes.stories.svelte.d.ts +3 -0
- package/dist/animation/TransitionPatterns.stories.svelte +359 -0
- package/dist/animation/TransitionPatterns.stories.svelte.d.ts +3 -0
- package/dist/animation/containerTransform.d.ts +35 -12
- package/dist/animation/containerTransform.js +31 -138
- package/dist/animation/enterExit.d.ts +30 -3
- package/dist/animation/enterExit.js +69 -22
- package/dist/animation/index.d.ts +2 -1
- package/dist/animation/index.js +2 -1
- package/dist/animation/presence.svelte.d.ts +47 -0
- package/dist/animation/presence.svelte.js +84 -0
- package/dist/animation/shapeMorph.svelte.d.ts +52 -0
- package/dist/animation/shapeMorph.svelte.js +112 -0
- package/dist/animation/shapesAnimatableSmall.d.ts +28 -22
- package/dist/animation/shapesAnimatableSmall.js +32 -23
- package/dist/animation/sharedAxisTransition.d.ts +57 -37
- package/dist/animation/sharedAxisTransition.js +74 -22
- package/dist/animation/skeleton.d.ts +16 -0
- package/dist/animation/skeleton.js +28 -0
- package/dist/animation/spring.d.ts +61 -0
- package/dist/animation/spring.js +53 -0
- package/dist/animation/springValue.svelte.d.ts +22 -0
- package/dist/animation/springValue.svelte.js +42 -0
- package/dist/animation/transition.d.ts +3 -1
- package/dist/animation/transition.js +3 -1
- package/dist/attachments/clickOutside.d.ts +13 -0
- package/dist/attachments/clickOutside.js +18 -0
- package/dist/attachments/index.d.ts +1 -0
- package/dist/attachments/index.js +1 -0
- package/dist/components/buttons/FAB.svelte +23 -16
- package/dist/components/buttons/FABMenu.svelte +24 -29
- package/dist/components/buttons/FABMenu.svelte.d.ts +2 -1
- package/dist/components/buttons/FABMenuItem.svelte +23 -20
- package/dist/components/buttons/FABMenuItem.svelte.d.ts +5 -1
- package/dist/components/buttons/split-button/SplitButton.svelte +10 -17
- package/dist/components/buttons/split-button/theme.js +1 -1
- package/dist/components/buttons/types.d.ts +3 -3
- package/dist/components/cards/theme.js +1 -1
- package/dist/components/containers/bottom-sheet/BottomSheet.stories.svelte +8 -12
- package/dist/components/containers/bottom-sheet/BottomSheet.svelte +67 -55
- package/dist/components/containers/bottom-sheet/BottomSheet.svelte.d.ts +1 -1
- package/dist/components/containers/bottom-sheet/types.d.ts +8 -2
- package/dist/components/containers/context-menu/ContextMenu.svelte +23 -36
- package/dist/components/containers/context-menu/theme.js +2 -2
- package/dist/components/containers/dialogue/Dialogue.svelte +49 -65
- package/dist/components/containers/link-preview/LinkPreview.svelte +12 -23
- package/dist/components/containers/link-preview/theme.d.ts +3 -3
- package/dist/components/containers/menu/Menu.svelte +14 -21
- package/dist/components/containers/menu/MenuSub.svelte +10 -17
- package/dist/components/containers/pane/DraggablePane.svelte +90 -5
- package/dist/components/containers/pane/DraggablePane.svelte.d.ts +1 -1
- package/dist/components/containers/pane/theme.js +2 -2
- package/dist/components/containers/popover/Popover.svelte +31 -42
- package/dist/components/containers/popover/theme.d.ts +3 -3
- package/dist/components/containers/scroll-area/theme.js +2 -2
- package/dist/components/containers/side-sheet/SideSheet.stories.svelte +10 -15
- package/dist/components/containers/side-sheet/SideSheet.svelte +44 -44
- package/dist/components/containers/side-sheet/SideSheet.svelte.d.ts +4 -3
- package/dist/components/containers/side-sheet/types.d.ts +8 -2
- package/dist/components/date/DateField.svelte +99 -92
- package/dist/components/date/DateField.svelte.d.ts +4 -5
- package/dist/components/date/DateRangeField.svelte +127 -155
- package/dist/components/date/DateRangeField.svelte.d.ts +4 -5
- package/dist/components/date/calendarMotion.d.ts +9 -0
- package/dist/components/date/calendarMotion.js +23 -0
- package/dist/components/date/theme.d.ts +95 -0
- package/dist/components/date/theme.js +103 -14
- package/dist/components/date/types.d.ts +13 -0
- package/dist/components/forms/checkbox/theme.js +2 -2
- package/dist/components/forms/command/theme.d.ts +3 -3
- package/dist/components/forms/command/theme.js +1 -1
- package/dist/components/forms/pin/theme.js +2 -2
- package/dist/components/forms/radio-group/theme.js +2 -2
- package/dist/components/forms/select/Select.svelte +42 -54
- package/dist/components/forms/select/theme.js +4 -4
- package/dist/components/forms/slider/Slider.svelte +144 -126
- package/dist/components/forms/slider/Slider.svelte.d.ts +3 -0
- package/dist/components/forms/slider/theme.d.ts +154 -48
- package/dist/components/forms/slider/theme.js +60 -20
- package/dist/components/forms/slider/types.d.ts +1 -1
- package/dist/components/forms/switch/theme.js +4 -4
- package/dist/components/forms/textfield/Textfield.svelte +1 -1
- package/dist/components/forms/textfield/theme.d.ts +12 -0
- package/dist/components/forms/textfield/theme.js +30 -10
- package/dist/components/forms/toggle-group/theme.js +1 -1
- package/dist/components/forms/tooltip/Tooltip.svelte +24 -35
- package/dist/components/nav/breadcrumb/theme.js +1 -1
- package/dist/components/nav/navbar/theme.js +2 -2
- package/dist/components/nav/rail/Rail.svelte +4 -5
- package/dist/components/nav/rail/theme.js +7 -7
- package/dist/components/nav/tabs/TabHolder.svelte +29 -3
- package/dist/components/nav/tabs/TabHolder.svelte.d.ts +3 -1
- package/dist/components/nav/tabs/theme.js +1 -1
- package/dist/components/progress/CircularProgress.svelte +78 -4
- package/dist/components/progress/CircularProgress.svelte.d.ts +5 -0
- package/dist/components/progress/LinearProgress.svelte +70 -7
- package/dist/components/progress/LinearProgress.svelte.d.ts +5 -0
- package/dist/components/progress/WavyLinearProgress.svelte +27 -3
- package/dist/components/progress/WavyLinearProgress.svelte.d.ts +3 -0
- package/dist/components/progress/_wavy.d.ts +2 -0
- package/dist/components/progress/_wavy.js +3 -1
- package/dist/components/snackbar/Snackbar.svelte +13 -17
- package/dist/components/table/TableBody.svelte +3 -16
- package/dist/components/table/TableBody.svelte.d.ts +1 -5
- package/dist/components/table/TableHeader.svelte +1 -1
- package/dist/components/table/theme.js +1 -1
- package/dist/components/table/types.d.ts +0 -7
- package/dist/components/time/TimeField.stories.svelte +41 -0
- package/dist/components/time/TimeField.stories.svelte.d.ts +19 -0
- package/dist/components/time/TimeField.svelte +69 -62
- package/dist/components/time/TimeField.svelte.d.ts +6 -0
- package/dist/components/time/TimepickerInput.svelte +8 -4
- package/dist/components/time/types.d.ts +11 -1
- package/dist/components/toolbar/theme.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/styles/component.css +21 -22
- package/dist/styles/motion.css +317 -38
- package/dist/utils/Layer.svelte +16 -29
- package/dist/utils/icon/LoadingIndicator.svelte +61 -22
- package/dist/utils/icon/loadingShapes.d.ts +1 -0
- package/dist/utils/icon/loadingShapes.js +16 -0
- package/package.json +5 -2
- package/dist/actions/clickOutside.d.ts +0 -3
- package/dist/actions/clickOutside.js +0 -13
- package/dist/actions/floating.d.ts +0 -20
- package/dist/actions/floating.js +0 -87
- package/dist/actions/index.d.ts +0 -6
- package/dist/actions/index.js +0 -6
- package/dist/actions/keyboard.d.ts +0 -3
- package/dist/actions/keyboard.js +0 -14
- package/dist/actions/portal.d.ts +0 -3
- package/dist/actions/portal.js +0 -8
- package/dist/actions/positionFloating.d.ts +0 -10
- package/dist/actions/positionFloating.js +0 -59
- package/dist/actions/rovingFocusGroup.d.ts +0 -20
- package/dist/actions/rovingFocusGroup.js +0 -82
- package/dist/animation/easing.d.ts +0 -6
- package/dist/animation/easing.js +0 -78
- package/dist/animation/outroClass.d.ts +0 -3
- package/dist/animation/outroClass.js +0 -20
- package/dist/animation/transitionTypes.d.ts +0 -8
- package/dist/animation/transitionTypes.js +0 -1
|
@@ -1,3 +1,30 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { AnimationOptions, DOMKeyframesDefinition } from 'motion';
|
|
2
|
+
/**
|
|
3
|
+
* An M3 enter/exit transition for {@link presence} / `Presence`. Keep keyframes to `transform`
|
|
4
|
+
* and `opacity` so Motion runs them on WAAPI (compositor), which is also what bits-ui's presence
|
|
5
|
+
* layer detects to delay unmounting.
|
|
6
|
+
*
|
|
7
|
+
* https://m3.material.io/styles/motion/transitions/transition-patterns#enter-and-exit
|
|
8
|
+
*
|
|
9
|
+
* Introduces a component in the context of the current screen, modal or not
|
|
10
|
+
* (https://m3.material.io/styles/motion/transitions/applying-transitions). Never use it to navigate between hierarchical screens.
|
|
11
|
+
* Clean fades: opacity always runs on `fastEffects` so it never lingers over the content behind.
|
|
12
|
+
* Edge-anchored sheets slide without fading, as M3 asks.
|
|
13
|
+
*
|
|
14
|
+
* Required for every component that mounts a surface within the current screen: dialogs, sheets,
|
|
15
|
+
* menus, popovers, tooltips, pickers, snackbars, the FAB menu. See the ownership table in
|
|
16
|
+
* `.claude/skills/material-design/references/motion-guide.md`.
|
|
17
|
+
*/
|
|
18
|
+
export interface PresenceTransition {
|
|
19
|
+
/** Where the enter animation starts. */
|
|
20
|
+
hidden: DOMKeyframesDefinition;
|
|
21
|
+
shown: DOMKeyframesDefinition;
|
|
22
|
+
/** Where the exit animation ends. Defaults to `hidden`. */
|
|
23
|
+
exited?: DOMKeyframesDefinition;
|
|
24
|
+
/** `transform-origin` applied while mounted. */
|
|
25
|
+
origin?: string;
|
|
26
|
+
enter: AnimationOptions;
|
|
27
|
+
exit: AnimationOptions;
|
|
28
|
+
}
|
|
29
|
+
export type EnterExitPreset = 'fade' | 'scale' | 'slideUp' | 'dialog' | 'sideSheet' | 'bottomSheet';
|
|
30
|
+
export declare const enterExit: Record<EnterExitPreset, PresenceTransition>;
|
|
@@ -1,23 +1,70 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
1
|
+
import { springTokens, springTransition } from './spring.js';
|
|
2
|
+
/*
|
|
3
|
+
Scale and offset values come from Material Components Android, the reference implementation of
|
|
4
|
+
the M3 transition patterns (github.com/material-components/material-components-android,
|
|
5
|
+
lib/java/com/google/android/material/transition):
|
|
6
|
+
- MaterialFade: enters from 0.8 scale, opacity done in the first 30%; exits by fading only.
|
|
7
|
+
- BaseTransientBottomBar (snackbar), slide mode: translates by its own full height.
|
|
8
|
+
Spring timings replace MDC's duration/easing pairs.
|
|
9
|
+
*/
|
|
10
|
+
const MATERIAL_FADE_START_SCALE = 0.8;
|
|
11
|
+
/* Spatial spring for the movement; the fade uses the fast effects spring so opacity lands early
|
|
12
|
+
(MDC fades in over the first 30% of the enter) and never overshoots. */
|
|
13
|
+
const enterWith = (spatial) => ({
|
|
14
|
+
...springTransition(spatial),
|
|
15
|
+
opacity: springTransition(springTokens.fastEffects)
|
|
16
|
+
});
|
|
17
|
+
/* Exits leave quickly and don't bounce on the way out: critically damped throughout. */
|
|
18
|
+
const exit = {
|
|
19
|
+
...springTransition(springTokens.effects),
|
|
20
|
+
opacity: springTransition(springTokens.fastEffects)
|
|
21
|
+
};
|
|
22
|
+
/* bits-ui sets this on every floating wrapper: the side of the content facing its anchor. */
|
|
23
|
+
const ANCHOR_ORIGIN = 'var(--bits-floating-transform-origin, center)';
|
|
24
|
+
export const enterExit = {
|
|
25
|
+
/** Opacity only — scrims, overlays, content swapped in place. */
|
|
26
|
+
fade: {
|
|
27
|
+
hidden: { opacity: 0 },
|
|
28
|
+
shown: { opacity: 1 },
|
|
29
|
+
enter: springTransition(springTokens.effects),
|
|
30
|
+
exit: springTransition(springTokens.fastEffects)
|
|
31
|
+
},
|
|
32
|
+
/** Anchored surfaces — menus, popovers, tooltips, pickers. Grows out of its anchor (MaterialFade). */
|
|
33
|
+
scale: {
|
|
34
|
+
hidden: { opacity: 0, transform: `scale(${MATERIAL_FADE_START_SCALE})` },
|
|
35
|
+
shown: { opacity: 1, transform: 'scale(1)' },
|
|
36
|
+
exited: { opacity: 0 },
|
|
37
|
+
origin: ANCHOR_ORIGIN,
|
|
38
|
+
enter: enterWith(springTokens.fastSpatial),
|
|
39
|
+
exit
|
|
40
|
+
},
|
|
41
|
+
/** Snackbars — slide in from below by their own height (MDC slide mode). */
|
|
42
|
+
slideUp: {
|
|
43
|
+
hidden: { opacity: 0, transform: 'translateY(100%)' },
|
|
44
|
+
shown: { opacity: 1, transform: 'translateY(0%)' },
|
|
45
|
+
enter: enterWith(springTokens.fastSpatial),
|
|
46
|
+
exit
|
|
47
|
+
},
|
|
48
|
+
/** Modal dialogs — MaterialFade from the center. */
|
|
49
|
+
dialog: {
|
|
50
|
+
hidden: { opacity: 0, transform: `scale(${MATERIAL_FADE_START_SCALE})` },
|
|
51
|
+
shown: { opacity: 1, transform: 'scale(1)' },
|
|
52
|
+
exited: { opacity: 0 },
|
|
53
|
+
enter: enterWith(springTokens.spatial),
|
|
54
|
+
exit
|
|
55
|
+
},
|
|
56
|
+
/** Side sheet anchored to the inline-end edge. Slides only: no opacity (M3: don't fade sheets). */
|
|
57
|
+
sideSheet: {
|
|
58
|
+
hidden: { transform: 'translateX(100%)' },
|
|
59
|
+
shown: { transform: 'translateX(0%)' },
|
|
60
|
+
enter: springTransition(springTokens.spatial),
|
|
61
|
+
exit: springTransition(springTokens.effects)
|
|
62
|
+
},
|
|
63
|
+
/** Bottom sheet anchored to the bottom edge. Slides only: no opacity (M3: don't fade sheets). */
|
|
64
|
+
bottomSheet: {
|
|
65
|
+
hidden: { transform: 'translateY(100%)' },
|
|
66
|
+
shown: { transform: 'translateY(0%)' },
|
|
67
|
+
enter: springTransition(springTokens.spatial),
|
|
68
|
+
exit: springTransition(springTokens.effects)
|
|
69
|
+
}
|
|
23
70
|
};
|
package/dist/animation/index.js
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Attachment } from 'svelte/attachments';
|
|
2
|
+
import type { PresenceTransition } from './enterExit.js';
|
|
3
|
+
/**
|
|
4
|
+
* Enter/exit attachment driven by Motion's `animate()`. Plays `transition.enter` from `hidden` to
|
|
5
|
+
* `shown` when the element mounts open, and `transition.exit` to `exited` (default `hidden`) when
|
|
6
|
+
* `isOpen()` turns false. Applies `transition.origin` as the element's `transform-origin`. Reopening mid-exit (or closing mid-enter) retargets from the current value — the
|
|
7
|
+
* spring keeps its velocity instead of restarting.
|
|
8
|
+
*
|
|
9
|
+
* Inside bits-ui content (`Popover.Content`, `DropdownMenu.Content`, `Dialog.Content`, …) drop
|
|
10
|
+
* `forceMount` and attach this to the element receiving `props`: bits-ui's presence layer waits
|
|
11
|
+
* for that element's `getAnimations()` before unmounting, and Motion runs transform/opacity
|
|
12
|
+
* through WAAPI, so the exit plays out in full.
|
|
13
|
+
*
|
|
14
|
+
* ```svelte
|
|
15
|
+
* <Popover.Content>
|
|
16
|
+
* {#snippet child({ wrapperProps, props, open })}
|
|
17
|
+
* <div {...wrapperProps}>
|
|
18
|
+
* <div {...props} {@attach presence(() => open, enterExit.scale)}>…</div>
|
|
19
|
+
* </div>
|
|
20
|
+
* {/snippet}
|
|
21
|
+
* </Popover.Content>
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* Outside bits-ui, use {@link Presence} so the element stays mounted until the exit finishes.
|
|
25
|
+
*/
|
|
26
|
+
export declare const presence: (isOpen: () => boolean, transition: PresenceTransition, onExitComplete?: () => void) => Attachment<HTMLElement>;
|
|
27
|
+
/**
|
|
28
|
+
* Keeps an element mounted until its exit animation finishes — the Motion replacement for a
|
|
29
|
+
* Svelte `out:` transition. Construct during component init.
|
|
30
|
+
*
|
|
31
|
+
* ```svelte
|
|
32
|
+
* <script>
|
|
33
|
+
* const sheet = new Presence(() => open);
|
|
34
|
+
* </script>
|
|
35
|
+
*
|
|
36
|
+
* {#if sheet.mounted}
|
|
37
|
+
* <div {@attach sheet.attach(enterExit.sideSheet)}>…</div>
|
|
38
|
+
* {/if}
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare class Presence {
|
|
42
|
+
#private;
|
|
43
|
+
constructor(isOpen: () => boolean);
|
|
44
|
+
/** True while open, and while the exit animation is still playing. */
|
|
45
|
+
mounted: boolean;
|
|
46
|
+
attach: (transition: PresenceTransition) => Attachment<HTMLElement>;
|
|
47
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { animate } from 'motion';
|
|
2
|
+
import { untrack } from 'svelte';
|
|
3
|
+
const fromTo = (from, to) => Object.fromEntries(Object.entries(to).map(([key, value]) => [key, [from[key], value]]));
|
|
4
|
+
/**
|
|
5
|
+
* Enter/exit attachment driven by Motion's `animate()`. Plays `transition.enter` from `hidden` to
|
|
6
|
+
* `shown` when the element mounts open, and `transition.exit` to `exited` (default `hidden`) when
|
|
7
|
+
* `isOpen()` turns false. Applies `transition.origin` as the element's `transform-origin`. Reopening mid-exit (or closing mid-enter) retargets from the current value — the
|
|
8
|
+
* spring keeps its velocity instead of restarting.
|
|
9
|
+
*
|
|
10
|
+
* Inside bits-ui content (`Popover.Content`, `DropdownMenu.Content`, `Dialog.Content`, …) drop
|
|
11
|
+
* `forceMount` and attach this to the element receiving `props`: bits-ui's presence layer waits
|
|
12
|
+
* for that element's `getAnimations()` before unmounting, and Motion runs transform/opacity
|
|
13
|
+
* through WAAPI, so the exit plays out in full.
|
|
14
|
+
*
|
|
15
|
+
* ```svelte
|
|
16
|
+
* <Popover.Content>
|
|
17
|
+
* {#snippet child({ wrapperProps, props, open })}
|
|
18
|
+
* <div {...wrapperProps}>
|
|
19
|
+
* <div {...props} {@attach presence(() => open, enterExit.scale)}>…</div>
|
|
20
|
+
* </div>
|
|
21
|
+
* {/snippet}
|
|
22
|
+
* </Popover.Content>
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* Outside bits-ui, use {@link Presence} so the element stays mounted until the exit finishes.
|
|
26
|
+
*/
|
|
27
|
+
export const presence = (isOpen, transition, onExitComplete) => (node) => {
|
|
28
|
+
let controls;
|
|
29
|
+
let started = false;
|
|
30
|
+
if (transition.origin)
|
|
31
|
+
node.style.transformOrigin = transition.origin;
|
|
32
|
+
$effect(() => {
|
|
33
|
+
const open = isOpen();
|
|
34
|
+
untrack(() => {
|
|
35
|
+
const { hidden, shown, exited = hidden, enter, exit } = transition;
|
|
36
|
+
if (open) {
|
|
37
|
+
controls = animate(node, started ? shown : fromTo(hidden, shown), enter);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const current = (controls = animate(node, exited, exit));
|
|
41
|
+
current.then(() => {
|
|
42
|
+
if (controls === current)
|
|
43
|
+
onExitComplete?.();
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
started = true;
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
return () => controls?.stop();
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Keeps an element mounted until its exit animation finishes — the Motion replacement for a
|
|
53
|
+
* Svelte `out:` transition. Construct during component init.
|
|
54
|
+
*
|
|
55
|
+
* ```svelte
|
|
56
|
+
* <script>
|
|
57
|
+
* const sheet = new Presence(() => open);
|
|
58
|
+
* </script>
|
|
59
|
+
*
|
|
60
|
+
* {#if sheet.mounted}
|
|
61
|
+
* <div {@attach sheet.attach(enterExit.sideSheet)}>…</div>
|
|
62
|
+
* {/if}
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export class Presence {
|
|
66
|
+
#isOpen;
|
|
67
|
+
// Plain field on purpose: set while open, read after close to hold the element for its exit.
|
|
68
|
+
#held = false;
|
|
69
|
+
#exits = $state(0);
|
|
70
|
+
constructor(isOpen) {
|
|
71
|
+
this.#isOpen = isOpen;
|
|
72
|
+
}
|
|
73
|
+
/** True while open, and while the exit animation is still playing. */
|
|
74
|
+
mounted = $derived.by(() => {
|
|
75
|
+
void this.#exits;
|
|
76
|
+
if (this.#isOpen())
|
|
77
|
+
this.#held = true;
|
|
78
|
+
return this.#held;
|
|
79
|
+
});
|
|
80
|
+
attach = (transition) => presence(this.#isOpen, transition, () => {
|
|
81
|
+
this.#held = false;
|
|
82
|
+
this.#exits++;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type AnimationPlaybackControls } from 'motion';
|
|
2
|
+
import type { Attachment } from 'svelte/attachments';
|
|
3
|
+
import { type SpringToken } from './spring.js';
|
|
4
|
+
/**
|
|
5
|
+
* The 35 shapes of the M3 Expressive shape library, in the order of the spec sheet.
|
|
6
|
+
* https://m3.material.io/styles/shape
|
|
7
|
+
*/
|
|
8
|
+
export declare const shapeNames: readonly ["circle", "square", "slanted", "arch", "semicircle", "oval", "pill", "triangle", "arrow", "fan", "diamond", "clamshell", "pentagon", "gem", "verySunny", "sunny", "fourSidedCookie", "sixSidedCookie", "sevenSidedCookie", "nineSidedCookie", "twelveSidedCookie", "fourLeafClover", "eightLeafClover", "burst", "softBurst", "boom", "softBoom", "flower", "puffy", "puffyDiamond", "ghostish", "pixelCircle", "pixelTriangle", "bun", "heart"];
|
|
9
|
+
export type ShapeName = (typeof shapeNames)[number];
|
|
10
|
+
/**
|
|
11
|
+
* Every shape as a 720-point path in a 380 × 380 viewBox (`<svg viewBox="0 0 380 380">`). All
|
|
12
|
+
* share one command structure, start point and winding, so any two morph cleanly. Use these for
|
|
13
|
+
* large, hero-sized shapes; for anything icon-sized prefer {@link animatableShapesSmall}.
|
|
14
|
+
*
|
|
15
|
+
* Importing the map pulls in all 35 paths (~315 KB unminified). To ship only a few, import the
|
|
16
|
+
* individual `pathAnimatable*` constants instead.
|
|
17
|
+
*/
|
|
18
|
+
export declare const animatableShapes: Record<"circle" | "square" | "slanted" | "arch" | "semicircle" | "oval" | "pill" | "triangle" | "arrow" | "fan" | "diamond" | "clamshell" | "pentagon" | "gem" | "verySunny" | "sunny" | "fourSidedCookie" | "sixSidedCookie" | "sevenSidedCookie" | "nineSidedCookie" | "twelveSidedCookie" | "fourLeafClover" | "eightLeafClover" | "burst" | "softBurst" | "boom" | "softBoom" | "flower" | "puffy" | "puffyDiamond" | "ghostish" | "pixelCircle" | "pixelTriangle" | "bun" | "heart", string>;
|
|
19
|
+
/**
|
|
20
|
+
* Every shape as a 120-point path in a 48 × 48 viewBox (38 × 38 shape, centred). Same guarantees
|
|
21
|
+
* as {@link animatableShapes} at an eighth of the size: the set for icons, avatars, indicators and
|
|
22
|
+
* FABs. Never mix paths from the two sets in one morph — their point counts differ.
|
|
23
|
+
*/
|
|
24
|
+
export declare const animatableShapesSmall: Record<"circle" | "square" | "slanted" | "arch" | "semicircle" | "oval" | "pill" | "triangle" | "arrow" | "fan" | "diamond" | "clamshell" | "pentagon" | "gem" | "verySunny" | "sunny" | "fourSidedCookie" | "sixSidedCookie" | "sevenSidedCookie" | "nineSidedCookie" | "twelveSidedCookie" | "fourLeafClover" | "eightLeafClover" | "burst" | "softBurst" | "boom" | "softBoom" | "flower" | "puffy" | "puffyDiamond" | "ghostish" | "pixelCircle" | "pixelTriangle" | "bun" | "heart", string>;
|
|
25
|
+
/**
|
|
26
|
+
* Morphs an SVG `<path>` from its current `d` to `to` on an M3 spring (default `fastSpatial`,
|
|
27
|
+
* the token for shape morphs). Both paths must come from the same animatable set. Motion drives
|
|
28
|
+
* the progress; the spring's overshoot carries through into the shape. Under
|
|
29
|
+
* `prefers-reduced-motion` the shape swaps instantly (M3: turn off shape morphing).
|
|
30
|
+
*
|
|
31
|
+
* Starting a new morph mid-flight picks up from the in-between shape on screen, so stop the
|
|
32
|
+
* previous controls first — {@link shapeMorph} does this for you.
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* morphShape(path, animatableShapesSmall.heart);
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare const morphShape: (path: SVGPathElement, to: string, spring?: SpringToken) => AnimationPlaybackControls | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Attachment that keeps a `<path>` on `shape()` and morphs whenever it changes. The first shape
|
|
41
|
+
* renders without animating.
|
|
42
|
+
*
|
|
43
|
+
* ```svelte
|
|
44
|
+
* <svg viewBox="0 0 48 48">
|
|
45
|
+
* <path
|
|
46
|
+
* fill="currentColor"
|
|
47
|
+
* {@attach shapeMorph(() => animatableShapesSmall[selected ? 'sunny' : 'circle'])}
|
|
48
|
+
* />
|
|
49
|
+
* </svg>
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare const shapeMorph: (shape: () => string, spring?: SpringToken) => Attachment<SVGPathElement>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { animate, mix } from 'motion';
|
|
2
|
+
import { untrack } from 'svelte';
|
|
3
|
+
import * as large from './shapesAnimatable.js';
|
|
4
|
+
import * as small from './shapesAnimatableSmall.js';
|
|
5
|
+
import { springTokens, springTransition } from './spring.js';
|
|
6
|
+
/**
|
|
7
|
+
* The 35 shapes of the M3 Expressive shape library, in the order of the spec sheet.
|
|
8
|
+
* https://m3.material.io/styles/shape
|
|
9
|
+
*/
|
|
10
|
+
export const shapeNames = [
|
|
11
|
+
'circle',
|
|
12
|
+
'square',
|
|
13
|
+
'slanted',
|
|
14
|
+
'arch',
|
|
15
|
+
'semicircle',
|
|
16
|
+
'oval',
|
|
17
|
+
'pill',
|
|
18
|
+
'triangle',
|
|
19
|
+
'arrow',
|
|
20
|
+
'fan',
|
|
21
|
+
'diamond',
|
|
22
|
+
'clamshell',
|
|
23
|
+
'pentagon',
|
|
24
|
+
'gem',
|
|
25
|
+
'verySunny',
|
|
26
|
+
'sunny',
|
|
27
|
+
'fourSidedCookie',
|
|
28
|
+
'sixSidedCookie',
|
|
29
|
+
'sevenSidedCookie',
|
|
30
|
+
'nineSidedCookie',
|
|
31
|
+
'twelveSidedCookie',
|
|
32
|
+
'fourLeafClover',
|
|
33
|
+
'eightLeafClover',
|
|
34
|
+
'burst',
|
|
35
|
+
'softBurst',
|
|
36
|
+
'boom',
|
|
37
|
+
'softBoom',
|
|
38
|
+
'flower',
|
|
39
|
+
'puffy',
|
|
40
|
+
'puffyDiamond',
|
|
41
|
+
'ghostish',
|
|
42
|
+
'pixelCircle',
|
|
43
|
+
'pixelTriangle',
|
|
44
|
+
'bun',
|
|
45
|
+
'heart'
|
|
46
|
+
];
|
|
47
|
+
const byName = (paths, prefix) => Object.fromEntries(shapeNames.map((name) => [name, paths[prefix + name[0].toUpperCase() + name.slice(1)]]));
|
|
48
|
+
/**
|
|
49
|
+
* Every shape as a 720-point path in a 380 × 380 viewBox (`<svg viewBox="0 0 380 380">`). All
|
|
50
|
+
* share one command structure, start point and winding, so any two morph cleanly. Use these for
|
|
51
|
+
* large, hero-sized shapes; for anything icon-sized prefer {@link animatableShapesSmall}.
|
|
52
|
+
*
|
|
53
|
+
* Importing the map pulls in all 35 paths (~315 KB unminified). To ship only a few, import the
|
|
54
|
+
* individual `pathAnimatable*` constants instead.
|
|
55
|
+
*/
|
|
56
|
+
export const animatableShapes = byName(large, 'pathAnimatable');
|
|
57
|
+
/**
|
|
58
|
+
* Every shape as a 120-point path in a 48 × 48 viewBox (38 × 38 shape, centred). Same guarantees
|
|
59
|
+
* as {@link animatableShapes} at an eighth of the size: the set for icons, avatars, indicators and
|
|
60
|
+
* FABs. Never mix paths from the two sets in one morph — their point counts differ.
|
|
61
|
+
*/
|
|
62
|
+
export const animatableShapesSmall = byName(small, 'pathAnimatableSmall');
|
|
63
|
+
const prefersReducedMotion = () => typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
64
|
+
/**
|
|
65
|
+
* Morphs an SVG `<path>` from its current `d` to `to` on an M3 spring (default `fastSpatial`,
|
|
66
|
+
* the token for shape morphs). Both paths must come from the same animatable set. Motion drives
|
|
67
|
+
* the progress; the spring's overshoot carries through into the shape. Under
|
|
68
|
+
* `prefers-reduced-motion` the shape swaps instantly (M3: turn off shape morphing).
|
|
69
|
+
*
|
|
70
|
+
* Starting a new morph mid-flight picks up from the in-between shape on screen, so stop the
|
|
71
|
+
* previous controls first — {@link shapeMorph} does this for you.
|
|
72
|
+
*
|
|
73
|
+
* ```ts
|
|
74
|
+
* morphShape(path, animatableShapesSmall.heart);
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export const morphShape = (path, to, spring = springTokens.fastSpatial) => {
|
|
78
|
+
const from = path.getAttribute('d');
|
|
79
|
+
if (!from || from === to || prefersReducedMotion()) {
|
|
80
|
+
path.setAttribute('d', to);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const shape = mix(from, to);
|
|
84
|
+
return animate(0, 1, {
|
|
85
|
+
...springTransition(spring),
|
|
86
|
+
onUpdate: (progress) => path.setAttribute('d', shape(progress))
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Attachment that keeps a `<path>` on `shape()` and morphs whenever it changes. The first shape
|
|
91
|
+
* renders without animating.
|
|
92
|
+
*
|
|
93
|
+
* ```svelte
|
|
94
|
+
* <svg viewBox="0 0 48 48">
|
|
95
|
+
* <path
|
|
96
|
+
* fill="currentColor"
|
|
97
|
+
* {@attach shapeMorph(() => animatableShapesSmall[selected ? 'sunny' : 'circle'])}
|
|
98
|
+
* />
|
|
99
|
+
* </svg>
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export const shapeMorph = (shape, spring) => (path) => {
|
|
103
|
+
let controls;
|
|
104
|
+
$effect(() => {
|
|
105
|
+
const to = shape();
|
|
106
|
+
untrack(() => {
|
|
107
|
+
controls?.stop();
|
|
108
|
+
controls = morphShape(path, to, spring);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
return () => controls?.stop();
|
|
112
|
+
};
|