@noxlovette/material 0.4.8 → 0.5.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/dist/animation/TransitionPatterns.stories.svelte +357 -0
- package/dist/animation/TransitionPatterns.stories.svelte.d.ts +3 -0
- package/dist/animation/containerTransform.d.ts +26 -12
- package/dist/animation/containerTransform.js +22 -138
- package/dist/animation/enterExit.d.ts +21 -3
- package/dist/animation/enterExit.js +69 -22
- package/dist/animation/index.d.ts +1 -1
- package/dist/animation/index.js +1 -1
- package/dist/animation/presence.svelte.d.ts +47 -0
- package/dist/animation/presence.svelte.js +84 -0
- package/dist/animation/sharedAxisTransition.d.ts +35 -37
- package/dist/animation/sharedAxisTransition.js +50 -22
- package/dist/animation/skeleton.d.ts +11 -0
- package/dist/animation/skeleton.js +23 -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 +85 -92
- package/dist/components/date/DateField.svelte.d.ts +2 -5
- package/dist/components/date/DateRangeField.svelte +113 -155
- package/dist/components/date/DateRangeField.svelte.d.ts +2 -5
- package/dist/components/date/theme.d.ts +95 -0
- package/dist/components/date/theme.js +99 -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 +9 -0
- package/dist/components/forms/textfield/theme.js +26 -9
- 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/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 +62 -61
- 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 +15 -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,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. */
|
|
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. */
|
|
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
|
+
}
|
|
@@ -1,38 +1,36 @@
|
|
|
1
|
-
import type
|
|
2
|
-
type
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
css: (t: number, u: number) => string;
|
|
37
|
-
};
|
|
1
|
+
import { type ViewTransitionTargetDefinition } from 'motion';
|
|
2
|
+
import { type SpringToken } from './spring.js';
|
|
3
|
+
export type NavigationDirection = 'forward' | 'backward';
|
|
4
|
+
interface NavigationOptions {
|
|
5
|
+
/** The region whose content changes. Omit to transition the whole page. */
|
|
6
|
+
target?: ViewTransitionTargetDefinition;
|
|
7
|
+
/** `forward` moves deeper / to the next item, `backward` returns. */
|
|
8
|
+
direction?: NavigationDirection;
|
|
9
|
+
spring?: SpringToken;
|
|
10
|
+
}
|
|
11
|
+
export interface SharedAxisOptions extends NavigationOptions {
|
|
12
|
+
/** `x` for horizontal steps, `y` for vertical, `z` for parent → child depth. */
|
|
13
|
+
axis?: 'x' | 'y' | 'z';
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* M3 forward and backward (shared axis): outgoing and incoming content travel together along one
|
|
17
|
+
* axis while fading through each other.
|
|
18
|
+
* https://m3.material.io/styles/motion/transitions/transition-patterns#forward-and-backward
|
|
19
|
+
*
|
|
20
|
+
* `update` performs the DOM change (`async () => { step++; await tick(); }`). Built on Motion's
|
|
21
|
+
* `animateView()` — both states never need to coexist in the DOM.
|
|
22
|
+
*/
|
|
23
|
+
export declare const sharedAxis: (update: () => void | Promise<void>, { target, axis, direction, spring }?: SharedAxisOptions) => import("motion-dom").ViewTransitionBuilder;
|
|
24
|
+
/**
|
|
25
|
+
* M3 lateral: peer screens at the same level (tabs, carousels) slide past each other edge to edge,
|
|
26
|
+
* without fading — the new screen pushes the old one out.
|
|
27
|
+
* https://m3.material.io/styles/motion/transitions/transition-patterns#lateral
|
|
28
|
+
*/
|
|
29
|
+
export declare const lateral: (update: () => void | Promise<void>, { target, direction, spring }?: NavigationOptions) => import("motion-dom").ViewTransitionBuilder;
|
|
30
|
+
/**
|
|
31
|
+
* M3 top level (fade through): destinations with no spatial relationship, e.g. navigation bar
|
|
32
|
+
* items. The outgoing screen fades out, then the incoming one fades in while scaling up from 92%.
|
|
33
|
+
* https://m3.material.io/styles/motion/transitions/transition-patterns#top-level
|
|
34
|
+
*/
|
|
35
|
+
export declare const fadeThrough: (update: () => void | Promise<void>, { target, spring }?: Omit<NavigationOptions, "direction">) => import("motion-dom").ViewTransitionBuilder;
|
|
38
36
|
export {};
|
|
@@ -1,23 +1,51 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
duration: options.duration ?? 500,
|
|
8
|
-
easing: options.easing ?? easeEmphasized,
|
|
9
|
-
css: (t, u) => {
|
|
10
|
-
const opacity = (t - 0.35) * (1 / 0.35);
|
|
11
|
-
if (options.direction == 'Z') {
|
|
12
|
-
const factor = options.leaving ? u * 0.1 + 1 : t * 0.2 + 0.8;
|
|
13
|
-
let css = `transform: scale(${factor.toFixed(3)});`;
|
|
14
|
-
if (!options.leaving)
|
|
15
|
-
css += `opacity: ${opacity.toFixed(3)};`;
|
|
16
|
-
return css;
|
|
17
|
-
}
|
|
18
|
-
const factor = u * (options.rightSeam ? -30 : 30);
|
|
19
|
-
return (`transform: translate${options.direction}(${factor.toFixed(3)}px);` +
|
|
20
|
-
`opacity: ${opacity.toFixed(3)}`);
|
|
21
|
-
}
|
|
22
|
-
};
|
|
1
|
+
import { animateView } from 'motion';
|
|
2
|
+
import { springTokens, springTransition } from './spring.js';
|
|
3
|
+
const SHARED_AXIS_OFFSET_PX = 30;
|
|
4
|
+
const view = (update, target, spring) => {
|
|
5
|
+
const builder = animateView(update, springTransition(spring));
|
|
6
|
+
return target ? builder.add(target) : builder;
|
|
23
7
|
};
|
|
8
|
+
/* Outgoing content clears quickly; incoming content waits a beat so the two never overlap fully. */
|
|
9
|
+
const fadeOut = springTransition(springTokens.fastEffects);
|
|
10
|
+
const fadeIn = { ...springTransition(springTokens.effects), delay: 0.05 };
|
|
11
|
+
/**
|
|
12
|
+
* M3 forward and backward (shared axis): outgoing and incoming content travel together along one
|
|
13
|
+
* axis while fading through each other.
|
|
14
|
+
* https://m3.material.io/styles/motion/transitions/transition-patterns#forward-and-backward
|
|
15
|
+
*
|
|
16
|
+
* `update` performs the DOM change (`async () => { step++; await tick(); }`). Built on Motion's
|
|
17
|
+
* `animateView()` — both states never need to coexist in the DOM.
|
|
18
|
+
*/
|
|
19
|
+
export const sharedAxis = (update, { target, axis = 'x', direction = 'forward', spring = springTokens.spatial } = {}) => {
|
|
20
|
+
const forward = direction === 'forward';
|
|
21
|
+
const [outgoing, incoming] = axis === 'z'
|
|
22
|
+
? [`scale(${forward ? 1.1 : 0.8})`, `scale(${forward ? 0.8 : 1.1})`]
|
|
23
|
+
: [
|
|
24
|
+
`translate${axis.toUpperCase()}(${forward ? -SHARED_AXIS_OFFSET_PX : SHARED_AXIS_OFFSET_PX}px)`,
|
|
25
|
+
`translate${axis.toUpperCase()}(${forward ? SHARED_AXIS_OFFSET_PX : -SHARED_AXIS_OFFSET_PX}px)`
|
|
26
|
+
];
|
|
27
|
+
const rest = axis === 'z' ? 'scale(1)' : `translate${axis.toUpperCase()}(0px)`;
|
|
28
|
+
return view(update, target, spring)
|
|
29
|
+
.old({ opacity: [1, 0], transform: [rest, outgoing] }, { opacity: fadeOut })
|
|
30
|
+
.new({ opacity: [0, 1], transform: [incoming, rest] }, { opacity: fadeIn });
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* M3 lateral: peer screens at the same level (tabs, carousels) slide past each other edge to edge,
|
|
34
|
+
* without fading — the new screen pushes the old one out.
|
|
35
|
+
* https://m3.material.io/styles/motion/transitions/transition-patterns#lateral
|
|
36
|
+
*/
|
|
37
|
+
export const lateral = (update, { target, direction = 'forward', spring = springTokens.spatial } = {}) => {
|
|
38
|
+
const sign = direction === 'forward' ? 1 : -1;
|
|
39
|
+
const builder = view(update, target, spring)
|
|
40
|
+
.old({ transform: ['translateX(0%)', `translateX(${-sign * 100}%)`] })
|
|
41
|
+
.new({ transform: [`translateX(${sign * 100}%)`, 'translateX(0%)'] });
|
|
42
|
+
return target ? builder.crop(true) : builder;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* M3 top level (fade through): destinations with no spatial relationship, e.g. navigation bar
|
|
46
|
+
* items. The outgoing screen fades out, then the incoming one fades in while scaling up from 92%.
|
|
47
|
+
* https://m3.material.io/styles/motion/transitions/transition-patterns#top-level
|
|
48
|
+
*/
|
|
49
|
+
export const fadeThrough = (update, { target, spring = springTokens.spatial } = {}) => view(update, target, spring)
|
|
50
|
+
.old({ opacity: [1, 0] }, fadeOut)
|
|
51
|
+
.new({ opacity: [0, 1], transform: ['scale(0.92)', 'scale(1)'] }, { opacity: { ...springTransition(springTokens.effects), delay: 0.09 } });
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Attachment } from 'svelte/attachments';
|
|
2
|
+
/**
|
|
3
|
+
* M3 skeleton loader: a placeholder pulses until real content replaces it. Swap the placeholder
|
|
4
|
+
* for the content with `presence(…, enterExit.fade)` so the reveal is a fade, not a cut.
|
|
5
|
+
* https://m3.material.io/styles/motion/transitions/transition-patterns#skeleton-loaders
|
|
6
|
+
*
|
|
7
|
+
* ```svelte
|
|
8
|
+
* <div class="bg-md-sys-color-surface-container-highest h-4 rounded-sm" {@attach skeleton}></div>
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export declare const skeleton: Attachment<HTMLElement>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { animate } from 'motion';
|
|
2
|
+
import { springTokens, springTransition } from './spring.js';
|
|
3
|
+
/* Hold at each end of the pulse — a critically damped spring alone settles in ~370ms, which reads
|
|
4
|
+
as flicker. Not an M3 value: tune by eye. */
|
|
5
|
+
const PULSE_HOLD_S = 0.5;
|
|
6
|
+
/**
|
|
7
|
+
* M3 skeleton loader: a placeholder pulses until real content replaces it. Swap the placeholder
|
|
8
|
+
* for the content with `presence(…, enterExit.fade)` so the reveal is a fade, not a cut.
|
|
9
|
+
* https://m3.material.io/styles/motion/transitions/transition-patterns#skeleton-loaders
|
|
10
|
+
*
|
|
11
|
+
* ```svelte
|
|
12
|
+
* <div class="bg-md-sys-color-surface-container-highest h-4 rounded-sm" {@attach skeleton}></div>
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export const skeleton = (node) => {
|
|
16
|
+
const controls = animate(node, { opacity: [1, 0.4] }, {
|
|
17
|
+
...springTransition(springTokens.slowEffects),
|
|
18
|
+
repeat: Infinity,
|
|
19
|
+
repeatType: 'reverse',
|
|
20
|
+
repeatDelay: PULSE_HOLD_S
|
|
21
|
+
});
|
|
22
|
+
return () => controls.stop();
|
|
23
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { spring } from 'motion';
|
|
2
|
+
/**
|
|
3
|
+
* M3 Expressive spring tokens: `stiffness` plus a `dampingRatio`
|
|
4
|
+
*
|
|
5
|
+
* - **spatial** springs move things (position, size, shape) and overshoot slightly.
|
|
6
|
+
* - **effects** springs change things in place (opacity, color) and are critically damped.
|
|
7
|
+
*
|
|
8
|
+
* https://m3.material.io/styles/motion/overview/specs
|
|
9
|
+
*/
|
|
10
|
+
export declare const springTokens: {
|
|
11
|
+
readonly fastSpatial: {
|
|
12
|
+
readonly stiffness: 800;
|
|
13
|
+
readonly dampingRatio: 0.6;
|
|
14
|
+
};
|
|
15
|
+
readonly spatial: {
|
|
16
|
+
readonly stiffness: 380;
|
|
17
|
+
readonly dampingRatio: 0.8;
|
|
18
|
+
};
|
|
19
|
+
readonly slowSpatial: {
|
|
20
|
+
readonly stiffness: 200;
|
|
21
|
+
readonly dampingRatio: 0.8;
|
|
22
|
+
};
|
|
23
|
+
readonly fastEffects: {
|
|
24
|
+
readonly stiffness: 3800;
|
|
25
|
+
readonly dampingRatio: 1;
|
|
26
|
+
};
|
|
27
|
+
readonly effects: {
|
|
28
|
+
readonly stiffness: 1600;
|
|
29
|
+
readonly dampingRatio: 1;
|
|
30
|
+
};
|
|
31
|
+
readonly slowEffects: {
|
|
32
|
+
readonly stiffness: 800;
|
|
33
|
+
readonly dampingRatio: 1;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
export type SpringToken = {
|
|
37
|
+
stiffness: number;
|
|
38
|
+
dampingRatio: number;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* A spring token as Motion transition options, for `animate()` / `animateView()`. Physics-based
|
|
42
|
+
* (stiffness/damping, mass 1), so an animation that interrupts another inherits its velocity.
|
|
43
|
+
*
|
|
44
|
+
* ```ts
|
|
45
|
+
* animate(node, { transform: 'scale(1)' }, springTransition(springTokens.fastSpatial));
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare const springTransition: ({ stiffness, dampingRatio }: SpringToken) => {
|
|
49
|
+
readonly type: typeof spring;
|
|
50
|
+
readonly stiffness: number;
|
|
51
|
+
readonly damping: number;
|
|
52
|
+
readonly mass: 1;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* A spring as CSS `transition` parts: a settle `duration` and a `linear()` `easing`.
|
|
56
|
+
* Used by `scripts/generate-motion-springs.ts` to bake the tokens into `styles/motion.css`.
|
|
57
|
+
*/
|
|
58
|
+
export declare const springCss: (token: SpringToken, resolutionMs?: number) => {
|
|
59
|
+
duration: string;
|
|
60
|
+
easing: string;
|
|
61
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { generateLinearEasing, spring } from 'motion';
|
|
2
|
+
/**
|
|
3
|
+
* M3 Expressive spring tokens: `stiffness` plus a `dampingRatio`
|
|
4
|
+
*
|
|
5
|
+
* - **spatial** springs move things (position, size, shape) and overshoot slightly.
|
|
6
|
+
* - **effects** springs change things in place (opacity, color) and are critically damped.
|
|
7
|
+
*
|
|
8
|
+
* https://m3.material.io/styles/motion/overview/specs
|
|
9
|
+
*/
|
|
10
|
+
export const springTokens = {
|
|
11
|
+
fastSpatial: { stiffness: 800, dampingRatio: 0.6 },
|
|
12
|
+
spatial: { stiffness: 380, dampingRatio: 0.8 },
|
|
13
|
+
slowSpatial: { stiffness: 200, dampingRatio: 0.8 },
|
|
14
|
+
fastEffects: { stiffness: 3800, dampingRatio: 1 },
|
|
15
|
+
effects: { stiffness: 1600, dampingRatio: 1 },
|
|
16
|
+
slowEffects: { stiffness: 800, dampingRatio: 1 }
|
|
17
|
+
};
|
|
18
|
+
const STEP_MS = 10;
|
|
19
|
+
const MAX_MS = 5000;
|
|
20
|
+
const resolveSpring = (token) => {
|
|
21
|
+
// Motion takes an absolute damping coefficient; M3 specifies a ratio (mass is 1).
|
|
22
|
+
const { stiffness, damping } = springTransition(token);
|
|
23
|
+
const generator = spring({ keyframes: [0, 1], stiffness, damping });
|
|
24
|
+
let duration = 0;
|
|
25
|
+
while (!generator.next(duration).done && duration < MAX_MS)
|
|
26
|
+
duration += STEP_MS;
|
|
27
|
+
return { duration, at: (progress) => generator.next(duration * progress).value };
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* A spring token as Motion transition options, for `animate()` / `animateView()`. Physics-based
|
|
31
|
+
* (stiffness/damping, mass 1), so an animation that interrupts another inherits its velocity.
|
|
32
|
+
*
|
|
33
|
+
* ```ts
|
|
34
|
+
* animate(node, { transform: 'scale(1)' }, springTransition(springTokens.fastSpatial));
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export const springTransition = ({ stiffness, dampingRatio }) => ({
|
|
38
|
+
// The generator itself, not the string 'spring': the view-transition / WAAPI path
|
|
39
|
+
// (`animateView`, NativeAnimation) only bakes springs from the function and otherwise falls
|
|
40
|
+
// back to a 300ms easeOut.
|
|
41
|
+
type: spring,
|
|
42
|
+
stiffness,
|
|
43
|
+
damping: dampingRatio * 2 * Math.sqrt(stiffness),
|
|
44
|
+
mass: 1
|
|
45
|
+
});
|
|
46
|
+
/**
|
|
47
|
+
* A spring as CSS `transition` parts: a settle `duration` and a `linear()` `easing`.
|
|
48
|
+
* Used by `scripts/generate-motion-springs.ts` to bake the tokens into `styles/motion.css`.
|
|
49
|
+
*/
|
|
50
|
+
export const springCss = (token, resolutionMs = STEP_MS) => {
|
|
51
|
+
const { duration, at } = resolveSpring(token);
|
|
52
|
+
return { duration: `${duration}ms`, easing: generateLinearEasing(at, duration, resolutionMs) };
|
|
53
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type SpringToken } from './spring.js';
|
|
2
|
+
/**
|
|
3
|
+
* A number that springs towards a reactive target — for values rendered as geometry (a progress
|
|
4
|
+
* fill, a slider handle) rather than as a Motion-animated element. Retargeting mid-flight keeps
|
|
5
|
+
* the spring's velocity. Construct during component init.
|
|
6
|
+
*
|
|
7
|
+
* ```svelte
|
|
8
|
+
* <script>
|
|
9
|
+
* const shown = new SpringValue(() => percent, springTokens.slowEffects);
|
|
10
|
+
* </script>
|
|
11
|
+
*
|
|
12
|
+
* <div style:width="{shown.current}%"></div>
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* `immediate` jumps straight to the target while it returns true — e.g. while a handle is dragged,
|
|
16
|
+
* where the value must track the pointer 1:1.
|
|
17
|
+
*/
|
|
18
|
+
export declare class SpringValue {
|
|
19
|
+
#private;
|
|
20
|
+
current: number;
|
|
21
|
+
constructor(target: () => number, spring: SpringToken, immediate?: () => boolean);
|
|
22
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { animate } from 'motion';
|
|
2
|
+
import { untrack } from 'svelte';
|
|
3
|
+
import { springTransition } from './spring.js';
|
|
4
|
+
/**
|
|
5
|
+
* A number that springs towards a reactive target — for values rendered as geometry (a progress
|
|
6
|
+
* fill, a slider handle) rather than as a Motion-animated element. Retargeting mid-flight keeps
|
|
7
|
+
* the spring's velocity. Construct during component init.
|
|
8
|
+
*
|
|
9
|
+
* ```svelte
|
|
10
|
+
* <script>
|
|
11
|
+
* const shown = new SpringValue(() => percent, springTokens.slowEffects);
|
|
12
|
+
* </script>
|
|
13
|
+
*
|
|
14
|
+
* <div style:width="{shown.current}%"></div>
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* `immediate` jumps straight to the target while it returns true — e.g. while a handle is dragged,
|
|
18
|
+
* where the value must track the pointer 1:1.
|
|
19
|
+
*/
|
|
20
|
+
export class SpringValue {
|
|
21
|
+
current = $state(0);
|
|
22
|
+
#controls;
|
|
23
|
+
constructor(target, spring, immediate) {
|
|
24
|
+
this.current = untrack(target);
|
|
25
|
+
$effect(() => {
|
|
26
|
+
const to = target();
|
|
27
|
+
const jump = immediate?.() ?? false;
|
|
28
|
+
untrack(() => {
|
|
29
|
+
this.#controls?.stop();
|
|
30
|
+
if (jump) {
|
|
31
|
+
this.current = to;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
this.#controls = animate(this.current, to, {
|
|
35
|
+
...springTransition(spring),
|
|
36
|
+
onUpdate: (latest) => (this.current = latest)
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
$effect(() => () => this.#controls?.stop());
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from './containerTransform.js';
|
|
2
2
|
export * from './enterExit.js';
|
|
3
|
-
export * from './
|
|
3
|
+
export * from './presence.svelte.js';
|
|
4
4
|
export * from './sharedAxisTransition.js';
|
|
5
|
+
export * from './skeleton.js';
|
|
6
|
+
export * from './springValue.svelte.js';
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from './containerTransform.js';
|
|
2
2
|
export * from './enterExit.js';
|
|
3
|
-
export * from './
|
|
3
|
+
export * from './presence.svelte.js';
|
|
4
4
|
export * from './sharedAxisTransition.js';
|
|
5
|
+
export * from './skeleton.js';
|
|
6
|
+
export * from './springValue.svelte.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Attachment } from 'svelte/attachments';
|
|
2
|
+
/**
|
|
3
|
+
* Calls `handler` when a click lands outside the element. Listens in the capture phase so
|
|
4
|
+
* it still fires when something inside the page stops propagation.
|
|
5
|
+
*
|
|
6
|
+
* Attach it conditionally so the document listener only exists while there is something to
|
|
7
|
+
* dismiss:
|
|
8
|
+
*
|
|
9
|
+
* ```svelte
|
|
10
|
+
* <div {@attach open && clickOutside(() => (open = false))}>...</div>
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare function clickOutside(handler: (event: MouseEvent) => void): Attachment<HTMLElement>;
|