@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,38 +1,58 @@
|
|
|
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
|
-
|
|
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
|
+
* The default for common hierarchical navigation (https://m3.material.io/styles/motion/transitions/applying-transitions).
|
|
21
|
+
* Don't use it for hero moments; use `containerTransform` for those. Keep one axis per relationship
|
|
22
|
+
* across the app (coherent spatial model), and move one `target` region as a unit instead of
|
|
23
|
+
* animating many elements independently.
|
|
24
|
+
*
|
|
25
|
+
* `update` performs the DOM change (`async () => { step++; await tick(); }`). Built on Motion's
|
|
26
|
+
* `animateView()` — both states never need to coexist in the DOM.
|
|
27
|
+
*
|
|
28
|
+
* Not built into any component: hierarchy levels and wizard steps live in the consuming app.
|
|
29
|
+
*/
|
|
30
|
+
export declare const sharedAxis: (update: () => void | Promise<void>, { target, axis, direction, spring }?: SharedAxisOptions) => import("motion-dom").ViewTransitionBuilder;
|
|
31
|
+
/**
|
|
32
|
+
* M3 lateral: peer screens at the same level (tabs, carousels) slide past each other edge to edge,
|
|
33
|
+
* without fading — the new screen pushes the old one out.
|
|
34
|
+
* https://m3.material.io/styles/motion/transitions/transition-patterns#lateral
|
|
35
|
+
*
|
|
36
|
+
* Only for peers in one set (https://m3.material.io/styles/motion/transitions/applying-transitions). Never use it for hierarchical screens
|
|
37
|
+
* (use `sharedAxis`) or navbar/rail/drawer destinations (use `fadeThrough`, since the implied swipe
|
|
38
|
+
* conflicts with carousels and swipeable list items). Don't add a fade: it hides the peer
|
|
39
|
+
* relationship and makes the slide look like forward/backward.
|
|
40
|
+
*
|
|
41
|
+
* Built in: `TabHolder` (switching content panels) and `DateField`/`DateRangeField` (changing the
|
|
42
|
+
* visible month). Any new component that pages between peer views must use this too.
|
|
43
|
+
*/
|
|
44
|
+
export declare const lateral: (update: () => void | Promise<void>, { target, direction, spring }?: NavigationOptions) => import("motion-dom").ViewTransitionBuilder;
|
|
45
|
+
/**
|
|
46
|
+
* M3 top level (fade through): destinations with no spatial relationship, e.g. navigation bar
|
|
47
|
+
* items. The outgoing screen fades out, then the incoming one fades in while scaling up from 92%.
|
|
48
|
+
* https://m3.material.io/styles/motion/transitions/transition-patterns#top-level
|
|
49
|
+
*
|
|
50
|
+
* The quick fade for navigation bar, rail and drawer destinations (https://m3.material.io/styles/motion/transitions/applying-transitions).
|
|
51
|
+
* It deliberately creates no spatial connection between screens, and it's the pattern to use
|
|
52
|
+
* instead of `lateral` there.
|
|
53
|
+
*
|
|
54
|
+
* Not built into `Navbar`/`Rail`: they don't own the content region that changes, so the app wraps
|
|
55
|
+
* its own route change in this.
|
|
56
|
+
*/
|
|
57
|
+
export declare const fadeThrough: (update: () => void | Promise<void>, { target, spring }?: Omit<NavigationOptions, "direction">) => import("motion-dom").ViewTransitionBuilder;
|
|
38
58
|
export {};
|
|
@@ -1,23 +1,75 @@
|
|
|
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
|
+
M3 "clean fades" (https://m3.material.io/styles/motion/transitions/applying-transitions): fade out before fading in; any cross-fade stays short
|
|
10
|
+
and hidden in the fastest part of the motion. Don't lengthen these. */
|
|
11
|
+
const fadeOut = springTransition(springTokens.fastEffects);
|
|
12
|
+
const fadeIn = { ...springTransition(springTokens.effects), delay: 0.05 };
|
|
13
|
+
/**
|
|
14
|
+
* M3 forward and backward (shared axis): outgoing and incoming content travel together along one
|
|
15
|
+
* axis while fading through each other.
|
|
16
|
+
* https://m3.material.io/styles/motion/transitions/transition-patterns#forward-and-backward
|
|
17
|
+
*
|
|
18
|
+
* The default for common hierarchical navigation (https://m3.material.io/styles/motion/transitions/applying-transitions).
|
|
19
|
+
* Don't use it for hero moments; use `containerTransform` for those. Keep one axis per relationship
|
|
20
|
+
* across the app (coherent spatial model), and move one `target` region as a unit instead of
|
|
21
|
+
* animating many elements independently.
|
|
22
|
+
*
|
|
23
|
+
* `update` performs the DOM change (`async () => { step++; await tick(); }`). Built on Motion's
|
|
24
|
+
* `animateView()` — both states never need to coexist in the DOM.
|
|
25
|
+
*
|
|
26
|
+
* Not built into any component: hierarchy levels and wizard steps live in the consuming app.
|
|
27
|
+
*/
|
|
28
|
+
export const sharedAxis = (update, { target, axis = 'x', direction = 'forward', spring = springTokens.spatial } = {}) => {
|
|
29
|
+
const forward = direction === 'forward';
|
|
30
|
+
const [outgoing, incoming] = axis === 'z'
|
|
31
|
+
? [`scale(${forward ? 1.1 : 0.8})`, `scale(${forward ? 0.8 : 1.1})`]
|
|
32
|
+
: [
|
|
33
|
+
`translate${axis.toUpperCase()}(${forward ? -SHARED_AXIS_OFFSET_PX : SHARED_AXIS_OFFSET_PX}px)`,
|
|
34
|
+
`translate${axis.toUpperCase()}(${forward ? SHARED_AXIS_OFFSET_PX : -SHARED_AXIS_OFFSET_PX}px)`
|
|
35
|
+
];
|
|
36
|
+
const rest = axis === 'z' ? 'scale(1)' : `translate${axis.toUpperCase()}(0px)`;
|
|
37
|
+
return view(update, target, spring)
|
|
38
|
+
.old({ opacity: [1, 0], transform: [rest, outgoing] }, { opacity: fadeOut })
|
|
39
|
+
.new({ opacity: [0, 1], transform: [incoming, rest] }, { opacity: fadeIn });
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* M3 lateral: peer screens at the same level (tabs, carousels) slide past each other edge to edge,
|
|
43
|
+
* without fading — the new screen pushes the old one out.
|
|
44
|
+
* https://m3.material.io/styles/motion/transitions/transition-patterns#lateral
|
|
45
|
+
*
|
|
46
|
+
* Only for peers in one set (https://m3.material.io/styles/motion/transitions/applying-transitions). Never use it for hierarchical screens
|
|
47
|
+
* (use `sharedAxis`) or navbar/rail/drawer destinations (use `fadeThrough`, since the implied swipe
|
|
48
|
+
* conflicts with carousels and swipeable list items). Don't add a fade: it hides the peer
|
|
49
|
+
* relationship and makes the slide look like forward/backward.
|
|
50
|
+
*
|
|
51
|
+
* Built in: `TabHolder` (switching content panels) and `DateField`/`DateRangeField` (changing the
|
|
52
|
+
* visible month). Any new component that pages between peer views must use this too.
|
|
53
|
+
*/
|
|
54
|
+
export const lateral = (update, { target, direction = 'forward', spring = springTokens.spatial } = {}) => {
|
|
55
|
+
const sign = direction === 'forward' ? 1 : -1;
|
|
56
|
+
const builder = view(update, target, spring)
|
|
57
|
+
.old({ transform: ['translateX(0%)', `translateX(${-sign * 100}%)`] })
|
|
58
|
+
.new({ transform: [`translateX(${sign * 100}%)`, 'translateX(0%)'] });
|
|
59
|
+
return target ? builder.crop(true) : builder;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* M3 top level (fade through): destinations with no spatial relationship, e.g. navigation bar
|
|
63
|
+
* items. The outgoing screen fades out, then the incoming one fades in while scaling up from 92%.
|
|
64
|
+
* https://m3.material.io/styles/motion/transitions/transition-patterns#top-level
|
|
65
|
+
*
|
|
66
|
+
* The quick fade for navigation bar, rail and drawer destinations (https://m3.material.io/styles/motion/transitions/applying-transitions).
|
|
67
|
+
* It deliberately creates no spatial connection between screens, and it's the pattern to use
|
|
68
|
+
* instead of `lateral` there.
|
|
69
|
+
*
|
|
70
|
+
* Not built into `Navbar`/`Rail`: they don't own the content region that changes, so the app wraps
|
|
71
|
+
* its own route change in this.
|
|
72
|
+
*/
|
|
73
|
+
export const fadeThrough = (update, { target, spring = springTokens.spatial } = {}) => view(update, target, spring)
|
|
74
|
+
.old({ opacity: [1, 0] }, fadeOut)
|
|
75
|
+
.new({ opacity: [0, 1], transform: ['scale(0.92)', 'scale(1)'] }, { opacity: { ...springTransition(springTokens.effects), delay: 0.09 } });
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
* M3 "stable layouts" (https://m3.material.io/styles/motion/transitions/applying-transitions): the placeholder takes the loaded
|
|
8
|
+
* content's footprint so nothing pops in or shifts position when the data arrives.
|
|
9
|
+
*
|
|
10
|
+
* Not built into any component — none of them load their own data. Apps apply it to placeholders.
|
|
11
|
+
*
|
|
12
|
+
* ```svelte
|
|
13
|
+
* <div class="bg-md-sys-color-surface-container-highest h-4 rounded-sm" {@attach skeleton}></div>
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare const skeleton: Attachment<HTMLElement>;
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
* M3 "stable layouts" (https://m3.material.io/styles/motion/transitions/applying-transitions): the placeholder takes the loaded
|
|
12
|
+
* content's footprint so nothing pops in or shifts position when the data arrives.
|
|
13
|
+
*
|
|
14
|
+
* Not built into any component — none of them load their own data. Apps apply it to placeholders.
|
|
15
|
+
*
|
|
16
|
+
* ```svelte
|
|
17
|
+
* <div class="bg-md-sys-color-surface-container-highest h-4 rounded-sm" {@attach skeleton}></div>
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export const skeleton = (node) => {
|
|
21
|
+
const controls = animate(node, { opacity: [1, 0.4] }, {
|
|
22
|
+
...springTransition(springTokens.slowEffects),
|
|
23
|
+
repeat: Infinity,
|
|
24
|
+
repeatType: 'reverse',
|
|
25
|
+
repeatDelay: PULSE_HOLD_S
|
|
26
|
+
});
|
|
27
|
+
return () => controls.stop();
|
|
28
|
+
};
|
|
@@ -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>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { on } from 'svelte/events';
|
|
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 function clickOutside(handler) {
|
|
14
|
+
return (node) => on(document, 'click', (event) => {
|
|
15
|
+
if (!event.composedPath().includes(node))
|
|
16
|
+
handler(event);
|
|
17
|
+
}, { capture: true });
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './clickOutside.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './clickOutside.js';
|
|
@@ -15,8 +15,7 @@ Floating action buttons (FABs) help people take primary actions.
|
|
|
15
15
|
import type { FABProps } from './types.js';
|
|
16
16
|
import FABMenu from './FABMenu.svelte';
|
|
17
17
|
import { Layer, Icon, LoadingIndicator } from '../../utils/index.js';
|
|
18
|
-
import {
|
|
19
|
-
import { Button, type ButtonRootProps } from 'bits-ui';
|
|
18
|
+
import { Button, DropdownMenu, type ButtonRootProps } from 'bits-ui';
|
|
20
19
|
|
|
21
20
|
let {
|
|
22
21
|
children,
|
|
@@ -30,44 +29,43 @@ Floating action buttons (FABs) help people take primary actions.
|
|
|
30
29
|
expanded = false,
|
|
31
30
|
class: className,
|
|
32
31
|
withMenu,
|
|
32
|
+
onclick,
|
|
33
33
|
...restProps
|
|
34
34
|
}: FABProps = $props();
|
|
35
35
|
|
|
36
36
|
const { base, icon, label: labelClass } = $derived(fab({ size, config, expanded }));
|
|
37
37
|
|
|
38
38
|
let showMenu = $state(false);
|
|
39
|
-
let wrapperEl = $state<HTMLElement | null>(null);
|
|
40
39
|
|
|
41
40
|
const btnCls = $derived(
|
|
42
41
|
base({
|
|
43
42
|
class: clsx(
|
|
44
43
|
className,
|
|
45
|
-
'transition-[border-radius]
|
|
44
|
+
'transition-[border-radius] md-sys-motion-fast-spatial',
|
|
46
45
|
withMenu && showMenu && '!rounded-full'
|
|
47
46
|
)
|
|
48
47
|
})
|
|
49
48
|
);
|
|
50
49
|
|
|
51
|
-
function handleClick(e: MouseEvent) {
|
|
50
|
+
function handleClick(e: MouseEvent, triggerClick?: unknown) {
|
|
52
51
|
if (disabled || loading) {
|
|
53
52
|
e.preventDefault();
|
|
54
53
|
return;
|
|
55
54
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
showMenu = !showMenu;
|
|
59
|
-
}
|
|
55
|
+
(onclick as ((e: MouseEvent) => void) | null | undefined)?.(e);
|
|
56
|
+
if (typeof triggerClick === 'function') triggerClick(e);
|
|
60
57
|
}
|
|
61
58
|
</script>
|
|
62
59
|
|
|
63
|
-
|
|
60
|
+
{#snippet fabButton(triggerProps: Record<string, unknown>)}
|
|
64
61
|
<Button.Root
|
|
65
62
|
{disabled}
|
|
66
63
|
{formaction}
|
|
67
|
-
class={btnCls}
|
|
68
|
-
onclick={handleClick}
|
|
69
64
|
data-cy="m3-fab"
|
|
70
65
|
{...restProps as ButtonRootProps}
|
|
66
|
+
{...triggerProps}
|
|
67
|
+
class={btnCls}
|
|
68
|
+
onclick={(e: MouseEvent) => handleClick(e, triggerProps.onclick)}
|
|
71
69
|
>
|
|
72
70
|
{#if loading}
|
|
73
71
|
<LoadingIndicator />
|
|
@@ -81,10 +79,19 @@ Floating action buttons (FABs) help people take primary actions.
|
|
|
81
79
|
</p>
|
|
82
80
|
{/if}
|
|
83
81
|
</Button.Root>
|
|
82
|
+
{/snippet}
|
|
84
83
|
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
{#if withMenu}
|
|
85
|
+
<DropdownMenu.Root bind:open={showMenu}>
|
|
86
|
+
<DropdownMenu.Trigger disabled={disabled || loading}>
|
|
87
|
+
{#snippet child({ props })}
|
|
88
|
+
{@render fabButton(props)}
|
|
89
|
+
{/snippet}
|
|
90
|
+
</DropdownMenu.Trigger>
|
|
91
|
+
<FABMenu>
|
|
87
92
|
{@render children?.()}
|
|
88
93
|
</FABMenu>
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
</DropdownMenu.Root>
|
|
95
|
+
{:else}
|
|
96
|
+
{@render fabButton({})}
|
|
97
|
+
{/if}
|
|
@@ -1,42 +1,37 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
@component
|
|
3
|
-
|
|
3
|
+
The floating panel of a FAB menu. Rendered by `FAB` when `withMenu` is set — must live inside
|
|
4
|
+
its `DropdownMenu.Root`, which owns open state, positioning, and outside-click/Escape dismissal.
|
|
4
5
|
Should be used with `FABMenuItem` as children.
|
|
5
6
|
-->
|
|
6
7
|
<script lang="ts">
|
|
7
8
|
import clsx from 'clsx';
|
|
8
|
-
import {
|
|
9
|
+
import { DropdownMenu } from 'bits-ui';
|
|
9
10
|
import { fabMenu } from './theme.js';
|
|
10
11
|
import type { FABMenuProps } from './types.js';
|
|
11
|
-
import { enterExit } from '../../animation/
|
|
12
|
-
import { easeEmphasizedAccel, easeEmphasizedDecel } from '../../animation/easing.js';
|
|
13
|
-
import { floating, portal } from '../../actions/index.js';
|
|
12
|
+
import { enterExit, presence } from '../../animation/index.js';
|
|
14
13
|
|
|
15
|
-
let { children, class: className,
|
|
14
|
+
let { children, class: className, ...restProps }: FABMenuProps = $props();
|
|
16
15
|
|
|
17
16
|
const { base } = fabMenu();
|
|
18
17
|
</script>
|
|
19
18
|
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
.fab-menu {
|
|
40
|
-
transform-origin: bottom right;
|
|
41
|
-
}
|
|
42
|
-
</style>
|
|
19
|
+
<DropdownMenu.Portal>
|
|
20
|
+
<DropdownMenu.Content side="top" align="end" sideOffset={8} collisionPadding={8}>
|
|
21
|
+
{#snippet child({ wrapperProps, props, open })}
|
|
22
|
+
<div {...wrapperProps}>
|
|
23
|
+
<div
|
|
24
|
+
data-cy="m3-fab-menu"
|
|
25
|
+
{...restProps}
|
|
26
|
+
{...props}
|
|
27
|
+
class={base({
|
|
28
|
+
class: clsx(className, 'overflow-y-auto')
|
|
29
|
+
})}
|
|
30
|
+
{@attach presence(() => open, enterExit.scale)}
|
|
31
|
+
>
|
|
32
|
+
{@render children()}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
{/snippet}
|
|
36
|
+
</DropdownMenu.Content>
|
|
37
|
+
</DropdownMenu.Portal>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { FABMenuProps } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* The floating panel of a FAB menu. Rendered by `FAB` when `withMenu` is set — must live inside
|
|
4
|
+
* its `DropdownMenu.Root`, which owns open state, positioning, and outside-click/Escape dismissal.
|
|
4
5
|
* Should be used with `FABMenuItem` as children.
|
|
5
6
|
*/
|
|
6
7
|
declare const FABMenu: import("svelte").Component<FABMenuProps, {}, "">;
|